Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit 2f172d2

Browse files
nielslangeopr
andauthored
Add docs about translation handling (#6405)
* Create translation handling files * Add docs about “Translation basics” * Update docs for “Translation basics” * Add docs about “Translations in PHP files” * Add docs about “Translations in JS/TS files” * Update docs for “Translation basics” * Add docs for “Translation management” * Add docs for “Translations in FSE templates” * Add “Translations” to “WooCommerce Blocks Handbook” * Add docs for “Lazy-load translations” * Rewrite first paragraph of “Translations in FSE templates” * Fix typo * Update repo URL * Remove obsolete space and comma * Update repo URL * Rename lazy-load file * Add section about “sprintf()” to “Translations in JS/TS files” * Remove reference to “npm install @wordpress/i18n” * Add docs for “Translation files and loading” * Rename readme.md to README.md * Remove double file extension * Remove self-explaining explenations * Update docs/README.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-basics.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-basics.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-loading.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-loading.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-loading.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-loading.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-loading.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-loading.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translation-management.md Co-authored-by: Thomas Roberts <[email protected]> * Update docs/translations/translations-in-FSE-templates.md Co-authored-by: Thomas Roberts <[email protected]> * Clarify how to use variables in translations * Change “i18n” to “internationalization (i18n)” * Link translation functions to the WordPress reference * Add Translation management to Translation management * Update “Translations in FSE templates” * Lint Markdown files * Update chunk translation register functions * Update function that loads the translation file * Update information about lazy-loading * Adjust lazy-loading docs title and file name Co-authored-by: Thomas Roberts <[email protected]>
1 parent 3784e51 commit 2f172d2

9 files changed

+1044
-1
lines changed

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,6 @@ The following tutorials from <https://developer.woocommerce.com/category/tutoria
128128

129129
[We're hiring!](https://woocommerce.com/careers/) Come work with us!
130130

131-
🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./docs/README.md)
131+
🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-blocks/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./docs/readme.md)
132132

133133
<!-- /FEEDBACK -->

