Skip to content

add i18n support - #6796

Draft
benedikt-bartscher wants to merge 44 commits into
reflex-dev:mainfrom
benedikt-bartscher:add-i18n
Draft

benedikt-bartscher wants to merge 44 commits into
reflex-dev:mainfrom
benedikt-bartscher:add-i18n

Conversation

@benedikt-bartscher

@benedikt-bartscher benedikt-bartscher commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

closes #2339

@codspeed

codspeed Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 6.71%

⚠️ 10 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 147 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

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)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[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

  1. P1 Switcher leaves server text unchanged ▶

Summary

Adds optional internationalization through reflex-i18n:

  • Apps can translate text and format values using a visitor’s locale.
  • Apps can serve translated pages under locale-based URL paths.
  • Plugins can set event context and add routes or computed-value dependencies.
  • The Reflex CLI adds commands for translation catalogs and installed packages.
  • Hot reload removes dependency links to states that are being reloaded.

No new findings were accepted.

Reviews (38) · Last reviewed commit: "Fix first-load locale, regional catalog ..." · Reviewed by Greptile

Comment thread packages/reflex-base/src/reflex_base/event/processor/scope.py Outdated
Comment thread packages/reflex-i18n/src/reflex_i18n/runtime.py
Comment thread packages/reflex-i18n/src/reflex_i18n/_web/i18n.js
Comment thread packages/reflex-i18n/src/reflex_i18n/_web/i18n.js Outdated
Comment thread reflex/__init__.py
@benedikt-bartscher
benedikt-bartscher marked this pull request as ready for review July 18, 2026 11:09
@benedikt-bartscher
benedikt-bartscher requested review from a team and Alek99 as code owners July 18, 2026 11:09
@benedikt-bartscher benedikt-bartscher changed the title first i18n wip add basic i18n support Jul 18, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/reflex-i18n/pyproject.toml Outdated
Comment on lines +12 to +13
"reflex-base >= 0.9.7.dev0",
"reflex >= 0.9.7.dev0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this open for the maintainers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread reflex/reflex.py Outdated
@benedikt-bartscher benedikt-bartscher changed the title add basic i18n support add i18n support Jul 18, 2026
@benedikt-bartscher

Copy link
Copy Markdown
Contributor Author

@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 masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@benedikt-bartscher
benedikt-bartscher marked this pull request as ready for review September 4, 2026 12:26

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/reflex-i18n/src/reflex_i18n/_web/i18n.js Outdated
Comment thread packages/reflex-i18n/src/reflex_i18n/catalog.py Outdated
Comment thread packages/reflex-i18n/src/reflex_i18n/catalog.py Outdated
Comment thread packages/reflex-i18n/src/reflex_i18n/runtime.py
Comment thread packages/reflex-i18n/src/reflex_i18n/runtime.py Outdated
Comment thread packages/reflex-base/src/reflex_base/vars/dep_tracking.py
Comment thread packages/reflex-base/src/reflex_base/vars/dep_tracking.py Outdated
Comment thread reflex/istate/proxy.py Outdated
Comment thread packages/reflex-i18n/src/reflex_i18n/plugin.py
Comment thread reflex/i18n.py
…r/plural conflict check, module-qualified and nested dep tracking, config validation

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/reflex-i18n/src/reflex_i18n/config.py Outdated
Comment thread packages/reflex-i18n/src/reflex_i18n/registry.py
Comment thread packages/reflex-i18n/src/reflex_i18n/registry.py

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider b5af4f2

more harmonious interfacing with the var system makes the code easier to maintain

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/reflex-i18n/src/reflex_i18n/state.py Outdated
const key = locale + " " + JSON.stringify(options ?? {});
let formatter = cache.get(key);
if (formatter === undefined) {
formatter = new Ctor(locale, options);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
formatter = new Ctor(locale, options);
formatter = new Ctor(locale.replace(/_/g, "-"), options);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/reflex-i18n/src/reflex_i18n/cli.py Outdated
Comment thread packages/reflex-i18n/src/reflex_i18n/cli.py
Comment thread packages/reflex-base/src/reflex_base/plugins/base.py
Comment thread tests/units/reflex_i18n/test_state.py
Comment thread docs/i18n/overview.md Outdated
Comment thread tests/units/reflex_i18n/test_format.py
Comment thread tests/units/reflex_i18n/test_catalog.py Outdated
Comment thread tests/units/reflex_i18n/test_plugin.py
Comment thread packages/reflex-i18n/src/reflex_i18n/_web/i18n.js Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread docs/i18n/overview.md Outdated

@FarhanAliRaza FarhanAliRaza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment on lines +135 to +136
return read_po(f, locale=locale)
return Catalog(locale=locale)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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
@masenf masenf added the documentation Improvements or additions to documentation label Oct 6, 2026
@benedikt-bartscher
benedikt-bartscher marked this pull request as draft October 8, 2026 20:15
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.
Comment on lines +451 to +453
: (event) => {
event.preventDefault();
setLocale(locale);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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

Comment thread packages/reflex-i18n/src/reflex_i18n/cli.py Outdated
Comment thread packages/reflex-i18n/pyproject.toml Outdated
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.

This branch has not been deployed

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

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Internationalization (I18N) and localization (L10N) (Feature Request)

3 participants