Skip to content

Instantly share code, notes, and snippets.

@kjrocker
Last active October 26, 2025 23:49
Show Gist options
  • Select an option

  • Save kjrocker/7064fd282a4789571b1c3d77c1bc9e4e to your computer and use it in GitHub Desktop.

Select an option

Save kjrocker/7064fd282a4789571b1c3d77c1bc9e4e to your computer and use it in GitHub Desktop.
A basic framework for fully-static sites that only need to manage their content in git, and have content that can be managed outside a CMS.
/**
* Each key is a locale/language (en, nl, de, etc...)
* Each value is an object mapping string keys ("home.hero.header") to their text "Welcome!"
*/
export const ui = {
en: {
"home.header.topper": "Topper",
"home.header.title": "Title",
"home.header.cta": "CTA",
},
nl: {
"home.header.topper": "Topper",
"home.header.title": "Title",
"home.header.cta": "CTA",
},
} as const;
import { ui } from "./content";
import When from "./when.astro";
export { When };
export type Locales = "en" | "nl";
export const defaultLang: Locales = "en";
type Keys = keyof (typeof ui)[typeof defaultLang];
export interface TranslationFunction<Lang extends Locales = "en"> {
(key: Keys): string;
map: (map: Record<Lang, string>) => string;
}
/**
* Create functions with 'const t = useTranslations(Astro.currentLocale)'
* t("<string-key>") for long blocks of text
* t.map({ en: "english", nl: "dutch" }) for short phrases
*/
export const useTranslations = <Lang extends Locales>(
lang: Lang = defaultLang as Lang,
): TranslationFunction<Lang> => {
// @ts-expect-error Doing the internal types is annoying, so I don't.
const t = (key: Keys) => ui[lang][key] ?? ui[defaultLang][key];
t.map = (map: Record<Lang, string>) => map[lang];
return t as TranslationFunction<Lang>;
};

Internationalization System

This project uses a simple i18n system with three main tools:

Translation Function (t)

For most text content. Keys are defined in content.ts.

const t = useTranslations(Astro.currentLocale);
const text = t("home.hero.header"); // "Test"

Map Function (t.map)

For short text that relates to untranslated content (units, titles, etc).

const unit = t.map({ en: "kg", nl: "kg" });
const title = t.map({ en: "CEO", nl: "Directeur" });

When Component

Last resort for direct HTML manipulation based on locale.

<When lang="en">English content</When>
<When lang="nl">Dutch content</When>

Adding Content

Add new translations to src/i18n/content.ts in the ui object.

---
import { defaultLang, type Locales } from ".";
export interface Props {
lang: Locales;
}
const locale = Astro.currentLocale ?? defaultLang;
---
{Astro.props.lang === locale ? <slot /> : null}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment