34993d417f
Introduce path-prefixed locale routing under app/[lang] with a middleware that detects locale from cookie/Accept-Language (default fa-IR) and redirects. Add fa-IR (source of truth) and en-US dictionaries, a server getDictionary, a client I18nProvider/useT, locale-aware Link + router helpers, and a language switcher. The root [lang] layout sets html lang/dir and the per-locale font (Peyda for fa, Inter for en). Landing sections and the login/register/auth shell now read all copy from the dictionaries; dashboard localization follows in a later commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useMemo } from 'react';
|
|
import {
|
|
useRouter as useNextRouter,
|
|
usePathname as useNextPathname,
|
|
} from 'next/navigation';
|
|
import { locales, type Locale } from './config';
|
|
import { useLocale } from './I18nProvider';
|
|
|
|
const localePrefixes = locales.map((l) => `/${l}`);
|
|
|
|
/** Prefix an internal href with the given locale. Leaves external/hash/already-prefixed hrefs untouched. */
|
|
export function localizeHref(href: string, locale: Locale): string {
|
|
if (!href.startsWith('/')) return href; // external, hash, or relative
|
|
// already locale-prefixed?
|
|
if (localePrefixes.some((p) => href === p || href.startsWith(p + '/'))) return href;
|
|
return href === '/' ? `/${locale}` : `/${locale}${href}`;
|
|
}
|
|
|
|
/** Strip a leading locale segment from a pathname, returning the locale-agnostic path. */
|
|
export function stripLocale(pathname: string): string {
|
|
for (const p of localePrefixes) {
|
|
if (pathname === p) return '/';
|
|
if (pathname.startsWith(p + '/')) return pathname.slice(p.length);
|
|
}
|
|
return pathname;
|
|
}
|
|
|
|
/** Replace (or add) the locale segment of a pathname. Used by the language switcher. */
|
|
export function swapLocale(pathname: string, locale: Locale): string {
|
|
return localizeHref(stripLocale(pathname), locale);
|
|
}
|
|
|
|
/** A next/navigation router whose push/replace/prefetch auto-prefix the active locale. */
|
|
export function useLocalizedRouter() {
|
|
const router = useNextRouter();
|
|
const locale = useLocale();
|
|
return useMemo(
|
|
() => ({
|
|
...router,
|
|
push: (href: string, opts?: Parameters<typeof router.push>[1]) =>
|
|
router.push(localizeHref(href, locale), opts),
|
|
replace: (href: string, opts?: Parameters<typeof router.replace>[1]) =>
|
|
router.replace(localizeHref(href, locale), opts),
|
|
prefetch: (href: string, opts?: Parameters<typeof router.prefetch>[1]) =>
|
|
router.prefetch(localizeHref(href, locale), opts),
|
|
}),
|
|
[router, locale],
|
|
);
|
|
}
|
|
|
|
/** The current pathname with the locale segment stripped (e.g. /dashboard/apps). */
|
|
export function usePathname(): string {
|
|
const pathname = useNextPathname();
|
|
return useMemo(() => stripLocale(pathname ?? '/'), [pathname]);
|
|
}
|
|
|
|
/** Returns a memoized localizer bound to the active locale. */
|
|
export function useLocalize() {
|
|
const locale = useLocale();
|
|
return useCallback((href: string) => localizeHref(href, locale), [locale]);
|
|
}
|