Enforce lazy translations: add no-module-level-translations ESLint rule - #4336
Enforce lazy translations: add no-module-level-translations ESLint rule#4336sejas wants to merge 2 commits into
Conversation
…y translations Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📊 Performance Test ResultsComparing dcfede5 vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
bcotrim
left a comment
There was a problem hiding this comment.
Found a bug while testing: in Settings → AI, the Instructions textarea loses focus after every keystroke.
It comes from the getFields() change in studio-code-panel.tsx — the new array identity on each render makes DataForm remount the field.
Which leads to something to consider: apps/ui reloads the window on language change, so module-level translations can't go stale there. The rule's premise only holds for apps/studio, which swaps locale data live without a reload. Could we exempt apps/ui alongside apps/cli and revert the changes in that app?
That would fix the focus bug for free.
Longer term we could explore dropping the reload, but imo the impact doesn't justify the work.
What do you think?
Related issues
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.How AI was used in this PR
AI assistance authored the ESLint rule and applied 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 the affectednpm testsuites.Proposed Changes
This adds a lint rule that catches the mistake at author time and makes the existing offenders lazy so they react to language changes:
studio/no-module-level-translationsESLint rule flags__(),_x(),_n(), and_nx()calls that run at module scope. It allows translations 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 forapps/cli.AGENTS.md.packages/commonhelpers, 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 addingconst X = __( 'Test' )at module scope in a renderer file and confirm the rule reports it, then wrap it asconst getX = () => __( 'Test' )and confirm it passes.npm run typecheck— passes.npm test -- tools/eslint-plugin-studio— passes (includes the rule's unit tests).Pre-merge Checklist