Repository navigation
add i18n support - #6796
add i18n support#6796benedikt-bartscher wants to merge 44 commits into
Conversation
Merging this PR will degrade performance by 6.71%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ |
test_cli_startup[help] |
803.4 ms | 861.3 ms | -6.71% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing benedikt-bartscher:add-i18n (cc80aea) with main (d3f4ab1)
[Medium impact] The PR adds locale-aware behavior to app rendering and extends Reflex plugin and CLI workflows. The PR still needs the cookie-mode language switcher to update the server before merging. Findings
SummaryAdds optional internationalization through
No new findings were accepted. Reviews (38) · Last reviewed commit: "Fix first-load locale, regional catalog ..." · Reviewed by Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b9a5f5342
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "reflex-base >= 0.9.7.dev0", | ||
| "reflex >= 0.9.7.dev0", |
There was a problem hiding this comment.
Remove development pins before publishing reflex-i18n
With the new dispatch-release option, selecting reflex-i18n will create a release and trigger .github/workflows/publish.yml, whose publish job runs scripts/check_min_deps.py --check-dev-pins "$PACKAGE" and explicitly rejects published dependencies containing *.dev pins. Because these two requirements are part of reflex-i18n's published metadata, the publish workflow for any reflex-i18n release will fail at that gate until they are pinned to released versions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
I'll leave this open for the maintainers
There was a problem hiding this comment.
Still for the maintainers, but with the current picture after merging main (c00c5ff): reflex itself carries the same kind of pin (reflex-base >= 0.9.12.dev0), so a dev pin in-tree is the normal state and the publish gate is what forces the bump. reflex-i18n's constraint is genuinely unreleased — it needs register_event_scope_provider, register_implicit_dependency and ExpandRoutesContext, all added by this PR — so no published floor would be truthful, and I'd rather not invent one. It uses none of the API #7268 raised floors for. check_min_deps.py reflex-i18n passes; --check-dev-pins reports the pin as expected.
|
@masenf could you please take a brief look at this from an architectural design and DX perspective? it's totally okay if you have smth completly different in mind |
masenf
left a comment
There was a problem hiding this comment.
@benedikt-bartscher thanks for adding this and sorry for the review back-up. i did an initial look and i think the added extension points make sense and the API feels nice.
one potential issue i see affects SEO performance of the SSG static versions of the site -- since the translation depends on state, other languages might not be indexable by crawlers.
e68fb65 to
3e540c0
Compare
There was a problem hiding this comment.
All reported issues were addressed across 55 files
Requires human review: Auto-approval blocked because this review re-detected 2 unresolved issues already reported by Cubic.
Re-trigger cubic
…r/plural conflict check, module-qualified and nested dep tracking, config validation
There was a problem hiding this comment.
All reported issues were addressed across 18 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…fy registry lifetime
Resolve conflicts in tests/units/istate/test_proxy.py, tests/units/test_state.py and uv.lock. Mark I18nState.is_user_defined with _override_base_method for main's reserved state name validation, and regenerate pyi_hashes.json.
… package Adding reflex-i18n pushes the package count past the workflow_dispatch input limit, so reflex-release sync renders the free-text package field instead of per-package checkboxes.
There was a problem hiding this comment.
6 issues found across 56 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/reflex-i18n/src/reflex_i18n/catalog.py">
<violation number="1" location="packages/reflex-i18n/src/reflex_i18n/catalog.py:39">
P2: When a `.po` file lacks a plural or language header, `read_po_catalog` gives Babel no locale, so generated modules use the generic English plural rule. Pass the locale derived from the catalog filename to `read_po`, matching the CLI loader.</violation>
</file>
<file name="packages/reflex-i18n/src/reflex_i18n/nav.py">
<violation number="1" location="packages/reflex-i18n/src/reflex_i18n/nav.py:57">
P2: When `I18nPlugin` uses its default cookie-based mode, this still renders links such as `/de/current-page` even though URL-prefixed routes are not generated, so every switcher link can 404. Require URL routing before creating this component, or make the switcher use the cookie-based `setLocale` behavior when routing is disabled.</violation>
</file>
<file name="packages/reflex-i18n/src/reflex_i18n/runtime.py">
<violation number="1" location="packages/reflex-i18n/src/reflex_i18n/runtime.py:190">
P2: Configured locales containing underscores never negotiate from normal BCP 47 `Accept-Language` tags. Normalize supported locales from `_` to `-` when building both lookup maps, as is already done for the request tag.</violation>
</file>
<file name="packages/reflex-i18n/src/reflex_i18n/_web/i18n.js">
<violation number="1" location="packages/reflex-i18n/src/reflex_i18n/_web/i18n.js:122">
P2: Localized prerendered pages have no route-specific `<html lang>` or `dir` until hydration, reducing accessibility and SEO correctness for crawlers and users. Emit these attributes from the server-rendered document or another SSR-aware route metadata mechanism.</violation>
<violation number="2" location="packages/reflex-i18n/src/reflex_i18n/_web/i18n.js:163">
P2: When an underscore-separated RTL locale is configured, this check marks the document as left-to-right. Split on both supported locale separators, and apply the same normalization in `LocaleRoute`.</violation>
<violation number="3" location="packages/reflex-i18n/src/reflex_i18n/_web/i18n.js:220">
P1: When a configured locale uses an underscore, `getFormatter` passes the tag directly to `Intl`, which throws before formatting any value. Normalize underscores to hyphens before constructing the formatter.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const key = locale + " " + JSON.stringify(options ?? {}); | ||
| let formatter = cache.get(key); | ||
| if (formatter === undefined) { | ||
| formatter = new Ctor(locale, options); |
There was a problem hiding this comment.
P1: When a configured locale uses an underscore, getFormatter passes the tag directly to Intl, which throws before formatting any value. Normalize underscores to hyphens before constructing the formatter.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-i18n/src/reflex_i18n/_web/i18n.js, line 220:
<comment>When a configured locale uses an underscore, `getFormatter` passes the tag directly to `Intl`, which throws before formatting any value. Normalize underscores to hyphens before constructing the formatter.</comment>
<file context>
@@ -0,0 +1,410 @@
+ const key = locale + " " + JSON.stringify(options ?? {});
+ let formatter = cache.get(key);
+ if (formatter === undefined) {
+ formatter = new Ctor(locale, options);
+ if (cache.size >= FORMATTER_CACHE_LIMIT) {
+ cache.delete(cache.keys().next().value);
</file context>
| formatter = new Ctor(locale, options); | |
| formatter = new Ctor(locale.replace(/_/g, "-"), options); |
There was a problem hiding this comment.
Fixed at the root in 9ec44ec: I18nConfig now rejects underscore locales outright instead of normalizing them at each use site. Locales double as URL segments, Intl tags and Accept-Language matches, all hyphen-separated, so the underscore spelling has no consistent meaning here.
There was a problem hiding this comment.
All reported issues were addressed across 26 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
FarhanAliRaza
left a comment
There was a problem hiding this comment.
I exercised the translation example in Chrome with cookie and URL routing, compared client and server translations, and ran a production export and the focused tests. I also reran catalog initialization and loading with language-only and regional locale tags.
See the inline comments.
| ? undefined | ||
| : (event) => { | ||
| event.preventDefault(); | ||
| setLocale(locale); |
There was a problem hiding this comment.
[P2] Synchronize the backend locale from the cookie-mode switcher
With default cookie routing, this calls only the provider’s client setter; it never dispatches I18nState.set_locale. In the live en/de example, clicking de changes rx.t("Hello") to Hallo, but a computed var returning gettext("Welcome") stays Welcome, even after another backend event. The same var becomes Willkommen when using rx.i18n.set_locale("de"). Please make the cookie-mode switcher update both the client and server locale so dynamic translations follow the selected language.
| return read_po(f, locale=locale) | ||
| return Catalog(locale=locale) |
There was a problem hiding this comment.
[P2] Convert accepted locale tags before passing them to Babel
I18nConfig accepts hyphenated tags such as pt-BR and zh-Hant, but both calls pass them verbatim to Babel, which expects underscore-separated locale identifiers. uv run reflex i18n init pt-BR fails here with ValueError: expected only letters, got 'pt-br'; reading an existing catalog fails the same way, while de succeeds. Normalize the locale for both Babel calls (as the runtime catalog loader already does), while retaining the configured tag in filenames and URLs.
# Conflicts: # .github/workflows/dispatch_release.yml # packages/reflex-base/src/reflex_base/vars/dep_tracking.py # tests/units/test_state.py
Upstream now deletes the dispatch-time state tree locals before a background handler runs (reflex-dev#7447), while this branch enters the event scope around that handler with the same root state. The background scope is now created before the locals are deleted, and _EventScope drops its root state reference once entered, so a registered provider (e.g. the i18n locale) no longer pins the tree; test_scope.py covers the release. reflex-i18n takes upstream's workspace-wide Python 3.11 floor and the 3.11-3.15 classifiers. uv.lock is upstream's, relocked with uv 0.12.5 (the CI version) to add reflex-i18n and babel; pyi_hashes.json is regenerated.
| : (event) => { | ||
| event.preventDefault(); | ||
| setLocale(locale); |
There was a problem hiding this comment.
Switcher leaves server text unchanged
In cookie mode, LanguageSwitcher only writes the browser cookie and changes React state. It never sends I18nState.set_locale, so server translations keep using the old locale_cookie, even on later ordinary events. Browser cookies are read back during navigation or connection setup, not on each event.
Send the server locale change too, as the Python set_locale helper does.
Knowledge Base Used: State management and event processing
Build the hydrate snapshot in a locale scope entered after browser storage is applied, convert hyphenated tags for Babel in the catalog CLI, and raise the reflex and reflex-base floors to the dev release that has the APIs reflex-i18n imports.
closes #2339