| name | fluent-core-langneg |
| description | Use when implementing language negotiation or locale fallback chains with @fluent/langneg. Prevents wrong strategy selection and broken fallback behavior. Covers negotiateLanguages() strategies, BCP 47 locale handling, acceptedLanguages() for HTTP headers, and fallback chain construction. Keywords: negotiateLanguages, @fluent/langneg, BCP 47, Accept-Language, locale fallback, filtering, matching, lookup, detect user language, browser language, language fallback, pick best language.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires @fluent/langneg 0.7+. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
fluent-core-langneg
Quick Reference
Package Overview
| Property | Value |
|---|
| Package | @fluent/langneg |
| Version | 0.7.0 |
| License | Apache-2.0 |
| Dependencies | None (zero runtime dependencies) |
| Install | npm install @fluent/langneg |
Exports
| Export | Type | Purpose |
|---|
negotiateLanguages | Function | Match user-requested locales against available locales |
acceptedLanguages | Function | Parse HTTP Accept-Language header into sorted locale array |
filterMatches | Function | Low-level matching engine (exported but rarely used directly) |
Strategy Comparison
Given requested ["de-DE", "fr-FR"] and available ["it", "de", "en-US", "fr-CA", "de-DE", "fr", "de-AU"]:
| Strategy | Result | Behavior |
|---|
"filtering" (default) | ["de-DE", "de", "fr", "fr-CA"] | ALL matches for ALL requested locales |
"matching" | ["de-DE", "fr"] | BEST single match per requested locale |
"lookup" | ["de-DE"] | SINGLE best match across all requested locales |
Critical Warnings
NEVER use the "lookup" strategy without providing defaultLocale — the function throws an Error if defaultLocale is undefined with lookup strategy.
NEVER pass non-BCP 47 locale strings (e.g., "english", "ENUS") — the negotiation algorithm expects standard BCP 47 tags like "en-US", "fr-CA", "sr-Latn".
ALWAYS provide defaultLocale in production code — without it, negotiation can return an empty array if no matches are found, leaving the user with no translations.
negotiateLanguages()
Full Signature
import { negotiateLanguages } from "@fluent/langneg";
function negotiateLanguages(
requestedLocales: Readonly<Array<string>>,
availableLocales: Readonly<Array<string>>,
options?: {
strategy?: "filtering" | "matching" | "lookup";
defaultLocale?: string;
}
): Array<string>
Parameters
| Parameter | Type | Description |
|---|
requestedLocales | Readonly<Array<string>> | User's preferred locales (e.g., navigator.languages) |
availableLocales | Readonly<Array<string>> | Locales the application supports |
options.strategy | "filtering" | "matching" | "lookup" | Negotiation algorithm (default: "filtering") |
options.defaultLocale | string | Fallback locale; appended to results if not already present |
Return Value
Array<string> — locale identifiers ordered by preference.
defaultLocale Behavior
- For
"filtering" and "matching": defaultLocale is appended to results if provided and not already present in the match results
- For
"lookup": defaultLocale is REQUIRED — the function throws Error if omitted; pushed to results if no matches found
Client-Side Usage
import { negotiateLanguages } from "@fluent/langneg";
const AVAILABLE_LOCALES = ["en-US", "fr", "de", "ja"];
const userLocales = negotiateLanguages(
navigator.languages,
AVAILABLE_LOCALES,
{ defaultLocale: "en-US" }
);
Server-Side Usage
import { acceptedLanguages, negotiateLanguages } from "@fluent/langneg";
const requested = acceptedLanguages(req.headers["accept-language"] || "");
const supported = negotiateLanguages(requested, AVAILABLE_LOCALES, {
defaultLocale: "en-US",
});
Strategies In Depth
Filtering (Default)
Returns ALL available locales that match ANY requested locale. Most permissive — produces the longest fallback chain.
const result = negotiateLanguages(
["de-DE", "fr-FR"],
["it", "de", "en-US", "fr-CA", "de-DE", "fr", "de-AU"],
{ strategy: "filtering", defaultLocale: "en-US" }
);
ALWAYS use filtering when building a fallback chain for FluentBundle sequences — it maximizes translation coverage.
Matching
Returns the BEST single match for EACH requested locale. One result per request entry.
const result = negotiateLanguages(
["de-DE", "fr-FR"],
["it", "de", "en-US", "fr-CA", "de-DE", "fr", "de-AU"],
{ strategy: "matching", defaultLocale: "en-US" }
);
ALWAYS use matching when you need a concise preference list without redundant variants.
Lookup
Returns the SINGLE best match across ALL requested locales. Most restrictive — exactly one result.
const result = negotiateLanguages(
["de-DE", "fr-FR"],
["it", "de", "en-US", "fr-CA", "de-DE", "fr", "de-AU"],
{ strategy: "lookup", defaultLocale: "en-US" }
);
ALWAYS use lookup when you need exactly one locale (e.g., selecting a date format library, choosing a single UI direction).
Strategy Selection Decision Tree
Need to select locales?
├── Need a full fallback chain for translations?
│ └── YES → Use "filtering" (default)
│ Returns all matching locales for maximum coverage
│
├── Need one best locale per user preference?
│ └── YES → Use "matching"
│ Returns best match per requested locale
│
└── Need exactly one locale for the entire app?
└── YES → Use "lookup"
Returns single best match
⚠ REQUIRES defaultLocale or throws Error
acceptedLanguages()
Parses HTTP Accept-Language headers into a sorted array of locale strings.
import { acceptedLanguages } from "@fluent/langneg";
const locales = acceptedLanguages("fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5");
- Parses quality values (
q=) from the header string
- Sorts by descending quality, preserving order for equal weights
- ALWAYS use on the server instead of
navigator.languages (which is unavailable in Node.js)
- Throws
TypeError if the argument is not a string
BCP 47 Locale Identifiers
All locale strings in @fluent/langneg follow BCP 47 (IETF language tag standard):
| Format | Example | Components |
|---|
| Language | en, fr, de | ISO 639 language code |
| Language + Region | en-US, fr-CA, de-DE | Language + ISO 3166 country code |
| Language + Script | sr-Latn, zh-Hans | Language + ISO 15924 script code |
Subtag Matching
The negotiation algorithms perform subtag matching — a requested "de-DE" will match an available "de" as a fallback. The library includes minimal likely-subtags data to resolve generic locales (e.g., requesting "en" can match both "en-GB" and "en-US").
Input locales are coerced to strings internally via Array.from(...).map(String).
Integration with @fluent/react
ALWAYS use negotiateLanguages to determine the locale order before creating FluentBundle instances:
import { negotiateLanguages } from "@fluent/langneg";
import { FluentBundle, FluentResource } from "@fluent/bundle";
import { ReactLocalization } from "@fluent/react";
const negotiated = negotiateLanguages(
navigator.languages,
["en-US", "fr", "de"],
{ defaultLocale: "en-US" }
);
const bundles = negotiated.map((locale) => {
const bundle = new FluentBundle(locale);
bundle.addResource(new FluentResource(MESSAGES[locale]));
return bundle;
});
const l10n = new ReactLocalization(bundles);
The order returned by negotiateLanguages determines the fallback chain — the first bundle is the preferred locale, subsequent bundles serve as fallbacks for missing translations.
Reference Links
Official Sources