saccoai-multilingual

Installation
SKILL.md

This skill adds full multilingual support to any Next.js site. It installs next-intl, extracts every translatable string into structured JSON namespace files, generates AI-assisted translations with Swiss-specific context, wires up the LanguageSwitcher and hreflang tags, and produces a validation report with per-locale coverage percentages.

Default languages: DE + FR + IT + EN (full Swiss). Configurable down to any subset. Works on pipeline-built sites and any existing Next.js project.

Inputs

Input Required Default Description
Project path Yes cwd An existing built Next.js project
Languages No de, fr, it, en Comma-separated locale codes
Default locale No First in list The primary language (source of truth for extraction)
Translation mode No ai-assisted ai-assisted (AI SDK + AI Gateway + human review) or manual (empty files for human translation)

Execution Model

Single-agent hybrid. Phases 1–3, 5–6 are sequential. Phase 4 (translate) uses superpowers dispatching-parallel-agents to translate all non-primary locales simultaneously — one agent per locale.

Preconditions

  • Next.js project: package.json must include a next dependency. Abort with a clear message if not found.
  • Existing i18n detection: Before proceeding, check for existing i18n config (see Phase 1 Step 1). If found, report the current setup and ask whether to extend or replace — do not proceed automatically.

Phase 1: Analyze

