mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-02 00:14:23 +00:00
fix(i18n): map Norwegian locale alias to Bokmål
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { detectBrowserLocale } from '../detect-locale';
|
||||
import { localeFromAcceptLanguage, normalizeLocaleTag } from '../locale-resolution';
|
||||
|
||||
const originalLanguages = Object.getOwnPropertyDescriptor(navigator, 'languages');
|
||||
|
||||
function setBrowserLanguages(languages: string[]) {
|
||||
Object.defineProperty(navigator, 'languages', {
|
||||
configurable: true,
|
||||
value: languages,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
if (originalLanguages) {
|
||||
Object.defineProperty(navigator, 'languages', originalLanguages);
|
||||
} else {
|
||||
Reflect.deleteProperty(navigator, 'languages');
|
||||
}
|
||||
});
|
||||
|
||||
describe('Norwegian locale aliases', () => {
|
||||
it.each(['no', 'no-NO'])('normalizes %s to Bokmål', (tag) => {
|
||||
expect(normalizeLocaleTag(tag)).toBe('nb');
|
||||
});
|
||||
|
||||
it('keeps native Bokmål tags on nb', () => {
|
||||
expect(normalizeLocaleTag('nb-NO')).toBe('nb');
|
||||
});
|
||||
|
||||
it('resolves no-NO from Accept-Language using quality ranking', () => {
|
||||
expect(localeFromAcceptLanguage('da;q=0.5, no-NO;q=0.9, en;q=0.8')).toBe('nb');
|
||||
});
|
||||
|
||||
it.each(['no', 'no-NO'])('detects browser language %s as Bokmål', (tag) => {
|
||||
setBrowserLanguages([tag]);
|
||||
expect(detectBrowserLocale('en')).toBe('nb');
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { routing } from './routing';
|
||||
import { useLocaleStore } from '@/stores/locale-store';
|
||||
import { normalizeLocaleTag } from './locale-resolution';
|
||||
|
||||
/**
|
||||
* Pick the best supported locale for a first-time visitor from their browser
|
||||
@@ -15,7 +16,7 @@ export function detectBrowserLocale(fallback: string): string {
|
||||
const prefs = navigator.languages?.length ? navigator.languages : [navigator.language];
|
||||
for (const tag of prefs) {
|
||||
if (!tag) continue;
|
||||
const base = tag.toLowerCase().split('-')[0];
|
||||
const base = normalizeLocaleTag(tag);
|
||||
if (base === 'en') return fallback; // English is the top preference -> keep default
|
||||
if (supported.has(base)) return base; // first supported non-English preference wins
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { routing } from './routing';
|
||||
|
||||
/** Map a BCP 47 language tag to the locale code Bulwark supports. */
|
||||
export function normalizeLocaleTag(tag: string): string {
|
||||
const base = tag.trim().toLowerCase().split('-')[0];
|
||||
return base === 'no' ? 'nb' : base;
|
||||
}
|
||||
|
||||
/** Pick the highest-ranked supported locale from an Accept-Language header. */
|
||||
export function localeFromAcceptLanguage(header: string | null): string | null {
|
||||
if (!header) return null;
|
||||
const supported = new Set<string>(routing.locales as readonly string[]);
|
||||
const ranked = header
|
||||
.split(',')
|
||||
.map((part) => {
|
||||
const [tag, q] = part.trim().split(';q=');
|
||||
return { locale: normalizeLocaleTag(tag), q: q ? parseFloat(q) : 1 };
|
||||
})
|
||||
.sort((a, b) => b.q - a.q);
|
||||
|
||||
for (const { locale } of ranked) {
|
||||
if (supported.has(locale)) return locale;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
+2
-19
@@ -1,29 +1,12 @@
|
||||
import { getRequestConfig } from 'next-intl/server';
|
||||
import { headers } from 'next/headers';
|
||||
import { localeFromAcceptLanguage, normalizeLocaleTag } from './locale-resolution';
|
||||
import { mergeMessages } from './merge-messages';
|
||||
import { routing, type Locale } from './routing';
|
||||
|
||||
// Default a first-time visitor (no explicit stored choice) to the best supported
|
||||
// language from their browser's Accept-Language header, so "Auto" is the real
|
||||
// default and the first server render already matches the browser language.
|
||||
function localeFromAcceptLanguage(header: string | null): string | null {
|
||||
if (!header) return null;
|
||||
const supported = new Set<string>(routing.locales as readonly string[]);
|
||||
const ranked = header
|
||||
.split(',')
|
||||
.map((part) => {
|
||||
const [tag, q] = part.trim().split(';q=');
|
||||
return { base: tag.toLowerCase().split('-')[0], q: q ? parseFloat(q) : 1 };
|
||||
})
|
||||
.sort((a, b) => b.q - a.q);
|
||||
for (const { base } of ranked) {
|
||||
if (supported.has(base)) return base;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
let locale = await requestLocale;
|
||||
if (locale) locale = normalizeLocaleTag(locale);
|
||||
|
||||
if (!locale || !routing.locales.includes(locale as Locale)) {
|
||||
const accept = (await headers()).get('accept-language');
|
||||
|
||||
Reference in New Issue
Block a user