Migrating from an i18n System
If your site already uses a translation system — Astro's built-in i18n, astro-i18next, next-intl, or any other framework-specific i18n solution — you can migrate to the Rosey/RCC stack. This page covers what changes, what you gain, and how hard it is.
#Why migrate
What you gain:
- Visual editing — editors see and edit translations in context on the page, not in a disconnected key-value editor
- Stale translation detection — when source text changes, out-of-date translations are flagged automatically
- AI-friendly locale files — the structured JSON format enables incremental, deterministic AI translation with no wasted tokens
- Framework-agnostic — works with any SSG that outputs static HTML (Astro, Hugo, Eleventy, Jekyll, etc.)
- No source-level translation plumbing — no
t()functions, no locale-aware imports, no per-locale routing middleware
What you trade:
- Build-time locale awareness — your components no longer know which locale they're rendering for at build time. Locale-specific date formatting or conditional rendering needs a different approach (see split-by-directory).
- Post-build step — Rosey runs after your SSG build to generate locale pages. This adds a step to your build pipeline.
What Rosey replaces
| Old system concept | Rosey equivalent |
|---|---|
i18n config / locale routing |
Rosey generates locale pages at /{locale}/... URLs automatically |
t("key") function / translation dictionaries |
data-rosey="key" attributes on HTML elements |
Per-locale page copies (/fr/about.astro) |
Rosey clones pages and injects translations from locale JSON files |
| Translation files (JSON/YAML/PO dictionaries) | rosey/locales/{code}.json with { original, value, _base_original } per entry |
| Language picker with locale URL helpers | Static picker with data-rosey-ignore on links (details) |
| Locale fallbacks (page-level) | Rosey falls back per-key to default-language text |
Astro.currentLocale / locale detection |
Not needed — pages are built once, Rosey handles locale output |
Migration difficulty by pattern
| Pattern | Difficulty | Notes |
|---|---|---|
Dictionary-based UI strings (t() calls) |
Straightforward | Replace t("key") with static text + data-rosey. Extract existing translations into Rosey format. Mostly find-and-replace. |
| Split-by-directory content (per-locale content collections) | Minimal change | Keep locale collections as-is. Rosey merges with pre-existing locale pages and only translates data-rosey elements. See split-by-directory. |
| Full page duplication (identical structure, different strings) | Moderate | Delete locale copies, add data-rosey to the default-language page, map existing translations to Rosey keys. |
Locale-conditional logic (Astro.currentLocale, date formatting) |
Case-by-case | Audit each usage. Drop locale-awareness where acceptable, or move to split-by-directory for pages that genuinely need it. |
Getting started
Using agent skills (recommended)
Agent skills with detailed step-by-step migration workflows are maintained in CloudCannon/agent-skills. Add them to your project:
npx skills add CloudCannon/agent-skills --all
The make-site-multilingual skill walks through the full process in its "Migrating from an existing i18n system" appendix: detecting the current system, extracting translations, removing old infrastructure, applying the Rosey stack, and verifying the result. Its astro.md companion includes an Astro-specific supplement with concrete before/after patterns for Astro's built-in i18n.
Manual migration
Follow the Getting Started guide for the Rosey/RCC setup, and refer to the high-level migration steps:
- Extract existing translations into Rosey's locale JSON format
- Remove the old i18n system (packages, config, routing,
t()calls) - Verify the site builds and renders correctly in the default language
- Apply the Rosey stack (tag elements, import the connector, configure CloudCannon, set up postbuild)
- Merge extracted translations into the generated locale files
Astro's built-in i18n
Astro's "built-in i18n" is routing infrastructure only — it provides locale-aware URL routing, helper functions, and browser language detection, but no translation runtime. The t() function and dictionary pattern come from a recipe in the Astro docs (example code you copy into your project), not a framework feature.
This means the migration is mainly about:
- Replacing the recipe-pattern dictionary and
t()calls withdata-roseyattributes - Removing the
i18nconfig block fromastro.config.mjs - Deleting duplicate page directories (for pages where only strings differ)
- Replacing
getRelativeLocaleUrl()calls with plain paths (Rosey rewrites links automatically)
For detailed before/after code patterns, use the make-site-multilingual agent skill (its "Migrating from an existing i18n system" appendix), or see the Astro migration patterns directly (the "Migrating an Astro Site Off Its Existing i18n" section).