Set default locale according to browser locale (#11805)

We didn't get much complaints on Localization so I guess we can expand
it more and make it the default behavior to use the browser's locale
when you signup

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Félix Malfait
2025-04-30 13:11:40 +02:00
committed by GitHub
parent e55ff5ac4a
commit cb513bc7a8
7 changed files with 140 additions and 18 deletions

View File

@ -23,3 +23,4 @@ export { isValidUrl } from './url/isValidUrl';
export { isDefined } from './validation/isDefined';
export { isValidLocale } from './validation/isValidLocale';
export { isValidUuid } from './validation/isValidUuid';
export { normalizeLocale } from './validation/normalizeLocale';

View File

@ -0,0 +1,53 @@
import { SOURCE_LOCALE } from '@/translations';
import { normalizeLocale } from '../normalizeLocale';
describe('normalizeLocale', () => {
it('should return SOURCE_LOCALE when the input is null', () => {
expect(normalizeLocale(null)).toBe(SOURCE_LOCALE);
});
it('should return the locale when there is a direct match in APP_LOCALES', () => {
// Test a few valid locales
expect(normalizeLocale('en')).toBe('en');
expect(normalizeLocale('fr-FR')).toBe('fr-FR');
expect(normalizeLocale('es-ES')).toBe('es-ES');
});
it('should handle case-insensitive matches', () => {
// Test with lowercase variants of the locales
expect(normalizeLocale('fr-fr')).toBe('fr-FR');
expect(normalizeLocale('es-es')).toBe('es-ES');
expect(normalizeLocale('DE-de')).toBe('de-DE');
});
it('should match just the language part if full locale not found', () => {
// Test with just the language code
expect(normalizeLocale('fr')).toBe('fr-FR');
expect(normalizeLocale('es')).toBe('es-ES');
expect(normalizeLocale('de')).toBe('de-DE');
});
it('should handle language codes that might map to multiple locales', () => {
// Test for language codes that might have multiple possible mappings
// For example, 'pt' could map to either 'pt-PT' or 'pt-BR'
// The implementation should map consistently to one of them
expect(normalizeLocale('pt')).toBeTruthy();
// Verify it's one of the expected values
expect(['pt-PT', 'pt-BR']).toContain(normalizeLocale('pt'));
});
it('should return SOURCE_LOCALE for unsupported or invalid locales', () => {
expect(normalizeLocale('invalid-locale')).toBe(SOURCE_LOCALE);
expect(normalizeLocale('xx-XX')).toBe(SOURCE_LOCALE);
expect(normalizeLocale('')).toBe(SOURCE_LOCALE);
});
it('should handle SOURCE_LOCALE and its variants correctly', () => {
expect(normalizeLocale(SOURCE_LOCALE)).toBe(SOURCE_LOCALE);
// If SOURCE_LOCALE is 'en', test 'en-US', 'en-GB', etc.
if (SOURCE_LOCALE === 'en') {
expect(normalizeLocale('en-US')).toBe(SOURCE_LOCALE);
expect(normalizeLocale('en-GB')).toBe(SOURCE_LOCALE);
}
});
});

View File

@ -1,3 +1,4 @@
export * from './isValidUuid';
export * from './isDefined';
export * from './isValidLocale';
export * from './isValidUuid';
export * from './normalizeLocale';

View File

@ -0,0 +1,53 @@
import { APP_LOCALES, SOURCE_LOCALE } from '@/translations';
/**
* Maps language codes to full locale keys in APP_LOCALES
* Example: 'fr' -> 'fr-FR', 'en' -> 'en'
*/
const languageToLocaleMap = Object.keys(APP_LOCALES).reduce<
Record<string, string>
>((map, locale) => {
// Extract the language code (part before the hyphen or the whole code if no hyphen)
const language = locale.split('-')[0].toLowerCase();
// Only add to the map if not already added or if the current locale is the source locale
// This ensures language codes map to their full locale version (e.g., 'es' -> 'es-ES')
// but preserves 'en' -> 'en' since it's the source locale
if (!map[language] || locale === SOURCE_LOCALE) {
map[language] = locale;
}
return map;
}, {});
/**
* Normalizes a locale string to match our supported formats
*/
export const normalizeLocale = (
value: string | null,
): keyof typeof APP_LOCALES => {
if (value === null) {
return SOURCE_LOCALE;
}
// Direct match in our supported locales
if (value in APP_LOCALES) {
return value as keyof typeof APP_LOCALES;
}
// Try case-insensitive match (e.g., 'fr-fr' -> 'fr-FR')
const caseInsensitiveMatch = Object.keys(APP_LOCALES).find(
(locale) => locale.toLowerCase() === value.toLowerCase(),
);
if (caseInsensitiveMatch) {
return caseInsensitiveMatch as keyof typeof APP_LOCALES;
}
// Try matching just the language part (e.g., 'fr' -> 'fr-FR')
const languageCode = value?.trim() ? value.split('-')[0].toLowerCase() : '';
if (languageToLocaleMap[languageCode]) {
return languageToLocaleMap[languageCode] as keyof typeof APP_LOCALES;
}
return SOURCE_LOCALE;
};