fix(i18n): localize security challenge
This commit is contained in:
@@ -38,6 +38,7 @@ Updated: 2026-08-29
|
|||||||
- Localized the Admin System runtime and CV benchmark diagnostics, including probe/email failures, model/Ollama state, parser findings, benchmark summaries, fallback guidance, and locale-aware timestamps while leaving runtime/provider/file values untouched.
|
- Localized the Admin System runtime and CV benchmark diagnostics, including probe/email failures, model/Ollama state, parser findings, benchmark summaries, fallback guidance, and locale-aware timestamps while leaving runtime/provider/file values untouched.
|
||||||
- Consolidated active list/dashboard/Kanban/reminder loading and failure presentation through the shared resource notice. Retry/progress accessibility follows EN/NB, duplicate fallback text is suppressed, and Kanban drag/keyboard announcements are localized.
|
- Consolidated active list/dashboard/Kanban/reminder loading and failure presentation through the shared resource notice. Retry/progress accessibility follows EN/NB, duplicate fallback text is suppressed, and Kanban drag/keyboard announcements are localized.
|
||||||
- Migrated Admin Audit to the shared retryable resource state so an unavailable API is no longer misreported as an empty log. Audit actions/dates, notification-setting feedback, profile-load fallbacks, and CV extraction run notifications now follow EN/NB while stored event/profile content remains unchanged.
|
- Migrated Admin Audit to the shared retryable resource state so an unavailable API is no longer misreported as an empty log. Audit actions/dates, notification-setting feedback, profile-load fallbacks, and CV extraction run notifications now follow EN/NB while stored event/profile content remains unchanged.
|
||||||
|
- Localized the pre-authentication security-check label and the embedded Turnstile challenge language, so switching to Bokmål no longer leaves the verification widget in English.
|
||||||
|
|
||||||
### In progress
|
### In progress
|
||||||
|
|
||||||
@@ -99,6 +100,7 @@ Updated: 2026-08-29
|
|||||||
- Post-fix complete regression: backend 716/716 and frontend 62 suites with 268/268 tests passed; the optimized Next production build and integrated TypeScript check passed.
|
- Post-fix complete regression: backend 716/716 and frontend 62 suites with 268/268 tests passed; the optimized Next production build and integrated TypeScript check passed.
|
||||||
- Shared view-state focused verification: 3 suites and 15/15 tests passed, including Bokmål loading/retry and Kanban interaction coverage; TypeScript passed.
|
- Shared view-state focused verification: 3 suites and 15/15 tests passed, including Bokmål loading/retry and Kanban interaction coverage; TypeScript passed.
|
||||||
- Admin Audit/settings/profile focused verification: 3 suites and 17/17 tests passed, including unavailable-versus-empty recovery and Bokmål audit actions that preserve stored event content; TypeScript passed.
|
- Admin Audit/settings/profile focused verification: 3 suites and 17/17 tests passed, including unavailable-versus-empty recovery and Bokmål audit actions that preserve stored event content; TypeScript passed.
|
||||||
|
- Turnstile/auth recovery focused verification: 4 suites and 18/18 tests passed; the provider widget receives the Bokmål language code and TypeScript passed.
|
||||||
- Backend matcher/intelligence focused verification: 35/35 passed, including detection of a manually created Norwegian advert with no saved translation.
|
- Backend matcher/intelligence focused verification: 35/35 passed, including detection of a manually created Norwegian advert with no saved translation.
|
||||||
- Full backend: 712/712 tests passed on .NET 9.
|
- Full backend: 712/712 tests passed on .NET 9.
|
||||||
- Next optimized production build and TypeScript: passed after the Job Workspace/checklist batch.
|
- Next optimized production build and TypeScript: passed after the Job Workspace/checklist batch.
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { useI18n } from "../i18n/I18nProvider";
|
||||||
|
|
||||||
type Props = { siteKey: string; action: "login" | "register"; onToken: (token: string) => void; };
|
type Props = { siteKey: string; action: "login" | "register"; onToken: (token: string) => void; };
|
||||||
|
|
||||||
export default function TurnstileWidget({ siteKey, action, onToken }: Props) {
|
export default function TurnstileWidget({ siteKey, action, onToken }: Props) {
|
||||||
|
const { language, t } = useI18n();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let widgetId: string | undefined;
|
let widgetId: string | undefined;
|
||||||
@@ -11,7 +13,7 @@ export default function TurnstileWidget({ siteKey, action, onToken }: Props) {
|
|||||||
const render = () => {
|
const render = () => {
|
||||||
if (cancelled || !ref.current || !(window as any).turnstile) return;
|
if (cancelled || !ref.current || !(window as any).turnstile) return;
|
||||||
widgetId = (window as any).turnstile.render(ref.current, {
|
widgetId = (window as any).turnstile.render(ref.current, {
|
||||||
sitekey: siteKey, action, theme: "auto", size: "flexible",
|
sitekey: siteKey, action, theme: "auto", size: "flexible", language: language === "nb" ? "nb" : "en",
|
||||||
callback: onToken,
|
callback: onToken,
|
||||||
"expired-callback": () => onToken(""),
|
"expired-callback": () => onToken(""),
|
||||||
"error-callback": () => onToken(""),
|
"error-callback": () => onToken(""),
|
||||||
@@ -28,7 +30,7 @@ export default function TurnstileWidget({ siteKey, action, onToken }: Props) {
|
|||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
}
|
}
|
||||||
return () => { cancelled = true; if (widgetId && (window as any).turnstile) (window as any).turnstile.remove(widgetId); };
|
return () => { cancelled = true; if (widgetId && (window as any).turnstile) (window as any).turnstile.remove(widgetId); };
|
||||||
}, [action, onToken, siteKey]);
|
}, [action, language, onToken, siteKey]);
|
||||||
|
|
||||||
return <Box><Typography variant="caption" color="text.secondary">Security check</Typography><Box ref={ref} sx={{ minHeight: 65, mt: 0.5 }} /></Box>;
|
return <Box><Typography variant="caption" color="text.secondary">{t("securityCheck")}</Typography><Box ref={ref} sx={{ minHeight: 65, mt: 0.5 }} /></Box>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -903,6 +903,7 @@ export const translations = {
|
|||||||
proFeatureView: "View Pro",
|
proFeatureView: "View Pro",
|
||||||
proFeatureDismiss: "Dismiss {title}",
|
proFeatureDismiss: "Dismiss {title}",
|
||||||
registrationUnavailable: "Registration is currently unavailable.",
|
registrationUnavailable: "Registration is currently unavailable.",
|
||||||
|
securityCheck: "Security check",
|
||||||
dashboardTotalCount: "{count} total",
|
dashboardTotalCount: "{count} total",
|
||||||
jobTableSelectJob: "Select {title}",
|
jobTableSelectJob: "Select {title}",
|
||||||
characterCount: "{count} characters",
|
characterCount: "{count} characters",
|
||||||
@@ -3215,6 +3216,7 @@ export const translations = {
|
|||||||
proFeatureView: "Se Pro",
|
proFeatureView: "Se Pro",
|
||||||
proFeatureDismiss: "Lukk {title}",
|
proFeatureDismiss: "Lukk {title}",
|
||||||
registrationUnavailable: "Registrering er ikke tilgjengelig for øyeblikket.",
|
registrationUnavailable: "Registrering er ikke tilgjengelig for øyeblikket.",
|
||||||
|
securityCheck: "Sikkerhetskontroll",
|
||||||
dashboardTotalCount: "{count} totalt",
|
dashboardTotalCount: "{count} totalt",
|
||||||
jobTableSelectJob: "Velg {title}",
|
jobTableSelectJob: "Velg {title}",
|
||||||
characterCount: "{count} tegn",
|
characterCount: "{count} tegn",
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
|
||||||
|
import TurnstileWidget from "./components/TurnstileWidget";
|
||||||
|
import { I18nProvider } from "./i18n/I18nProvider";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
window.localStorage.clear();
|
||||||
|
(window as any).turnstile = { render: jest.fn(() => "widget-1"), remove: jest.fn() };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete (window as any).turnstile;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders the security check and provider widget in Bokmål", () => {
|
||||||
|
window.localStorage.setItem("uiLanguage", "nb-NO");
|
||||||
|
|
||||||
|
render(<I18nProvider><TurnstileWidget siteKey="site-key" action="login" onToken={jest.fn()} /></I18nProvider>);
|
||||||
|
|
||||||
|
expect(screen.getByText("Sikkerhetskontroll")).toBeInTheDocument();
|
||||||
|
expect((window as any).turnstile.render).toHaveBeenCalledWith(expect.any(HTMLElement), expect.objectContaining({
|
||||||
|
sitekey: "site-key",
|
||||||
|
action: "login",
|
||||||
|
language: "nb",
|
||||||
|
}));
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user