feat(i18n): add multi-language UI support with 5 languages
- Add i18n framework with locale detection and cookie persistence - Support English, Traditional Chinese (zh-TW), Simplified Chinese (zh-CN), Japanese (ja), Korean (ko) - Add LanguageSelector component in navigation header - Create locale JSON files with full translations - Update all page components (root, user, results, history) with i18n support - Add client-side translation helpers for dynamic content - Auto-detect user's preferred language from browser settings - Bump version to v0.1.3
This commit is contained in:
parent
e792dfedf1
commit
33301033ae
21 changed files with 1205 additions and 89 deletions
|
|
@ -7,21 +7,25 @@ import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
|||
import { DownloadIcon } from "../icons/download";
|
||||
import { DeleteIcon } from "../icons/delete";
|
||||
import { EyeIcon } from "../icons/eye";
|
||||
import { type SupportedLocale, type Translator, createTranslator, defaultLocale } from "../i18n";
|
||||
import { localeService } from "../i18n/service";
|
||||
import { userService } from "./user";
|
||||
|
||||
function ResultsArticle({
|
||||
job,
|
||||
files,
|
||||
outputPath,
|
||||
t = createTranslator(defaultLocale),
|
||||
}: {
|
||||
job: Jobs;
|
||||
files: Filename[];
|
||||
outputPath: string;
|
||||
t?: Translator;
|
||||
}) {
|
||||
return (
|
||||
<article class="article">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-xl">Results</h1>
|
||||
<h1 class="text-xl">{t("results", "title")}</h1>
|
||||
<div class="flex flex-row gap-4">
|
||||
<a
|
||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||
|
|
@ -29,7 +33,7 @@ function ResultsArticle({
|
|||
href={`${WEBROOT}/delete/${job.id}`}
|
||||
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
|
||||
>
|
||||
<DeleteIcon /> <p>Delete</p>
|
||||
<DeleteIcon /> <p>{t("common", "delete")}</p>
|
||||
</a>
|
||||
<a
|
||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||
|
|
@ -38,10 +42,10 @@ function ResultsArticle({
|
|||
class="flex btn-primary flex-row gap-2 text-contrast"
|
||||
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
|
||||
>
|
||||
<DownloadIcon /> <p>Tar</p>
|
||||
<DownloadIcon /> <p>{t("results", "downloadTar")}</p>
|
||||
</a>
|
||||
<button class="flex btn-primary flex-row gap-2 text-contrast" onclick="downloadAll()">
|
||||
<DownloadIcon /> <p>All</p>
|
||||
<DownloadIcon /> <p>{t("results", "downloadAll")}</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -72,7 +76,7 @@ function ResultsArticle({
|
|||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Converted File Name
|
||||
{t("results", "convertedFileName")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
|
|
@ -80,7 +84,7 @@ function ResultsArticle({
|
|||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Status
|
||||
{t("results", "status")}
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
|
|
@ -88,7 +92,7 @@ function ResultsArticle({
|
|||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Actions
|
||||
{t("results", "actions")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -130,9 +134,10 @@ function ResultsArticle({
|
|||
|
||||
export const results = new Elysia()
|
||||
.use(userService)
|
||||
.use(localeService)
|
||||
.get(
|
||||
"/results/:jobId",
|
||||
async ({ params, set, cookie: { job_id }, user }) => {
|
||||
async ({ params, set, cookie: { job_id }, user, locale, t }) => {
|
||||
if (job_id?.value) {
|
||||
// Clear the job_id cookie since we are viewing the results
|
||||
job_id.remove();
|
||||
|
|
@ -146,7 +151,7 @@ export const results = new Elysia()
|
|||
if (!job) {
|
||||
set.status = 404;
|
||||
return {
|
||||
message: "Job not found.",
|
||||
message: t("errors", "jobNotFound"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -158,16 +163,16 @@ export const results = new Elysia()
|
|||
.all(params.jobId);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Result">
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Result" locale={locale}>
|
||||
<>
|
||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn locale={locale} t={t} />
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<ResultsArticle job={job} files={files} outputPath={outputPath} />
|
||||
<ResultsArticle job={job} files={files} outputPath={outputPath} t={t} />
|
||||
</main>
|
||||
<script src={`${WEBROOT}/results.js`} defer />
|
||||
</>
|
||||
|
|
@ -178,7 +183,7 @@ export const results = new Elysia()
|
|||
)
|
||||
.post(
|
||||
"/progress/:jobId",
|
||||
async ({ set, params, cookie: { job_id }, user }) => {
|
||||
async ({ set, params, cookie: { job_id }, user, t }) => {
|
||||
if (job_id?.value) {
|
||||
// Clear the job_id cookie since we are viewing the results
|
||||
job_id.remove();
|
||||
|
|
@ -192,7 +197,7 @@ export const results = new Elysia()
|
|||
if (!job) {
|
||||
set.status = 404;
|
||||
return {
|
||||
message: "Job not found.",
|
||||
message: t("errors", "jobNotFound"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +208,7 @@ export const results = new Elysia()
|
|||
.as(Filename)
|
||||
.all(params.jobId);
|
||||
|
||||
return <ResultsArticle job={job} files={files} outputPath={outputPath} />;
|
||||
return <ResultsArticle job={job} files={files} outputPath={outputPath} t={t} />;
|
||||
},
|
||||
{ auth: true },
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue