Översättningsladdning på serversidan och meddelandekataloger
Läs in namnområdesindelade meddelandekataloger i serverkomponenter utan att skicka alla lokaler till klienten.
Översättningsladdning på serversidan och meddelandekataloger är en gratis lektion i Next.js 15 fullstack (App Router + Server Actions) på CoddyKit. Detta är lektion 2 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Next.js 15 fullstack (App Router + Server Actions), och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Next.js 15 fullstack (App Router + Server Actions) innehåller totalt 4 lektioner.
Varför översättningar bör läsas in på serversidan
I Next.js 15 med App Router körs serverkomponenter uteslutande på servern. Det ger oss en kraftfull möjlighet: vi kan läsa in översättningsfiler vid request-tillfället utan att någonsin skicka oanvända locale-paket till klienten.
Det naiva tillvägagångssättet — att importera alla översättningar vid byggtillfället och paketera dem i klienten — orsakar flera problem:
- Stora JavaScript-paket som gör den första sidladdningen långsammare
- Strängar för alla lokaler skickas även när bara en locale är aktiv
- Ingen möjlighet att läsa in översättningar vid behov per route eller namespace
Serverlösningen håller översättningsdata på servern och levererar endast renderad HTML till klienten, vilket håller paketen små.
Förstå meddelandekataloger och namespaces
En meddelandekatalog är en strukturerad fil (JSON, YAML eller liknande) som innehåller nyckel-värde-par för en specifik locale. Namespacing delar upp kataloger i logiska områden, så att ni bara läser in det som en viss sida eller komponent faktiskt behöver.
En typisk projektstruktur kan se ut så här:
messages/en/common.json— gemensamma UI-strängar (knappar och etiketter)messages/en/home.json— strängar som är specifika för startsidanmessages/en/dashboard.json— dashboard-strängarmessages/tr/common.json— motsvarande turkiska strängar
När en serverkomponent för dashboarden renderas läser den endast in dashboard.json och common.json för den aktiva lokalen — inte hela katalogträdet.
Konfigurera messages-katalogen
Börja med att skapa namespacade JSON-filer under en messages-katalog i projektroten. Varje locale får en egen undermapp och varje namespace får en egen fil.
JSON-strukturen använder kapslade nycklar för att gruppera relaterade strängar. Det gör det enkelt att skala utan nyckelkollisioner mellan namespaces.
// messages/en/common.json
{
"nav": {
"home": "Home",
"about": "About",
"dashboard": "Dashboard"
},
"actions": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete"
}
}
// messages/en/dashboard.json
{
"title": "Your Dashboard",
"welcome": "Welcome back, {name}!",
"stats": {
"totalUsers": "Total Users",
"revenue": "Revenue",
"activeNow": "Active Now"
},
"empty": "No data available yet."
}
// messages/tr/dashboard.json
{
"title": "Panonuz",
"welcome": "Tekrar hoş geldiniz, {name}!",
"stats": {
"totalUsers": "Toplam Kullanıcı",
"revenue": "Gelir",
"activeNow": "Şu An Aktif"
},
"empty": "Henüz veri yok."
}Bygga en typsäker översättningsladdare
Skapa ett verktyg som läser JSON-filer från disken vid request-tillfället. Eftersom detta körs i en serverkomponent är fs-åtkomst tillgänglig, och resultatet hamnar aldrig i klientens paket.
Viktiga designbeslut för denna laddare:
- Ta emot parametrarna
localeochnamespaceför att bara läsa in det som behövs - Använd TypeScript-generics så att anroparna får typade returvärden
- Kasta ett tydligt fel om en namespace-fil saknas, så att felkonfigurationer upptäcks tidigt
// lib/i18n/loader.ts
import { readFile } from 'fs/promises';
import path from 'path';
export type Messages = Record<string, unknown>;
export async function loadMessages<T extends Messages>(
locale: string,
namespace: string
): Promise<T> {
const filePath = path.join(
process.cwd(),
'messages',
locale,
`${namespace}.json`
);
try {
const raw = await readFile(filePath, 'utf-8');
return JSON.parse(raw) as T;
} catch (error) {
throw new Error(
`[i18n] Failed to load namespace "${namespace}" for locale "${locale}". ` +
`Expected file at: ${filePath}`
);
}
}Läsa den aktiva lokalen från requesten
I Next.js 15 lagras den aktiva lokalen vanligtvis i URL:ens sökvägssegment (till exempel /en/dashboard och /tr/dashboard). App Router gör den tillgänglig via route-parametrar i layout- och sidkomponenter.
Ett vanligt mönster är att definiera ett dynamiskt segment [locale] i roten av app-katalogen, så att locale blir tillgänglig som en parameter i hela underträdet.
// app/[locale]/layout.tsx
import { ReactNode } from 'react';
interface LocaleLayoutProps {
children: ReactNode;
params: Promise<{ locale: string }>;
}
export default async function LocaleLayout({
children,
params,
}: LocaleLayoutProps) {
// In Next.js 15, params is a Promise — always await it
const { locale } = await params;
// Validate locale to prevent path traversal attacks
const supportedLocales = ['en', 'tr', 'de', 'fr'];
if (!supportedLocales.includes(locale)) {
// Middleware should have redirected already, but guard here too
throw new Error(`Unsupported locale: ${locale}`);
}
return (
<html lang={locale}>
<body>{children}</body>
</html>
);
}Läsa in översättningar direkt i en serverkomponent
När laddningsverktyget och locale-parametern finns på plats kan en serverkomponent läsa in sina namespace-översättningar med ett enda anrop av await. Data används under renderingen och serialiseras aldrig till klienten.
Observera hur detta mönster håller komponenten ren: inga context-providers, inga hooks och ingen ny hämtning av översättningar på klientsidan.
// app/[locale]/dashboard/page.tsx
import { loadMessages } from '@/lib/i18n/loader';
interface DashboardMessages {
title: string;
welcome: string;
stats: {
totalUsers: string;
revenue: string;
activeNow: string;
};
empty: string;
}
interface PageProps {
params: Promise<{ locale: string }>;
}
export default async function DashboardPage({ params }: PageProps) {
const { locale } = await params;
// Only dashboard namespace is loaded — not the entire catalog
const t = await loadMessages<DashboardMessages>(locale, 'dashboard');
return (
<main>
<h1>{t.title}</h1>
<p>{t.welcome.replace('{name}', 'Mehmet')}</p>
<ul>
<li>{t.stats.totalUsers}</li>
<li>{t.stats.revenue}</li>
<li>{t.stats.activeNow}</li>
</ul>
</main>
);
}Cacha översättningar med Reacts Cache API
En enda begäran kan rendera flera serverkomponenter som behöver samma namnrymd. Utan cachning utlöser varje komponent ett separat readFile-anrop för samma fil.
React 18+ levereras med funktionen cache(), som memorerar asynkrona funktioner per begäran. Om laddaren omsluts med cache() läses filen endast en gång per kombination av språkvariant och namnrymd under varje begäran, även om tio komponenter anropar den.
// lib/i18n/loader.ts (updated with per-request caching)
import { readFile } from 'fs/promises';
import { cache } from 'react';
import path from 'path';
export type Messages = Record<string, unknown>;
// cache() deduplicates calls within the same React render pass
const readMessageFile = cache(async (locale: string, namespace: string) => {
const filePath = path.join(
process.cwd(),
'messages',
locale,
`${namespace}.json`
);
const raw = await readFile(filePath, 'utf-8');
return JSON.parse(raw);
});
export async function loadMessages<T extends Messages>(
locale: string,
namespace: string
): Promise<T> {
try {
return await readMessageFile(locale, namespace) as T;
} catch {
throw new Error(
`[i18n] Missing namespace "${namespace}" for locale "${locale}"`
);
}
}Ladda flera namnrymder i en layout
En layoutkomponent behöver ofta strängar från flera namnrymder — till exempel navigeringsetiketter från common och rubriker för sidavsnitt från en funktionsspecifik namnrymd. Ladda dem parallellt med Promise.all för att undvika fördröjningar i vattenfall.
Detta mönster är idiomatiskt i serverkomponenter: starta allt asynkront arbete samtidigt och använd sedan resultaten synkront under JSX-renderingen.
// app/[locale]/dashboard/layout.tsx
import { ReactNode } from 'react';
import { loadMessages } from '@/lib/i18n/loader';
interface CommonMessages {
nav: { home: string; about: string; dashboard: string };
actions: { save: string; cancel: string; delete: string };
}
interface DashboardLayoutMessages {
title: string;
}
export default async function DashboardLayout({
children,
params,
}: {
children: ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
// Parallel loading — no sequential waterfall
const [common, dashboard] = await Promise.all([
loadMessages<CommonMessages>(locale, 'common'),
loadMessages<DashboardLayoutMessages>(locale, 'dashboard'),
]);
return (
<div>
<nav>
<a href="/">{common.nav.home}</a>
<a href="/dashboard">{common.nav.dashboard}</a>
</nav>
<h1>{dashboard.title}</h1>
<main>{children}</main>
</div>
);
}Bygg en översättningshjälpfunktion med avgränsat omfång
Att upprepade gånger skriva t.stats.totalUsers blir omständligt i stora komponenter. En liten hjälpfunktion med avgränsat omfång omsluter meddelandeobjektet och löser nyckelvägar med punktnotation, vilket gör mallarna mer lättlästa.
Eftersom hjälpfunktionen är en ren verktygsfunktion som körs på servern tillför den ingen kostnad till klientpaketet.
// lib/i18n/t.ts
type NestedMessages = { [key: string]: string | NestedMessages };
/**
* Resolve a dot-notation path from a messages object.
* Example: get(messages, 'stats.totalUsers') => 'Total Users'
*/
export function get(obj: NestedMessages, path: string): string {
const parts = path.split('.');
let current: string | NestedMessages = obj;
for (const part of parts) {
if (typeof current !== 'object' || current === null) {
return path; // Return key as fallback rather than crashing
}
current = current[part];
}
return typeof current === 'string' ? current : path;
}
/**
* Bind a messages object to produce a scoped t() function.
*/
export function createTranslator(messages: NestedMessages) {
return function t(key: string, vars?: Record<string, string>): string {
let value = get(messages, key);
if (vars) {
for (const [k, v] of Object.entries(vars)) {
value = value.replace(new RegExp(`\\{${k}\\}`, 'g'), v);
}
}
return value;
};
}Skicka översatta strängar till klientkomponenter
Klientkomponenter (markerade med 'use client') kan inte anropa loadMessages direkt — de körs i webbläsaren. Det korrekta mönstret är att läsa in översättningarna i en överordnad serverkomponent och bara skicka de strängar som behövs som props.
På så sätt sker inläsningen av översättningar på servern samtidigt som interaktivitet fortfarande är möjlig. Klientkomponenten tar emot vanliga strängar — den har inget beroende av något i18n-bibliotek alls.
// components/DeleteButton.tsx (client component)
'use client';
import { useState } from 'react';
interface DeleteButtonProps {
// Translated strings passed from server parent — no i18n lib needed here
labels: {
confirm: string;
cancel: string;
deleting: string;
};
onDelete: () => Promise<void>;
}
export function DeleteButton({ labels, onDelete }: DeleteButtonProps) {
const [pending, setPending] = useState(false);
async function handleClick() {
setPending(true);
await onDelete();
setPending(false);
}
return (
<button onClick={handleClick} disabled={pending}>
{pending ? labels.deleting : labels.confirm}
</button>
);
}
// app/[locale]/items/page.tsx (server component — loads & passes strings)
import { loadMessages } from '@/lib/i18n/loader';
import { DeleteButton } from '@/components/DeleteButton';
export default async function ItemsPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const common = await loadMessages<{
actions: { delete: string; cancel: string; deleting: string };
}>(locale, 'common');
return (
<DeleteButton
labels={{
confirm: common.actions.delete,
cancel: common.actions.cancel,
deleting: common.actions.deleting,
}}
onDelete={async () => { 'use server'; /* action here */ }}
/>
);
}Generera statiska parametrar för lokaliserade rutter
För statiskt genererade sidor behöver Next.js känna till alla giltiga kombinationer av språkvariant och slug i förväg. generateStaticParams körs vid byggtillfället och returnerar alla kombinationer som ska förhandsrenderas.
Om detta kombineras med serverbaserad inläsning av översättningar förhandsrenderas varje språkvariant med sin egen katalog — statiska sidor behöver inte hämta översättningar under körning.
// app/[locale]/blog/[slug]/page.tsx
import { loadMessages } from '@/lib/i18n/loader';
const SUPPORTED_LOCALES = ['en', 'tr', 'de'] as const;
type SupportedLocale = typeof SUPPORTED_LOCALES[number];
interface BlogPost {
slug: string;
title: Record<SupportedLocale, string>;
content: Record<SupportedLocale, string>;
}
// Simulated data source
const posts: BlogPost[] = [
{ slug: 'getting-started', title: { en: 'Getting Started', tr: 'Başlangıç', de: 'Erste Schritte' }, content: { en: '...', tr: '...', de: '...' } },
{ slug: 'advanced-tips', title: { en: 'Advanced Tips', tr: 'Gelişmiş İpuçları', de: 'Fortgeschrittene Tipps' }, content: { en: '...', tr: '...', de: '...' } },
];
// Build time: enumerate all locale × slug combinations
export async function generateStaticParams() {
return SUPPORTED_LOCALES.flatMap((locale) =>
posts.map((post) => ({ locale, slug: post.slug }))
);
}
export default async function BlogPostPage({
params,
}: {
params: Promise<{ locale: string; slug: string }>;
}) {
const { locale, slug } = await params;
const t = await loadMessages<{ blog: { readMore: string } }>(locale, 'common');
const post = posts.find((p) => p.slug === slug)!;
const loc = locale as SupportedLocale;
return (
<article>
<h1>{post.title[loc]}</h1>
<p>{post.content[loc]}</p>
</article>
);
}Vilket tillvägagångssätt undviker korrekt att skicka alla språkvarianters översättningar till klienten?
Ett Next.js 15-projekt med App Router stöder 5 språkvarianter. Teamet vill säkerställa att endast engelska strängar används när en användare besöker den engelska instrumentpanelen och att inga översättningsdata inkluderas i JavaScript-paketet som skickas till webbläsaren. Vilken implementation uppnår detta?
Sammanfattning: Serverbaserad inläsning av översättningar och meddelandekataloger
I den här lektionen lärde du dig att läsa in namnrymdsindelade meddelandekataloger helt på servern i Next.js 15-applikationer med App Router:
- Meddelandekataloger organiseras som filer enligt
messages/{locale}/{namespace}.json, så att varje språk och domän hålls åtskilda - En laddarverktygsfunktion som använder
fs/promisesläser endast in den namnrymd som behövs vid begäran — oanvända språkvarianter läses aldrig in - React-funktionen
cache()eliminerar dubblerade filläsningar under en enda renderingspassering och förhindrar redundant I/O när flera komponenter behöver samma namnrymd - Den aktiva språkvarianten hämtas från ruttsegmentet
[locale]viaawait params(en Promise i Next.js 15) - Ladda flera namnrymder parallellt med
Promise.allför att undvika sekventiella vattenfall - Klientkomponenter tar endast emot vanliga sträng-props — de har inget beroende av något i18n-bibliotek och inga översättningsdata i sitt paket
generateStaticParamsräknar upp alla kombinationer av språkvariant och rutt vid byggtillfället, vilket möjliggör helt statiskt förhandsrenderade sidor per språkvariant
Den här arkitekturen ger snabba, minimala paket samtidigt som all översättningslogik hålls där den hör hemma — på servern.
Lär dig TypeScript med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 22
- Lektioner
- 88
Vanliga frågor
Är lektionen ”Översättningsladdning på serversidan och meddelandekataloger” gratis?
Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Next.js 15 fullstack (App Router + Server Actions), inklusive ”Översättningsladdning på serversidan och meddelandekataloger”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Next.js 15 fullstack (App Router + Server Actions) innehåller totalt 4 lektioner.
Vad lär jag mig i ”Översättningsladdning på serversidan och meddelandekataloger”?
Läs in namnområdesindelade meddelandekataloger i serverkomponenter utan att skicka alla lokaler till klienten. Ni övar på Next.js 15 fullstack (App Router + Server Actions) med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Next.js 15 fullstack (App Router + Server Actions)?
Du behöver inga förkunskaper. Utbildningen i Next.js 15 fullstack (App Router + Server Actions) på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.
Hur lång tid tar lektionen ”Översättningsladdning på serversidan och meddelandekataloger”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Next.js 15 fullstack (App Router + Server Actions)-lektionen?
Ja. Varje Next.js 15 fullstack (App Router + Server Actions)-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Lokalisering och subpath-routing med middleware
- Översättningsladdning på serversidan och meddelandekataloger
- Lokaliserade metadata, webbplatskartor och hreflang
- Formatera datum, tal och pluralformer per lokal