Skip to content

Enforce lazy translations: add no-module-level-translations ESLint rule - #3688

Closed
sejas wants to merge 7 commits into
trunkfrom
stu-1771-lint-translate-constants
Closed

Enforce lazy translations: add no-module-level-translations ESLint rule#3688
sejas wants to merge 7 commits into
trunkfrom
stu-1771-lint-translate-constants

Conversation

@sejas

@sejas sejas commented Jun 3, 2026

Copy link
Copy Markdown
Member

Related issues

How AI was used in this PR

AI assistance was used to author the new ESLint rule and to apply the repetitive "wrap the translation in a lazy getter" refactor across the affected files. Every change was reviewed by hand and verified with npm run lint, npm run typecheck, and npm test (1961 tests passing).

Proposed Changes

Translatable strings are sometimes evaluated at module-load time (e.g. const LABEL = __( 'Label' )). In the long-lived desktop renderer the user can change the app language at runtime, but a string captured at import time stays frozen in the language that was active when the module first loaded — so parts of the UI silently fail to translate after a language switch.

This adds a lint rule that catches the mistake at author time and makes the existing offenders lazy so they react to language changes:

  • New studio/no-module-level-translations ESLint rule flags __(), _x(), _n(), and _nx() calls that run at module scope. It allows translations that are wrapped in a function (evaluated at render/call time) and ignores bare discarded statements used only to feed the translation extractor. The one-shot CLI loads its locale before importing modules, so the rule is turned off for apps/cli.
  • Documents the convention in AGENTS.md.
  • Makes all flagged strings lazy (widget/control labels, settings, skills constants, shared tools/common helpers, etc.), so they now follow the active language after a runtime switch.

No user-visible text changes; the user-facing effect is that affected labels now translate correctly when the language is changed without a restart.

Testing Instructions

  • npm run lint — passes; try adding const X = __( 'Test' ) at module scope in a renderer file and confirm the rule reports it, then wrap it as const getX = () => __( 'Test' ) and confirm it passes.
  • npm run typecheck — passes.
  • npm test — passes (includes the rule's unit tests under tools/eslint-plugin-studio).
  • Manually: switch the app language in Settings and confirm the affected labels (widget controls, settings options) update without restarting.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
sejas and others added 3 commits June 3, 2026 13:07
Translations evaluated at module load are captured before the locale loads
and never update when the language changes at runtime in the renderer. Add
the studio/no-module-level-translations rule (off for the one-shot CLI),
document the convention in AGENTS.md, wire the plugin tests into vitest, and
make all flagged translations lazy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Allow discarded translation statements (e.g. `__( 'Next' );`) that exist only
to feed the translation extractor, removing the need for an inline eslint-disable
in additional-phrases.ts. Drop the unused configurable `functions` option.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pre-push hook lints changed files individually; the eslint-plugin-studio
sources/tests and the vitest config files are not part of any tsconfig, so the
typescript-eslint project service reported parsing errors. Add them to
allowDefaultProject so they lint cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sejas sejas self-assigned this Jun 3, 2026
@sejas

sejas commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

I think the direction is good. I'll create a fresh PR to cover new code and avoid resolving conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant