#What is the best way to import YAML translations?
150 messages · Page 1 of 1 (latest)
Hi @vagrant fractal. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
There are a couple of approaches for importing YAML translations in Astro, depending on your use case:
1. Using Starlight's i18nLoader() (for Starlight docs sites)
If you're using Starlight, the recommended approach is to use the built-in i18nLoader(), which automatically loads JSON and YAML files from the src/content/i18n/ directory:
import { defineCollection } from 'astro:content';
import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders';
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),
};
2. Using a Rollup plugin (for general Astro projects)
For non-Starlight projects where you want to directly import YAML files, you can install @rollup/plugin-yaml and configure it in your Astro config:
npm install @rollup/plugin-yaml --save-dev
// astro.config.mjs
import { defineConfig } from 'astro/config';
import yaml from '@rollup/plugin-yaml';
export default defineConfig({
vite: {
plugins: [yaml()]
}
});
Then import YAML data directly:
import yml from './data.yml';
3. Using the file() loader in content collections
You can also use Astro's built-in file() loader to load YAML files as a content collection:
import { defineCollection } from 'astro:content';
import { file } from 'astro/loaders';
const translations = defineCollection({
loader: file("src/data/translations.yaml"),
});
export const collections = { translations };
Which to choose? If you're on a Starlight site, use i18nLoader(). For general Astro projects using content collections, use the file() loader. If you need to directly import YAML files in components, use the Rollup plugin approach.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
I am building my translation system (i18n) which approach is best?
I am building my translation system (i18n) which approach is best?
@muted veldt I am building my translation system (i18n) which approach is best?
Let me check the knowledge base for you again. One moment! :books:
Based on your previous question about YAML translations, here's guidance on choosing the best i18n approach:
If you're using Starlight
Use the built-in i18nLoader() with the i18nSchema(). This is the most integrated approach for Starlight docs sites:
import { defineCollection } from 'astro:content';
import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders';
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),
};
Place your translation JSON/YAML files in src/content/i18n/ (e.g., en.json, fr.json). You can then access translations in components via Astro.locals.t('your.key'). [Starlight i18n guide]
Starlight also supports plugin-provided translations via the i18n:setup hook if you're building a plugin. [Starlight plugins]
If you're building a custom i18n system in plain Astro
Use dynamic routing with content collections. Create language-specific subdirectories and use [lang] route parameters:
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
const blogCollection = defineCollection({
schema: z.object({ title: z.string(), author: z.string(), date: z.date() })
});
export const collections = { blog: blogCollection };
Then serve content at routes like /en/blog/ and /fr/blog/ using dynamic pages. [i18n recipe]
Summary:
- Starlight site → use
i18nLoader()+i18nSchema() - Custom Astro site → use content collections with dynamic
[lang]routing - Need to directly import YAML files → use the
@rollup/plugin-yamlRollup plugin [YAML recipe]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Do I need directly yaml or no? Also can I use the code from starlight if its not starlight actually? Or rather no
do I need directly yaml or no? Also can I use the code from starlight if its not starlight actually? Or rather no @muted veldt
Let me check the knowledge base for you again. One moment! :books:
Great questions! Let me address both:
Do you need to directly import YAML?
Not necessarily. For UI translation strings in a custom Astro project, the simpler approach from the knowledge sources is to create a plain TypeScript/JavaScript dictionary file (no YAML needed):
// src/i18n/ui.ts
export const ui = {
en: {
'nav.home': 'Home',
'nav.about': 'About',
},
fr: {
'nav.home': 'Accueil',
'nav.about': 'À propos',
},
} as const;
Then use helper functions to access the right strings based on the current URL. [i18n recipe] YAML is just one option if you prefer that file format — it's not required.
Can you use Starlight's i18n code in a non-Starlight project?
No. The Starlight-specific APIs (i18nLoader(), i18nSchema(), Astro.locals.t(), etc.) are part of the @astrojs/starlight package and depend on Starlight being installed and configured. They won't work in a plain Astro project. [Starlight i18n guide]
What to use instead for a plain Astro project
For a custom Astro site, the recommended approach is:
- A UI strings dictionary (TS/JS file) as shown above
- Helper functions like
getLangFromUrl()anduseTranslations()that you write yourself - Dynamic
[lang]routing for translated content pages
Bottom line: Skip YAML unless you have a specific reason to use it, and don't use Starlight's APIs outside of a Starlight project.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt i meant like should i use yaml through rollup or content collections
Let me check the knowledge base for you again. One moment! :books:
For a custom i18n system, content collections with the file() loader is the better choice over the Rollup plugin approach. Here's why:
-
The
file()loader is purpose-built for loading structured data (including YAML) into collections, giving you type safety, schema validation, and Astro's content APIs (getCollection(), etc.). [file() loader] -
The Rollup plugin (
@rollup/plugin-yaml) is simpler but just lets youimportYAML files directly — no schema validation, no content APIs, and your editor won't provide types without extra setup. [YAML recipe]
That said, for UI translation strings specifically (nav labels, button text, etc.), the knowledge sources actually recommend a plain TypeScript dictionary file (src/i18n/ui.ts) — no YAML or content collections needed at all. [i18n recipe]
Bottom line:
- UI strings → TypeScript dictionary (simplest, no YAML needed)
- Translated content/pages → content collections with
file()loader (if YAML) orglob()loader (if Markdown files per language) - Rollup plugin → only if you need to directly
importYAML in components and don't want to use collections
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt why not yaml? i will need it for a CMS is it bad practice to put it in yaml?
Let me check the knowledge base for you again. One moment! :books:
YAML is not bad practice at all! My previous answer may have been misleading. To clarify:
-
Astro's built-in
file()loader natively supports YAML files without any extra setup. You can point it directly at your YAML files and get full schema validation and type safety. [file() loader] -
If you're fetching content from a CMS at build time, you could also build a custom loader that fetches from your CMS API and loads it into a content collection — giving you all the same benefits regardless of the underlying format. [custom loaders]
So for your use case (CMS + YAML translations), the file() loader is a perfectly valid and clean approach:
import { defineCollection } from 'astro:content';
import { file } from 'astro/loaders';
const translations = defineCollection({
loader: file("src/data/translations.yaml"),
});
export const collections = { translations };
The Rollup plugin is only needed if you want to import YAML files directly in components outside of the content collections system. For CMS-driven content, content collections with the file() loader (or a custom loader) is the recommended path. [file() loader]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt Create me a schema for this
projects: Projects
services: Services
switchLanguage: Change language
cta: Let's discuss
Let me check the knowledge base for you again. One moment! :books:
Based on the YAML structure you've shared, here's a Zod schema you can use with Astro's file() loader:
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
import { file } from 'astro/loaders';
const translations = defineCollection({
loader: file("src/data/translations.yaml"),
schema: z.object({
nav: z.object({
projects: z.string(),
services: z.string(),
switchLanguage: z.string(),
cta: z.string(),
}),
}),
});
export const collections = { translations };
A couple of notes:
- Import
zfrom"astro/zod"(not directly from"zod") to ensure version compatibility with Astro. [Zod API] - The
file()loader natively supports YAML, so no extra parsing setup is needed. [file() loader] - If you want fields to be optional (e.g. if some locales might not have all keys), append
.optional()to anyz.string()field.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt can you add there my translatiosn i have them in src/content/i18n/x.yml
Let me check the knowledge base for you again. One moment! :books:
Based on your YAML file location (src/content/i18n/x.yml), here's the updated config:
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
import { file } from 'astro/loaders';
const translations = defineCollection({
loader: file("src/content/i18n/x.yml"),
schema: z.object({
nav: z.object({
projects: z.string(),
services: z.string(),
switchLanguage: z.string(),
cta: z.string(),
}),
}),
});
export const collections = { translations };
The only change from the previous example is the path passed to file() — now pointing to src/content/i18n/x.yml. The file() loader natively supports YAML, so no custom parser is needed. [file() loader]
Also, remember that each entry in the YAML file needs a unique id field (or be structured as an object where the key acts as the id), since the file() loader requires this to identify entries. [file() loader]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
i meant like @muted veldt i have en.yml and sk.yml files
Let me check the knowledge base for you again. One moment! :books:
Got it! Since you have multiple YAML files (one per locale), use the glob() loader instead of file(), as it can load all matching files from a directory at once:
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
import { glob } from 'astro/loaders';
const translations = defineCollection({
loader: glob({ pattern: "**/*.yml", base: "./src/content/i18n" }),
schema: z.object({
nav: z.object({
projects: z.string(),
services: z.string(),
switchLanguage: z.string(),
cta: z.string(),
}),
}),
});
export const collections = { translations };
This will automatically pick up both en.yml and sk.yml (and any future locale files you add). Each file's name (e.g. en, sk) will be used as the entry id, so you can query a specific locale with getEntry('translations', 'en'). [glob() loader]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt OK now whats the best way to use the translations in actual files
Let me check the knowledge base for you again. One moment! :books:
Based on your setup (content collections with glob() loader for en.yml and sk.yml), here's how to use the translations in your pages and components:
1. Query the translation entry for the current language
Use getEntry() to fetch the right locale's translations:
---
import { getEntry } from 'astro:content';
// Get the current language from the URL (e.g. /en/ or /sk/)
const lang = Astro.currentLocale ?? 'en';
// Fetch the matching translation file (en.yml or sk.yml)
const t = await getEntry('translations', lang);
---
<nav>
<a href={`/${lang}/projects`}>{t.data.nav.projects}</a>
<a href={`/${lang}/services`}>{t.data.nav.services}</a>
<button>{t.data.nav.cta}</button>
</nav>
[querying collections; currentLocale]
2. Get the language from the URL (without built-in i18n routing)
If you're not using Astro's built-in i18n routing, you can extract the language from the URL manually using a helper, as shown in the Astro i18n recipe:
// src/i18n/utils.ts
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split('/');
if (['en', 'sk'].includes(lang)) return lang;
return 'en'; // default
}
Then in your component:
---
import { getEntry } from 'astro:content';
import { getLangFromUrl } from '../i18n/utils';
const lang = getLangFromUrl(Astro.url);
const t = await getEntry('translations', lang);
---
<p>{t.data.nav.switchLanguage}</p>
Summary
- Use
getEntry('translations', lang)to fetch the right YAML file for the current language. - Access your strings via
t.data.nav.projects,t.data.nav.cta, etc. (matching your schema). - Use
Astro.currentLocaleif you have Astro's built-in i18n routing configured, or agetLangFromUrl()helper otherwise.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt how does the helper compare to Astros built in locale function
Let me check the knowledge base for you again. One moment! :books:
Based on the knowledge sources, here's how the manual getLangFromUrl() helper compares to Astro's built-in Astro.currentLocale:
getLangFromUrl() (manual helper)
- A custom function you write yourself, typically in
src/i18n/utils.ts - Parses the language from the URL pathname by splitting on
/ - Works regardless of whether Astro's built-in i18n routing is configured
- Useful when building a fully custom i18n system [i18n recipe]
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split('/');
if (lang in ui) return lang as keyof typeof ui;
return defaultLang;
}
Astro.currentLocale (built-in)
- Provided automatically by Astro when you configure the
i18noption inastro.config.mjs - Computes the locale from the current URL using your configured
locales - If the URL has no
/[locale]/prefix, it defaults toi18n.defaultLocaleautomatically - Available on all pages, including statically prerendered ones [currentLocale]
Which to use?
- If you have Astro's built-in i18n routing configured (
i18n.locales,i18n.defaultLocaleinastro.config.mjs), useAstro.currentLocale— it's simpler and automatically handles edge cases like missing prefixes. - If you're building a fully manual i18n system without Astro's routing config, use the
getLangFromUrl()helper.
Since you already have en.yml and sk.yml set up, configuring Astro's built-in i18n routing and using Astro.currentLocale would be the cleaner path. [configure i18n routing]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
and where should i set the language labels etc @muted veldt
Let me check the knowledge base for you again. One moment! :books:
Based on your setup, the language labels (for things like a language switcher) are best defined in a simple TypeScript/JS file. The Astro i18n recipe recommends a src/i18n/ui.ts file:
// src/i18n/ui.ts
export const languages = {
en: 'English',
sk: 'Slovenčina',
};
export const defaultLang = 'en';
Then you can use it in a language switcher component like this:
---
import { languages } from '../i18n/ui';
import { getLangFromUrl, useTranslatedPath } from '../i18n/utils';
const lang = getLangFromUrl(Astro.url);
const translatePath = useTranslatedPath(lang);
---
<ul>
{Object.entries(languages).map(([lang, label]) => (
<li>
<a href={translatePath('/', lang)}>{label}</a>
</li>
))}
</ul>
So in your case:
- Language labels (e.g. "English", "Slovenčina" for the switcher UI) →
src/i18n/ui.ts - Page/component translations (nav, buttons, etc.) → your
en.yml/sk.ymlfiles insrc/content/i18n/, queried viagetEntry('translations', lang)
This keeps the language switcher metadata separate from your actual translation strings.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
and how to write proper helper function for it something like this
import { getEntry } from 'astro:content';
export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof (typeof ui)[typeof defaultLang]) {
return await getEntry('translations', lang);
};
}
btw whats the ui file for
@muted veldt and how to write proper helper function for it something like this
import { getEntry } from 'astro:content';
export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof (typeof ui)[typeof defaultLang]) {
return await getEntry('translations', lang);
};
}
btw whats the ui file for
Let me check the knowledge base for you again. One moment! :books:
Good questions! Let me address both.
The ui.ts file — what's it for?
The src/i18n/ui.ts file from the recipe serves two purposes:
- Language labels for the switcher (e.g.
"English","Slovenčina") - UI string dictionaries — a fallback/alternative to YAML files for simple translation strings
Since you're already using YAML files via content collections, you mainly need ui.ts for the language labels and defaultLang. You don't need to duplicate your translations there. [i18n recipe]
Fixing your helper function
Your approach has a couple of issues:
getEntry()is async, so it can't be called inside a synchronous helper that returns a sync function- The inner
t()function should accept a key and return the specific string, not the whole entry
The cleanest pattern for your setup is to fetch the entry once and pass the data to a helper:
// src/i18n/utils.ts
import { getEntry } from 'astro:content';
export async function useTranslations(lang: string) {
const entry = await getEntry('translations', lang);
const translations = entry?.data;
return function t(key: string) {
// Navigate nested keys like "nav.projects"
return key.split('.').reduce((obj: any, k) => obj?.[k], translations) ?? key;
};
}
Then use it in your components like this:
---
import { useTranslations } from '../i18n/utils';
import { getLangFromUrl } from '../i18n/utils';
const lang = getLangFromUrl(Astro.url);
const t = await useTranslations(lang);
---
<a href="/">{t('nav.projects')}</a>
<button>{t('nav.cta')}</button>
The key difference from the recipe's original useTranslations() is that yours needs to be async because it calls getEntry(). [querying collections]
The knowledge sources don't explicitly show this exact async pattern combined with content collections, so treat the nested key traversal (key.split('.')) as a practical extension — the core getEntry() usage is directly from the docs.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt and put default language there or elsewhere
Let me check the knowledge base for you again. One moment! :books:
Based on the conversation so far, the defaultLang is best kept in your src/i18n/ui.ts file alongside the language labels — that's exactly where the Astro i18n recipe places it:
// src/i18n/ui.ts
export const languages = {
en: 'English',
sk: 'Slovenčina',
};
export const defaultLang = 'en';
Then import it wherever you need it — for example in your utils.ts:
// src/i18n/utils.ts
import { getEntry } from 'astro:content';
import { defaultLang } from './ui';
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split('/');
if (['en', 'sk'].includes(lang)) return lang;
return defaultLang; // falls back to 'en'
}
export async function useTranslations(lang: string) {
const entry = await getEntry('translations', lang);
const translations = entry?.data;
return function t(key: string) {
return key.split('.').reduce((obj: any, k) => obj?.[k], translations) ?? key;
};
}
So the split is:
src/i18n/ui.ts→defaultLang, language labels for the switchersrc/content/i18n/en.yml/sk.yml→ actual translation stringssrc/i18n/utils.ts→ helper functions that tie it all together
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt why ui.ts shouldnt it be smth else or cant i fetch from astro settings
Let me check the knowledge base for you again. One moment! :books:
Good question! You're right to question this. If you configure Astro's built-in i18n routing, you can get the default locale directly from Astro's config rather than maintaining a separate ui.ts file.
With Astro's built-in i18n routing configured in astro.config.mjs:
import { defineConfig } from "astro/config";
export default defineConfig({
i18n: {
defaultLocale: "en",
locales: ["en", "sk"],
}
});
You can then use Astro.currentLocale in your pages/components, which automatically reads from your config and defaults to i18n.defaultLocale if no locale prefix is found in the URL. [configure i18n routing]
This means:
- No need for
defaultLanginui.ts— Astro handles it via config - No need for
getLangFromUrl()—Astro.currentLocaledoes this for you [browser language detection]
You'd still keep ui.ts for the language labels (for the switcher UI), but it becomes much simpler:
// src/i18n/ui.ts
export const languages = {
en: 'English',
sk: 'Slovenčina',
};
And your utils.ts no longer needs to import defaultLang from ui.ts at all — just use Astro.currentLocale directly in your components.
Bottom line: If you set up Astro's built-in i18n routing, ui.ts only needs the language labels for the switcher. Everything else (defaultLocale, current locale detection) comes from Astro's config and built-in APIs.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
export function useTranslations(lang: LanguageCode) {
return function t(key: TranslationKey) {
return ui[lang][key] || ui[defaultLang][key];
};
}
import { getEntry } from "astro:content";
import { languages, defaultLang, type LanguageCode } from "../i18n/ui";
export async function useTranslations(lang: string) {
const entry = await getEntry("translations", lang);
const translations = entry?.data;
return function t(key: string) {
return key.split(".").reduce((obj: any, k) => obj?.[k], translations) ?? key;
};
}
``` @muted veldt what the differences and which
Let me check the knowledge base for you again. One moment! :books:
Here's a breakdown of the two approaches:
Approach 1 — TypeScript dictionary (ui.ts)
import { ui, defaultLang, type LanguageCode, type TranslationKey } from "./ui";
export function useTranslations(lang: LanguageCode) {
return function t(key: TranslationKey) {
return ui[lang][key] || ui[defaultLang][key];
};
}
- Synchronous — no
awaitneeded - Translations live in a TypeScript object in
ui.ts - Fully typed —
TranslationKeyis inferred from the object, so you get autocomplete and type errors for missing keys - Automatic fallback to
defaultLangif a key is missing in the current language - No content collections involved [i18n recipe]
Approach 2 — Content collections (en.yml / sk.yml)
export async function useTranslations(lang: string) {
const entry = await getEntry("translations", lang);
const translations = entry?.data;
return function t(key: string) {
return key.split(".").reduce((obj: any, k) => obj?.[k], translations) ?? key;
};
}
- Async — requires
awaiteverywhere you call it - Translations live in YAML files loaded via content collections
- Key type is just
string— less type safety, no autocomplete for keys - Falls back to returning the key itself if not found (not the default language's value)
- Better suited for CMS-driven or large-scale content [querying collections]
Which to use?
Given that you mentioned you're using YAML files from a CMS, Approach 2 fits your setup better. The main trade-off is losing the strong typing on translation keys.
If type safety on keys matters to you, Approach 1 is cleaner — but you'd have to move your translations from YAML into a TypeScript file, which may not suit a CMS workflow.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt genereate also TS types for everything
Let me check the knowledge base for you again. One moment! :books:
Based on your setup (YAML files via content collections), here's Approach 2 with TypeScript types added throughout:
// src/i18n/utils.ts
import { getEntry } from "astro:content";
import type { CollectionEntry } from "astro:content";
// Type for the nested nav structure matching your YAML schema
interface NavTranslations {
projects: string;
services: string;
switchLanguage: string;
cta: string;
}
// Type for the full translation data shape
interface TranslationData {
nav: NavTranslations;
}
// Type for supported language codes
export type LanguageCode = "en" | "sk";
// Type for dot-notation keys (e.g. "nav.projects")
type DotNotationKeys<T, Prefix extends string = ""> = {
[K in keyof T]: T[K] extends object
? DotNotationKeys<T[K], `${Prefix}${K & string}.`>
: `${Prefix}${K & string}`;
}[keyof T];
export type TranslationKey = DotNotationKeys<TranslationData>;
export async function useTranslations(lang: LanguageCode) {
const entry: CollectionEntry<"translations"> | undefined = await getEntry(
"translations",
lang
);
const translations = entry?.data as TranslationData | undefined;
return function t(key: TranslationKey): string {
return (
key.split(".").reduce((obj: unknown, k: string) =>
obj && typeof obj === "object" ? (obj as Record<string, unknown>)[k] : undefined,
translations
) as string ?? key
);
};
}
The DotNotationKeys utility type will automatically derive all valid keys ("nav.projects", "nav.services", "nav.switchLanguage", "nav.cta") from your TranslationData interface, so you get autocomplete and type errors if you use an invalid key.
Note that the DotNotationKeys recursive type and the CollectionEntry typing are based on standard TypeScript patterns and Astro's content collection types — the knowledge sources confirm CollectionEntry is available from astro:content [defining schemas], but the recursive dot-notation type is a TypeScript utility not explicitly shown in the knowledge sources, so treat that part as a practical extension.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
export async function useTranslations(lang: string) {
const entry = await getEntry("translations", lang);
const translations = entry?.data;
return function t(key: string) {
return key.split(".").reduce((obj: any, k) => obj?.[k], translations) ?? key;
};
}
``````export const languages = {
sk: "Slovenčina",
en: "English",
} as const;
export type LanguageCode = keyof typeof languages;
export const defaultLang = "sk";
```make @muted veldt type for ts
Let me check the knowledge base for you again. One moment! :books:
Here's your code with full TypeScript types, using LanguageCode from your ui.ts and deriving translation keys from your YAML schema:
src/i18n/ui.ts — no changes needed, already well-typed:
export const languages = {
sk: "Slovenčina",
en: "English",
} as const;
export type LanguageCode = keyof typeof languages;
export const defaultLang: LanguageCode = "sk";
src/i18n/utils.ts — with full types:
import { getEntry } from "astro:content";
import type { CollectionEntry } from "astro:content";
import type { LanguageCode } from "./ui";
// Mirror your YAML schema
interface NavTranslations {
projects: string;
services: string;
switchLanguage: string;
cta: string;
}
interface TranslationData {
nav: NavTranslations;
}
// Derive dot-notation keys from TranslationData (e.g. "nav.projects")
type DotNotationKeys<T, Prefix extends string = ""> = {
[K in keyof T]: T[K] extends object
? DotNotationKeys<T[K], `${Prefix}${K & string}.`>
: `${Prefix}${K & string}`;
}[keyof T];
export type TranslationKey = DotNotationKeys<TranslationData>;
export async function useTranslations(lang: LanguageCode) {
const entry: CollectionEntry<"translations"> | undefined = await getEntry(
"translations",
lang
);
const translations = entry?.data as TranslationData | undefined;
return function t(key: TranslationKey): string {
return (
key.split(".").reduce(
(obj: unknown, k: string) =>
obj && typeof obj === "object"
? (obj as Record<string, unknown>)[k]
: undefined,
translations
) as string ?? key
);
};
}
Key changes:
langis now typed asLanguageCodeinstead ofstring, so only"en"or"sk"are acceptedTranslationKeyis derived automatically fromTranslationData, giving you autocomplete on valid keys like"nav.projects","nav.cta", etc.CollectionEntry<"translations">types the entry fromgetEntry()[astro:content types]
Note: The
DotNotationKeysrecursive utility type is a standard TypeScript pattern — the knowledge sources confirmCollectionEntryis available fromastro:content[querying collections], but the recursive type itself is a TypeScript extension not explicitly shown in the docs.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt do i need to mirror it loos useless
Let me check the knowledge base for you again. One moment! :books:
You're right to question it — the TranslationData interface is essentially duplicating your Zod schema. It's not strictly necessary, but the knowledge sources don't provide a built-in way to automatically infer the TypeScript type from a content collection's schema for use outside of Astro files.
What you can do is use Astro's generated types. When you define a schema in your collection, Astro automatically generates TypeScript types for it. [defining schemas] So CollectionEntry<"translations">["data"] will already be typed based on your Zod schema in content.config.ts.
This means you can simplify by removing the manual TranslationData interface and just use the inferred type:
import { getEntry } from "astro:content";
import type { CollectionEntry } from "astro:content";
import type { LanguageCode } from "./ui";
type TranslationData = CollectionEntry<"translations">["data"];
type DotNotationKeys<T, Prefix extends string = ""> = {
[K in keyof T]: T[K] extends object
? DotNotationKeys<T[K], `${Prefix}${K & string}.`>
: `${Prefix}${K & string}`;
}[keyof T];
export type TranslationKey = DotNotationKeys<TranslationData>;
export async function useTranslations(lang: LanguageCode) {
const entry = await getEntry("translations", lang);
const translations = entry?.data;
return function t(key: TranslationKey): string {
return (
key.split(".").reduce(
(obj: unknown, k: string) =>
obj && typeof obj === "object"
? (obj as Record<string, unknown>)[k]
: undefined,
translations
) as string ?? key
);
};
}
Now TranslationData is derived directly from your collection schema — no duplication needed. If you update your Zod schema in content.config.ts, the types here update automatically. [astro:content types]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
sk: "Slovenčina",
en: "Englis``` any type here @muted veldt
Let me check the knowledge base for you again. One moment! :books:
You don't need any extra type annotation here — TypeScript already infers the type correctly from as const. Your LanguageCode type derived from it is all you need:
export const languages = {
sk: "Slovenčina",
en: "English",
} as const;
export type LanguageCode = keyof typeof languages;
// LanguageCode = "sk" | "en"
export type LanguageLabel = (typeof languages)[LanguageCode];
// LanguageLabel = "Slovenčina" | "English"
The as const assertion makes the values readonly string literals instead of just string, which is what allows keyof typeof languages to produce the precise "sk" | "en" union type rather than just string. No explicit type annotation on languages itself is needed or useful here. [i18n recipe]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt type DotNotationKeys<T, Prefix extends string = ""> = {
[K in keyof T]: T[K] extends object
? DotNotationKeys<T[K], ${Prefix}${K & string}.>
: ${Prefix}${K & string};
}[keyof T];
whats thsi
Let me check the knowledge base for you again. One moment! :books:
This is a TypeScript utility type, not specific to Astro — it's a general TypeScript pattern I used in the previous answers to derive dot-notation string keys from a nested object type.
Here's what it does step by step:
type DotNotationKeys<T, Prefix extends string = ""> = {
[K in keyof T]: T[K] extends object
? DotNotationKeys<T[K], `${Prefix}${K & string}.`>
: `${Prefix}${K & string}`;
}[keyof T];
[K in keyof T]— iterates over every key in typeTT[K] extends object— checks if the value at that key is a nested object- If yes → recurse deeper, appending the current key +
.to the prefix - If no → it's a leaf value, so return the full dot-notation string (e.g.
"nav.projects")
- If yes → recurse deeper, appending the current key +
[keyof T]at the end — collapses the mapped type into a union of all the resulting strings
So given your TranslationData shape:
// nav.projects, nav.services, nav.switchLanguage, nav.cta
type TranslationKey = DotNotationKeys<TranslationData>;
It produces: "nav.projects" | "nav.services" | "nav.switchLanguage" | "nav.cta"
This gives you autocomplete and type safety when calling t("nav.projects") — TypeScript will error if you pass an invalid key.
That said, this is a pure TypeScript utility — it's not from the Astro docs. I introduced it as a practical extension to type your t() function.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt now how to utilize ts here
Let me check the knowledge base for you again. One moment! :books:
Based on your full setup, here's how to use everything together in an actual Astro component:
---
// src/components/Nav.astro
import { useTranslations } from '../i18n/utils';
import { languages } from '../i18n/ui';
import type { LanguageCode } from '../i18n/ui';
const lang = (Astro.currentLocale ?? 'sk') as LanguageCode;
const t = await useTranslations(lang);
---
<nav>
<a href={`/${lang}/projects`}>{t('nav.projects')}</a>
<a href={`/${lang}/services`}>{t('nav.services')}</a>
<button>{t('nav.cta')}</button>
</nav>
<!-- Language switcher -->
<ul>
{Object.entries(languages).map(([code, label]) => (
<li>
<a href={`/${code}/`}>{label}</a>
</li>
))}
</ul>
Key points:
Astro.currentLocalegives you the current language from Astro's built-in i18n routing — cast it toLanguageCodesince it returnsstring | undefined[configure i18n routing]await useTranslations(lang)fetches the right YAML file and returns the typedt()functiont('nav.projects')— TypeScript will autocomplete and error on invalid keys thanks toTranslationKey(theDotNotationKeysutility)languagesfromui.tsis used for the switcher labels, typed as"sk" | "en"viaas const
The TypeScript types flow through automatically:
LanguageCode→ constrains what you can pass touseTranslations()TranslationKey→ constrains what you can pass tot()CollectionEntry<"translations">["data"]→ inferred from your Zod schema incontent.config.ts[astro:content types]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt how to make the one i am using rn bold and also how to sort it from bold one to normal
Let me check the knowledge base for you again. One moment! :books:
Based on the conversation, you're asking about making the current language bold in your language switcher and sorting it first. Here's how to do that in your Nav component:
---
import { useTranslations } from '../i18n/utils';
import { languages } from '../i18n/ui';
import type { LanguageCode } from '../i18n/ui';
const lang = (Astro.currentLocale ?? 'sk') as LanguageCode;
const t = await useTranslations(lang);
// Sort so current language comes first
const sortedLanguages = Object.entries(languages).sort(([code]) =>
code === lang ? -1 : 1
);
---
<ul>
{sortedLanguages.map(([code, label]) => (
<li>
<a
href={`/${code}/`}
aria-current={code === lang ? 'true' : undefined}
style={code === lang ? 'font-weight: bold;' : undefined}
>
{label}
</a>
</li>
))}
</ul>
Key points:
code === langcompares each language code againstAstro.currentLocaleto identify the active one [currentLocale]font-weight: boldis applied inline only to the current languagearia-current="true"is good practice for accessibility on the active language link.sort()moves the current language to the top — entries returning-1sort before those returning1
You can also use a CSS class instead of inline styles if you prefer:
<a
href={`/${code}/`}
class={code === lang ? 'active' : undefined}
>
{label}
</a>
<style>
.active {
font-weight: bold;
}
</style>
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Object.entries(languages).map(([code, label]) => (
<a
href={`/${code}`}
class:list={[
"flex items-center p-4 transition-colors duration-300 ease-in-out hover:bg-black/5",
code == lang && "font-medium",
]}
>
{label}
</a>
))
}``` y ts not work @muted veldt current item
Let me check the knowledge base for you again. One moment! :books:
The issue is that code == lang uses loose equality (==) instead of strict equality (===), but more likely the real problem is a type mismatch: code from Object.entries(languages) is typed as string, while lang is typed as LanguageCode ("sk" | "en"). This can cause unexpected behavior.
Also, class:list accepts strings, objects, and arrays — your usage is correct syntax-wise. [class:list directive]
Try this fix — use strict equality and ensure the types align:
{
Object.entries(languages).map(([code, label]) => (
<a
href={`/${code}`}
class:list={[
"flex items-center p-4 transition-colors duration-300 ease-in-out hover:bg-black/5",
{ "font-medium": code === lang },
]}
>
{label}
</a>
))
}
Key changes:
===instead of==— strict equality is safer with TypeScript- Object syntax
{ "font-medium": code === lang }— this is the idiomaticclass:listpattern for conditional classes; truthy keys get added to the class [class:list directive]
Also double-check that lang is correctly set from Astro.currentLocale before the map:
const lang = (Astro.currentLocale ?? 'sk') as LanguageCode;
If Astro.currentLocale is undefined (e.g. i18n routing not configured), lang will always be 'sk' and no item will match code === lang for 'en'.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
whats wron @muted veldt
Let me check the knowledge base for you again. One moment! :books:
Based on the conversation history, you're asking about why the current language isn't being highlighted in your language switcher. Looking at your code, the most likely issue is that Astro.currentLocale may not be returning what you expect.
A few things to check:
-
Is Astro's built-in i18n routing configured?
Astro.currentLocaleonly works correctly if you havei18n.localesandi18n.defaultLocaleset inastro.config.mjs. Without it,Astro.currentLocalewill beundefined. [currentLocale] -
Does your URL have a
/[locale]/prefix?Astro.currentLocalereads the locale from the URL. If your page is at/instead of/sk/or/en/, it defaults toi18n.defaultLocale— so make sure your routes include the locale prefix. -
Use the object syntax for conditional classes — your
class:listshould look like:
class:list={[
"flex items-center p-4 transition-colors duration-300 ease-in-out hover:bg-black/5",
{ "font-medium": code === lang },
]}
Try adding a quick debug check in your frontmatter to see what Astro.currentLocale actually returns:
---
const lang = (Astro.currentLocale ?? 'sk') as LanguageCode;
console.log('currentLocale:', Astro.currentLocale, 'lang:', lang);
---
That will tell you if the locale is being detected correctly from the URL.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
does ts look ok?
export const languages = {
sk: "Slovenčina",
en: "English",
} as const;
export type LanguageCode = keyof typeof languages;
export const defaultLang: LanguageCode = "sk";
import { getEntry } from "astro:content";
import type { CollectionEntry } from "astro:content";
import type { LanguageCode } from "./ui";
type TranslationData = CollectionEntry<"translations">["data"];
type DotNotationKeys<T, Prefix extends string = ""> = {
[K in keyof T]: T[K] extends object
? DotNotationKeys<T[K], ${Prefix}${K & string}.>
: ${Prefix}${K & string};
}[keyof T];
export type TranslationKey = DotNotationKeys<TranslationData>;
export async function useTranslations(lang: LanguageCode) {
const entry = await getEntry("translations", lang);
const translations = entry?.data;
return function t(key: TranslationKey) {
return key.split(".").reduce((obj: any, k) => obj?.[k], translations) ?? key;
};
}
@muted veldt
Let me check the knowledge base for you again. One moment! :books:
The TypeScript looks good overall! A couple of small things to note:
1. Use import type for type-only imports
Astro recommends using import type for type imports to avoid bundling issues: [type imports]
import { getEntry } from "astro:content";
import type { CollectionEntry } from "astro:content";
import type { LanguageCode } from "./ui";
You already have this correct — good.
2. The t() return type could be more explicit
Currently t() implicitly returns any because of the reduce with obj: any. You can tighten it:
return function t(key: TranslationKey): string {
return (
key.split(".").reduce(
(obj: unknown, k: string) =>
obj && typeof obj === "object"
? (obj as Record<string, unknown>)[k]
: undefined,
translations
) as string ?? key
);
};
3. Everything else looks correct:
as const+keyof typeof languagesforLanguageCode✅defaultLang: LanguageCodeannotation ✅CollectionEntry<"translations">["data"]to inferTranslationDatafrom your Zod schema automatically ✅ [astro:content types]DotNotationKeysutility for typed key autocomplete ✅useTranslationstyped to only acceptLanguageCode✅
The main improvement worth making is replacing obj: any with obj: unknown and a proper type guard to avoid the implicit any leaking through.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt
return function t(key: TranslationKey): string {
const result = key
.split(".")
.reduce((obj: unknown, k) => (obj as any)?.[k], translations);
return typeof result === "string" ? result : key;
};
is this better
Let me check the knowledge base for you again. One moment! :books:
Yes, this is better than the previous version. Here's why it's an improvement:
return function t(key: TranslationKey): string {
const result = key
.split(".")
.reduce((obj: unknown, k) => (obj as any)?.[k], translations);
return typeof result === "string" ? result : key;
};
Improvements over the previous version:
- Explicit runtime type check —
typeof result === "string"properly validates the result before returning it, rather than blindly casting withas string - Safe fallback — if the key doesn't resolve to a string (e.g. missing key, wrong path), it falls back to returning
keyitself, which is the correct behavior - Cleaner reduce — using
(obj as any)?.[k]is a reasonable pragmatic choice here since the intermediate values during traversal are genuinely unknown-shaped nested objects
The only minor caveat is the as any cast inside the reduce — it's a practical trade-off to avoid verbose type gymnastics for intermediate traversal steps, and the final typeof result === "string" check at the end ensures type safety where it actually matters (the return value).
Overall this is a solid, clean implementation. The explicit typeof guard is the right approach here.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
return function t<K extends TranslationKey>(
key: K
): DotNotationValue<typeof translations, K> | K {
const result = key
.split(".")
.reduce<unknown>(
(obj, k) =>
obj && typeof obj === "object"
? (obj as Record<string, unknown>)[k]
: undefined,
translations
);
return result !== undefined ? (result as DotNotationValue<typeof translations, K>) : key;
};
@muted veldt is this better than previous or too much
Let me check the knowledge base for you again. One moment! :books:
This is more type-safe in theory, but it's likely too much for this use case. Here's why:
What it adds:
DotNotationValue<typeof translations, K>attempts to infer the exact return type based on the key passed in, rather than just returningstring- The generic
K extends TranslationKeypreserves the specific key type through the function
Why it's probably overkill:
DotNotationValueis another recursive utility type you'd need to define and maintain — it's not from the Astro docs or knowledge sources, it's pure TypeScript complexity- In practice, all your translation values are
string(as defined in your Zod schema), so the return type will always bestring | K— the extra generic inference buys you nothing over juststring - It makes the code harder to read and maintain for no real practical benefit
The previous version is the right balance:
return function t(key: TranslationKey): string {
const result = key
.split(".")
.reduce<unknown>(
(obj, k) =>
obj && typeof obj === "object"
? (obj as Record<string, unknown>)[k]
: undefined,
translations
);
return typeof result === "string" ? result : key;
};
This is already type-safe where it matters — TranslationKey constrains valid inputs, and the runtime typeof check ensures a safe string return. The more complex version would only be worth it if your translation values had different types (e.g. some keys return numbers or objects), which yours don't.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt so ishould verify the t() keys
Let me check the knowledge base for you again. One moment! :books:
Based on the conversation, you're already verifying t() keys through the TranslationKey type — TypeScript will error at compile time if you pass an invalid key.
Your current setup already handles this:
-
Compile-time —
TranslationKey(derived fromDotNotationKeys<TranslationData>) constrains what keys are valid. Passingt('nav.nonexistent')will be a TypeScript error. -
Runtime — the
typeof result === "string"check handles the case where a key doesn't resolve, falling back to returning the key itself:
return typeof result === "string" ? result : key;
So yes, you're already verifying keys at both levels. The only thing you could add is a dev-mode warning when a key falls back:
return function t(key: TranslationKey): string {
const result = key
.split(".")
.reduce<unknown>(
(obj, k) =>
obj && typeof obj === "object"
? (obj as Record<string, unknown>)[k]
: undefined,
translations
);
if (typeof result !== "string") {
console.warn(`[i18n] Missing translation key: "${key}" for lang: "${lang}"`);
}
return typeof result === "string" ? result : key;
};
This is optional but useful during development to catch keys that exist in one locale's YAML but are missing in another. The TypeScript type checking is the primary verification mechanism here — the runtime warning is just a safety net for mismatched YAML files.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt is it worth it to impelment this using astro middleware
Let me check the knowledge base for you again. One moment! :books:
Based on your current setup, implementing i18n translations via Astro middleware is not necessary and likely overkill for what you're doing.
Here's when middleware would make sense for i18n:
- Redirecting to a default locale — e.g. redirecting
/to/sk/automatically - Fallback routing — redirecting missing translated pages to another locale
- Manual routing control — if you set
routing: "manual"and need full control over locale detection and redirects [manual routing]
For example, Astro's middleware helper functions like redirectToDefaultLocale() and redirectToFallback() are only available when routing: "manual" is set. [redirectToFallback]
For your use case (YAML translations via content collections + t() helper), middleware adds no value because:
- You're already using
Astro.currentLocaleto detect the language from the URL - Your
useTranslations()fetches the right YAML file per page render - Translation lookup happens at the component level, not the request level
The only scenario where middleware could help your setup is if you want to store the translation function in context.locals so you don't have to call await useTranslations(lang) in every component individually:
// src/middleware.ts
import { defineMiddleware } from "astro:middleware";
import { useTranslations } from "./i18n/utils";
import type { LanguageCode } from "./i18n/ui";
export const onRequest = defineMiddleware(async (context, next) => {
const lang = (context.currentLocale ?? "sk") as LanguageCode;
context.locals.t = await useTranslations(lang);
return next();
});
That's a reasonable convenience, but it's not required. Stick with your current approach unless you find yourself repeating await useTranslations(lang) in many components.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt i meant like use Astro.locals.t() in files instead of like now with t()
Let me check the knowledge base for you again. One moment! :books:
Yes, that's exactly what the middleware approach enables — you store the t() function in context.locals once, and then access it as Astro.locals.t() in any component without calling await useTranslations(lang) each time.
Here's how to set it up:
src/middleware.ts
import { defineMiddleware } from "astro:middleware";
import { useTranslations } from "./i18n/utils";
import type { LanguageCode } from "./i18n/ui";
export const onRequest = defineMiddleware(async (context, next) => {
const lang = (context.currentLocale ?? "sk") as LanguageCode;
context.locals.t = await useTranslations(lang);
return next();
});
Then in any component:
---
// No need to import useTranslations or call await
const t = Astro.locals.t;
---
<a href="/">{t('nav.projects')}</a>
<button>{t('nav.cta')}</button>
To get TypeScript to recognize Astro.locals.t, add a type declaration in your src/env.d.ts:
import type { TranslationKey } from "./i18n/utils";
declare namespace App {
interface Locals {
t: (key: TranslationKey) => string;
}
}
This gives you full autocomplete on Astro.locals.t('nav.projects') across all your components, and you only pay the cost of fetching the translation entry once per request in the middleware rather than per component.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt so should i implement it or pass
Let me check the knowledge base for you again. One moment! :books:
Based on your setup, here's a quick decision guide:
Implement it (middleware approach) if:
- You have many components that all need
t()— it removes the repetitiveawait useTranslations(lang)boilerplate from every component - You want a cleaner, more centralized i18n setup
Skip it (keep current approach) if:
- You only use
t()in a few components — the overhead of setting up middleware isn't worth it - You prefer keeping things simple and explicit
The middleware approach is a convenience optimization, not a requirement. Your current await useTranslations(lang) approach works correctly. The only real benefit of moving to Astro.locals.t() is ergonomics — one less await call per component. [middleware guide]
If you're building a site with many pages and components that all use translations, it's worth implementing. If it's a small site, pass.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@muted veldt create me a name for this task on my todo list so i can deal with it later
Let me check the knowledge base for you again. One moment! :books:
I'm sorry, but I'm only able to help with questions about Astro! For naming your todo task, you'd need a general-purpose assistant.
That said, if you'd like to come back and implement the middleware-based Astro.locals.t() approach we discussed, I'm here to help whenever you're ready.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Let me check the knowledge base for you again. One moment! :books:
It looks like your message got cut off! Could you share the code you have at the top of your frontmatter? That way I can help you figure out how to simplify it with the middleware approach or advise on your current setup.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@vagrant fractal Kapa.ai is still learning and improving, please let me know how I did by reacting below.
import { useTranslations } from "../../i18n/utils";
import { languages, defaultLang } from "../../i18n/ui";
import type { LanguageCode } from "../../i18n/ui";
const lang = (Astro.currentLocale ?? defaultLang) as LanguageCode;
const { t, translations } = await useTranslations(lang);
@muted veldt frontmatter
Let me check the knowledge base for you again. One moment! :books:
Based on your current frontmatter, here's how the middleware approach would simplify it:
Current (per component):
import { useTranslations } from "../../i18n/utils";
import { languages, defaultLang } from "../../i18n/ui";
import type { LanguageCode } from "../../i18n/ui";
const lang = (Astro.currentLocale ?? defaultLang) as LanguageCode;
const { t, translations } = await useTranslations(lang);
With middleware (Astro.locals.t):
const t = Astro.locals.t;
// That's it
All the imports, the lang detection, and the await call move into the middleware once, and every component just uses Astro.locals.t directly. [middleware guide]