Provide a general summary of the issue here
React Aria's LocalizedStringDictionary.getStringsForLocale resolves an locale by language before considering its script subtag. As a result, sr-Latn-RS (Serbian in Latin script) matches the Cyrillic sr entry before the Latin sr-Latn one, so DateField/DatePicker segment placeholders render in Cyrillic for Serbian (Latin) users.
🤔 Expected Behavior?
For a locale that carries an explicit script subtag, LocalizedStringDictionary.getStringsForLocale should prefer a language-script entry over the bare language entry.
For sr-Latn-RS (Serbian, Latin script), given a dictionary that has both sr and sr-Latn, it should resolve to sr-Latn (Latin).
😯 Current Behavior
It resolves to sr (Cyrillic). The resolver checks the language-only match (step 2) before the regional/sibling scan (step 3), so for sr-Latn-RS the language sr matches first and the Latin sr-Latn entry is never reached.
Real-world impact: React Aria DateField/DatePicker segment placeholders render in Cyrillic (гггг/мм/дд) for sr-Latn-RS users, even though the sr-Latn placeholder added.
💁 Possible Solution
Add a language-script lookup before the language-only fallback in getStringsForLocale, plus a getScript helper that mirrors the existing getLanguage (packages/@internationalized/string/src/LocalizedStringDictionary.ts):
function getStringsForLocale<K extends string, T extends LocalizedString>(locale: string, strings: LocalizedStrings<K, T>, defaultLocale = 'en-US') {
// If there is an exact match, use it.
if (strings[locale]) {
return strings[locale];
}
let language = getLanguage(locale);
// If there is no exact match, try a language + script match before falling back to the
// language alone. For example, sr-Latn-RS (Serbian in Latin script) should match sr-Latn
// rather than sr, which defaults to Cyrillic.
let script = getScript(locale);
if (script && strings[`${language}-${script}`]) {
return strings[`${language}-${script}`];
}
// Attempt to find the closest match by language.
if (strings[language]) {
return strings[language];
}
for (let key in strings) {
if (key.startsWith(language + '-')) {
return strings[key];
}
}
// Nothing close, use english.
return strings[defaultLocale];
}
function getScript(locale: string): string | undefined {
// @ts-ignore
if (Intl.Locale) {
// @ts-ignore
return new Intl.Locale(locale).script;
}
return undefined;
}
This only fires when the locale has an explicit script and a language-script key exists, so it changes resolution for Latin-script Serbian only and is a no-op for every other locale (the existing regional sibling fallback, e.g. de-AT → de-DE, is preserved).
🔦 Context
We use 20+ locales, including sr-Latn-RS (Serbia, Latin script). Date fields showed Cyrillic placeholders while the rest of the UI is Latin.
🖥️ Steps to Reproduce
Minimal, isolated to @internationalized/string:
import {LocalizedStringDictionary} from '@internationalized/string';
const dict = new LocalizedStringDictionary({
sr: {year: 'гггг'}, // Cyrillic
'sr-Latn': {year: 'gggg'} // Latin
});
dict.getStringForLocale('year', 'sr-Latn-RS');
// → 'гггг' (Cyrillic) ❌ expected 'gggg' (Latin)
dict.getStringForLocale('year', 'sr-Latn');
// → 'gggg' (works, because it's an exact match)
In React Aria this surfaces as a DateField with locale="sr-Latn-RS" showing Cyrillic segment placeholders.
Version
@internationalized/string@3.2.9, @react-stately/datepicker@3.17.1 (also reproduces on older 3.2.x / 3.12.x).
What browsers are you seeing the problem on?
Chrome
If other, please specify.
No response
What operating system are you using?
N/A
🧢 Your Company/Team
No response
🕷 Tracking Issue
No response
Provide a general summary of the issue here
React Aria's
LocalizedStringDictionary.getStringsForLocaleresolves an locale by language before considering its script subtag. As a result,sr-Latn-RS(Serbian in Latin script) matches the Cyrillicsrentry before the Latinsr-Latnone, soDateField/DatePickersegment placeholders render in Cyrillic for Serbian (Latin) users.🤔 Expected Behavior?
For a locale that carries an explicit script subtag,
LocalizedStringDictionary.getStringsForLocaleshould prefer alanguage-scriptentry over the barelanguageentry.For
sr-Latn-RS(Serbian, Latin script), given a dictionary that has bothsrandsr-Latn, it should resolve tosr-Latn(Latin).😯 Current Behavior
It resolves to
sr(Cyrillic). The resolver checks the language-only match (step 2) before the regional/sibling scan (step 3), so forsr-Latn-RSthe languagesrmatches first and the Latinsr-Latnentry is never reached.Real-world impact: React Aria
DateField/DatePickersegment placeholders render in Cyrillic (гггг/мм/дд) forsr-Latn-RSusers, even though thesr-Latnplaceholder added.💁 Possible Solution
Add a
language-scriptlookup before the language-only fallback ingetStringsForLocale, plus agetScripthelper that mirrors the existinggetLanguage(packages/@internationalized/string/src/LocalizedStringDictionary.ts):This only fires when the locale has an explicit script and a
language-scriptkey exists, so it changes resolution for Latin-script Serbian only and is a no-op for every other locale (the existing regional sibling fallback, e.g.de-AT→de-DE, is preserved).🔦 Context
We use 20+ locales, including
sr-Latn-RS(Serbia, Latin script). Date fields showed Cyrillic placeholders while the rest of the UI is Latin.🖥️ Steps to Reproduce
Minimal, isolated to
@internationalized/string:In React Aria this surfaces as a
DateFieldwithlocale="sr-Latn-RS"showing Cyrillic segment placeholders.Version
@internationalized/string@3.2.9,@react-stately/datepicker@3.17.1(also reproduces on older 3.2.x / 3.12.x).What browsers are you seeing the problem on?
Chrome
If other, please specify.
No response
What operating system are you using?
N/A
🧢 Your Company/Team
No response
🕷 Tracking Issue
No response