How languages work
SwiftAgency ships in English, German and Indonesian. Every page exists in every language, visitors can switch language without losing their place, and search engines are told which page is which translation.
The languages
Section titled “The languages”These are read from src/i18n/config.ts when the docs are built:
| Code | Language | URLs | Dates | Open Graph |
|---|---|---|---|---|
en | English | /work | en-GB | en_GB |
de | Deutsch | /de/work | de-DE | de_DE |
id | Bahasa Indonesia | /id/work | id-ID | id_ID |
The default language has no prefix in the URL. The others live under their code.
Two kinds of text
Section titled “Two kinds of text”Interface text is everything built into the pages: navigation, buttons, headings, the services and prices, form labels and errors. It lives in one dictionary per language in src/i18n/ui/:
const en = { nav: { work: 'Work', services: 'Services', // … }, cta: { contact: 'Start a project', work: 'View work', }, // …};Content is what you publish: case studies, articles, roles and legal pages. Translations sit in a language folder next to the original, with the same file name. See Translating content.
Dictionaries are checked
Section titled “Dictionaries are checked”en.ts defines the shape; de.ts and id.ts are typed as Translation. If a key is missing from one of them, or misspelled, npm run check fails and names the file and key. You cannot ship a page with a blank label by accident.
Placeholders such as {count} are filled in by format():
format(dict.home.allProjects, { count: 8 }); // "All 8 projects"In a component
Section titled “In a component”---import { localizePath, t, useLocale } from '@/i18n/utils';
const locale = useLocale(Astro);const dict = t(locale);---
<a href={localizePath('/work', locale)}>{dict.cta.work}</a>useLocale(Astro)returns the language of the page being built.t(locale)returns its dictionary.localizePath(path, locale)adds the language prefix:/workbecomes/de/work. External links andmailto:are left alone.formatDate(date, locale)formats a date the local way: 21 May 2026, 21. Mai 2026, 21 Mei 2026.
What visitors and search engines get
Section titled “What visitors and search engines get”- A language switcher in the header and menu. It keeps the visitor on the same page in the other language.
<html lang>set per page, so screen readers use the right voice.hreflanglinks to every translation plus anx-default, andog:localewith its alternates.- A sitemap that lists each page’s translations.
- Structured data in the page’s language.
- A 404 page that switches to the visitor’s language from the URL they tried.
- Honest fallbacks. Content not yet translated is shown in English and marked with
lang="en", so a German page never pretends English text is German.
One language only
Section titled “One language only”To publish only in English, remove de and id from locales in src/i18n/config.ts, remove their imports from the dictionaries map in src/i18n/utils.ts, and delete src/i18n/ui/de.ts, src/i18n/ui/id.ts and the de/ and id/ folders in every collection in src/content/.
The language switcher hides itself when there is only one language, and URLs stay as they are.