Skip to content

Backend/i18n: Externalize fallbacks languages from I18Next#9158

Merged
tristanlabelle merged 4 commits into
developfrom
backend/refactor/i18next-no-fallback
Jun 29, 2026
Merged

Backend/i18n: Externalize fallbacks languages from I18Next#9158
tristanlabelle merged 4 commits into
developfrom
backend/refactor/i18next-no-fallback

Conversation

@tristanlabelle

@tristanlabelle tristanlabelle commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Instead of baking the locale fallbacks into the I18Next object, pass a locales list when localizing strings.

This allows implementing all-or-nothing localization, say for emails, as follows (pseudocode):

  try:
    return localize_emails(locales=["fr-CA", "fr"]) # No fallback to English, will raise an error.
  except LocalizationError:
    return localize_emails(locales=["en"])

It also splits the responsibility of knowing our fallback chain from the implementation of the I18Next format.

Testing

Updated tests.

Backend checklist

  • Added tests for any new code or added a regression test if fixing a bug
  • Run the backend locally and it works
  • Added migrations if there are any database changes, rebased onto develop if necessary for linear migration history

For maintainers

  • Maintainers can push commits to my branch
  • Maintainers can merge this PR for me

@CouchersBot

CouchersBot commented Jun 21, 2026

Copy link
Copy Markdown
Member

Mobile

Install the Dev Tool (iOS via TestFlight, iOS Simulator, or Android .apk) here.

Scan the QR with your phone camera, or tap Open in Dev Tool on the device, to open this branch in the installed Dev Tool dev client.

    iOS         Android    
    QR to open the iOS build         QR to open the Android build    
    Open in Dev Tool         Open in Dev Tool    
Deep links

iOS

couchers-devtool://expo-development-client/?url=https%3A%2F%2F2d306527--ota.preview.couchershq.org%2Fios%2Fmanifest

Android

couchers-devtool://expo-development-client/?url=https%3A%2F%2F2d306527--ota.preview.couchershq.org%2Fandroid%2Fmanifest

Web (Vercel)

View the Vercel web preview for this branch.

Backend

Schema Schema diff Sample emails

Other

Protos Backend coverage Web coverage

@aapeliv aapeliv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I feel like this is not quite the right system: now you have different areas of the code worrying about locale lists, which to me shouldn't be the concern of application code?

I feel like what you want here is an "all-or-nothing localization block", where you guarante that every translation there is localized to a given language or fully falls back. We may want a similar one for e.g. notifications (so you don't get a partially translated notification)? Similarly you could imagine you'd want it in an RPC. Though that seems difficult to achieve in cases where you can't fall back and retry with a try-except? It seems it may require some kind of lazy evaluation in most cases?

If you feel this is the best way, I'm also happy with this. But I feel like a cleaner abstraction would be even a limited version of this kind of "localization context/block" that raises if there is a missing translation, but does not contain the local fallback chain, just a boolean basically that it's in an all-or-nothing section, or similar. Then at least we don't need to embed the locale fallback chain in application code.

@tristanlabelle

Copy link
Copy Markdown
Contributor Author

I feel like this is not quite the right system: now you have different areas of the code worrying about locale lists, which to me shouldn't be the concern of application code?

I convinced myself that having low-level loc APIs take a locale list is the most flexible approach, and that's probably why all the javascript Intl.Xxx APIs are designed that way. Even if you could know ahead of time or lazy evaluate all loc string lookups to know if some will fail, you'd need locale lists, because you wouldn't want to try pt and otherwise pt-BR, and otherwise en, you'd want to try ["pt", "pt-BR"] (mutually intelligible fallbacks) and otherwise just "en".

But also currently the date/language/region lookups do not support fallbacks and I'm worried a language could get broken because of this without us realizing. There's no I18Next for those so I'd want to also take a locale list, and in that case I want the decision of the fallback chain to above both I18Next and the date/language/region functions, i.e. in LocalizationContext.

For transational localization, the API would look something like LocalizationContext.localize(callback: Function[list[Locale]]), calling the callback a second time if the first time throws. I could also encapsulate the list of locales in a LocaleChain object and take that in low-level functions if the list is too ugly.

aapeliv
aapeliv previously approved these changes Jun 28, 2026

@aapeliv aapeliv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thanks for engaging with it. i think your solution here is clean enough, and i'm sure it will evolve over time anyway!

@tristanlabelle
tristanlabelle force-pushed the backend/refactor/i18next-no-fallback branch from 482ea8f to 2d30652 Compare June 29, 2026 12:44
@tristanlabelle
tristanlabelle merged commit ccf9079 into develop Jun 29, 2026
4 checks passed
@tristanlabelle
tristanlabelle deleted the backend/refactor/i18next-no-fallback branch June 29, 2026 13:16
@github-actions

Copy link
Copy Markdown
Contributor

📝 Release Notes

This PR does not need to be included in release notes.

Reason: This PR is an internal backend refactor of the localization fallback system. While it may enable better all-or-nothing localization behavior in the future, it does not by itself introduce a clear user-facing feature, visible UX improvement, or a specific important bug fix that end users would notice now.

🤖 Bot Debug Information

Model: couchers.openai.gpt-5.4
Decision: exclude
Reasoning: This PR is an internal backend refactor of the localization fallback system. While it may enable better all-or-nothing localization behavior in the future, it does not by itself introduce a clear user-facing feature, visible UX improvement, or a specific important bug fix that end users would notice now.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants