Localization

Keep localized API reads, canonical slugs, formatting, and interface copy consistent.

Use the board’s configured language for canonical board content and SDK formatting. Your application still owns route translation and copy outside the SDK’s vocabulary.

Prerequisites

  • A locale decision at the server boundary.
  • A single request locale shared by data loading and rendering.
  • An existing i18n system for your application-specific interface copy.

The board context exposes its canonical language. Operators set it in Cavuno under Settings → General → Board language. It is the single publishing language for board-owned UI copy, date and number formatting, and canonical taxonomy and salary slugs. It is not automatically the browser language and it does not translate your application’s own interface.

Send a request language

Pass Accept-Language per request when your application negotiates a locale:

ts
// src/data/localized-board.ts
import type { FetchOptions } from '@cavuno/board';
import { board } from '../lib/board';
function localized(locale: string): FetchOptions {
return { headers: { 'accept-language': locale } };
}
export async function loadLocalizedJobs(locale: string) {
const options = localized(locale);
const [context, jobs] = await Promise.all([
board.context(options),
board.jobs.list({ limit: 20 }, options),
]);
return { context, jobs };
}

If the entire client always uses one locale, set the same header with globalHeaders when creating the client. Do not mutate a shared server client’s headers between users.

Use canonical localized slugs

Some salary and taxonomy methods accept a locale and return both source and canonical slugs:

ts
const context = await board.context();
const title = await board.salaries.titles.retrieve(inboundSlug, {
locale: context.language,
});
if (title.canonicalSlug !== inboundSlug) {
// Permanently redirect to salaryTitlePath(title.canonicalSlug).
}

Never translate an identifier or slug locally. Use display names for UI and returned canonical slugs for URLs.

Format with the same language

ts
import {
formatDate,
formatSalaryRange,
locationLabel,
uiCopy,
} from '@cavuno/board/format';
const copy = uiCopy(context.language, context.labels);
const published = formatDate(context.language, job.publishedAt);
const location = locationLabel(context.language, job);
const salary = formatSalaryRange(
context.language,
job.salaryMin,
job.salaryMax,
job.salaryTimeframe,
job.salaryCurrency,
);

Keep your framework’s document language, date output, salary output, and SDK labels on the same server-selected value to avoid hydration differences.

Expected result

API reads, canonical slugs, document language, and SDK display strings agree on one board language. Non-canonical inbound slugs produce permanent redirects instead of duplicate pages.

Verify the result

  • The document lang value matches the board language used by formatters.
  • A localized salary or taxonomy alias permanently redirects to the returned canonical slug.
  • Server and client render identical date, currency, and location strings.
  • Navigation and application chrome outside the SDK are translated by your own i18n system.
  • Canonical and sitemap URLs use canonical slugs, not translated guesses.

Errors and edge cases

  • Do not treat browser locale as the canonical board language without a product decision; it can produce different HTML on server and client.
  • An invalid locale falls back inside some formatting helpers, but should still be treated as a configuration error in your app.
  • Place and taxonomy display names can be localized while IDs remain stable.
  • The fixed remote-location wrapper copy has narrower localization coverage than all board-supplied place names; review it in every supported language.

Production cautions

  • Include locale in any cache key for localized reads.
  • Do not cache localized authenticated responses publicly.
  • Test dates around UTC day boundaries and currencies with symbol-after-number conventions.
  • Crawl every supported locale for canonical and redirect loops before launch.

Continue with Framework guides to wire these framework-neutral helpers into your runtime.