# 07 — Internationalization (i18n)

Translation strings live in `languages/*.yml`. The active language is the site's `_config.yml` `language` key (this site uses `en`).

## Bundled languages
`en.yml`, `zh-CN.yml`, `zh-TW.yml`, `ja.yml`, `es.yml`, `fr.yml`.

`en.yml` is the **canonical/most-complete** file and the fallback when a key is missing elsewhere. When adding a UI string, add it to `en.yml` first, then translate into the others.

## How language resolution works
Several scripts (e.g. `image-exif.js`, `masonry-generator.js`, `config-export.js`) implement the same resolution logic:

1. Take `hexo.config.language` (first entry if it's an array), default `en`.
2. Apply aliases — notably **`jp` → `ja`**.
3. Try `languages/<lang>.yml`; if missing, fall back to the base subtag (`zh-CN` → `zh`); if still missing, fall back to `en.yml`.
4. Loaded YAML is cached per file path for the build.

In templates, the standard Hexo translation helper `__('key')` (and `_p` for pluralization) reads these files. Some scripts use a local translator with `%s` substitution and a fallback string, e.g. `t('image_exif.field.aperture', 'Aperture')`.

## Key groups (in `en.yml`)
| Group | Examples | Used by |
|-------|----------|---------|
| Site/UI | `search`, `toc`, `prev`, `next`, `read_more`, `top` | navigation, paginator, TOC |
| Menu | `about`, `links`, `friends`, `timeline`, `shuoshuo` | navbar, sidebar |
| Counters | `site_uv`, `site_pv`, `wordcount`, `min2read` | statistics, article info |
| Footer | `runtime`, `days`/`hours`/`minutes`/`seconds`, `rights`, `site_posts`, `site_wordcount` | footer (with `%s`/uptime) |
| `copyright.*` | `author`, `title`, `license_content`, `all_rights_reserved`, `public_domain` | article copyright block |
| `ago.*` | `second`/`minute`/…/`year` ("%s ago") | relative timestamps (also exported to `window.lang_ago`) |
| `expired` | stale-post notice ("written %s days ago…") | article info |
| `image_exif.*` | `ui.toggle`, `section.{camera,lens,exposure,other}`, `field.*`, `exposure_program.*`, `metering_mode.*`, `white_balance.*`, `flash.*`, `value.*` | the [`exifimage`](05-tag-plugins.md#exifimage) plugin |

### Placeholders
- `%s` — generic substitution (used by the script-side translator and Hexo).
- `%d` — numeric (e.g. `page: Page %d`).
- HTML entities are allowed in values (e.g. `&#169;` in copyright strings).

## Adding a new string
1. Add `my_key: My text` to `languages/en.yml` (nest it logically).
2. Mirror it into the other five files (translated; copy English as a placeholder if unsure).
3. Reference it: `<%= __('my_key') %>` in EJS, or `t('my_key', 'Fallback')` in a script.
4. If the string must reach client JS, export it via [`config-export.js`](04-scripts.md#scriptsconfig-exportjs) (the way `ago.*` becomes `window.lang_ago`).

## Adding a new language
1. Copy `en.yml` to `languages/<code>.yml` and translate every value.
2. Set the site's `_config.yml` `language: <code>`.
3. If the code needs an alias (like `jp`→`ja`), the resolver already handles base-subtag fallback; add an explicit alias in the scripts' `LANGUAGE_ALIASES` only if required.
