Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Backend Locale Convention: cn vs zh

Rails apps in this workspace expose two separate Chinese locales to clients. Confusing them produces invisible translation bugs (the wrong strings render with no warning).

TL;DR

API param valueScriptInternal cultureHuman nameUsed by
locale=cnTraditional Chinesezh-HK繁體中文Hong Kong, Taiwan, Macau clients
locale=zhSimplified Chinesezh-CN简体中文Mainland China clients

Never send or accept locale=zh-CN / locale=zh-HK / locale=zh-Hans / locale=zh-Hant. Rails routes scope to the short codes:

# config/routes.rb
scope '(:locale)', locale: /#{MyLocaleManager.available_locales.join('|')}/

The full culture name is only used internally by MyLocaleManager#culture_names when handing the locale to underlying .NET / OS libraries.

Where this is defined

hh-server/app/my_lib/my_locale_manager.rb:

CN = :cn   # existing CN is Traditional
ZH = :zh   # Simplified Chinese (existing CN is Traditional)

AVAILABLE_LOCALES = [EN, TH, CN, ES, FR, DE, RU, MS, KO, JA, ID, VI, ZH]

culture_names = {
  cn: 'zh-HK',   # Traditional Chinese (HK)
  zh: 'zh-CN',   # Simplified Chinese (Mainland)
}

human_names = {
  cn: '繁體中文',
  zh: '简体中文',
}

MyLocaleManager.parse is substring-match on the param value, so a client sending locale=zh_CN still resolves to :zh — but prefer the bare cn / zh for cleanliness.

YAML file convention

LocaleMain fileSplit files
cn (Traditional)config/locales/cn.ymlviews.cn.yml, actions.cn.yml, errors.cn.yml, home.cn.yml, partner.cn.yml, partner_errors.cn.yml, stripe.cn.yml
zh (Simplified)config/locales/zh.ymlviews.zh.yml, actions.zh.yml, errors.zh.yml

A key added for cn belongs in cn.yml or the matching *.cn.yml split — not in *.zh.yml. Conversely, Simplified keys belong in the zh tree.

Existing cn content is already a partial mix of Simplified and Traditional characters (long-standing tech debt). New keys should follow the file’s declared script — Simplified characters in *.zh.yml, Traditional characters in *.cn.yml.

What this is NOT

  • This convention is Rails-only. Other surfaces (HH-Pegasus Nuxt, HH-Lion Tiger public API, mobile apps) use different locale code conventions. See the cross-links below before assuming a value applies everywhere.
  • cn is a non-standard locale code. The proper ISO codes are zh-CN (Simplified), zh-HK / zh-TW (Traditional). The Rails app uses cn for backwards compatibility; do not “fix” it to the standard codes — many clients and admin UIs depend on the short codes.

Cross-surface locale conventions

These are intentionally different — do not unify them in one PR:

  • HH-Pegasus (Nuxt): accepts cn, zh, zh_hans, zh_hant and normalizes via normalizeLanguage() (zh_hantcn, zh_hanszh). See HHPegasus/refactoring/usehttp-lang-parameter-migration.md.
  • HH-Lion Tiger public API: X-Hh-Language: en|th|cn (Simplified and Traditional both surface as cn; no separate zh).
  • HH-Felidae: same X-Hh-Language shape as Tiger.
  • day.js in HH-Pegasus: cn / zh_hant = Traditional Chinese (Hong Kong), zh_hans = Simplified Chinese. See HHPegasus/how-to/use-dayjs.md.

Adding a new key for vendor API / mainland clients

Pattern used by PR #8281 (hh-server CU-86d3e2j8z):

  1. Add the Traditional translation to config/locales/cn.yml (or split file if it belongs to views.* / actions.* / errors.*).
  2. Add the Simplified translation to config/locales/zh.yml (or matching *.zh.yml split).
  3. Both files should be added in the same PR so locale parity is guaranteed — a cn-only key silently degrades for locale=zh clients.

Gotcha — pluralization keys

Chinese does not pluralize, but Rails activerecord errors still use the one / other / zero pluralization keys. Set all three to the same string:

activerecord:
  attributes:
    reservation:
      adult:
        one: 成人
        other: 成人
        zero: 成人

Don’t translate one / other / zero differently — Chinese has no plural form, so any difference is a translation bug.

  • Features/Chinese_Translation_20250826194957.md — product-side Chinese translation rollout (different scope)
  • HHPegasus/refactoring/usehttp-lang-parameter-migration.md — Nuxt-side locale normalization
  • HHPegasus/how-to/use-dayjs.md — day.js locale configuration
  • HHFelidae/tiger-public-api.md — Tiger API language header