mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
@@ -4134,6 +4134,18 @@ export default function ProviderDetailPage() {
|
||||
Experimental OAuth
|
||||
</Button>
|
||||
)}
|
||||
{providerId === "codex" && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="upload_file"
|
||||
onClick={() => gateConnectionFlow(() => setImportCodexModalOpen(true))}
|
||||
>
|
||||
{typeof t.has === "function" && t.has("importCodexAuth")
|
||||
? t("importCodexAuth")
|
||||
: "Import auth"}
|
||||
</Button>
|
||||
)}
|
||||
{providerId === "claude" && (
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ThemeProvider } from "@/shared/components/ThemeProvider";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages, getLocale } from "next-intl/server";
|
||||
import { RTL_LOCALES } from "@/i18n/config";
|
||||
import { normalizeComplianceEventTypes } from "@/i18n/request";
|
||||
import { getSettings } from "@/lib/db/settings";
|
||||
import type { Viewport } from "next";
|
||||
import { PwaRegister } from "@/shared/components/PwaRegister";
|
||||
@@ -52,7 +53,7 @@ export async function generateMetadata() {
|
||||
|
||||
export default async function RootLayout({ children }) {
|
||||
const locale = await getLocale();
|
||||
const messages = await getMessages();
|
||||
const messages = normalizeComplianceEventTypes((await getMessages()) as Record<string, unknown>);
|
||||
const isRtl = RTL_LOCALES.includes(locale as (typeof RTL_LOCALES)[number]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -34,6 +34,67 @@ export function deepMergeFallback(
|
||||
return target;
|
||||
}
|
||||
|
||||
function setNestedValue(target: Record<string, unknown>, dottedKey: string, value: unknown): void {
|
||||
const segments = dottedKey.split(".");
|
||||
let cursor: Record<string, unknown> = target;
|
||||
|
||||
for (let index = 0; index < segments.length; index += 1) {
|
||||
const segment = segments[index];
|
||||
if (!segment || segment === "__proto__" || segment === "constructor" || segment === "prototype") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index === segments.length - 1) {
|
||||
cursor[segment] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const next = cursor[segment];
|
||||
if (next && typeof next === "object" && !Array.isArray(next)) {
|
||||
cursor = next as Record<string, unknown>;
|
||||
continue;
|
||||
}
|
||||
|
||||
const created: Record<string, unknown> = {};
|
||||
cursor[segment] = created;
|
||||
cursor = created;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeComplianceEventTypes(
|
||||
messages: Record<string, unknown>
|
||||
): Record<string, unknown> {
|
||||
const compliance =
|
||||
messages.compliance && typeof messages.compliance === "object" && !Array.isArray(messages.compliance)
|
||||
? (messages.compliance as Record<string, unknown>)
|
||||
: null;
|
||||
const eventTypes =
|
||||
compliance?.eventTypes &&
|
||||
typeof compliance.eventTypes === "object" &&
|
||||
!Array.isArray(compliance.eventTypes)
|
||||
? (compliance.eventTypes as Record<string, unknown>)
|
||||
: null;
|
||||
|
||||
if (!compliance || !eventTypes) return messages;
|
||||
|
||||
const normalizedEventTypes: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(eventTypes)) {
|
||||
if (key.includes(".")) {
|
||||
setNestedValue(normalizedEventTypes, key, value);
|
||||
} else {
|
||||
normalizedEventTypes[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...messages,
|
||||
compliance: {
|
||||
...compliance,
|
||||
eventTypes: normalizedEventTypes,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default getRequestConfig(async () => {
|
||||
const cookieStore = await cookies();
|
||||
let locale: string = cookieStore.get(LOCALE_COOKIE)?.value || "";
|
||||
@@ -47,13 +108,17 @@ export default getRequestConfig(async () => {
|
||||
locale = DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
const localeMessages = (await import(`./messages/${locale}.json`)).default;
|
||||
const localeMessages = normalizeComplianceEventTypes(
|
||||
(await import(`./messages/${locale}.json`)).default as Record<string, unknown>
|
||||
);
|
||||
|
||||
// G1: fall back to EN for any missing key. EN is loaded only once per request
|
||||
// and only when the active locale is not EN itself (no-op).
|
||||
let messages = localeMessages as Record<string, unknown>;
|
||||
if (locale !== FALLBACK_LOCALE) {
|
||||
const fallbackMessages = (await import(`./messages/${FALLBACK_LOCALE}.json`)).default as Record<string, unknown>;
|
||||
const fallbackMessages = normalizeComplianceEventTypes(
|
||||
(await import(`./messages/${FALLBACK_LOCALE}.json`)).default as Record<string, unknown>
|
||||
);
|
||||
messages = deepMergeFallback({ ...localeMessages }, fallbackMessages);
|
||||
}
|
||||
|
||||
@@ -66,9 +131,9 @@ export default getRequestConfig(async () => {
|
||||
// translations are shipped.
|
||||
let mergedMessages: Record<string, unknown> = messages as Record<string, unknown>;
|
||||
if (locale !== DEFAULT_LOCALE) {
|
||||
const enMessages = (
|
||||
await import(`./messages/${DEFAULT_LOCALE}.json`)
|
||||
).default as Record<string, unknown>;
|
||||
const enMessages = normalizeComplianceEventTypes(
|
||||
(await import(`./messages/${DEFAULT_LOCALE}.json`)).default as Record<string, unknown>
|
||||
);
|
||||
mergedMessages = { ...enMessages, ...mergedMessages };
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,30 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { normalizeComplianceEventTypes } from "../../src/i18n/request";
|
||||
|
||||
const root = join(import.meta.dirname, "../..");
|
||||
const read = (p: string) => readFileSync(join(root, p), "utf8");
|
||||
const en = JSON.parse(read("src/i18n/messages/en.json"));
|
||||
const pt = JSON.parse(read("src/i18n/messages/pt-BR.json"));
|
||||
const rawEn = JSON.parse(read("src/i18n/messages/en.json"));
|
||||
const rawPt = JSON.parse(read("src/i18n/messages/pt-BR.json"));
|
||||
const en = normalizeComplianceEventTypes(rawEn);
|
||||
const pt = normalizeComplianceEventTypes(rawPt);
|
||||
|
||||
function getNestedValue(record: Record<string, unknown>, dottedKey: string): unknown {
|
||||
return dottedKey.split(".").reduce<unknown>((cursor, segment) => {
|
||||
if (!cursor || typeof cursor !== "object" || Array.isArray(cursor)) return undefined;
|
||||
return (cursor as Record<string, unknown>)[segment];
|
||||
}, record);
|
||||
}
|
||||
|
||||
test("audit: compliance.eventTypes exists with en/pt-BR parity and key coverage", () => {
|
||||
const enKeys = Object.keys(en.compliance?.eventTypes ?? {});
|
||||
const ptKeys = Object.keys(pt.compliance?.eventTypes ?? {});
|
||||
assert.ok(enKeys.length >= 30, `expected >=30 event-type labels, got ${enKeys.length}`);
|
||||
assert.deepEqual(enKeys.sort(), ptKeys.sort(), "en/pt-BR eventTypes keys must match");
|
||||
const rawEnKeys = Object.keys(rawEn.compliance?.eventTypes ?? {});
|
||||
const rawPtKeys = Object.keys(rawPt.compliance?.eventTypes ?? {});
|
||||
assert.ok(rawEnKeys.length >= 30, `expected >=30 event-type labels, got ${rawEnKeys.length}`);
|
||||
assert.deepEqual(rawEnKeys.sort(), rawPtKeys.sort(), "en/pt-BR eventTypes keys must match");
|
||||
for (const k of ["provider.credentials.created", "auth.login.success", "quota.pool.created", "sync.token.revoked"]) {
|
||||
assert.ok(en.compliance.eventTypes[k], `en missing eventTypes.${k}`);
|
||||
assert.ok(pt.compliance.eventTypes[k], `pt-BR missing eventTypes.${k}`);
|
||||
assert.ok(getNestedValue(en.compliance.eventTypes as Record<string, unknown>, k), `en missing eventTypes.${k}`);
|
||||
assert.ok(getNestedValue(pt.compliance.eventTypes as Record<string, unknown>, k), `pt-BR missing eventTypes.${k}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user