Detect existing i18n setup and inventory all translatable content.

  1. Check for existing i18n config: look for next.config.ts (or .js) i18n block, next-intl, next-i18next, or any custom routing middleware that handles locale detection.

  2. If i18n already exists: report the current setup (library, configured locales, routing strategy). Ask: "An existing i18n setup was found ({details}). Extend it with new locales, or replace it with next-intl? (extend/replace)" Wait for confirmation before continuing.

  3. Inventory all translatable content:

    • Page content from src/data/*.ts (if pipeline-built) or inline JSX text nodes
    • Metadata (title, description) from page files and layouts
    • Navigation labels from header/footer components
    • UI strings: button labels, form placeholders, error messages, aria-labels
    • Structured data (JSON-LD) text fields: name, description, addressLocality
    • Alt text on <Image> and <img> elements
  4. Confirm before proceeding: Print "{N} translatable strings found across {M} pages. Proceed with {languages} in {mode} mode? (yes/no)"

Phase 2: Configure

Set up Next.js i18n routing with next-intl.

  1. Install next-intl:

    npm install next-intl
    
  2. Configure path-based routing: /de/, /fr/, /it/, /en/. Every page is accessible at /{locale}/path. The root / redirects to the default locale.

  3. Default locale redirect: e.g. if Italian is default, //it/. Configure in middleware.

  4. Create src/i18n/config.ts:

    export const locales = ['de', 'fr', 'it', 'en'] as const;
    export type Locale = typeof locales[number];
    export const defaultLocale: Locale = 'de';
    export const localeNames: Record<Locale, string> = {
      de: 'Deutsch',
      fr: 'Français',
      it: 'Italiano',
      en: 'English',
    };
    

    Populate from the user's input languages and default locale.

  5. Set up middleware (middleware.ts at project root or src/):

    • Use createMiddleware from next-intl for locale detection and routing
    • Detect locale from URL prefix first, then Accept-Language header as fallback
    • Redirect bare / to /{defaultLocale}/
  6. Create message loading infrastructure: src/locales/{locale}/{namespace}.json directory structure. Create empty directories for all locales now; namespace files are populated in Phase 3.

Phase 3: Extract

Pull all translatable strings into structured namespace files.

  1. Namespace file structure — for each locale:

    • src/locales/{locale}/common.json — shared strings: nav labels, footer text, button labels, error messages, form placeholders
    • src/locales/{locale}/home.json — homepage content: hero heading, subheading, section titles, CTA text
    • src/locales/{locale}/metadata.json — page <title> and <meta name="description"> for every page, plus OG title/description
    • src/locales/{locale}/{page-slug}.json — per-page content for each page beyond the homepage
  2. Replace inline text in components:

    • Client components: const t = useTranslations('namespace'){t('key')}
    • Server components and layouts: const t = await getTranslations('namespace'){t('key')}
    • Preserve interpolation variables exactly: {name}, {count}, {count, plural, one {# item} other {# items}}
  3. Generate primary locale files: populate only the primary locale's JSON files with the original extracted content. Other locales remain empty at this stage — they are filled in Phase 4.

    Example src/locales/de/common.json:

    {
      "nav": {
        "home": "Startseite",
        "about": "Über uns",
        "contact": "Kontakt"
      },
      "buttons": {
        "submit": "Absenden",
        "learnMore": "Mehr erfahren"
      }
    }
    

Phase 4: Translate

Generate translations for all non-primary locales.

AI-assisted mode (default)

Use superpowers dispatching-parallel-agents — one agent per non-primary locale — each running the following steps:

  1. System prompt (send to AI SDK + AI Gateway for each translation request):

    You are a professional Swiss translator. Translate the following JSON strings from {source_locale} to {target_locale}.
    
    Swiss-specific conventions:
    - German (de): Use formal "Sie" (not "du"). Use Swiss German conventions where applicable (e.g., "ss" not "ß").
    - French (fr): Use Swiss French conventions. Avoid Belgicisms and Canadianisms.
    - Italian (it): Use Ticino Italian register (formal, standard Italian — not Milan business Italian).
    
    Rules:
    - Preserve all interpolation variables exactly: {name}, {count}, {count, plural, ...} — do not translate variable names
    - Preserve HTML tags within strings: <strong>, <em>, <a href="..."> — keep tags, translate only text content
    - Keep JSON keys unchanged — only translate values
    - If you are unsure about a translation, prefix the value with "⚠️ REVIEW: " so it can be flagged for human review
    - Return valid JSON only
    
  2. Translate every namespace file for that locale.

  3. Flag low-confidence translations: any value prefixed with ⚠️ REVIEW: is collected into src/locales/translation-review.md:

    # Translation Review
    
    ## {locale} — {namespace}.json
    | Key | Source | Translation | Reason |
    |-----|--------|-------------|--------|
    | nav.contact | Kontakt | ⚠️ REVIEW: Contact | Ambiguous in context |
    

Manual mode

  1. Generate all locale JSON files with the correct key structure but "TODO" as every value:
    {
      "nav": {
        "home": "TODO",
        "about": "TODO"
      }
    }
    
  2. Stop after generating the files. Print instructions:
    Manual translation mode: locale files generated with TODO placeholders.
    Files to translate:
      src/locales/fr/common.json
      src/locales/fr/home.json
      ... (full list)
    Fill in all TODO values, then re-run Phase 5 to integrate.
    
  3. Do not proceed to Phase 5 automatically.

Phase 5: Integrate

Wire translations into the site's infrastructure and navigation.

  1. <LanguageSwitcher> component (src/components/language-switcher.tsx):

    • Renders a list of locale links using locales and localeNames from src/i18n/config.ts
    • Uses next-intl's useRouter and usePathname to switch locale while preserving the current path
    • Highlights the active locale
    • Matches the site's existing nav pattern (inline list, dropdown, or icon — read the header component to match the style)
    • Add to the site header alongside the existing navigation
  2. <html lang> attribute: update the root layout to set lang={locale} dynamically from the next-intl locale:

    <html lang={locale}>
    
  3. hreflang link tags: add to the root layout <head> for every page:

    {locales.map((loc) => (
      <link key={loc} rel="alternate" hrefLang={loc} href={`https://{domain}/{loc}{pathname}`} />
    ))}
    <link rel="alternate" hrefLang="x-default" href={`https://{domain}/{defaultLocale}{pathname}`} />
    
  4. Per-locale sitemaps in app/sitemap.ts:

    • Generate one sitemap entry per page per locale
    • Include alternates.languages map on each entry (all locale variants of the same page)
    • Example entry:
      {
        url: 'https://example.com/de/about',
        alternates: { languages: { de: '/de/about', fr: '/fr/about', it: '/it/about', en: '/en/about' } }
      }
      
  5. robots.ts: add all per-locale sitemap URLs to the sitemap array.

  6. Locale-aware structured data (JSON-LD): update JSON-LD objects to include inLanguage: locale dynamically.

  7. OG tags per locale: update generateMetadata functions to include:

    • openGraph.locale: current locale (e.g., de_CH, fr_CH)
    • openGraph.alternateLocale: array of all other locales

Phase 6: Validate

Verify completeness and correctness before signing off.

  1. Completeness check: for every key in the primary locale's JSON files, verify the same key exists in every other locale's corresponding file. Flag any missing keys.

  2. Missing translation report with per-locale coverage percentage:

    de: 100% (primary)
    fr: 94% — 8 keys missing in home.json, metadata.json
    it: 100%
    en: 87% — 18 keys missing across 3 files
    
  3. hreflang validation: for every page, verify that all locale variants link to each other correctly. Check for self-referential hreflang and x-default presence.

  4. Visual comparison: take a screenshot of the same representative page (homepage) in each locale. Check for layout breaks caused by longer or shorter translations (German text is typically 30–40% longer than English; flag if text overflows containers).

  5. Output — save to .saccoai/i18n/validation.md:

    # i18n Validation Report
    
    **Date**: {date}
    **Project**: {project name}
    **Locales**: {list}
    
    ## Coverage
    | Locale | Coverage | Missing Keys |
    |--------|----------|--------------|
    | de     | 100%     ||
    | fr     | 94%      | 8            |
    | it     | 100%     ||
    | en     | 87%      | 18           |
    
    ## Missing Keys
    (per-locale, per-file breakdown)
    
    ## hreflang Status
    (per-page pass/fail)
    
    ## Layout Issues
    (flagged pages with screenshot evidence)
    

Composition

saccoai-website-rebuild ──→ saccoai-multilingual (optional Phase 5.5, between Build and SEO)
any Next.js project ──────→ saccoai-multilingual (standalone)

When a user specifies target languages during a website rebuild, the rebuild pipeline invokes this skill after the Build phase and before SEO — because SEO needs the locale-aware sitemaps and hreflang tags to be in place before generating the sitemap and structured data.

Standalone: invoke directly on any existing Next.js project regardless of how it was built.

Standalone Usage

Invoke this skill directly when:

  • An existing site needs translations added (Swiss client, multilingual market)
  • The user says "add German", "make it multilingual", "translate the site", "add DE/FR/IT", or "i18n"
  • A new Swiss client project requires the full four-language setup from day one
  • The user wants to add a single additional language to an already-multilingual site

When invoked standalone, ask for: the project path (default: cwd), languages (default: de, fr, it, en), default locale (default: first in list), and translation mode (default: ai-assisted). Then run all six phases.

Edge Cases

  • Existing i18n config found: report the current setup (library, locales, routing). Ask whether to extend (add new locales) or replace (migrate to next-intl) before proceeding. Do not touch the codebase until confirmed.

  • Interpolation variables in source strings: preserve {name}, {count}, and ICU plural patterns like {count, plural, one {# item} other {# items}} exactly. Include this instruction in the AI translation prompt and validate the output — if a variable is missing or renamed in a translated value, flag it as ⚠️ REVIEW:.

  • Long translations breaking layout: German text is typically 30–40% longer than English source. Flag in the validation report with screenshot evidence. The fix is the developer's responsibility, but the report surfaces the specific pages and components affected.

  • Manual mode: all locale files are generated with "TODO" values. The skill stops after Phase 3 and prints the full list of files to translate with instructions. Phase 5 (integrate) is not run automatically — the user must re-invoke the skill (or run Phase 5 explicitly) after translations are complete.

Related skills

More from saccoai/agent-skills

Installs
1
First Seen
Mar 27, 2026