Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ jobs:
- name: Regenerate from live OpenAPI
run: npm run generate

# The translation template is a build artifact, regenerated every release so it
# always describes the code being shipped. wp.org extracts its own copy from the
# source; this one serves translators working from the GitHub zip.
- name: Refresh the translation template
run: |
curl -sSL -o /tmp/wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php /tmp/wp-cli.phar i18n make-pot . languages/roxyapi.pot --slug=roxyapi --skip-audit --allow-root

- name: Lint
run: vendor/bin/phpcs --standard=phpcs.xml.dist

Expand Down
58 changes: 51 additions & 7 deletions bin/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,47 @@ function extractFormSpec( op ) {
return { sections, flatFields };
}

/**
* Form-spec keys whose value is prose a VISITOR reads, so it has to reach the
* translators. Everything else in a field spec is machinery: `name` is the
* request key, `type` picks the control, and `enum` values are the API's own
* strings submitted verbatim (FormRenderer derives their display text with
* ucwords(), so translating them here would post a translated value).
*
* ONE list, consumed by BOTH form emitters. The long-tail forms and the hero
* forms build their spec arrays in separate functions, so this must stay a
* single shared list or a change applies to only half the forms.
*/
const TRANSLATABLE_SPEC_KEYS = new Set( [ 'label', 'help', 'placeholder' ] );

/**
* A PHP string literal wrapped in `__()` so `wp i18n make-pot` and
* translate.wordpress.org can both extract it.
*
* Extraction is purely STATIC: the tooling reads the literal at the call site
* and never evaluates anything, so the string must be inlined here rather than
* passed through a variable. An empty or non-string value falls back to a plain
* literal, because an empty msgid collides with the PO header.
* @param v
*/
function translatablePhp( v ) {
return typeof v === 'string' && v !== ''
? `__( ${ phpLiteral( v ) }, 'roxyapi' )`
: phpLiteral( v );
}

/**
* PHP for one `'key' => value` entry of a form-spec array, translated when the
* key is visitor-facing.
* @param key
* @param value
*/
function specEntryPhp( key, value ) {
return TRANSLATABLE_SPEC_KEYS.has( key )
? translatablePhp( value )
: phpLiteral( value );
}

/**
* PHP literal for a JS value — strings/numbers/bools/lists. Strict ASCII safe.
* @param v
Expand Down Expand Up @@ -700,15 +741,18 @@ function emitFormPhp( op ) {
const parts = Object.entries( f )
.map(
( [ k, v ] ) =>
`\t\t\t\t\t\t'${ k }' => ${ phpLiteral( v ) },`
`\t\t\t\t\t\t'${ k }' => ${ specEntryPhp(
k,
v
) },`
)
.join( '\n' );
return `\t\t\t\t\tarray(\n${ parts }\n\t\t\t\t\t),`;
} )
.join( '\n' );
return `\t\t\t\tarray(
'name' => ${ phpLiteral( s.name ) },
'label' => ${ phpLiteral( s.label ) },
'label' => ${ translatablePhp( s.label ) },
'fields' => array(
${ fieldsPhp }
),
Expand All @@ -721,7 +765,7 @@ ${ fieldsPhp }
const parts = Object.entries( f )
.map(
( [ k, v ] ) =>
`\t\t\t\t\t'${ k }' => ${ phpLiteral( v ) },`
`\t\t\t\t\t'${ k }' => ${ specEntryPhp( k, v ) },`
)
.join( '\n' );
return `\t\t\t\tarray(\n${ parts }\n\t\t\t\t),`;
Expand Down Expand Up @@ -750,7 +794,7 @@ class ${ className } {
public static function spec(): array {
return array(
'operation_id' => ${ phpLiteral( op.operationId ) },
'title' => ${ phpLiteral( title ) },
'title' => ${ translatablePhp( title ) },
'submit_label' => __( 'Get reading', 'roxyapi' ),
'sections' => array(
${ sectionsPhp }
Expand Down Expand Up @@ -2347,7 +2391,7 @@ function heroFormFieldToPhp( field, indent ) {
continue;
}
lines.push(
`${ indent }\t'${ key }' => ${ phpLiteral( field[ key ] ) },`
`${ indent }\t'${ key }' => ${ specEntryPhp( key, field[ key ] ) },`
);
}
return `${ indent }array(\n${ lines.join( '\n' ) }\n${ indent }),`;
Expand Down Expand Up @@ -2391,7 +2435,7 @@ function emitHeroFormPhp( tagSuffix, cfg ) {
.join( '\n' );
sectionsPhp = `\t\t\tarray(
'name' => ${ phpLiteral( sectionName ) },
'label' => ${ phpLiteral( sectionLabel ) },
'label' => ${ translatablePhp( sectionLabel ) },
'fields' => array(
${ fieldEntries }
),
Expand Down Expand Up @@ -2437,7 +2481,7 @@ class ${ formClassName } {
return array(
'operation_id' => ${ phpLiteral( formId ) },
'render_operation_id' => ${ phpLiteral( cfg.operationId ) },
'title' => ${ phpLiteral( title ) },
'title' => ${ translatablePhp( title ) },
'submit_label' => __( ${ phpLiteral( submitLabel ) }, 'roxyapi' ),
'sections' => array(
${ sectionsPhp }
Expand Down
Loading