Localization
Telling the server which language to answer in, and decoding the multilingual labels it returns.
Two halves, and they are independent. One key tells the server what language to work in; one function decodes the labels it sends back.
Telling the server
uiLanguage is sent as ui-language in the body of every request that has one, and it also becomes the request
locale:
client.configure({ uiLanguage: "fr" });The resolution order for locale, most specific first:
So a per-request locale overrides the configuration, and with neither set the browser's language is the
fallback. Omitted entirely when nothing is configured, which leaves the server on its own default.
The browser used to have the last word
locale was resolved as body.locale || navigator.language, so an interface switched to English still asked the
server in fr-FR — and the server went on localising accordingly. Setting uiLanguage now moves both fields
together.
Decoding what comes back
Labels in a Sinequa configuration are multilingual strings: one value carrying every translation.
all[fr]tous[de]alleRendered as-is, the user sees every language at once. sysLang(label, locale) returns the right one, and passes
a plain string through unchanged — so it is safe to apply to anything.
import { createAtomicClient, sysLang } from "@sinequa/atomic";
const language = navigator.language;
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
// Sent as `ui-language`, and used as the request `locale`.
uiLanguage: language,
});
await client.initializeConfig();
await client.auth.login();
const result = await client.api.query.search({
name: "<your-query>",
text: "report",
pageSize: 10,
});
// Every label that comes from the configuration goes through sysLang.
for (const aggregation of result.aggregations) {
console.log(sysLang(aggregation.name, language));
for (const item of aggregation.items ?? []) {
// `display` is the label; `value` is what a filter uses. Only the label is
// multilingual — never decode a value.
console.log(" ", sysLang(String(item.display ?? item.value), language), item.count);
}
}
for (const tab of result.tabs ?? []) {
console.log("tab:", sysLang(tab.display, language));
}import { Pipe, type PipeTransform } from "@angular/core";
import { sysLang } from "@sinequa/atomic";
/**
* Decodes a Sinequa multilingual label — `all[fr]tous[de]alle` — for the current
* locale. A pipe rather than a call per template, so no label can be forgotten.
*/
@Pipe({ name: "localized" })
export class LocalizedPipe implements PipeTransform {
transform(value: string | number | boolean | null | undefined): string {
if (value === null || value === undefined) return "";
return sysLang(String(value), navigator.language);
}
}import { ChangeDetectionStrategy, Component, input } from "@angular/core";
import type { Aggregation } from "@sinequa/atomic";
import { LocalizedPipe } from "./localized.pipe";
@Component({
selector: "app-localized-facet",
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [LocalizedPipe],
template: `
<h3>{{ aggregation().name | localized }}</h3>
<ul>
@for (item of aggregation().items ?? []; track item.value) {
<!-- The label is localized; the value is not, and is what a filter uses. -->
<li>{{ (item.display ?? item.value) | localized }} ({{ item.count }})</li>
}
</ul>
`,
})
export class LocalizedFacetComponent {
readonly aggregation = input.required<Aggregation>();
}import { sysLang } from "@sinequa/atomic";
import type { Aggregation } from "@sinequa/atomic";
/**
* Decodes a Sinequa multilingual label — `all[fr]tous[de]alle` — for the current
* locale. One component, so no label can be forgotten.
*/
export function Localized({ value }: { value: string | number | boolean | null | undefined }) {
if (value === null || value === undefined) return null;
return <>{sysLang(String(value), navigator.language)}</>;
}
export function LocalizedFacet({ aggregation }: { aggregation: Aggregation }) {
return (
<>
<h3>
<Localized value={aggregation.name} />
</h3>
<ul>
{(aggregation.items ?? []).map((item) => (
<li key={String(item.value)}>
{/* The label is localized; the value is not, and is what a filter uses. */}
<Localized value={item.display ?? item.value} /> ({item.count})
</li>
))}
</ul>
</>
);
}Where multilingual labels appear
Anywhere a label comes from the server's configuration:
Aggregation names, and item display | Facet headings and values |
Tab.display | Tab labels |
CCSortingChoice.display | Sort options |
CCColumnInfo.label, labelPlural | Column headings |
CCScope.display | Scope names |
Suggestion category | Autocomplete group headings |
Document titles and metadata values are not multilingual — they come from the indexed content, not from the
configuration. Passing them through sysLang is harmless but pointless.
Search language versus interface language
Two different things, and the query keeps them apart:
uiLanguage— what language the server answers in: labels, messages.questionLanguageon a query — what language the search terms are interpreted in, overriding automatic detection.
A French interface searching English documents sets uiLanguage: "fr" and leaves questionLanguage to
detection, or pins it to "en". See Query anatomy.