feat: add internationalization message platform
This commit is contained in:
@@ -28,7 +28,8 @@ import {
|
||||
RouteBoundary,
|
||||
} from "../boundaries/render-error-boundary.jsx";
|
||||
import { ChunkRecoveryBoundary } from "../boundaries/chunk-recovery-boundary.js";
|
||||
import { PageHeader } from "../design-system/index.js";
|
||||
import { Button, PageHeader } from "../design-system/index.js";
|
||||
import { LocaleProvider, useLocale } from "../i18n/index.js";
|
||||
import { AppShell } from "../layouts/app-shell.jsx";
|
||||
import { useApplication } from "../providers/application-provider.js";
|
||||
import { SessionProvider, useSession } from "../providers/session-provider.jsx";
|
||||
@@ -54,6 +55,8 @@ export function useRouteInput(): ParsedRouteInput {
|
||||
}
|
||||
|
||||
function RouteLoadingSurface({ definition }: { definition: RouteDefinition }) {
|
||||
const { message, resolve } = useLocale();
|
||||
const title = resolve(`route.${definition.routeId}.title`);
|
||||
return (
|
||||
<section
|
||||
className="ui-page route-loading"
|
||||
@@ -62,8 +65,10 @@ function RouteLoadingSurface({ definition }: { definition: RouteDefinition }) {
|
||||
data-surface={definition.loadingSurface}
|
||||
>
|
||||
<div className="ui-skeleton" aria-hidden="true" />
|
||||
<p>화면을 준비하고 있습니다.</p>
|
||||
<span className="visually-hidden">{definition.title} 로딩 중</span>
|
||||
<p>{message("route.loading")}</p>
|
||||
<span className="visually-hidden">
|
||||
{message("route.loadingNamed", { title })}
|
||||
</span>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -73,35 +78,41 @@ function RouteFailureSurface({
|
||||
}: {
|
||||
definition?: RouteDefinition;
|
||||
}) {
|
||||
const { message } = useLocale();
|
||||
return (
|
||||
<section
|
||||
className="ui-page"
|
||||
data-surface={definition?.errorSurface ?? "route-boundary"}
|
||||
>
|
||||
<PageHeader
|
||||
title="화면을 표시하지 못했습니다."
|
||||
description="잠시 후 다시 시도해 주세요. 문제가 계속되면 운영 지원 참조 정보를 확인하세요."
|
||||
title={message("route.failure.title")}
|
||||
description={message("route.failure.description")}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function InvalidRouteSurface({ code }: { code: string }) {
|
||||
const { message } = useLocale();
|
||||
return (
|
||||
<section className="ui-page" data-surface="invalid-route">
|
||||
<PageHeader
|
||||
title="올바르지 않은 주소입니다."
|
||||
description="주소의 경로 또는 검색 조건을 확인해 주세요."
|
||||
title={message("route.invalid.title")}
|
||||
description={message("route.invalid.description")}
|
||||
/>
|
||||
<p data-route-error={code}>안전한 탐색 링크를 사용해 주세요.</p>
|
||||
<p data-route-error={code}>{message("route.invalid.action")}</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteLifecycle({ definition }: { definition: RouteDefinition }) {
|
||||
const location = useLocation();
|
||||
const { message, resolve } = useLocale();
|
||||
useEffect(() => {
|
||||
document.title = `${definition.title} · Frontend Skeleton`;
|
||||
document.title = message("route.documentTitle", {
|
||||
title: resolve(`route.${definition.routeId}.title`),
|
||||
appName: message("common.appName"),
|
||||
});
|
||||
const main = document.getElementById("main-content");
|
||||
main?.focus({ preventScroll: true });
|
||||
try {
|
||||
@@ -111,7 +122,7 @@ function RouteLifecycle({ definition }: { definition: RouteDefinition }) {
|
||||
} catch {
|
||||
// Non-browser test hosts may not implement scrolling.
|
||||
}
|
||||
}, [definition, location.key, location.pathname]);
|
||||
}, [definition, location.key, location.pathname, message, resolve]);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -146,6 +157,7 @@ function ProtectedRoute({
|
||||
}) {
|
||||
const location = useLocation();
|
||||
const { sessionState, beginSignIn, recover } = useSession();
|
||||
const { message } = useLocale();
|
||||
const [pending, setPending] = useState(false);
|
||||
const [failed, setFailed] = useState(false);
|
||||
const decision = decideRouteAccess(routeId, sessionState);
|
||||
@@ -173,8 +185,8 @@ function ProtectedRoute({
|
||||
return (
|
||||
<section className="ui-page" data-surface="auth-integration-required">
|
||||
<PageHeader
|
||||
title="로그인 연동이 필요합니다."
|
||||
description="외부 인증 소유자가 연결되면 이 보호 라우트를 사용할 수 있습니다."
|
||||
title={message("route.auth.integration.title")}
|
||||
description={message("route.auth.integration.description")}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@@ -183,24 +195,30 @@ function ProtectedRoute({
|
||||
return (
|
||||
<section className="ui-page" data-surface="authentication-required">
|
||||
<PageHeader
|
||||
title={recovering ? "세션을 복구하고 있습니다." : "세션이 필요합니다."}
|
||||
title={
|
||||
recovering
|
||||
? message("route.auth.recovering.title")
|
||||
: message("route.auth.required.title")
|
||||
}
|
||||
description={
|
||||
recovering
|
||||
? "기존 세션 확인을 계속하려면 복구를 실행하세요."
|
||||
: "이 화면은 인증 연동 지점을 확인하기 위한 보호 라우트입니다."
|
||||
? message("route.auth.recovering.description")
|
||||
: message("route.auth.required.description")
|
||||
}
|
||||
/>
|
||||
<button
|
||||
className="ui-button"
|
||||
type="button"
|
||||
<Button
|
||||
disabled={pending}
|
||||
onClick={() => void continueSession()}
|
||||
>
|
||||
{pending ? "처리 중…" : recovering ? "세션 복구" : "로그인 시작"}
|
||||
</button>
|
||||
{pending
|
||||
? message("common.processing")
|
||||
: recovering
|
||||
? message("action.recoverSession")
|
||||
: message("action.signIn")}
|
||||
</Button>
|
||||
{failed ? (
|
||||
<p className="ui-terminal-error" role="alert">
|
||||
세션 작업을 완료하지 못했습니다.
|
||||
{message("shell.session.actionFailed")}
|
||||
</p>
|
||||
) : null}
|
||||
</section>
|
||||
@@ -300,10 +318,12 @@ export function AppRouter({
|
||||
[basename, buildId],
|
||||
);
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<SessionProvider>
|
||||
<RouterProvider router={router} />
|
||||
</SessionProvider>
|
||||
</ThemeProvider>
|
||||
<LocaleProvider>
|
||||
<ThemeProvider>
|
||||
<SessionProvider>
|
||||
<RouterProvider router={router} />
|
||||
</SessionProvider>
|
||||
</ThemeProvider>
|
||||
</LocaleProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user