update arabic to be default
Build & Push / Pipeline Tests (push) Successful in 1m56s
Test / Type Check (all packages) (push) Successful in 56s
Build & Push / Build & Push Docker Image (push) Successful in 3m39s
Test / API Unit Tests (push) Successful in 1m12s
Test / Homepage Unit Tests (push) Successful in 52s
Test / Carplace Unit Tests (push) Successful in 43s
Test / Admin Unit Tests (push) Successful in 52s
Test / Dashboard Unit Tests (push) Successful in 45s
Test / API Integration Tests (push) Successful in 1m8s
Build & Push / Pipeline Tests (push) Successful in 1m56s
Test / Type Check (all packages) (push) Successful in 56s
Build & Push / Build & Push Docker Image (push) Successful in 3m39s
Test / API Unit Tests (push) Successful in 1m12s
Test / Homepage Unit Tests (push) Successful in 52s
Test / Carplace Unit Tests (push) Successful in 43s
Test / Admin Unit Tests (push) Successful in 52s
Test / Dashboard Unit Tests (push) Successful in 45s
Test / API Integration Tests (push) Successful in 1m8s
This commit is contained in:
@@ -284,7 +284,7 @@ function createFormState(company: CompanyDetail): FormState {
|
||||
publicCountry: company.brand?.publicCountry ?? '',
|
||||
websiteUrl: company.brand?.websiteUrl ?? '',
|
||||
whatsappNumber: company.brand?.whatsappNumber ?? '',
|
||||
defaultLocale: company.brand?.defaultLocale ?? 'en',
|
||||
defaultLocale: company.brand?.defaultLocale ?? 'ar',
|
||||
defaultCurrency: company.brand?.defaultCurrency ?? 'MAD',
|
||||
isListedOnCarplace: company.brand?.isListedOnCarplace ?? true,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Metadata } from 'next'
|
||||
import Script from 'next/script'
|
||||
import { AdminI18nProvider } from '@/components/I18nProvider'
|
||||
import './globals.css'
|
||||
|
||||
@@ -9,9 +10,11 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className="dark" suppressHydrationWarning>
|
||||
<html lang="ar" dir="rtl" className="dark" suppressHydrationWarning>
|
||||
<head>
|
||||
<script
|
||||
<Script
|
||||
id="admin-theme-bootstrap"
|
||||
strategy="beforeInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
"(function(){try{var m=document.cookie.match(/(?:^|; )rentaldrivego-theme=([^;]+)/);var theme=m?decodeURIComponent(m[1]):(localStorage.getItem('rentaldrivego-theme')||localStorage.getItem('admin-theme'));if(theme!=='light'&&theme!=='dark'){theme='dark'}document.documentElement.classList.remove('light','dark');document.documentElement.classList.add(theme);document.documentElement.style.colorScheme=theme;document.body&&document.body.setAttribute('data-theme',theme)}catch(e){}})();",
|
||||
|
||||
@@ -109,10 +109,10 @@ type AdminI18nContext = {
|
||||
const Context = createContext<AdminI18nContext | null>(null)
|
||||
|
||||
export function AdminI18nProvider({ children }: { children: React.ReactNode }) {
|
||||
const [language, setLanguage] = useState<AdminLanguage>('en')
|
||||
const [language, setLanguage] = useState<AdminLanguage>('ar')
|
||||
const [theme, setTheme] = useState<AdminTheme>('dark')
|
||||
// Skip the very first write so we don't overwrite a stored preference with
|
||||
// the default 'en' value before the hydration read-effect has applied it.
|
||||
// Skip the very first write so we don't overwrite a stored preference before
|
||||
// the hydration read-effect has applied it.
|
||||
const skipFirstLangWrite = useRef(true)
|
||||
const skipFirstThemeWrite = useRef(true)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { cookies } from 'next/headers'
|
||||
import Script from 'next/script'
|
||||
import CarplaceShell from '@/components/CarplaceShell'
|
||||
import { getCarplaceMessages } from '@/lib/carplace/messages'
|
||||
import { getCarplaceLanguage } from '@/lib/i18n.server'
|
||||
@@ -36,8 +37,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
return (
|
||||
<html lang={language} dir={language === 'ar' ? 'rtl' : 'ltr'} suppressHydrationWarning>
|
||||
<head>
|
||||
{/* Runs before hydration to prevent flash of wrong theme */}
|
||||
<script
|
||||
<Script
|
||||
id="carplace-theme-bootstrap"
|
||||
strategy="beforeInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
"(function(){try{var m=document.cookie.match(/(?:^|; )rentaldrivego-theme=([^;]+)/);var theme=m?decodeURIComponent(m[1]):localStorage.getItem('rentaldrivego-theme');if(theme!=='light'&&theme!=='dark'){theme='dark'}document.documentElement.classList.toggle('dark',theme==='dark');document.documentElement.style.colorScheme=theme;document.body&&document.body.setAttribute('data-theme',theme)}catch(e){}})();",
|
||||
|
||||
@@ -118,15 +118,6 @@ export function getFooterContent(language: CarplaceLanguage): FooterContent {
|
||||
return footerLabels[language]
|
||||
}
|
||||
|
||||
function detectBrowserLanguage(): CarplaceLanguage | null {
|
||||
if (typeof navigator === 'undefined') return null
|
||||
for (const language of Array.from(navigator.languages ?? [navigator.language])) {
|
||||
const code = language.split('-')[0].toLowerCase()
|
||||
if (isCarplaceLanguage(code)) return code
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
type PreferencesContextValue = {
|
||||
language: CarplaceLanguage
|
||||
theme: Theme
|
||||
@@ -145,7 +136,7 @@ export function useCarplacePreferences() {
|
||||
|
||||
export default function CarplaceShell({
|
||||
children,
|
||||
initialLanguage = 'en',
|
||||
initialLanguage = 'ar',
|
||||
initialTheme = 'dark',
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
@@ -180,7 +171,7 @@ export default function CarplaceShell({
|
||||
|
||||
useEffect(() => {
|
||||
const storedLanguage = readScopedPreference(SHARED_LANGUAGE_KEY)
|
||||
const resolvedLanguage = isCarplaceLanguage(storedLanguage) ? storedLanguage : detectBrowserLanguage()
|
||||
const resolvedLanguage = isCarplaceLanguage(storedLanguage) ? storedLanguage : 'ar'
|
||||
if (resolvedLanguage) setLanguageState(resolvedLanguage)
|
||||
|
||||
const storedTheme = readScopedPreference(SHARED_THEME_KEY)
|
||||
|
||||
@@ -22,9 +22,9 @@ describe('getCarplaceLanguage', () => {
|
||||
await expect(getCarplaceLanguage()).resolves.toBe(language)
|
||||
})
|
||||
|
||||
it('defaults to English when the shared cookie is absent or invalid', async () => {
|
||||
await expect(getCarplaceLanguage()).resolves.toBe('en')
|
||||
it('defaults to Arabic when the shared cookie is absent or invalid', async () => {
|
||||
await expect(getCarplaceLanguage()).resolves.toBe('ar')
|
||||
cookieValues.set(SHARED_LANGUAGE_COOKIE, 'es')
|
||||
await expect(getCarplaceLanguage()).resolves.toBe('en')
|
||||
await expect(getCarplaceLanguage()).resolves.toBe('ar')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,5 +3,5 @@ import { SHARED_LANGUAGE_COOKIE, isCarplaceLanguage, type CarplaceLanguage } fro
|
||||
|
||||
export async function getCarplaceLanguage(): Promise<CarplaceLanguage> {
|
||||
const cookieValue = (await cookies()).get(SHARED_LANGUAGE_COOKIE)?.value
|
||||
return isCarplaceLanguage(cookieValue) ? cookieValue : 'en'
|
||||
return isCarplaceLanguage(cookieValue) ? cookieValue : 'ar'
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ describe('Carplace language bootstrap', () => {
|
||||
|
||||
it.each([
|
||||
['ar-MA,fr;q=0.8', 'ar'],
|
||||
['de-DE,fr-FR;q=0.7', 'fr'],
|
||||
['de-DE,es;q=0.9', 'en'],
|
||||
['de-DE,fr-FR;q=0.7', 'ar'],
|
||||
['de-DE,es;q=0.9', 'ar'],
|
||||
])('resolves %s as %s', async (acceptLanguage, expected) => {
|
||||
const { proxy } = await import('./proxy')
|
||||
const response = proxy(request({ headers: { 'accept-language': acceptLanguage } }) as never) as any
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
const SHARED_LANGUAGE_COOKIE = 'rentaldrivego-language'
|
||||
const DEFAULT_LANGUAGE: Language = 'ar'
|
||||
|
||||
type Language = 'en' | 'fr' | 'ar'
|
||||
|
||||
@@ -9,15 +10,6 @@ function isValidLanguage(value: string | null | undefined): value is Language {
|
||||
return value === 'en' || value === 'fr' || value === 'ar'
|
||||
}
|
||||
|
||||
function detectFromAcceptLanguage(header: string | null): Language {
|
||||
if (!header) return 'en'
|
||||
for (const entry of header.split(',')) {
|
||||
const code = entry.split(';')[0].trim().split('-')[0].toLowerCase()
|
||||
if (isValidLanguage(code)) return code
|
||||
}
|
||||
return 'en'
|
||||
}
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
if (request.headers.has('x-middleware-subrequest')) {
|
||||
return new NextResponse('Unsupported internal request header', { status: 400 })
|
||||
@@ -26,7 +18,7 @@ export function proxy(request: NextRequest) {
|
||||
const currentLanguage = request.cookies.get(SHARED_LANGUAGE_COOKIE)?.value
|
||||
if (isValidLanguage(currentLanguage)) return NextResponse.next()
|
||||
|
||||
const language = detectFromAcceptLanguage(request.headers.get('accept-language'))
|
||||
const language = DEFAULT_LANGUAGE
|
||||
const requestHeaders = new Headers(request.headers)
|
||||
const existingCookies = request.headers.get('cookie') ?? ''
|
||||
const languageCookie = `${SHARED_LANGUAGE_COOKIE}=${language}`
|
||||
|
||||
@@ -25,7 +25,7 @@ export const metadata: Metadata = {
|
||||
}
|
||||
|
||||
function resolveInitialLanguage(value: string | undefined): 'en' | 'fr' | 'ar' {
|
||||
return value === 'fr' || value === 'ar' || value === 'en' ? value : 'en'
|
||||
return value === 'fr' || value === 'ar' || value === 'en' ? value : 'ar'
|
||||
}
|
||||
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
@@ -86,7 +86,7 @@ describe("dashboard public auth pages", () => {
|
||||
const text = collectText(page).join(" ");
|
||||
const signInLink = findElement(
|
||||
page,
|
||||
(element) => element.props.href === "/en/dark/sign-in",
|
||||
(element) => element.props.href === "/ar/dark/sign-in",
|
||||
);
|
||||
|
||||
expect(text).toContain("Invitation accepted");
|
||||
|
||||
@@ -1519,22 +1519,12 @@ const I18nContext = createContext<I18nContextValue | null>(null)
|
||||
const DASHBOARD_LANGUAGE_KEY = 'dashboard-language'
|
||||
|
||||
function normalizeLanguage(value: string | null | undefined): DashboardLanguage {
|
||||
return value === 'fr' || value === 'ar' || value === 'en' ? value : 'en'
|
||||
}
|
||||
|
||||
function detectBrowserLanguage(): DashboardLanguage | null {
|
||||
if (typeof navigator === 'undefined') return null
|
||||
const langs = Array.from(navigator.languages ?? [navigator.language])
|
||||
for (const lang of langs) {
|
||||
const code = lang.split('-')[0].toLowerCase()
|
||||
if (code === 'fr' || code === 'ar' || code === 'en') return code as DashboardLanguage
|
||||
}
|
||||
return null
|
||||
return value === 'fr' || value === 'ar' || value === 'en' ? value : 'ar'
|
||||
}
|
||||
|
||||
export function DashboardI18nProvider({
|
||||
children,
|
||||
initialLanguage = 'en',
|
||||
initialLanguage = 'ar',
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
initialLanguage?: DashboardLanguage
|
||||
@@ -1580,8 +1570,8 @@ export function DashboardI18nProvider({
|
||||
const next = normalizeLanguage(stored)
|
||||
if (next !== language) setLanguageState(next)
|
||||
} else {
|
||||
const detected = detectBrowserLanguage()
|
||||
if (detected && detected !== language) setLanguageState(detected)
|
||||
const defaultLanguage = normalizeLanguage(null)
|
||||
if (defaultLanguage !== language) setLanguageState(defaultLanguage)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -135,8 +135,8 @@ describe('DashboardAccessGuard route helpers', () => {
|
||||
})
|
||||
|
||||
it('builds sign-in redirects with public dashboard return paths', () => {
|
||||
expect(buildSignInRedirect('/reservations')).toBe('/en/dark/sign-in?redirect=%2Fdashboard%2Freservations')
|
||||
expect(buildSignInRedirect('/dashboard/fleet')).toBe('/en/dark/sign-in?redirect=%2Fdashboard%2Ffleet')
|
||||
expect(buildSignInRedirect('/reservations')).toBe('/ar/dark/sign-in?redirect=%2Fdashboard%2Freservations')
|
||||
expect(buildSignInRedirect('/dashboard/fleet')).toBe('/ar/dark/sign-in?redirect=%2Fdashboard%2Ffleet')
|
||||
})
|
||||
|
||||
it('treats subscription as an owner-only recovery route independent of menu registration', () => {
|
||||
|
||||
@@ -21,8 +21,8 @@ describe('dashboard path normalization', () => {
|
||||
})
|
||||
|
||||
it('builds the canonical homepage sign-in URL with a dashboard return path', () => {
|
||||
expect(buildHomepageSignInPath('/reservations')).toBe('/en/dark/sign-in?redirect=%2Fdashboard%2Freservations')
|
||||
expect(buildHomepageSignInPath('/reservations')).toBe('/ar/dark/sign-in?redirect=%2Fdashboard%2Freservations')
|
||||
expect(buildHomepageSignInPath('/dashboard/fleet', { locale: 'fr', theme: 'dark' })).toBe('/fr/dark/sign-in?redirect=%2Fdashboard%2Ffleet')
|
||||
expect(buildHomepageSignInPath()).toBe('/en/dark/sign-in')
|
||||
expect(buildHomepageSignInPath()).toBe('/ar/dark/sign-in')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const DASHBOARD_BASE_PATH = '/dashboard'
|
||||
const DEFAULT_SIGN_IN_LOCALE = 'en'
|
||||
const DEFAULT_SIGN_IN_LOCALE = 'ar'
|
||||
const DEFAULT_SIGN_IN_THEME = 'dark'
|
||||
|
||||
export function toDashboardAppPath(path?: string | null): string {
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('dashboard proxy', () => {
|
||||
|
||||
const response = proxy(request('http://dashboard:3001/dashboard/team') as never)
|
||||
|
||||
expect(response).toEqual({ kind: 'redirect', url: 'https://rentaldrivego.example/en/dark/sign-in?redirect=%2Fdashboard%2Fteam' })
|
||||
expect(response).toEqual({ kind: 'redirect', url: 'https://rentaldrivego.example/ar/dark/sign-in?redirect=%2Fdashboard%2Fteam' })
|
||||
})
|
||||
|
||||
it('redirects unprefixed internal app paths with a public dashboard return path', async () => {
|
||||
@@ -85,7 +85,7 @@ describe('dashboard proxy', () => {
|
||||
|
||||
const response = proxy(request('http://dashboard:3001/reservations') as never)
|
||||
|
||||
expect(response).toEqual({ kind: 'redirect', url: 'https://rentaldrivego.example/en/dark/sign-in?redirect=%2Fdashboard%2Freservations' })
|
||||
expect(response).toEqual({ kind: 'redirect', url: 'https://rentaldrivego.example/ar/dark/sign-in?redirect=%2Fdashboard%2Freservations' })
|
||||
})
|
||||
|
||||
it('ignores spoofed forwarded host/proto when building the dashboard sign-in redirect', async () => {
|
||||
@@ -98,7 +98,7 @@ describe('dashboard proxy', () => {
|
||||
},
|
||||
}) as never)
|
||||
|
||||
expect(response).toEqual({ kind: 'redirect', url: 'https://market.example.com/en/dark/sign-in?redirect=%2Fdashboard%2Fbilling' })
|
||||
expect(response).toEqual({ kind: 'redirect', url: 'https://market.example.com/ar/dark/sign-in?redirect=%2Fdashboard%2Fbilling' })
|
||||
})
|
||||
|
||||
it('ignores internal forwarded hosts when building the dashboard sign-in redirect', async () => {
|
||||
@@ -111,7 +111,7 @@ describe('dashboard proxy', () => {
|
||||
},
|
||||
}) as never)
|
||||
|
||||
expect(response).toEqual({ kind: 'redirect', url: 'https://market.example.com/en/dark/sign-in?redirect=%2Fdashboard%2Ffleet' })
|
||||
expect(response).toEqual({ kind: 'redirect', url: 'https://market.example.com/ar/dark/sign-in?redirect=%2Fdashboard%2Ffleet' })
|
||||
})
|
||||
|
||||
it('redirects legacy dashboard sign-in requests to the localized homepage sign-in route', async () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { NextRequest } from 'next/server'
|
||||
const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL ?? 'http://localhost:3000'
|
||||
const DASHBOARD_PUBLIC_URL = process.env.NEXT_PUBLIC_DASHBOARD_URL ?? `${WEBSITE_URL.replace(/\/$/, '')}/dashboard`
|
||||
const DASHBOARD_BASE_PATH = '/dashboard'
|
||||
const DEFAULT_SIGN_IN_LOCALE = 'en'
|
||||
const DEFAULT_SIGN_IN_LOCALE = 'ar'
|
||||
const DEFAULT_SIGN_IN_THEME = 'dark'
|
||||
|
||||
function toDashboardAppPath(pathname: string): string {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"defaultLocale": "en",
|
||||
"defaultLocale": "ar",
|
||||
"locales": ["en", "fr", "ar"],
|
||||
"rootDetection": true,
|
||||
"routes": [
|
||||
@@ -82,7 +82,7 @@
|
||||
},
|
||||
"hreflang": {
|
||||
"values": ["en", "fr", "ar"],
|
||||
"xDefaultLocale": "en",
|
||||
"xDefaultLocale": "ar",
|
||||
"selfCanonical": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export type Locale = (typeof locales)[number];
|
||||
export type Direction = 'ltr' | 'rtl';
|
||||
export type RouteId = 'home' | 'sign-in' | 'forgot-password' | 'reset-password' | 'privacy' | 'terms' | 'accessibility';
|
||||
|
||||
export const defaultLocale: Locale = 'en';
|
||||
export const defaultLocale: Locale = 'ar';
|
||||
export const defaultMode: ThemePreference = 'dark';
|
||||
export const localeCookie = 'hpc-locale';
|
||||
export const localeStorageKey = 'hpc.locale';
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
isLocale,
|
||||
localeCookie,
|
||||
localeCookieMaxAgeSeconds,
|
||||
localeFromAcceptLanguage,
|
||||
localizedModePath,
|
||||
routeIdFromAnyLocaleSlug,
|
||||
routeIdFromSlug,
|
||||
@@ -47,11 +46,8 @@ function resolveApiOrigin(): string | undefined {
|
||||
return origins.size > 0 ? Array.from(origins).join(' ') : undefined;
|
||||
}
|
||||
|
||||
function resolveRequestLocale(request: NextRequest) {
|
||||
const cookieLocale = request.cookies.get(localeCookie)?.value;
|
||||
return isLocale(cookieLocale)
|
||||
? cookieLocale
|
||||
: localeFromAcceptLanguage(request.headers.get('accept-language')) || defaultLocale;
|
||||
function resolveRequestLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
function resolveRequestMode(request: NextRequest) {
|
||||
@@ -69,6 +65,13 @@ function setLocaleCookie(response: NextResponse, locale: string): void {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: false,
|
||||
});
|
||||
response.cookies.set(dashboardLanguageCookie, locale, {
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
maxAge: localeCookieMaxAgeSeconds,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
function setModeCookie(response: NextResponse, mode: string): void {
|
||||
@@ -98,7 +101,7 @@ export function proxy(request: NextRequest): NextResponse {
|
||||
requestHeaders.set('Content-Security-Policy', csp);
|
||||
|
||||
if (request.nextUrl.pathname === '/') {
|
||||
const locale = resolveRequestLocale(request);
|
||||
const locale = resolveRequestLocale();
|
||||
const mode = resolveRequestMode(request);
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = `/${locale}/${mode}`;
|
||||
@@ -110,11 +113,11 @@ export function proxy(request: NextRequest): NextResponse {
|
||||
|
||||
const pathSegments = request.nextUrl.pathname.split('/').filter(Boolean);
|
||||
|
||||
// Redirect locale-less paths (e.g. /sign-in -> /en/dark/sign-in), but not proxied app paths or files.
|
||||
// Redirect locale-less paths (e.g. /sign-in -> /ar/dark/sign-in), but not proxied app paths or files.
|
||||
const firstSegment = request.nextUrl.pathname.split('/')[1];
|
||||
const isFile = /\.\w+$/.test(request.nextUrl.pathname);
|
||||
if (firstSegment && !isFile && !isLocale(firstSegment) && !reservedProxySegments.has(firstSegment)) {
|
||||
const locale = resolveRequestLocale(request);
|
||||
const locale = resolveRequestLocale();
|
||||
const mode = resolveRequestMode(request);
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = `/${locale}/${mode}${request.nextUrl.pathname}`;
|
||||
|
||||
@@ -35,7 +35,7 @@ for (const scenario of [
|
||||
});
|
||||
}
|
||||
|
||||
test('root locale detection honors Accept-Language', async ({ browser }) => {
|
||||
test('root locale defaults to Arabic', async ({ browser }) => {
|
||||
const context = await browser.newContext({
|
||||
extraHTTPHeaders: { 'Accept-Language': 'ar;q=1,en;q=0.8' },
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('localization foundations', () => {
|
||||
it('selects a supported locale by quality', () => {
|
||||
expect(localeFromAcceptLanguage('de-DE;q=0.9, ar;q=0.8, fr;q=0.7')).toBe('ar');
|
||||
expect(localeFromAcceptLanguage('fr-CA, en;q=0.8')).toBe('fr');
|
||||
expect(localeFromAcceptLanguage(null)).toBe('en');
|
||||
expect(localeFromAcceptLanguage(null)).toBe('ar');
|
||||
});
|
||||
|
||||
it('validates locale direction and route lookup', () => {
|
||||
@@ -45,7 +45,7 @@ describe('localization foundations', () => {
|
||||
const links = hreflangMap(new URL('https://approved.test'), 'home');
|
||||
expect(links.en).toBe('https://approved.test/en/dark');
|
||||
expect(links.ar).toBe('https://approved.test/ar/dark');
|
||||
expect(links['x-default']).toBe('https://approved.test/en/dark');
|
||||
expect(links['x-default']).toBe('https://approved.test/ar/dark');
|
||||
});
|
||||
|
||||
it('preserves only approved campaign query values and valid hashes', () => {
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('homepage proxy app boundaries', () => {
|
||||
|
||||
expect(response).toMatchObject({
|
||||
kind: 'redirect',
|
||||
url: 'https://rentaldrivego.ma/fr/dark',
|
||||
url: 'https://rentaldrivego.ma/ar/dark',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user