feat: 增加多语言
This commit is contained in:
@@ -5,6 +5,7 @@ import axios, {
|
||||
} from "axios";
|
||||
|
||||
import { withAdminAuthHeader } from "@/lib/admin-auth";
|
||||
import { withAdminLocaleHeaders } from "@/lib/admin-locale";
|
||||
import { LotteryApiBizError, LotteryApiEnvelopeError } from "@/types/api/errors";
|
||||
import { isApiEnvelope } from "@/types/api/envelope";
|
||||
|
||||
@@ -40,7 +41,9 @@ export async function publicAdminRequest<T>(
|
||||
config: AxiosRequestConfig,
|
||||
): Promise<T> {
|
||||
try {
|
||||
const res = await adminHttp.request<unknown>(config);
|
||||
const res = await adminHttp.request<unknown>(
|
||||
withAdminLocaleHeaders(config),
|
||||
);
|
||||
return unwrapResponse<T>(res);
|
||||
} catch (err: unknown) {
|
||||
if (isAxiosError(err) && err.response?.data !== undefined) {
|
||||
@@ -54,7 +57,7 @@ export async function publicAdminRequest<T>(
|
||||
}
|
||||
|
||||
export async function request<T>(config: AxiosRequestConfig): Promise<T> {
|
||||
const merged = withAdminAuthHeader(config);
|
||||
const merged = withAdminAuthHeader(withAdminLocaleHeaders(config));
|
||||
try {
|
||||
const res = await adminHttp.request<unknown>(merged);
|
||||
return unwrapResponse<T>(res);
|
||||
|
||||
127
src/lib/admin-locale.ts
Normal file
127
src/lib/admin-locale.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { AxiosHeaders, type AxiosRequestConfig } from "axios";
|
||||
|
||||
/** 与 Laravel `NegotiateLotteryLocale`、`lottery.locales.supported`(zh/en/ne)一致 */
|
||||
export type AdminApiLocale = "zh" | "en" | "ne";
|
||||
|
||||
export const ADMIN_API_LOCALES: readonly AdminApiLocale[] = ["zh", "en", "ne"];
|
||||
|
||||
export const ADMIN_LOCALE_LABELS: Record<AdminApiLocale, string> = {
|
||||
zh: "简体中文",
|
||||
en: "English",
|
||||
ne: "नेपाली",
|
||||
};
|
||||
|
||||
const STORAGE_KEY = "lottery_admin_ui_locale";
|
||||
|
||||
let overrideLocale: AdminApiLocale | null = null;
|
||||
|
||||
function isApiLocale(value: string): value is AdminApiLocale {
|
||||
return value === "zh" || value === "en" || value === "ne";
|
||||
}
|
||||
|
||||
/** 覆盖后续 API 请求的 `X-Locale`(传 `null` 取消覆盖,改回 `document.documentElement.lang` / 默认) */
|
||||
export function setAdminRequestLocale(locale: string | null): void {
|
||||
if (locale === null) {
|
||||
overrideLocale = null;
|
||||
return;
|
||||
}
|
||||
const p = locale.trim().toLowerCase().split("-")[0] ?? "";
|
||||
overrideLocale = isApiLocale(p) ? p : null;
|
||||
}
|
||||
|
||||
function requestLocale(): AdminApiLocale {
|
||||
if (overrideLocale) {
|
||||
return overrideLocale;
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
const tag = document.documentElement.lang.trim().toLowerCase();
|
||||
const primary = tag.split("-")[0] ?? tag;
|
||||
if (isApiLocale(primary)) {
|
||||
return primary;
|
||||
}
|
||||
}
|
||||
return "en";
|
||||
}
|
||||
|
||||
/** 当前生效的语言(与即将发出的 API 头一致) */
|
||||
export function getAdminRequestLocale(): AdminApiLocale {
|
||||
return requestLocale();
|
||||
}
|
||||
|
||||
function acceptLanguage(loc: AdminApiLocale): string {
|
||||
if (loc === "zh") {
|
||||
return "zh-CN,zh;q=0.9,en;q=0.8";
|
||||
}
|
||||
if (loc === "ne") {
|
||||
return "ne,ne-NP;q=0.9,en;q=0.8";
|
||||
}
|
||||
return "en-US,en;q=0.9";
|
||||
}
|
||||
|
||||
/** 与 `<html lang>` 对齐的 BCP 47 标签 */
|
||||
export function adminHtmlLang(loc: AdminApiLocale): string {
|
||||
if (loc === "zh") {
|
||||
return "zh-Hans";
|
||||
}
|
||||
if (loc === "ne") {
|
||||
return "ne";
|
||||
}
|
||||
return "en";
|
||||
}
|
||||
|
||||
function readStoredUiLocale(): AdminApiLocale | null {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
}
|
||||
const raw = window.localStorage.getItem(STORAGE_KEY)?.trim().toLowerCase();
|
||||
if (raw && isApiLocale(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function writeStoredUiLocale(loc: AdminApiLocale): void {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
window.localStorage.setItem(STORAGE_KEY, loc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换界面语言:写入 localStorage、同步 `document.documentElement.lang`、设置 API `X-Locale` 覆盖。
|
||||
*/
|
||||
export function applyAdminUiLocale(loc: AdminApiLocale): void {
|
||||
setAdminRequestLocale(loc);
|
||||
writeStoredUiLocale(loc);
|
||||
if (typeof document !== "undefined") {
|
||||
document.documentElement.lang = adminHtmlLang(loc);
|
||||
}
|
||||
}
|
||||
|
||||
/** 启动时从 localStorage 恢复(应在客户端尽早调用一次) */
|
||||
export function hydrateAdminUiLocale(): void {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
const stored = readStoredUiLocale();
|
||||
if (stored) {
|
||||
setAdminRequestLocale(stored);
|
||||
document.documentElement.lang = adminHtmlLang(stored);
|
||||
}
|
||||
}
|
||||
|
||||
/** 供 `admin-http`:`X-Locale` + `Accept-Language` */
|
||||
export function withAdminLocaleHeaders(
|
||||
config: AxiosRequestConfig,
|
||||
): AxiosRequestConfig {
|
||||
const loc = requestLocale();
|
||||
const merged: AxiosRequestConfig = { ...config };
|
||||
const headers = AxiosHeaders.concat(
|
||||
merged.headers as Parameters<typeof AxiosHeaders.concat>[0],
|
||||
);
|
||||
headers.set("X-Locale", loc);
|
||||
headers.set("Accept-Language", acceptLanguage(loc));
|
||||
merged.headers = headers;
|
||||
return merged;
|
||||
}
|
||||
Reference in New Issue
Block a user