Skip to content

Localization

aparté’s built-in UI strings — the composer placeholder, the Copy / Retry buttons, the thinking… label, and so on — are translatable. English ships inside core as APARTE_DEFAULT_LOCALE, so an untranslated app is already in English with nothing to install.

Pass an AparteLocale to aparteGlobalConfig.setLocale. French is available as a package:

Terminal window
npm install @aparte/locale-fr
import { aparteGlobalConfig } from '@aparte/core';
import { fr } from '@aparte/locale-fr';
aparteGlobalConfig.setLocale(fr);

Set it once at startup, before the chat mounts. aparteGlobalConfig.getLocale() returns the active locale, and APARTE_DEFAULT_LOCALE (exported from @aparte/core) is the English baseline.

A locale switch is live: mounted components re-render immediately. To go back to English — say, in a language toggle — call aparteGlobalConfig.resetLocale():

function setLanguage(lang: 'fr' | 'en') {
if (lang === 'fr') aparteGlobalConfig.setLocale(fr);
else aparteGlobalConfig.resetLocale();
}

An AparteLocale is a flat record of string keys. The simplest custom locale starts from the English default and overrides what you need:

import { aparteGlobalConfig, APARTE_DEFAULT_LOCALE, type AparteLocale } from '@aparte/core';
const es: AparteLocale = {
...APARTE_DEFAULT_LOCALE,
inputPlaceholder: 'Escribe un mensaje...',
sendButton: 'Enviar',
copy: 'Copiar',
// …override the rest
};
aparteGlobalConfig.setLocale(es);

Spreading APARTE_DEFAULT_LOCALE guarantees every key is present even if aparté adds new strings in a later release — your translation overrides what it covers and inherits English for the rest. The AparteLocale type keeps the keys honest at compile time.

A plugin’s strings live in the same flat object as core’s: setLocale and extendLocale accept keys of your own alongside core’s, and getLocale() hands them back.

Read it off getLocale(), not through t(). AparteLocale is the closed list of the strings core itself renders, and t(key) is deliberately narrow against it — that is what makes a misspelt core key a compile error instead of an empty label nobody notices. Your key is not on that list, so it is read directly and defaulted at the call site, which is where its English belongs anyway:

import { aparteGlobalConfig, subscribeConfigChange, APARTE_DEFAULT_LOCALE } from '@aparte/core';
// Your plugin owns both halves: the key AND its English. An absent key reads
// `undefined`, so the plugin's own default belongs at the call site.
const label = (): string => aparteGlobalConfig.getLocale().myPluginRetry || 'Try again';
// Relabel on a live language switch — the same seam core's own components use.
// It returns an unsubscribe; call it when your element disconnects.
function mount(el: HTMLElement): () => void {
const paint = (): void => { el.textContent = label(); };
paint();
return subscribeConfigChange(el, paint);
}
aparteGlobalConfig.setLocale({ ...APARTE_DEFAULT_LOCALE, myPluginRetry: 'Réessayer' });

Two things that are not obvious, and that a plugin author will otherwise meet as a bug report:

setLocale() replaces, it does not merge. So handing core a locale package drops your plugin’s keys — setLocale(fr) leaves myPluginRetry undefined, because @aparte/locale-fr has never heard of it. The consumer merges:

import { aparteGlobalConfig } from '@aparte/core';
import { fr } from '@aparte/locale-fr';
aparteGlobalConfig.setLocale({ ...fr, myPluginRetry: 'Réessayer' });

A segment renderer gets this for free. If your plugin renders a segment type, implement relabel(element, segment) instead of subscribing by hand: core calls it on every config change, and the rule is attributes and text only — no child node added or removed — so a mounted preview keeps running and an expanded reasoning block stays expanded. See Customization.

One thing worth not translating: strings the model reads. @aparte/plugin-ask-user keeps its JSON-schema descriptions and its decline note in English on purpose — those are wire format, not interface copy. Its visible surface renders the question and the answer, which are data, so the plugin has nothing to localise at all.