Add i18n foundation (fa-IR/en-US) and localize landing + auth.
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>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
'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]);
|
||||
}
|
||||
Reference in New Issue
Block a user