Skip to content

Commit 04eefa2

Browse files
committed
feat: add i18n
1 parent 4586348 commit 04eefa2

17 files changed

+671
-111
lines changed

package-lock.json

Lines changed: 83 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
},
1111
"dependencies": {
1212
"@simplepdf/react-embed-pdf": "^1.9.0",
13+
"i18next": "^25.6.0",
1314
"onnxruntime-web": "^1.23.0",
1415
"pdf-lib": "^1.17.1",
1516
"pdfjs-dist": "^5.4.296",
1617
"react": "^19.2.0",
17-
"react-dom": "^19.2.0"
18+
"react-dom": "^19.2.0",
19+
"react-i18next": "^16.1.0"
1820
},
1921
"devDependencies": {
2022
"@tailwindcss/postcss": "^4.1.14",

src/FormFieldsDetection.tsx

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useRef } from "react";
2+
import { useTranslation } from "react-i18next";
23
import * as ort from "onnxruntime-web";
34
import * as pdfjsLib from "pdfjs-dist";
45
import { detectFormFields } from "./lib/formFieldDetection";
@@ -45,6 +46,7 @@ interface PdfFileState {
4546
}
4647

4748
export function FormFieldsDetection() {
49+
const { t } = useTranslation();
4850
const [pdfFile, setPdfFile] = useState<PdfFileState | null>(null);
4951
const [modelConfiguration, setModelConfiguration] =
5052
useState<ModelConfiguration>({
@@ -62,17 +64,41 @@ export function FormFieldsDetection() {
6264
const file = event.target.files?.[0];
6365

6466
if (!file || file.type !== "application/pdf") {
65-
setStatus({ type: "error", message: "Please select a valid PDF file" });
67+
setStatus({ type: "error", message: t("errors.invalidPdfFile") });
6668
return;
6769
}
6870

6971
const validationResult = await ensureValidPDF(file);
7072

7173
if (!validationResult.success) {
72-
setStatus({
73-
type: "error",
74-
message: validationResult.error.message,
75-
});
74+
const errorCode = validationResult.error.code;
75+
76+
if (errorCode === "pdf_encrypted_or_malformed") {
77+
setStatus({
78+
type: "error",
79+
message: (
80+
<>
81+
{t("errors.pdfEncryptedOrMalformed")}{" "}
82+
<a
83+
href="https://tools.pdf24.org/en/pdf-to-pdfa"
84+
target="_blank"
85+
rel="noopener noreferrer"
86+
className="underline hover:text-red-900"
87+
>
88+
tools.pdf24.org
89+
</a>{" "}
90+
{t("errors.pdfEncryptedNotAffiliated")}
91+
</>
92+
),
93+
});
94+
} else {
95+
setStatus({
96+
type: "error",
97+
message: t("errors.pdfProcessingFailed", {
98+
errorMessage: validationResult.error.errorMessage || "Unknown error",
99+
}),
100+
});
101+
}
76102
return;
77103
}
78104

@@ -84,7 +110,9 @@ export function FormFieldsDetection() {
84110
if (validationResult.data.warning) {
85111
setStatus({
86112
type: "warning",
87-
message: validationResult.data.warning.message,
113+
message: t("warnings.pdfHasAcrofields", {
114+
count: validationResult.data.warning.fieldsCount,
115+
}),
88116
});
89117
} else {
90118
setStatus({ type: "idle" });
@@ -102,20 +130,38 @@ export function FormFieldsDetection() {
102130
pdfFile: pdfFile.file,
103131
modelPath: MODEL_URLS[modelConfiguration.selectedModel],
104132
confidenceThreshold: modelConfiguration.confidenceThreshold,
105-
onUpdateDetectionStatus: (message) => {
106-
setStatus({ type: "loading", message });
133+
onUpdateDetectionStatus: (status) => {
134+
const translatedMessage = ((): string => {
135+
switch (status.type) {
136+
case "loading_pdf":
137+
return t("statusMessages.loadingPdf");
138+
case "running_detection":
139+
return t("statusMessages.runningDetection", {
140+
modelName: status.modelName,
141+
});
142+
case "processing_page":
143+
return t("statusMessages.processingPage", {
144+
current: status.current,
145+
total: status.total,
146+
});
147+
}
148+
})();
149+
150+
setStatus({ type: "loading", message: translatedMessage });
107151
},
108152
});
109153

110154
if (!detectionResult.success) {
111155
setStatus({
112156
type: "error",
113-
message: detectionResult.error.message,
157+
message: t("errors.detectionFailed", {
158+
errorMessage: detectionResult.error.message,
159+
}),
114160
});
115161
return;
116162
}
117163

118-
setStatus({ type: "loading", message: "Applying AcroFields to PDF..." });
164+
setStatus({ type: "loading", message: t("statusMessages.applyingAcroFields") });
119165

120166
const acroFieldsResult = await applyAcroFields({
121167
pdfFile: pdfFile.file,
@@ -126,7 +172,9 @@ export function FormFieldsDetection() {
126172
if (!acroFieldsResult.success) {
127173
setStatus({
128174
type: "error",
129-
message: acroFieldsResult.error.message,
175+
message: t("errors.acroFieldsFailed", {
176+
errorMessage: acroFieldsResult.error.message,
177+
}),
130178
});
131179
return;
132180
}

src/components/DetectionResults.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState } from "react";
2+
import { useTranslation } from "react-i18next";
23

34
interface DetectedField {
45
type: string;
@@ -24,6 +25,7 @@ interface DetectionResultsProps {
2425
}
2526

2627
export function DetectionResults({ result }: DetectionResultsProps) {
28+
const { t } = useTranslation();
2729
const [currentPageIndex, setCurrentPageIndex] = useState(0);
2830

2931
if (!result) {
@@ -49,7 +51,7 @@ export function DetectionResults({ result }: DetectionResultsProps) {
4951
{/* Visualization */}
5052
<div className="col-span-1 md:col-span-3">
5153
<h2 className="text-xl md:text-2xl font-bold text-gray-900 mb-4">
52-
Detected Form Fields
54+
{t("detectionResults.detectedFormFields")}
5355
</h2>
5456
<img
5557
src={currentPage.imageData}
@@ -98,7 +100,7 @@ export function DetectionResults({ result }: DetectionResultsProps) {
98100
99101
</button>
100102
<span className="text-sm font-medium">
101-
Page {currentPageIndex + 1} of {result.pages.length}
103+
{t("detectionResults.pageOfTotal", { current: currentPageIndex + 1, total: result.pages.length })}
102104
</span>
103105
<button
104106
onClick={handleNextPage}
@@ -118,26 +120,26 @@ export function DetectionResults({ result }: DetectionResultsProps) {
118120

119121
{/* Statistics */}
120122
<div className="col-span-1 md:col-span-1">
121-
<h2 className="text-xl md:text-2xl font-bold text-gray-900 mb-4">Statistics</h2>
123+
<h2 className="text-xl md:text-2xl font-bold text-gray-900 mb-4">{t("detectionResults.statistics")}</h2>
122124
<div className="bg-gray-50 rounded-lg p-4 md:p-6 space-y-3 md:space-y-4">
123125
<div className="flex justify-between">
124-
<span className="text-gray-600">Total Pages:</span>
126+
<span className="text-gray-600">{t("detectionResults.totalPages")}</span>
125127
<span className="font-semibold">{result.pages.length}</span>
126128
</div>
127129
<div className="flex justify-between">
128-
<span className="text-gray-600">Confidence Threshold:</span>
130+
<span className="text-gray-600">{t("detectionResults.confidenceThresholdLabel")}</span>
129131
<span className="font-semibold">{(result.confidenceThreshold * 100).toFixed(0)}%</span>
130132
</div>
131133
<div className="flex justify-between">
132-
<span className="text-gray-600">Fields Detected:</span>
134+
<span className="text-gray-600">{t("detectionResults.fieldsDetected")}</span>
133135
<span className="font-semibold">{totalFields}</span>
134136
</div>
135137
<div className="flex justify-between">
136-
<span className="text-gray-600">Current Page:</span>
138+
<span className="text-gray-600">{t("detectionResults.currentPage")}</span>
137139
<span className="font-semibold">{currentPage.fields.length}</span>
138140
</div>
139141
<div className="flex justify-between">
140-
<span className="text-gray-600">Processing Time:</span>
142+
<span className="text-gray-600">{t("detectionResults.processingTime")}</span>
141143
<span className="font-semibold text-emerald-600">
142144
{result.processingTime.toFixed(0)}ms
143145
</span>

0 commit comments

Comments
 (0)