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 value | Script | Internal culture | Human name | Used by |
|---|---|---|---|---|
locale=cn | Traditional Chinese | zh-HK | 繁體中文 | Hong Kong, Taiwan, Macau clients |
locale=zh | Simplified Chinese | zh-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
| Locale | Main file | Split files |
|---|---|---|
cn (Traditional) | config/locales/cn.yml | views.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.yml | views.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.
cnis a non-standard locale code. The proper ISO codes arezh-CN(Simplified),zh-HK/zh-TW(Traditional). The Rails app usescnfor 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_hantand normalizes vianormalizeLanguage()(zh_hant→cn,zh_hans→zh). SeeHHPegasus/refactoring/usehttp-lang-parameter-migration.md. - HH-Lion Tiger public API:
X-Hh-Language: en|th|cn(Simplified and Traditional both surface ascn; no separatezh). - HH-Felidae: same
X-Hh-Languageshape as Tiger. - day.js in HH-Pegasus:
cn/zh_hant= Traditional Chinese (Hong Kong),zh_hans= Simplified Chinese. SeeHHPegasus/how-to/use-dayjs.md.
Adding a new key for vendor API / mainland clients
Pattern used by PR #8281 (hh-server CU-86d3e2j8z):
- Add the Traditional translation to
config/locales/cn.yml(or split file if it belongs toviews.*/actions.*/errors.*). - Add the Simplified translation to
config/locales/zh.yml(or matching*.zh.ymlsplit). - Both files should be added in the same PR so locale parity is
guaranteed — a
cn-only key silently degrades forlocale=zhclients.
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.
Related docs
Features/Chinese_Translation_20250826194957.md— product-side Chinese translation rollout (different scope)HHPegasus/refactoring/usehttp-lang-parameter-migration.md— Nuxt-side locale normalizationHHPegasus/how-to/use-dayjs.md— day.js locale configurationHHFelidae/tiger-public-api.md— Tiger API language header