@ai-kits/wp-ag-kit 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -14
- package/STRUCTURE.md +9 -7
- package/agents/woocommerce-expert.md +32 -0
- package/agents/wordpress-expert.md +4 -2
- package/package.json +5 -3
- package/skills/woocommerce/SKILL.md +54 -0
- package/skills/woocommerce/references/backend-dev-guide.md +44 -0
- package/skills/woocommerce/references/code-entities.md +186 -0
- package/skills/woocommerce/references/code-quality.md +273 -0
- package/skills/woocommerce/references/code-review-guide.md +71 -0
- package/skills/woocommerce/references/coding-conventions.md +182 -0
- package/skills/woocommerce/references/copy-guidelines-guide.md +17 -0
- package/skills/woocommerce/references/data-integrity.md +164 -0
- package/skills/woocommerce/references/dependency-injection.md +102 -0
- package/skills/woocommerce/references/dev-cycle-guide.md +32 -0
- package/skills/woocommerce/references/file-entities.md +73 -0
- package/skills/woocommerce/references/hooks.md +87 -0
- package/skills/woocommerce/references/js-i18n-patterns.md +298 -0
- package/skills/woocommerce/references/markdown-guide.md +358 -0
- package/skills/woocommerce/references/markdown-linting.md +202 -0
- package/skills/woocommerce/references/php-i18n-patterns.md +83 -0
- package/skills/woocommerce/references/php-linting-patterns.md +304 -0
- package/skills/woocommerce/references/running-tests.md +249 -0
- package/skills/woocommerce/references/security-patterns.md +109 -0
- package/skills/woocommerce/references/sentence-case.md +177 -0
- package/skills/woocommerce/references/type-annotations.md +161 -0
- package/skills/woocommerce/references/unit-tests.md +362 -0
- package/skills/woocommerce/references/woocommerce-global-objects.md +89 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Dependency Injection
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Standard DI Pattern for `src/` Classes](#standard-di-pattern-for-src-classes)
|
|
6
|
+
- [Initializing Classes That Set Up Hooks](#initializing-classes-that-set-up-hooks)
|
|
7
|
+
- [Using the Container to Get Instances](#using-the-container-to-get-instances)
|
|
8
|
+
- [Why Use Dependency Injection?](#why-use-dependency-injection)
|
|
9
|
+
|
|
10
|
+
## Standard DI Pattern for `src/` Classes
|
|
11
|
+
|
|
12
|
+
Dependencies are injected via a `final` `init` method with `@internal` annotation (blank lines before/after).
|
|
13
|
+
|
|
14
|
+
**Example:**
|
|
15
|
+
|
|
16
|
+
```php
|
|
17
|
+
namespace Automattic\WooCommerce\Internal\Admin;
|
|
18
|
+
|
|
19
|
+
use Automattic\WooCommerce\Internal\Logging\Logger;
|
|
20
|
+
use Automattic\WooCommerce\Internal\DataStore\OrderDataStore;
|
|
21
|
+
|
|
22
|
+
class OrderProcessor {
|
|
23
|
+
private Logger $logger;
|
|
24
|
+
private OrderDataStore $data_store;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize the order processor with dependencies.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*
|
|
31
|
+
* @param Logger $logger The logger instance.
|
|
32
|
+
* @param OrderDataStore $data_store The order data store.
|
|
33
|
+
*/
|
|
34
|
+
final public function init( Logger $logger, OrderDataStore $data_store ) {
|
|
35
|
+
$this->logger = $logger;
|
|
36
|
+
$this->data_store = $data_store;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public function process( int $order_id ) {
|
|
40
|
+
$this->logger->log( "Processing order {$order_id}" );
|
|
41
|
+
// ...
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Initializing Classes That Set Up Hooks
|
|
47
|
+
|
|
48
|
+
Add to `includes/class-woocommerce.php` in `init_hooks()`:
|
|
49
|
+
|
|
50
|
+
- Use `$container->get( ClassName::class );`
|
|
51
|
+
- Add at end of "These classes set up hooks on instantiation" section
|
|
52
|
+
|
|
53
|
+
**Example in `includes/class-woocommerce.php`:**
|
|
54
|
+
|
|
55
|
+
```php
|
|
56
|
+
private function init_hooks() {
|
|
57
|
+
// ... existing code ...
|
|
58
|
+
|
|
59
|
+
// These classes set up hooks on instantiation
|
|
60
|
+
$container->get( SomeExistingClass::class );
|
|
61
|
+
$container->get( AnotherExistingClass::class );
|
|
62
|
+
$container->get( YourNewClass::class ); // Add your new class here
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Using the Container to Get Instances
|
|
67
|
+
|
|
68
|
+
When you need to get an instance of a class from the container elsewhere in the code:
|
|
69
|
+
|
|
70
|
+
```php
|
|
71
|
+
use Automattic\WooCommerce\Internal\Utils\DataParser;
|
|
72
|
+
|
|
73
|
+
// Get instance from container
|
|
74
|
+
$parser = wc_get_container()->get( DataParser::class );
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Singleton Behavior
|
|
78
|
+
|
|
79
|
+
**Important:** The container always retrieves the same instance of a given class (singleton pattern).
|
|
80
|
+
|
|
81
|
+
When different instances are needed (and only in this case), use `new` or the appropriate factory methods for the class when available.
|
|
82
|
+
|
|
83
|
+
**Example:**
|
|
84
|
+
|
|
85
|
+
```php
|
|
86
|
+
// Same instance every time - use container
|
|
87
|
+
$logger = wc_get_container()->get( Logger::class );
|
|
88
|
+
$same_logger = wc_get_container()->get( Logger::class ); // Same instance as above
|
|
89
|
+
|
|
90
|
+
// Different instances needed - use new or factory
|
|
91
|
+
$order1 = new WC_Order( 123 );
|
|
92
|
+
$order2 = new WC_Order( 456 ); // Different instance
|
|
93
|
+
|
|
94
|
+
// Using factory when available
|
|
95
|
+
$product = wc_get_product( 789 ); // Factory method
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Why Use Dependency Injection?
|
|
99
|
+
|
|
100
|
+
- Easy mocking in tests
|
|
101
|
+
- Swap dependencies without code changes
|
|
102
|
+
- Explicit dependencies in signature
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
# WooCommerce Development Cycle
|
|
3
|
+
|
|
4
|
+
This skill provides guidance for the WooCommerce development workflow, including running tests, code quality checks, and troubleshooting.
|
|
5
|
+
|
|
6
|
+
## Instructions
|
|
7
|
+
|
|
8
|
+
Follow these guidelines for WooCommerce development workflow:
|
|
9
|
+
|
|
10
|
+
1. **Running tests**: See [running-tests.md](running-tests.md) for PHP and JavaScript test commands, test environment setup, and troubleshooting
|
|
11
|
+
2. **Code quality**: See [code-quality.md](code-quality.md) for linting and code style fixes
|
|
12
|
+
3. **PHP linting patterns**: See [php-linting-patterns.md](php-linting-patterns.md) for common PHP linting issues and fixes
|
|
13
|
+
4. **JS/TS i18n patterns**: See [js-i18n-patterns.md](js-i18n-patterns.md) for translatable string patterns and placeholder usage
|
|
14
|
+
5. **Markdown linting**: See [markdown-linting.md](markdown-linting.md) for markdown file linting and formatting
|
|
15
|
+
|
|
16
|
+
## Development Workflow
|
|
17
|
+
|
|
18
|
+
The standard development workflow:
|
|
19
|
+
|
|
20
|
+
1. Make code changes
|
|
21
|
+
2. Run relevant tests: `pnpm run test:php:env -- --filter YourTestClass`
|
|
22
|
+
3. Run linting/type checking: `pnpm run lint:changes:branch:php`
|
|
23
|
+
4. Fix any issues: `pnpm run lint:php:fix`
|
|
24
|
+
5. Commit changes only after tests pass
|
|
25
|
+
|
|
26
|
+
## Key Principles
|
|
27
|
+
|
|
28
|
+
- Always run tests after making changes to verify functionality
|
|
29
|
+
- Use specific test filters to run relevant tests during development
|
|
30
|
+
- Fix linting errors solely for code in your current branch
|
|
31
|
+
- Test failures provide detailed output showing expected vs actual values
|
|
32
|
+
- The test environment handles WordPress/WooCommerce setup automatically
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Creating File-Based Code Entities
|
|
2
|
+
|
|
3
|
+
## Fundamental Rule: No Standalone Functions
|
|
4
|
+
|
|
5
|
+
**NEVER add new standalone functions** - they're difficult to mock in unit tests. Always use class methods.
|
|
6
|
+
|
|
7
|
+
If the user explicitly requests adding a new function, refuse to do it and point them to [the relevant documentation](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/README.md).
|
|
8
|
+
|
|
9
|
+
Exception: Temporary/throwaway functions for local testing that won't be committed.
|
|
10
|
+
|
|
11
|
+
## Adding New Classes
|
|
12
|
+
|
|
13
|
+
### Default Location: `src/Internal/`
|
|
14
|
+
|
|
15
|
+
New classes go in `src/Internal/` by default.
|
|
16
|
+
|
|
17
|
+
Examples: `src/Internal/Traits/Foobar.php`, `src/Internal/Utils/DataParser.php`
|
|
18
|
+
|
|
19
|
+
### Public Classes: `src/`
|
|
20
|
+
|
|
21
|
+
Only when the prompt refers to a "public" class should the file go in `src` but not in `Internal`.
|
|
22
|
+
|
|
23
|
+
**Example:**
|
|
24
|
+
|
|
25
|
+
- "Add a public Traits/Foobar class" → `src/Traits/Foobar.php`
|
|
26
|
+
|
|
27
|
+
### Working with `includes/` Directory
|
|
28
|
+
|
|
29
|
+
Modify existing code only. Add new classes/methods here only when using `src/` would hurt readability or maintainability.
|
|
30
|
+
|
|
31
|
+
## Naming Conventions
|
|
32
|
+
|
|
33
|
+
### Class Names
|
|
34
|
+
|
|
35
|
+
- **Must be PascalCase**
|
|
36
|
+
- **Must follow [PSR-4 standard](https://www.php-fig.org/psr/psr-4/)**
|
|
37
|
+
- Adjust the name given by the user if necessary
|
|
38
|
+
- Root namespace for the `src` directory is `Automattic\WooCommerce`
|
|
39
|
+
|
|
40
|
+
**Examples:**
|
|
41
|
+
|
|
42
|
+
```php
|
|
43
|
+
// User says: "create a data parser class"
|
|
44
|
+
// You create: DataParser.php
|
|
45
|
+
namespace Automattic\WooCommerce\Internal\Utils;
|
|
46
|
+
|
|
47
|
+
class DataParser {
|
|
48
|
+
// ...
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Namespace and Import Conventions
|
|
53
|
+
|
|
54
|
+
When referencing a namespaced class:
|
|
55
|
+
|
|
56
|
+
1. Always add a `use` statement with the fully qualified class name at the beginning of the file
|
|
57
|
+
2. Reference the short class name throughout the code
|
|
58
|
+
|
|
59
|
+
**Good:**
|
|
60
|
+
|
|
61
|
+
```php
|
|
62
|
+
use Automattic\WooCommerce\Internal\Utils\Foobar;
|
|
63
|
+
|
|
64
|
+
// Later in code:
|
|
65
|
+
$instance = $container->get( Foobar::class );
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Avoid:**
|
|
69
|
+
|
|
70
|
+
```php
|
|
71
|
+
// No use statement, using fully qualified name:
|
|
72
|
+
$instance = $container->get( \Automattic\WooCommerce\Internal\Utils\Foobar::class );
|
|
73
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Working with Hooks
|
|
2
|
+
|
|
3
|
+
## Hook Callback Naming Convention
|
|
4
|
+
|
|
5
|
+
Name hook callback methods: `handle_{hook_name}` with `@internal` annotation.
|
|
6
|
+
|
|
7
|
+
**Examples:**
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
/**
|
|
11
|
+
* Handle the woocommerce_init hook.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
public function handle_woocommerce_init() {
|
|
16
|
+
// Initialize components
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Handle the woocommerce_before_checkout hook.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*
|
|
24
|
+
* @param WC_Checkout $checkout The checkout object.
|
|
25
|
+
*/
|
|
26
|
+
public function handle_woocommerce_before_checkout( $checkout ) {
|
|
27
|
+
// Setup checkout process
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Hook Docblocks
|
|
32
|
+
|
|
33
|
+
If you modify a line that fires a hook without a docblock:
|
|
34
|
+
|
|
35
|
+
1. Add docblock with description and `@param` tags
|
|
36
|
+
2. Use `git log -S "hook_name"` to find when it was introduced
|
|
37
|
+
3. Add `@since` annotation with that version
|
|
38
|
+
|
|
39
|
+
```php
|
|
40
|
+
/**
|
|
41
|
+
* Fires after an order has been processed.
|
|
42
|
+
*
|
|
43
|
+
* @param int $order_id The processed order ID.
|
|
44
|
+
* @param array $order_data The order data.
|
|
45
|
+
*
|
|
46
|
+
* @since 8.2.0
|
|
47
|
+
*/
|
|
48
|
+
do_action( 'woocommerce_order_processed', $order_id, $order_data );
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Hook Documentation Requirements
|
|
52
|
+
|
|
53
|
+
All hooks must have docblocks that include:
|
|
54
|
+
|
|
55
|
+
- Description of when the hook fires
|
|
56
|
+
- `@param` tags for each parameter passed to the hook
|
|
57
|
+
- `@since` annotation with the version number (last line, with blank line before)
|
|
58
|
+
- For new hooks: Use the version from `includes/class-woocommerce.php` on trunk, removing `-dev` suffix
|
|
59
|
+
- For existing hooks: Use `git log -S "hook_name"` to find when it was introduced
|
|
60
|
+
|
|
61
|
+
**Action hook example:**
|
|
62
|
+
|
|
63
|
+
```php
|
|
64
|
+
/**
|
|
65
|
+
* Fires after a product is saved.
|
|
66
|
+
*
|
|
67
|
+
* @param int $product_id The product ID.
|
|
68
|
+
* @param WC_Product $product The product object.
|
|
69
|
+
*
|
|
70
|
+
* @since 9.5.0
|
|
71
|
+
*/
|
|
72
|
+
do_action( 'woocommerce_product_saved', $product_id, $product );
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Filter hook example:**
|
|
76
|
+
|
|
77
|
+
```php
|
|
78
|
+
/**
|
|
79
|
+
* Filters the product price before display.
|
|
80
|
+
*
|
|
81
|
+
* @param string $price The formatted price.
|
|
82
|
+
* @param WC_Product $product The product object.
|
|
83
|
+
*
|
|
84
|
+
* @since 9.5.0
|
|
85
|
+
*/
|
|
86
|
+
$price = apply_filters( 'woocommerce_product_price', $price, $product );
|
|
87
|
+
```
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# JavaScript/TypeScript i18n Patterns
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Translation Functions](#translation-functions)
|
|
7
|
+
- [Placeholder Patterns](#placeholder-patterns)
|
|
8
|
+
- [Translator Comments](#translator-comments)
|
|
9
|
+
- [Complex String Patterns](#complex-string-patterns)
|
|
10
|
+
- [Common Pitfalls](#common-pitfalls)
|
|
11
|
+
- [Quick Command Reference](#quick-command-reference)
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
WooCommerce uses WordPress i18n functions from `@wordpress/i18n` for
|
|
16
|
+
translatable strings. Brand names like "WooPayments" should use placeholders
|
|
17
|
+
to improve translation flexibility.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Translation Functions
|
|
24
|
+
|
|
25
|
+
### Basic Translation
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Simple string
|
|
29
|
+
__( 'Save changes', 'woocommerce' )
|
|
30
|
+
|
|
31
|
+
// String with placeholder
|
|
32
|
+
sprintf(
|
|
33
|
+
/* translators: %s: Payment provider name (e.g., WooPayments) */
|
|
34
|
+
__( 'Set up %s', 'woocommerce' ),
|
|
35
|
+
'WooPayments'
|
|
36
|
+
)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Plural Forms with `_n`
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { _n, sprintf } from '@wordpress/i18n';
|
|
43
|
+
|
|
44
|
+
sprintf(
|
|
45
|
+
/* translators: %d: Number of items */
|
|
46
|
+
_n(
|
|
47
|
+
'%d item selected',
|
|
48
|
+
'%d items selected',
|
|
49
|
+
count,
|
|
50
|
+
'woocommerce'
|
|
51
|
+
),
|
|
52
|
+
count
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Interpolated Elements with `createInterpolateElement`
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { createInterpolateElement } from '@wordpress/element';
|
|
60
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
61
|
+
|
|
62
|
+
createInterpolateElement(
|
|
63
|
+
sprintf(
|
|
64
|
+
/* translators: 1: Payment provider name */
|
|
65
|
+
__( 'Enable <strong>%1$s</strong> for your store.', 'woocommerce' ),
|
|
66
|
+
'WooPayments'
|
|
67
|
+
),
|
|
68
|
+
{
|
|
69
|
+
strong: <strong />,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Placeholder Patterns
|
|
75
|
+
|
|
76
|
+
### Single Placeholder
|
|
77
|
+
|
|
78
|
+
Use `%s` for a single placeholder:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
sprintf(
|
|
82
|
+
/* translators: %s: Payment provider name (e.g., WooPayments) */
|
|
83
|
+
__( 'Get paid with %s', 'woocommerce' ),
|
|
84
|
+
'WooPayments'
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Multiple Same Placeholders
|
|
89
|
+
|
|
90
|
+
Use numbered placeholders `%1$s` when the same value appears multiple times:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
sprintf(
|
|
94
|
+
/* translators: 1: Payment provider name (e.g., WooPayments) */
|
|
95
|
+
__(
|
|
96
|
+
'By using %1$s you agree to our Terms. Payments via %1$s are secure.',
|
|
97
|
+
'woocommerce'
|
|
98
|
+
),
|
|
99
|
+
'WooPayments'
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Multiple Different Placeholders
|
|
104
|
+
|
|
105
|
+
Use numbered placeholders `%1$s`, `%2$s` for different values:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
sprintf(
|
|
109
|
+
/* translators: 1: Payment provider name, 2: Extension names */
|
|
110
|
+
_n(
|
|
111
|
+
'Installing %1$s will activate %2$s extension.',
|
|
112
|
+
'Installing %1$s will activate %2$s extensions.',
|
|
113
|
+
extensionCount,
|
|
114
|
+
'woocommerce'
|
|
115
|
+
),
|
|
116
|
+
'WooPayments',
|
|
117
|
+
extensionNames
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Translator Comments
|
|
122
|
+
|
|
123
|
+
### Comment Placement
|
|
124
|
+
|
|
125
|
+
The translator comment must be placed **immediately before the `__()` or
|
|
126
|
+
`_n()` function**, not before `sprintf()`:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// ❌ WRONG - Comment before sprintf
|
|
130
|
+
/* translators: %s: Payment provider name */
|
|
131
|
+
sprintf(
|
|
132
|
+
__( 'Set up %s', 'woocommerce' ),
|
|
133
|
+
'WooPayments'
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// ✅ CORRECT - Comment inside sprintf, before __()
|
|
137
|
+
sprintf(
|
|
138
|
+
/* translators: %s: Payment provider name */
|
|
139
|
+
__( 'Set up %s', 'woocommerce' ),
|
|
140
|
+
'WooPayments'
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Comment Format for Numbered Placeholders
|
|
145
|
+
|
|
146
|
+
When using numbered placeholders like `%1$s`, use just the number in the comment:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// ❌ WRONG - Using %1$s in comment
|
|
150
|
+
/* translators: %1$s: Provider name, %2$s: Country */
|
|
151
|
+
|
|
152
|
+
// ✅ CORRECT - Using just numbers
|
|
153
|
+
/* translators: 1: Provider name, 2: Country */
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Descriptive Comments
|
|
157
|
+
|
|
158
|
+
Always provide context for translators:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// ❌ WRONG - No context
|
|
162
|
+
/* translators: %s: name */
|
|
163
|
+
|
|
164
|
+
// ✅ CORRECT - Clear context
|
|
165
|
+
/* translators: %s: Payment provider name (e.g., WooPayments) */
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Complex String Patterns
|
|
169
|
+
|
|
170
|
+
### Combining `sprintf`, `_n`, and `createInterpolateElement`
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
installText: ( extensionsString: string ) => {
|
|
174
|
+
const count = extensionsString.split( ', ' ).length;
|
|
175
|
+
return createInterpolateElement(
|
|
176
|
+
sprintf(
|
|
177
|
+
/* translators: 1: Provider name, 2: Extension names */
|
|
178
|
+
_n(
|
|
179
|
+
'Installing <strong>%1$s</strong> activates <strong>%2$s</strong>.',
|
|
180
|
+
'Installing <strong>%1$s</strong> activates <strong>%2$s</strong>.',
|
|
181
|
+
count,
|
|
182
|
+
'woocommerce'
|
|
183
|
+
),
|
|
184
|
+
'WooPayments',
|
|
185
|
+
extensionsString
|
|
186
|
+
),
|
|
187
|
+
{ strong: <strong /> }
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Strings with Links
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
createInterpolateElement(
|
|
196
|
+
sprintf(
|
|
197
|
+
/* translators: 1: Payment provider name */
|
|
198
|
+
__(
|
|
199
|
+
'Learn more about <a>%1$s</a> features.',
|
|
200
|
+
'woocommerce'
|
|
201
|
+
),
|
|
202
|
+
'WooPayments'
|
|
203
|
+
),
|
|
204
|
+
{
|
|
205
|
+
a: (
|
|
206
|
+
<a
|
|
207
|
+
href="https://example.com"
|
|
208
|
+
target="_blank"
|
|
209
|
+
rel="noopener noreferrer"
|
|
210
|
+
/>
|
|
211
|
+
),
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Common Pitfalls
|
|
217
|
+
|
|
218
|
+
### Curly Apostrophes
|
|
219
|
+
|
|
220
|
+
TypeScript files may use curly apostrophes (`'` U+2019) instead of straight
|
|
221
|
+
apostrophes (`'` U+0027). When editing, preserve the original character:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// Original uses curly apostrophe - preserve it
|
|
225
|
+
__( 'I don't want to install another plugin', 'woocommerce' )
|
|
226
|
+
// ^ This is U+2019, not U+0027
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### ESLint i18n Rules
|
|
230
|
+
|
|
231
|
+
The `@wordpress/i18n-translator-comments` rule requires comments directly
|
|
232
|
+
before the translation function:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// ❌ ESLint error - comment not adjacent to __()
|
|
236
|
+
const title = sprintf(
|
|
237
|
+
/* translators: %s: Provider name */
|
|
238
|
+
|
|
239
|
+
__( 'Set up %s', 'woocommerce' ),
|
|
240
|
+
'WooPayments'
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// ✅ Correct - comment directly before __()
|
|
244
|
+
const title = sprintf(
|
|
245
|
+
/* translators: %s: Provider name */
|
|
246
|
+
__( 'Set up %s', 'woocommerce' ),
|
|
247
|
+
'WooPayments'
|
|
248
|
+
);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Brand Names
|
|
252
|
+
|
|
253
|
+
Always use placeholders for brand names to improve translation flexibility:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// ✅ CORRECT - Use placeholder for brand name
|
|
257
|
+
title: sprintf(
|
|
258
|
+
/* translators: %s: Payment provider name */
|
|
259
|
+
__( 'Get paid with %s', 'woocommerce' ),
|
|
260
|
+
'WooPayments'
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
// ✅ CORRECT - Use placeholder in descriptions
|
|
264
|
+
description: sprintf(
|
|
265
|
+
/* translators: %s: Payment provider name */
|
|
266
|
+
__( 'Enable PayPal alongside %s', 'woocommerce' ),
|
|
267
|
+
'WooPayments'
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
// ❌ WRONG - Hardcoded brand name
|
|
271
|
+
title: __( 'Get paid with WooPayments', 'woocommerce' )
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Quick Command Reference
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# Lint specific file
|
|
278
|
+
npx eslint client/path/to/file.tsx
|
|
279
|
+
|
|
280
|
+
# Fix specific file
|
|
281
|
+
npx eslint --fix client/path/to/file.tsx
|
|
282
|
+
|
|
283
|
+
# Type check
|
|
284
|
+
pnpm run ts:check
|
|
285
|
+
|
|
286
|
+
# ❌ NEVER lint entire codebase
|
|
287
|
+
pnpm run lint # NO - lints everything
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Summary
|
|
291
|
+
|
|
292
|
+
| Pattern | Example |
|
|
293
|
+
|-------------------------------|----------------------------------------------|
|
|
294
|
+
| **Single placeholder** | `sprintf( __( 'Set up %s' ), 'Name' )` |
|
|
295
|
+
| **Repeated placeholder** | `sprintf( __( '%1$s via %1$s' ), 'Name' )` |
|
|
296
|
+
| **Multiple placeholders** | `sprintf( __( '%1$s in %2$s' ), 'A', 'B' )` |
|
|
297
|
+
| **Comment format (simple)** | `/* translators: %s: Provider name */` |
|
|
298
|
+
| **Comment format (numbered)** | `/* translators: 1: Provider, 2: Country */` |
|