@altified/next
v0.5.13
Published
Server-first translation, locale routing, and a dashboard-controlled language switcher for Next.js App Router projects.
Readme
@altified/next
Server-first translation, locale routing, and a dashboard-controlled language switcher for Next.js App Router projects.
Install and initialize
Requires Next.js 15 or newer and React 18 or newer.
pnpm add @altified/next
pnpm exec altified initYou can use npm install @altified/next and npx altified init instead.
The initializer detects JavaScript or TypeScript and whether the project uses a
src directory. For a Next.js project it:
- adds the server-only environment placeholders to
.env; - wraps the existing Next config with
withAltified; - creates a server-only client in
lib/altified; - creates the locale-routing
proxyfile; - keeps the site's existing
/sitemap.xmland adds a separate Altified sitemap containing only additional-language URLs; - adds
/altified-sitemap.xmlto an existingrobotsroute or staticrobots.txtwhen it can do so safely; otherwise it reports the exact manual change required.
Existing generated files and environment values are preserved. The site's
sitemap and robots crawl rules are preserved even with --force; the
initializer only merges the Altified sitemap entry into the existing sitemap
list. The flag can replace other integration files. Review the reported
warning if an unusual Next config or robots implementation cannot be changed
safely. The
initializer never inserts visual UI into your layout: place the language
switcher yourself wherever it belongs. Use --force only when you intentionally
want to replace generated files.
The sitemap integration uses the site's existing app/sitemap.js/ts function
directly when available (including generateSitemaps shards); for custom XML
or static sitemaps it reads the site's published /sitemap.xml, including any
sitemap index children. The original
site sitemap keeps serving that URL and remains the only source of page
selection, including dynamic pages and exclusions. Altified publishes an index
at /altified-sitemap.xml with locale-named children such as
/altified-sitemaps/fr-0.xml. Those children contain only URLs not already in
the site's sitemap. A source /jobs/nurse produces /fr/jobs/nurse, never a
second /jobs/nurse entry. Child files split at 50,000 URLs or 50 MB. If the
source function fails, a public XML source is unavailable, or Altified routing
is unavailable, the Altified route returns
503 rather than an incomplete successful sitemap. An empty source is also an
error. The source sitemap must itself successfully include the site's dynamic
public pages; Altified cannot recover URLs it omits. The site's own sitemap
function also controls its caching and revalidation.
If no sitemap exists, the initializer does not invent URLs or create the
Altified sitemap. Add a site-owned sitemap first, then run altified init.
Existing robots.js, robots.ts, or robots.txt files keep their crawl rules
and receive the Altified sitemap in their sitemap list when the initializer can
identify the existing sitemap format. If a static file does not contain an
absolute sitemap URL, or a custom route cannot be safely parsed, the initializer
prints the exact manual change required. If there is no robots file, the
generated one lists both sitemap URLs without changing crawl rules. Submitting
the Altified sitemap directly in Search Console remains supported in every
case.
The Altified sitemap is for discovery, not hreflang annotation: original
and translated pages must expose reciprocal hreflang links in their HTML.
The loader wraps static metadata exports automatically; for a custom
generateMetadata, call AltifiedMetadata from @altified/next/auto (or the
generated translateAltifiedMetadata helper) with the page's metadata and a
public origin. Verify on both locale versions that their HTML contains the
same reciprocal language links and a self-canonical URL. Also verify that the
translated page is actually translated and indexable. The SDK cannot guarantee
that arbitrary database-backed content has been translated; translate API
fields explicitly where needed.
Upgrading an older initialization: altified init safely restores a dynamic
sitemap.source.js/ts to sitemap.js/ts and archives recognized old
Altified-generated sitemap routes. If the source still contains the old
STATIC_ROUTES fallback, initialization stops: replace that fallback with the
site's complete sitemap function first. It will not guess missing dynamic URLs.
The public origin is derived from the request unless configured explicitly.
For deployments behind a reverse proxy that gives Next.js an internal request origin, set the public origin explicitly:
NEXT_PUBLIC_SITE_URL=https://your-production-domain.comThe generated sitemap and robots routes use this value when it is present.
For custom XML/static sitemaps, set ALTIFIED_SITE_URL or
NEXT_PUBLIC_SITE_URL explicitly so the SDK can fetch only the intended
public host, rather than trusting an incoming Host header.
The source sitemap path defaults to /sitemap.xml; an alternate path can be
set with ALTIFIED_SOURCE_SITEMAP_PATH, but it must stay on the same site and
must not point to an Altified sitemap.
For a locally running Altified backend:
pnpm exec altified init --localDashboard setup
In the Altified dashboard, create or open the project, add its source and target
languages, and copy the project ID and server secret into .env:
ALTIFIED_SECRET_KEY=altified_...
ALTIFIED_PROJECT_ID=your-project-uuidALTIFIED_SECRET_KEY is server-only. Never rename it to a NEXT_PUBLIC_
variable. The project default language and published locale routing come from
the Altified dashboard. ALTIFIED_DEFAULT_LOCALE is an optional local fallback,
not a required installation setting. Restart Next.js after changing .env.
Configure and publish the language switcher in the dashboard. The component loads that public configuration, so language order, labels, theme, and routing behavior can be changed without replacing the component.
Automatic JSX translation
The Next config installed by altified init translates literal JSX text in
server and client components:
export default function Page({ product }) {
return (
<main>
<h1>Welcome to our website</h1>
<p>{product.name}</p>
</main>
);
}Welcome to our website receives a stable generated translation key and is
resolved for the locale selected by the proxy. The compiler also follows fixed
copy held in local constants, objects, and arrays when those values are rendered
as text (including common .map() card/list patterns). Runtime values such as
{product.name} are intentionally left unchanged unless they are an explicitly
recognised copy field. Client components beginning with "use client" are
rewritten too, using the browser-safe client entry point from
@altified/next/auto.
If the secret is missing or the translation service is unavailable, the source text is rendered. Generated keys use the source file path and literal position; moving a literal or inserting an earlier literal can create a new key.
Translation coverage
Altified uses layered coverage so different Next.js project styles can opt into the safest path for each kind of copy:
- Automatic JSX: literal text in server-rendered JSX and files with
"use client"is translated regardless of tag (h1,p,button,span, and similar). Fixed copy in local variables, objects, and arrays is also translated when it is rendered, including data-driven cards and lists. Runtime expressions whose content cannot be proven remain unchanged. - Accessibility attributes: literal
alt,aria-label,placeholder, andtitlevalues on native HTML elements are translated without changing the component source. - Custom component props: recognised copy props, including
placeholderandaria-label, are resolved to strings before being passed to components. This keeps custom inputs compatible while their placeholder text is translated. - Client components: rewritten client components use the browser-safe
@altified/next/autoentry point. You can still useAltifiedClientProviderto preload a translations map from the server and avoid client-side loading flashes. - Metadata/head: static metadata exports and object values returned directly from
generateMetadataare translated automatically. For custom metadata helpers or non-object returns, usealtified.translatedMetadata()for translatedtitle,description, Open Graph, and Twitter text. - Backend/API data: translate explicitly by field name. Do not blindly translate whole response objects, because IDs, slugs, emails, prices, statuses, and user content can be damaged.
Client component example:
"use client";
import { AltifiedText } from "@altified/next/auto";
export function Header() {
return <nav><AltifiedText translationKey="nav.contact" defaultValue="Contact" /></nav>;
}Wrap that client tree with translations loaded from a server component:
import { AltifiedClientProvider } from "@altified/next/auto";
import { altified } from "@/lib/altified";
const navCopy = await altified.resolve(locale, [
{ key: "nav.contact", defaultValue: "Contact" },
{ key: "nav.pricing", defaultValue: "Pricing" },
]);
<AltifiedClientProvider translations={navCopy}>
<Header />
</AltifiedClientProvider>Metadata example:
export async function generateMetadata({ params }) {
const { locale } = await params;
return altified.translatedMetadata({
locale,
path: "/pricing",
origin: "https://example.com",
locales: ["en", "fr"],
hideDefaultLocale: true,
title: "Pricing",
description: "Simple plans for teams.",
openGraph: {
title: "Pricing",
description: "Simple plans for teams.",
},
});
}For the explicit altified.metadata() and altified.translatedMetadata()
helpers, keep locales, defaultLocale, and hideDefaultLocale aligned with
the published dashboard routing. AltifiedMetadata reads that published
routing automatically.
Explicit and batched translation
Use the generated server client when you need named keys, dynamic values, or a single batched request:
import { altified } from "@/lib/altified";
const copy = await altified.resolve(locale, [
{ key: "home.title", defaultValue: "Hello" },
{ key: "home.subtitle", defaultValue: "Welcome to our website" },
]);Use altified.t(key, options) for one explicit string. Use
altified.metadata() inside generateMetadata to build canonical and reciprocal
alternate-language URLs.
SEO helpers
The SDK also provides framework-friendly helpers for indexing controls, sitemaps,
and structured data. Import them from @altified/next/seo:
import { createRobots } from "@altified/next/seo";
export default function robots() {
return createRobots({
origin: "https://example.com",
disallow: ["/dashboard", "/search"],
});
}Build a localized sitemap from your public routes. Each localized entry includes
the reciprocal alternates.languages map and x-default URL:
import { createSitemap } from "@altified/next/seo";
export default function sitemap() {
return createSitemap({
origin: "https://example.com",
locales: ["en", "fr"],
routes: [
{ path: "/", priority: 1 },
{ path: "/pricing", changeFrequency: "monthly" },
],
});
}The initializer also exposes the sitemap integration helpers from
@altified/next/sitemap. The generated Altified routes use
createAltifiedTranslationSitemapFromEntries() for a standard Next sitemap,
or createAltifiedTranslationSitemapFromSite() for public XML. For a custom
integration that already has an array of pages, the lower-level helper can
still wrap a standard Next sitemap function:
import { createAltifiedSitemapFromSource } from "@altified/next/sitemap";
async function buildLocalizedSitemap(request) {
return createAltifiedSitemapFromSource({
source: getMyExistingSitemapEntries,
request,
});
}The source must return the standard Next.js sitemap array. This lower-level
helper returns original and translated entries; filter the original entries
if you are building a translated-only sitemap yourself. The default initializer
does this filtering and leaves the site's /sitemap.xml untouched.
Use noIndexMetadata() for not-found, private, search, or filter pages:
import { noIndexMetadata } from "@altified/next/seo";
export async function generateMetadata() {
return noIndexMetadata();
}Structured data helpers cover common content types and can be rendered safely
with AltifiedJsonLd:
import {
AltifiedJsonLd,
createArticleJsonLd,
createBreadcrumbJsonLd,
} from "@altified/next/seo";
export default function Article({ article }) {
return (
<>
<AltifiedJsonLd data={createArticleJsonLd({
headline: article.title,
description: article.description,
url: article.url,
image: article.image,
datePublished: article.publishedAt,
dateModified: article.updatedAt,
author: article.author,
inLanguage: article.locale,
})} />
<AltifiedJsonLd data={createBreadcrumbJsonLd(article.breadcrumbs)} />
<article>{article.content}</article>
</>
);
}These helpers do not fetch articles or decide whether a slug exists. The app
must still load its content on the server and call Next's notFound() for a
missing record so the response has the correct HTTP status.
Language switcher and locale routing
Initialization does not add a switcher to your UI. Import it in the header, navigation, or other component where you want the control to appear:
import { AltifiedLanguageSwitcher } from "@altified/next/switcher";
<AltifiedLanguageSwitcher
projectId={process.env.ALTIFIED_PROJECT_ID}
locale={locale}
apiUrl={process.env.ALTIFIED_API_URL}
/>The switcher only fetches the project's published public configuration; it does not expose the server secret. Its options open directly below the control on desktop and mobile.
The generated proxy.js or proxy.ts uses the same configuration:
import { createAltifiedProxy } from "@altified/next/proxy";
export const proxy = createAltifiedProxy({
projectId: process.env.ALTIFIED_PROJECT_ID,
});
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico|.*\\..*).*)"],
};It caches public routing configuration for five minutes, uses stale
configuration when refresh fails, and falls back to ALTIFIED_DEFAULT_LOCALE
before configuration has been loaded. It also remembers the visitor's selected
locale. For example, after selecting French, an ordinary internal link to
/terms is redirected to /fr/terms, so existing Next.js Link components do
not need locale-specific href values. The switcher does not intercept global
anchor clicks. Use transformed static links or localizePath for dynamic hrefs;
direct window.location assignments remain full browser navigations by design.
Forward locale to backend APIs
If your Next app calls a separate backend, use altifiedFetch in your shared API client so the active Altified locale is sent as Accept-Language.
import { altifiedFetch } from "@altified/next/fetch";
const response = await altifiedFetch(`${BACKEND_URL}/api/products/`, {
credentials: "include",
});In client components, altifiedFetch detects the locale from the Altified cookie, the document language, or the locale prefix in the URL. In server components, pass the locale explicitly when you already have it:
const response = await altifiedFetch(`${BACKEND_URL}/api/products/`, {
locale,
cache: "no-store",
});This is the recommended way to make a Django backend using django_altified receive the same locale as the current Next route.\n\nFor Axios, attach the interceptor to your shared Axios instance:\n\njs\nimport axios from "axios";\nimport { altifiedAxiosInterceptor } from "@altified/next/axios";\n\nconst api = axios.create({\n baseURL: BACKEND_URL,\n withCredentials: true,\n});\n\naltifiedAxiosInterceptor(api);\n
Production workflow
- Add source copy as server-rendered JSX literals or explicit named keys.
- Visit the pages in each configured locale so missing strings are registered and translated.
- Review and edit translations in the Altified dashboard.
- Add glossary terms for product names, technical terms, or words whose translation must stay consistent, then regenerate affected translations.
- Publish the translations and switcher configuration.
- Set
ALTIFIED_SECRET_KEY,ALTIFIED_PROJECT_ID, andALTIFIED_DEFAULT_LOCALEin the production environment and deploy.
A glossary tells the translation engine how specific terms should be translated—or that they must not be translated. It does not replace the translation editor: the editor controls a particular string, while a glossary applies terminology consistently across many strings and future translations.
