fantasia-i18n
$
npx mdskill add vishiri/fantasia-archive/fantasia-i18nManages i18n messages and locale structure for Fantasia Archive
- Adds or updates user-facing strings and locale files in the i18n directory
- Uses vue-i18n v9 and TypeScript for message schema and locale merging
- Enforces rules for locale file composition and character escaping
- Exports locale keys and merges modules for use in Quasar app and external scripts
SKILL.md
.github/skills/fantasia-i18nView on GitHub ↗
---
name: fantasia-i18n
description: >-
Manages vue-i18n messages and locale structure for Fantasia Archive. Use when
adding or changing user-facing strings, locale files under repo-root i18n/, or
markdown-backed documents wired into i18n.
---
# Fantasia Archive — i18n
## Setup
- **Plugin**: `vue-i18n` v9.
- **Locale registry**: **`i18n/index.ts`** at the **repository root** (next to **`src/`**) exports locale keys (e.g. `en-US`, `de`) and merges each locale module. The Quasar app and **`externalFileLoader`** import it as **`app/i18n`**.
- **TypeScript**: global message schema augmentation lives in **`src/boot/i18n.ts`** (`declare module 'vue-i18n'`). Under **`yarn lint:typescript`**, **`tsc`** may report **TS2665** against the ESM `module` resolution; the boot file documents a **`@ts-expect-error`** for that line. Keep **`eslint-disable @typescript-eslint/no-empty-object-type`** on the augmentation block (typescript-eslint v8 rule name). See [eslint-typescript.mdc](../../rules/eslint-typescript.mdc).
## Folder structure (`i18n/en-US/`)
```
i18n/
index.ts — merges en-US, fr, de, …
externalFileLoader.ts — standalone createI18n instance for Pinia / scripts
specialCharactersFixer.ts — escapes characters for vue-i18n message compiler
_tests/ — Vitest specs (**unit-i18n**)
en-US/
index.ts — composes the full locale tree; no hardcoded strings (see rules below)
documents/ — Markdown source files (.md); imported via ?raw and passed through specialCharacterFixer
components/<bucket>/<ComponentName>/ — mirrors src/components/: globals, elements, other (not foundation/; Storybook-only catalogues use inline English). **ComponentName** includes a leading underscore only for the three infrastructure helpers in [AGENTS.md](../../../AGENTS.md) **Helper / wrapper SFC naming (`_` prefix)** if one ever gains user-visible copy (none do today).
dialogs/ — one L_<DialogName>.ts per dialog (dialog copy; not the same as components/dialogs/)
floatingWindows/ — one L_<Feature>.ts per in-renderer floating window (movable/resizable surfaces; mirrors components/floatingWindows/)
pages/ — one L_<PageName>.ts per page
globalFunctionality/ — one L_<feature>.ts per app-wide, non-component concern (e.g. store notifications)
```
Do not place locale files in any other location. If no folder fits, use `globalFunctionality/`.
**Naming**: locale message files use the **`L_` prefix** (for example `L_appSettings.ts`). TypeScript **type aliases** elsewhere in the app use **`T_`** per project conventions — do not use `T_` for locale filenames.
## Key naming rules
- All top-level keys exported from `index.ts` use **camelCase with a lowercase first letter** (e.g. `globalWindowButtons`, `appControlMenus`, `dialogs`, `errorNotFound`, `globalFunctionality`).
- Sub-keys within `L_*.ts` modules also use camelCase with a lowercase first letter.
- `index.ts` must contain no hardcoded user-visible strings; every string lives in a dedicated `L_*.ts` file.
- The `documents` section holds processed markdown strings, not static text — it is the only section that calls `specialCharacterFixer`; do not add plain string keys there. Those strings are still compiled as **vue-i18n** messages: avoid literal **`{...}`** in Markdown (for example glob **`.*.{vue,css}`**) or the in-app changelog and other document dialogs can throw **message compilation** errors — see [fantasia-changelog-en-us](../fantasia-changelog-en-us/SKILL.md). Do not spell **CSS** rule blocks with **curly brace** pairs around selectors (for example a **.class** name followed by a block) — rephrase in plain prose; see [fantasia-markdown-dialogs](../fantasia-markdown-dialogs/SKILL.md) **Conventions**.
- **vue-i18n** (Intlify) message syntax treats **`|`** as special (plural / list). To show a literal vertical bar in user-visible text, write **`\\|`** in the TypeScript string (backslash + pipe in the source so the runtime message contains **`\|`**, which compiles to a single **`|`**). The same escape style applies to **`@`**, **`{`**, **`}`**, and **`\`** where you need them literally; see Intlify message syntax docs.
- App-wide strings that do not belong to a specific component, dialog, or page go in `globalFunctionality/`. Uncategorised strings live in `globalFunctionality/L_unsortedAppTexts.ts` under the `globalFunctionality.unsortedAppTexts` key.
## English (`en-US`) capitalization
- **Sentence-style** for **running prose** in **`L_*.ts`**, document body paragraphs, longer tooltips and Notify descriptions, plus **bold changelog references embedded in sentences**, etc.: capitalize the opening word (and each introductory chunk of slash-linked labels such as **Import / Export**), keep ordinary later words lowercase, preserve acronyms and product tokens (**CSS**, **FA**, **`HTML`**, **`UI`**, **`FA 1.0`**, Electron, Monaco, SQLite, **`macOS`**, **Fantasia Archive**).
- **Headline-style** for **chrome** that reads as discrete UI stems: **`AppControlMenus`** submenu rows, **Splash** primary actions copying menu labels, **`dialogs.keybindSettings.commands.*`** and matching **Action monitor** / **`globalFunctionality.faActionManager.labels`** wording, checklist captions beside toggles, principal **dialog** and **floating-window** headings (**Import / Export App Configuration**, **Fantasia Archive Settings**, etc.). Principal words capped; conventional short-word lowercasing applies (**into**, **the**, **to** when medial). Never ship submenu or keybind stems with an accidental lowercase first letter.
- Canonical markdown **H1** titles surfaced in dialogs: **Advanced Search Guide**, **Tips, Tricks & Trivia**.
- The four **`appControlMenus`** top **`title`** strings (**Project Management**, **Documents & Content**, **Settings & Tools**, **Help & Info**) use deliberate **menu-bar title casing** separate from submenu headline rules ([en-us-ui-copy-capitalization.mdc](../../rules/en-us-ui-copy-capitalization.mdc)).
- **German**, **French**, and other locales follow their own grammar; mirror semantic roles rather than copying English capitalization unless maintainers intentionally align wording.
## index.ts rules
- `index.ts` must contain **only imports and the composed export object**. No hardcoded user-visible strings.
- Every new section maps a camelCase top-level key to its imported `L_*` locale module (or, for `documents`, to its `specialCharacterFixer(...)` call).
- Import ordering in `index.ts`: markdown document imports first, then component `L_*` imports grouped by folder (`components/`, then `dialogs/`, then `floatingWindows/`, then `globalFunctionality/`, then `pages/`).
## Using strings in code
- **Vue templates**: `$t('camelCaseKey.subKey')` — do not import `useI18n` just to call `t(...)` when `$t` is available in the template.
- **TypeScript scripts and Pinia stores**: `import { i18n } from 'app/i18n/externalFileLoader'` then `i18n.global.t('camelCaseKey.subKey')`.
- Never hardcode user-visible prose directly in `.vue` templates, `_data/` files, or scripts; always route through an i18n key.
## Tests tied to copy changes
When you change or add keys that appear in **`toHaveText`**, **`toHaveAttribute('aria-label', …)`**, or imported **`L_*`** default exports inside Playwright specs, **grep** for the key path or the English default module and **run** every matching **`*.vitest.test.ts`**, **`*.playwright.test.ts`**, and **`*.playwright.spec.ts`** (rebuild Electron before Playwright when the bundled renderer matters). See [fantasia-testing](../fantasia-testing/SKILL.md) **Connected tests for any feature change**.
## Adding new strings — step by step
1. Identify the right folder (`components/<bucket>/<ComponentName>/`, `dialogs/`, `pages/`, or `globalFunctionality/`).
2. Create or open `L_<name>.ts` in that folder and add the new keys (camelCase, lowercase first letter).
3. If the file is new, import it in `index.ts` and assign it to a new camelCase top-level key.
4. Use the full dot-path in templates (`$t('topLevelKey.subKey')`) or scripts (`i18n.global.t('topLevelKey.subKey')`).
5. Mirror the structure in other active locales (`de`, etc.) when those are maintained.
## Storybook integration
- For Storybook mocks/loaders, import focused non-markdown `L_*` locale modules directly (for example `app/i18n/en-US/components/globals/GlobalWindowButtons/L_GlobalWindowButtons.ts`) instead of importing `i18n/en-US/index.ts`.
- Reason: the full locale entrypoint pulls markdown `documents/*.md`, which can break Storybook/Vite import analysis.
- If Storybook stories need document content (`documents.*`), provide explicit placeholder strings (for example lorem ipsum) in `.storybook-workspace/.storybook/mocks/externalFileLoader.ts` (`defaultMessages.documents`) rather than importing markdown files.
- **Mandatory mirror when extending the app locale tree**: [`.storybook-workspace/.storybook/mocks/externalFileLoader.ts`](../../../.storybook-workspace/.storybook/mocks/externalFileLoader.ts) builds `defaultMessages` for the Storybook `createI18n` instance. Whenever you register a new `L_*` module in `i18n/en-US/index.ts` under a namespace that any story or component might use (`dialogs.*`, `floatingWindows.*`, `globalFunctionality.*`, top-level sections, etc.), add the matching `import` and nested key to `defaultMessages` in the **same PR/commit** as the feature. Omitting this mirrors production i18n in the app but leaves Storybook showing untranslated key paths (for example `floatingWindows.appStyling.title`). After editing, smoke-check with `yarn storybook:run` on a story that exercises the new copy.
## Related
- [.cursor/rules/en-us-ui-copy-capitalization.mdc](../../rules/en-us-ui-copy-capitalization.mdc) — sentence-style **English** labels and preserved acronyms.
- [fantasia-testing](../fantasia-testing/SKILL.md) for Vitest + Playwright when locale strings affect tests.
- [fantasia-markdown-dialogs](../fantasia-markdown-dialogs/SKILL.md) for rendering markdown in dialogs.
## TypeScript interfaces and types (`types/`)
- Put shared `interface` / `type` declarations in repository-root `types/` (import with `app/types/...`). Prefer one domain-oriented module per feature area with brief JSDoc on exports (see `types/I_appMenusDataList.ts`). Do not add colocated `<filename>.types.ts` under `src/`, `src-electron/`, or `.storybook-workspace/`. Ambient augmentations for third-party modules also live under `types/` and are loaded with a side-effect import from the owning boot file or `src/stores/index.ts` (see `types/piniaModuleAugmentation.ts`).
- For JavaScript (`.js`), TypeScript (`.ts`), Vue (`.vue`), and JSON (`.json`, `.jsonc`, `.json5`) files, enforce expanded multi-line object literals via ESLint (`object-curly-newline` + `object-property-newline`) and keep files auto-fixable with `eslint --fix`.