This note records one round of internationalization-related adjustments in this blog.

The original problem was not that the site had no i18n support. The real problem was that i18n was initialized too late.

The page first rendered in a default language, and only after the browser started did it read the user’s real language from localStorage or a language detector. That caused obvious language flicker.

In this project, the clearest example was the bottom navigation: the first screen could appear in Chinese even though the user’s actual preference was English.

1. Why “switch language on the client later” is not enough

The common i18n flow usually looks like this:

  1. Output the default language at build time
  2. Read the user’s language after the browser boots
  3. Trigger one more language switch on the page

This causes several issues:

  • The first paint may be in the wrong language
  • Navigation or loading copy may flicker
  • Hydration becomes easier to destabilize

So the key question is not whether the app can switch languages later. The key is whether the language is already correct on the first paint.

2. Initialize the language as early as possible

The first step was to initialize the language as early as possible at the layout level.

In practice, that means executing a script early in head and writing the user’s language to:

  • document.documentElement.lang
  • a custom language marker attribute

The purpose is to keep the language context consistent before and after hydration, so the browser does not paint the wrong language first.

3. Static locale routes are the more complete solution

Only switching language on the client is still not enough.

A more complete solution is to generate two route sets directly:

  • /cn/intro
  • /en/intro
  • /cn/journey
  • /en/journey
  • /cn/knowledge
  • /en/knowledge

Once you do this, the English pages are built as English HTML, and the Chinese pages are built as Chinese HTML.

Benefits:

  • Correct language on the first paint
  • Clearer SEO
  • No need to render a default language and switch later
  • The route itself already carries locale meaning

4. The root route should only handle locale redirection

Once a project has explicit locale routes, the root path / should do one lightweight job:

  1. Read the user’s language preference
  2. Show a minimal loading or redirecting message
  3. Jump to the target locale page

There was also one small detail handled here: if the root route defaults to English, then its loading copy, script defaults, and noscript fallback should all be English as well, instead of mixing Chinese and English.