docs/translations/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Translations
2+
3+
This folder contains documentation around translation handling of WooCommerce Blocks:
4+
5+
- [Translation basics](./translation-basics.md)
6+
- [Translations in PHP files](./translations-in-PHP-files.md)
7+
- [Translations in JS/TS files](./translations-in-JS-TS-files.md)
8+
- [Translations in FSE templates](./translations-in-FSE-templates.md)
9+
- [Translations for lazy-loaded components](./translations-for-lazy-loaded-components.md)
10+
- [Translation loading](./translation-loading.md)
11+
- [Translation management](./translation-management.md)
12+
13+
<!-- FEEDBACK -->
14+
15+
---
16+
17+
[We're hiring!](https://woocommerce.com/careers/) Come work with us!
18+
19+
🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-blocks/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./docs/testing/README.md)
20+
21+
<!-- /FEEDBACK -->
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Translation basics
2+
3+
## Localization functions
4+
5+
Since [WordPress 2.1 "Ella"](https://wordpress.org/support/wordpress-version/version-2-1/), WordPress offers [internationalization (i18n)](https://developer.wordpress.org/plugins/internationalization/) for PHP files, and since [WordPress 5.0 "Bebo"](https://wordpress.org/support/wordpress-version/version-5-0/), it also offers i18n for JS files. Handling translations is pretty straight forward. Both PHP and JS handle translations similar. WordPress offers the functions:
6+
7+
- [`__()`](https://developer.wordpress.org/reference/functions/__/) → Available in PHP & JS/TS.
8+
- [`_e()`](https://developer.wordpress.org/reference/functions/_e/) → Available in PHP only.
9+
- [`_ex()`](https://developer.wordpress.org/reference/functions/_ex/) → Available in PHP only.
10+
- [`_n()`](https://developer.wordpress.org/reference/functions/_n/) → Available in PHP & JS/TS.
11+
- [`_x()`](https://developer.wordpress.org/reference/functions/_x/) → Available in PHP & JS/TS.
12+
- [`_nx()`](https://developer.wordpress.org/reference/functions/_nx/) → Available in PHP & JS/TS.
13+
- [`esc_html__()`](https://developer.wordpress.org/reference/functions/esc_html__/) → Available in PHP only.
14+
- [`esc_html_e()`](https://developer.wordpress.org/reference/functions/esc_html_e/) → Available in PHP only.
15+
- [`esc_html_x()`](https://developer.wordpress.org/reference/functions/esc_html_x/) → Available in PHP only.
16+
- [`esc_attr__()`](https://developer.wordpress.org/reference/functions/esc_attr__/) → Available in PHP only.
17+
- [`esc_attr_e()`](https://developer.wordpress.org/reference/functions/esc_attr_e/) → Available in PHP only.
18+
- [`esc_attr_x()`](https://developer.wordpress.org/reference/functions/esc_attr_x/) → Available in PHP only.
19+
20+
## GlotPress
21+
22+
All translations are handled using [GlotPress](https://wordpress.org/plugins/glotpress/). As the WooCommerce Blocks plugin is hosted on <https://wordpress.org/>, all plugin-related translations can be found and managed on <https://translate.wordpress.org/projects/wp-plugins/woo-gutenberg-products-block/>.
23+
24+
## Text domain
25+
26+
Prior to [WordPress 4.6 “Pepper Adams”](https://wordpress.org/support/wordpress-version/version-4-6/), a text domain had to be defined to make the strings translatable. While it’s no longer a requirement to have a text domain, it does no harm to still include it. If the text domain is available, it has to match the slug of the plugin and is defined in the header of the main plugin file `woocommerce-gutenberg-products-block.php`:
27+
28+
```php
29+
<?php
30+
/**
31+
* [...]
32+
* Text Domain: woo-gutenberg-products-block
33+
* [...]
34+
*/
35+
```
36+
37+
See also <https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#text-domains>.
38+
39+
## Loading Text Domain
40+
41+
Prior to [WordPress 4.6 “Pepper Adams”](https://wordpress.org/support/wordpress-version/version-4-6/), loading the text domain was required. As translations now take place on <https://translate.wordpress.org/>, loading the text domain using `load_plugin_textdomain()` is no longer required. In case the plugin does not load the text domain, the header of the main plugin file must include the definition `Requires at least:`. This definition must be set to 4.6 or higher.
42+
43+
See also <https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#loading-text-domain>.
44+
45+
## Domain Path
46+
47+
Only plugins that are not hosted in the official WordPress Plugin Directory need to define a `Domain Path`. As the WooCommerce Blocks plugin is hosted in the official WordPress Plugin Directory, it does not need a `Domain Path`.
48+
49+
See also <https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#domain-path>.
50+
51+
<!-- FEEDBACK -->
52+
53+
---
54+
55+
[We're hiring!](https://woocommerce.com/careers/) Come work with us!
56+
57+
🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-blocks/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./docs/testing/README.md)
58+
59+
<!-- /FEEDBACK -->
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# Translation files and loading
2+
3+
## Translation files
4+
5+
### POT (Portable Object Template) file
6+
7+
POT stands for `Portable Object Template` and contains all original strings, in English. It can be created manually using [WP-CLI](https://wp-cli.org/) or [Poedit](https://poedit.net/). As the WooCommerce Blocks plugin is hosted on WordPress.org, we don't need to manually create the POT file. GlotPress automatically generates that file in the background, once we release a new version of the plugin.
8+
9+
The POT file is human-readable and named `woo-gutenberg-products-block.pot`. It will not be downloaded to the site that is using the WooCommerce Blocks plugin. If we would generate the POT file manually, it would look like this:
10+
11+
```pot
12+
"Project-Id-Version: WooCommerce Blocks\n"
13+
"POT-Creation-Date: 2022-05-25 19:01+0700\n"
14+
"PO-Revision-Date: 2022-05-25 19:00+0700\n"
15+
"Last-Translator: \n"
16+
"Language-Team: \n"
17+
"MIME-Version: 1.0\n"
18+
"Content-Type: text/plain; charset=UTF-8\n"
19+
"Content-Transfer-Encoding: 8bit\n"
20+
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
21+
"X-Generator: Poedit 3.0.1\n"
22+
"X-Poedit-Basepath: ..\n"
23+
"X-Poedit-Flags-xgettext: --add-comments=translators:\n"
24+
"X-Poedit-WPHeader: woocommerce-gutenberg-products-block.php\n"
25+
"X-Poedit-SourceCharset: UTF-8\n"
26+
"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;"
27+
"esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;"
28+
"_nx_noop:3c,1,2;__ngettext_noop:1,2\n"
29+
"X-Poedit-SearchPath-0: .\n"
30+
"X-Poedit-SearchPathExcluded-0: *.min.js\n"
31+
"X-Poedit-SearchPathExcluded-1: vendor\n"
32+
"X-Poedit-SearchPathExcluded-2: node_modules\n"
33+
34+
#: assets/js/atomic/blocks/product-elements/add-to-cart/constants.js:8
35+
msgid "Add to Cart"
36+
msgstr ""
37+
38+
#: assets/js/atomic/blocks/product-elements/add-to-cart/edit.js:39
39+
#: assets/js/blocks/handpicked-products/block.js:42
40+
#: assets/js/blocks/product-best-sellers/block.js:34
41+
#: assets/js/blocks/product-category/block.js:157
42+
#: assets/js/blocks/product-new/block.js:36
43+
#: assets/js/blocks/product-on-sale/block.js:52
44+
#: assets/js/blocks/product-tag/block.js:121
45+
#: assets/js/blocks/product-top-rated/block.js:36
46+
#: assets/js/blocks/products-by-attribute/block.js:46
47+
#: assets/js/blocks/single-product/edit/layout-editor.js:56
48+
msgid "Layout"
49+
msgstr ""
50+
51+
[...]
52+
```
53+
54+
See also <https://developer.wordpress.org/plugins/internationalization/localization/#pot-portable-object-template-files>.
55+
56+
### PO (Portable Object) file
57+
58+
PO stands for `Portable Object`, contains both the original and the translated strings and is based on the POT file. Similar to the POT file, it can also be created manually using [WP-CLI](https://wp-cli.org/) or [Poedit](https://poedit.net/). As mentioned before, this step is not necessary for the WooCommerce Blocks plugin, as it's hosted on WordPress.org. The PO file will be generated within 30 minutes after a new translation had been added via <https://translate.wordpress.org/projects/wp-plugins/woo-gutenberg-products-block/>.
59+
60+
The PO file is human-readable and named `woo-gutenberg-products-block-{LANGUAGE-CODE}.po`, e.g. `woo-gutenberg-products-block-de_DE.po`. It can be found in the `/wp-content/languages/plugin` folder. The PO file looks like this:
61+
62+
```po
63+
# Translation of Plugins - WooCommerce Blocks - Stable (latest release) in German
64+
# This file is distributed under the same license as the Plugins - WooCommerce Blocks - Stable (latest release) package.
65+
msgid ""
66+
msgstr ""
67+
"PO-Revision-Date: 2022-05-22 10:58:25+0000\n"
68+
"MIME-Version: 1.0\n"
69+
"Content-Type: text/plain; charset=UTF-8\n"
70+
"Content-Transfer-Encoding: 8bit\n"
71+
"Plural-Forms: nplurals=2; plural=n != 1;\n"
72+
"X-Generator: GlotPress/4.0.0-alpha.1\n"
73+
"Language: de\n"
74+
"Project-Id-Version: Plugins - WooCommerce Blocks - Stable (latest release)\n"
75+
76+
#: assets/js/blocks/featured-product/block.json
77+
#: build/featured-product/block.json
78+
msgctxt "block description"
79+
msgid "Visually highlight a product or variation and encourage prompt action."
80+
msgstr "Ein Produkt oder eine Variante visuell hervorheben und zum sofortigen Handeln auffordern."
81+
82+
#: assets/js/blocks/featured-product/block.json
83+
#: build/featured-product/block.json
84+
msgctxt "block title"
85+
msgid "Featured Product"
86+
msgstr "Hervorgehobenes Produkt"
87+
88+
[...]
89+
```
90+
91+
See also <https://developer.wordpress.org/plugins/internationalization/localization/#po-portable-object-files>.
92+
93+
### MO (Machine Object) file
94+
95+
MO stands for `Machine Object`, contains both the original and the translated strings and is based on the PO file. As the POT and the PO files, the MO file can be created manually, but this is not necessary for the WooCommerce Blocks plugin.
96+
97+
The MO file is only machine-readable and named `woo-gutenberg-products-block-{LANGUAGE-CODE}.mo`, e.g. `woo-gutenberg-products-block-de_DE.mo` It can be found in the `/wp-content/languages/plugin` folder. As the MO file is machine-readable only, it cannot be viewed and the MO file only handles translations within PHP files.
98+
99+
See also <https://developer.wordpress.org/plugins/internationalization/localization/#mo-machine-object-files>.
100+
101+
### JSON files
102+
103+
As mentioned before, the MO file only handle translations within PHP files. Translations within JavaScript and TypeScript files are handled by JSON files. These JSON files contain both the original and the translated strings and are also based on the PO file. Similar to the MO file, the JSON files can be manually created, which is not needed in this case.
104+
105+
The JSON files are human-readable and named `woo-gutenberg-products-block-{LANGUAGE-CODE}-{md5(FILE-PATH)}.json`, e.g. `woo-gutenberg-products-block-de_DE-0a066f0c536e17452f6345c5d072335b.json`. They can be found in the `/wp-content/languages/plugin` folder. The JSON files look like this:
106+
107+
```json
108+
{
109+
"translation-revision-date": "2022-05-22 10:58:25+0000",
110+
"generator": "GlotPress/4.0.0-alpha.1",
111+
"domain": "messages",
112+
"locale_data": {
113+
"messages": {
114+
"": {
115+
"domain": "messages",
116+
"plural-forms": "nplurals=2; plural=n != 1;",
117+
"lang": "de"
118+
},
119+
"Price between %1$s and %2$s": [ "Preis zwischen %1$s und %2$s" ],
120+
"Discounted price:": [ "Reduzierter Preis:" ],
121+
"Previous price:": [ "Vorheriger Preis:" ]
122+
}
123+
},
124+
"comment": { "reference": "build/product-price-frontend.js" }
125+
}
126+
```
127+
128+
## Loading translations
129+
130+
Loading translations for PHP files works differently to loading translations for JS/TS files.
131+
132+
### Loading translations for PHP files
133+
134+
As mentioned in [Translation Basics](translation-basics.md), loading the translations for PHP files does not regquire any extra code, as long as the plugin is hosted on WordPress.org and does not support WordPress versions prior to 4.6.
135+
136+
### Loading translations for TJS/TS files
137+
138+
To load JS/TS translations, you need to execute the `wp_set_script_translations()` function. Currently, this function is part of the following code block in `/src/Assets/Api.php`:
139+
140+
```php
141+
/**
142+
* Registers a script according to `wp_register_script`, adding the correct prefix, and additionally loading translations.
143+
*
144+
* When creating script assets, the following rules should be followed:
145+
* 1. All asset handles should have a `wc-` prefix.
146+
* 2. If the asset handle is for a Block (in editor context) use the `-block` suffix.
147+
* 3. If the asset handle is for a Block (in frontend context) use the `-block-frontend` suffix.
148+
* 4. If the asset is for any other script being consumed or enqueued by the blocks plugin, use the `wc-blocks-` prefix.
149+
*
150+
* @since 2.5.0
151+
* @throws Exception If the registered script has a dependency on itself.
152+
*
153+
* @param string $handle Unique name of the script.
154+
* @param string $relative_src Relative url for the script to the path from plugin root.
155+
* @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
156+
* @param bool $has_i18n Optional. Whether to add a script translation call to this file. Default: true.
157+
*/
158+
public function register_script( $handle, $relative_src, $dependencies = [], $has_i18n = true ) {
159+
$script_data = $this->get_script_data( $relative_src, $dependencies );
160+
161+
if ( in_array( $handle, $script_data['dependencies'], true ) ) {
162+
if ( $this->package->feature()->is_development_environment() ) {
163+
$dependencies = array_diff( $script_data['dependencies'], [ $handle ] );
164+
add_action(
165+
'admin_notices',
166+
function() use ( $handle ) {
167+
echo '<div class="error"><p>';
168+
/* translators: %s file handle name. */
169+
printf( esc_html__( 'Script with handle %s had a dependency on itself which has been removed. This is an indicator that your JS code has a circular dependency that can cause bugs.', 'woo-gutenberg-products-block' ), esc_html( $handle ) );
170+
echo '</p></div>';
171+
}
172+
);
173+
} else {
174+
throw new Exception( sprintf( 'Script with handle %s had a dependency on itself. This is an indicator that your JS code has a circular dependency that can cause bugs.', $handle ) );
175+
}
176+
}
177+
178+
/**
179+
* Filters the list of script dependencies.
180+
*
181+
* @param array $dependencies The list of script dependencies.
182+
* @param string $handle The script's handle.
183+
* @return array
184+
*/
185+
$script_dependencies = apply_filters( 'woocommerce_blocks_register_script_dependencies', $script_data['dependencies'], $handle );
186+
187+
wp_register_script( $handle, $script_data['src'], $script_dependencies, $script_data['version'], true );
188+
189+
if ( $has_i18n && function_exists( 'wp_set_script_translations' ) ) {
190+
wp_set_script_translations( $handle, 'woo-gutenberg-products-block', $this->package->get_path( 'languages' ) );
191+
}
192+
}
193+
```
194+
195+
## Loading fallback translations
196+
197+
By default, the WooCommerce Blocks plugin tries to load the translation from <https://translate.wordpress.org/projects/wp-plugins/woo-gutenberg-products-block/>. If a translation cannot be loaded, the plugin tries to load the corresponding translation from <https://translate.wordpress.org/projects/wp-plugins/woocommerce/>.
198+
199+
The code that loads the fallback translation, is located in `woocommerce-gutenberg-products-block.php`.
200+
201+
## Loading fallback translations for PHP files
202+
203+
The following function handles the loading of fallback translations for PHP files:
204+
205+
```php
206+
/**
207+
* Filter translations so we can retrieve translations from Core when the original and the translated
208+
* texts are the same (which happens when translations are missing).
209+
*
210+
* @param string $translation Translated text based on WC Blocks translations.
211+
* @param string $text Text to translate.
212+
* @param string $domain The text domain.
213+
* @return string WC Blocks translation. In case it's the same as $text, Core translation.
214+
*/
215+
function woocommerce_blocks_get_php_translation_from_core( $translation, $text, $domain ) {
216+
if ( 'woo-gutenberg-products-block' !== $domain ) {
217+
return $translation;
218+
}
219+
220+
// When translation is the same, that could mean the string is not translated.
221+
// In that case, load it from core.
222+
if ( $translation === $text ) {
223+
return translate( $text, 'woocommerce' ); // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction, WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.TextDomainMismatch
224+
}
225+
return $translation;
226+
}
227+
228+
add_filter( 'gettext', 'woocommerce_blocks_get_php_translation_from_core', 10, 3 );
229+
```
230+
231+
## Loading fallback translations for JS/TS files
232+
233+
The following function handles the loading of fallback translations for JS/TS files:
234+
235+
```php
236+
/**
237+
* WordPress will look for translation in the following order:
238+
* - wp-content/plugins/woocommerce-blocks/languages/woo-gutenberg-products-block-{locale}-{handle}.json
239+
* - wp-content/plugins/woocommerce-blocks/languages/woo-gutenberg-products-block-{locale}-{md5-handle}.json
240+
* - wp-content/languages/plugins/woo-gutenberg-products-block-{locale}-{md5-handle}.json
241+
*
242+
* We check if the last one exists, and if it doesn't we try to load the
243+
* corresponding JSON file from the WC Core.
244+
*
245+
* @param string|false $file Path to the translation file to load. False if there isn't one.
246+
* @param string $handle Name of the script to register a translation domain to.
247+
* @param string $domain The text domain.
248+
*
249+
* @return string|false Path to the translation file to load. False if there isn't one.
250+
*/
251+
function load_woocommerce_core_json_translation( $file, $handle, $domain ) {
252+
if ( 'woo-gutenberg-products-block' !== $domain ) {
253+
return $file;
254+
}
255+
256+
$lang_dir = WP_LANG_DIR . '/plugins';
257+
258+
/**
259+
* We only care about the translation file of the feature plugin in the
260+
* wp-content/languages folder.
261+
*/
262+
if ( false === strpos( $file, $lang_dir ) ) {
263+
return $file;
264+
}
265+
266+
// If the translation file for feature plugin exist, use it.
267+
if ( is_readable( $file ) ) {
268+
return $file;
269+
}
270+
271+
global $wp_scripts;
272+
273+
if ( ! isset( $wp_scripts->registered[ $handle ], $wp_scripts->registered[ $handle ]->src ) ) {
274+
return $file;
275+
}
276+
277+
$handle_src = explode( '/build/', $wp_scripts->registered[ $handle ]->src );
278+
$handle_filename = $handle_src[1];
279+
$locale = determine_locale();
280+
$lang_dir = WP_LANG_DIR . '/plugins';
281+
282+
// Translations are always based on the unminified filename.
283+
if ( substr( $handle_filename, -7 ) === '.min.js' ) {
284+
$handle_filename = substr( $handle_filename, 0, -7 ) . '.js';
285+
}
286+
287+
$core_path_md5 = md5( 'packages/woocommerce-blocks/build/' . $handle_filename );
288+
289+
/**
290+
* Return file path of the corresponding translation file in the WC Core is
291+
* enough because `load_script_translations()` will check for its existence
292+
* before loading it.
293+
*/
294+
return $lang_dir . '/woocommerce-' . $locale . '-' . $core_path_md5 . '.json';
295+
}
296+
297+
add_filter( 'load_script_translation_file', 'load_woocommerce_core_json_translation', 10, 3 );
298+
```
299+
300+
<!-- FEEDBACK -->
301+
302+
---
303+
304+
[We're hiring!](https://woocommerce.com/careers/) Come work with us!
305+
306+
🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-blocks/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./docs/testing/README.md)
307+
308+
<!-- /FEEDBACK -->

0 commit comments

Comments
 (0)