Skip to content

add i18n support - #6796

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

benedikt-bartscher wants to merge 51 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 not alter performance

1 warning

⚠️ 18 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 148 untouched benchmarks


Comparing benedikt-bartscher:add-i18n (8517828) with main (656e8c7)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The reviewed changes appear safe to merge, and the switcher now updates both browser and server translations.

Summary

Adds internationalization through reflex-i18n, including translations, locale-aware formatting, language switching, URL-based locales, and catalog commands.

  • Reflex apps can translate text and format values for each locale.
  • Locale-prefixed app pages show the matching language and language links.
  • The Reflex command line can extract and check translation catalogs.
  • Reflex events and computed values can carry locale context across updates.
  • Reloaded state classes no longer leave dependency links behind.
  • The new i18n package is wired into the workspace and release workflow.

Reviews (43) · Last reviewed commit: "Tell the server when the cookie-mode swi..." · 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.

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

5 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/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 thread packages/reflex-i18n/src/reflex_i18n/_web/i18n.js
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.
@masenf masenf added the roadmap https://github.com/reflex-dev/reflex/issues/2727 label Oct 22, 2026
hydrate_and_load now also resolves the browser storage replay inside the
re-derived event scope: since reflex-dev#7572 the replay redelivers cached storage
dependents and recomputes those an override assignment invalidated, which
otherwise ran under the locale derived before storage was applied. The
hydrate scope test expects the redelivered value and covers the
reassignment case.
Comment thread packages/reflex-i18n/src/reflex_i18n/_web/i18n.js Outdated
Comment thread packages/reflex-i18n/src/reflex_i18n/plugin.py
Entering a StateProxy now also enters the event scope derived from the
freshly loaded state, so a locale changed while a background task waited
applies inside the block. The scope is closed on exit, restoring the
task's own.
…e provider

The client now negotiates the locale case-insensitively and returns the
configured spelling, as the server does. The redirect from an unprefixed
URL to its default-locale address moves from I18nProvider into
HreflangLinks, so it happens even when the provider is not rendered.
Comment thread reflex/istate/proxy.py Outdated
A scope provider that suppresses the block's exception now has the state persisted as on a clean exit and the suppression propagated to the caller instead of the exception being re-raised.
Comment thread packages/reflex-i18n/src/reflex_i18n/plugin.py
Listing the reflex.cli entry points read every installed package's
metadata on each `reflex --help`, adding about 8% to it. `reflex i18n`
is now registered like deploy and cloud when reflex-i18n is installed.
I18nProvider gets an on_locale_change trigger bound to
I18nState.set_locale, compiled like any event so its wire name follows
the active name resolver; the Python set_locale helper opts out of it
so the event isn't sent twice.

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 roadmap https://github.com/reflex-dev/reflex/issues/2727

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants