From 192546a02aa6c842b943c7b2cb732d1e4982f040 Mon Sep 17 00:00:00 2001 From: mortezaei Date: Sat, 22 Aug 2026 18:58:23 +0330 Subject: [PATCH] fix(question-file): add full multi-language i18n support and fix border interaction states --- src/components/Componentes/question-file.tsx | 251 ++++++++++++++----- 1 file changed, 187 insertions(+), 64 deletions(-) diff --git a/src/components/Componentes/question-file.tsx b/src/components/Componentes/question-file.tsx index f9e6fbe..b40521c 100644 --- a/src/components/Componentes/question-file.tsx +++ b/src/components/Componentes/question-file.tsx @@ -9,6 +9,7 @@ import { } from "@/hooks/marriage/use-upload-tmp-media"; import { getApiRequestUrl } from "@/lib/http"; import { isInFlutterWebView, uploadFile } from "@/lib/webview-actions"; +import { useI18n } from "@/translations/provider"; import { useQuestionAnswers } from "./question-answer-storage"; import QuestionTitle from "./question-title"; import { LoadingSkeleton } from "./loading-skeleton"; @@ -18,6 +19,107 @@ type QuestionFileProps = { disabled?: boolean; }; +const FILE_I18N: Record< + string, + { + uploadPrompt: string; + addMore: string; + uploading: string; + remove: string; + uploadFailed: string; + defaultDoc: string; + } +> = { + fa: { + uploadPrompt: "بارگذاری مدارک (کارت شناسایی، پاسپورت و...)", + addMore: "افزودن مدرک دیگر", + uploading: "در حال آپلود در تمپ...", + remove: "حذف مدرک", + uploadFailed: "خطا در آپلود. لطفاً دوباره تلاش کنید.", + defaultDoc: "مدرک", + }, + ar: { + uploadPrompt: "تحميل المستندات (بطاقة الهوية، جواز السفر...)", + addMore: "إضافة مستند آخر", + uploading: "جاري التحميل...", + remove: "حذف المستند", + uploadFailed: "فشل التحميل. يرجى المحاولة مرة أخرى.", + defaultDoc: "مستند", + }, + en: { + uploadPrompt: "Upload documents (ID card, passport...)", + addMore: "Add another document", + uploading: "Uploading to temporary storage...", + remove: "Remove document", + uploadFailed: "Upload failed. Please try again.", + defaultDoc: "document", + }, + tr: { + uploadPrompt: "Belgeleri yükleyin (Kimlik kartı, pasaport...)", + addMore: "Başka bir belge ekle", + uploading: "Yükleniyor...", + remove: "Belgeyi kaldır", + uploadFailed: "Yükleme başarısız oldu. Lütfen tekrar deneyin.", + defaultDoc: "belge", + }, + ur: { + uploadPrompt: "دستاویزات اپ لوڈ کریں (شناختی کارڈ، پاسپورٹ...)", + addMore: "ایک اور دستاویز شامل کریں", + uploading: "اپ لوڈ ہو رہا ہے...", + remove: "دستاویز حذف کریں", + uploadFailed: "اپ لوڈ ناکام ہو گیا۔ براہ کرم دوبارہ کوشش کریں۔", + defaultDoc: "دستاویز", + }, + ru: { + uploadPrompt: "Загрузить документы (удостоверение личности, паспорт...)", + addMore: "Добавить еще документ", + uploading: "Загрузка...", + remove: "Удалить документ", + uploadFailed: "Ошибка загрузки. Пожалуйста, попробуйте снова.", + defaultDoc: "документ", + }, + de: { + uploadPrompt: "Dokumente hochladen (Personalausweis, Reisepass...)", + addMore: "Weitere Datei hinzufügen", + uploading: "Wird hochgeladen...", + remove: "Dokument entfernen", + uploadFailed: "Upload fehlgeschlagen. Bitte erneut versuchen.", + defaultDoc: "Dokument", + }, + fr: { + uploadPrompt: "Télécharger des documents (carte d'identité, passeport...)", + addMore: "Ajouter un autre document", + uploading: "Téléchargement en cours...", + remove: "Supprimer le document", + uploadFailed: "Échec du téléchargement. Veuillez réessayer.", + defaultDoc: "document", + }, + es: { + uploadPrompt: "Subir documentos (DNI, pasaporte...)", + addMore: "Agregar otro documento", + uploading: "Subiendo...", + remove: "Eliminar documento", + uploadFailed: "Error al subir. Inténtalo de nuevo.", + defaultDoc: "documento", + }, + zh: { + uploadPrompt: "上传证件(身份证、护照等)", + addMore: "添加其他文件", + uploading: "上传中...", + remove: "删除文件", + uploadFailed: "上传失败,请重试。", + defaultDoc: "文件", + }, + id: { + uploadPrompt: "Unggah dokumen (KTP, paspor...)", + addMore: "Tambah dokumen lain", + uploading: "Mengunggah...", + remove: "Hapus dokumen", + uploadFailed: "Unggah gagal. Silakan coba lagi.", + defaultDoc: "dokumen", + }, +}; + /** Map question file extensions to upload_file mediaType. */ function resolveMediaType( extensions: string[], @@ -69,56 +171,64 @@ export function QuestionFile({ question, disabled, }: QuestionFileProps) { + const { locale } = useI18n(); + const t = + FILE_I18N[locale] ?? FILE_I18N[locale?.slice(0, 2)] ?? FILE_I18N.fa; + const { getAnswerValue, setAnswerValue } = useQuestionAnswers(); const storedValue = getAnswerValue(question); - const parseInitialDocs = useCallback((stored: unknown): UploadedDoc[] => { - if (!stored) return []; - if (Array.isArray(stored)) { - return stored - .filter((item): item is string | Record => Boolean(item)) - .map((item) => { - if (typeof item === "string") { + const parseInitialDocs = useCallback( + (stored: unknown): UploadedDoc[] => { + if (!stored) return []; + if (Array.isArray(stored)) { + return stored + .filter((item): item is string | Record => Boolean(item)) + .map((item) => { + if (typeof item === "string") { + const resolvedUrl = + item.startsWith("http") || + item.startsWith("blob:") || + item.startsWith("data:") + ? item + : getApiRequestUrl(item); + return { + url: item, + previewUrl: resolvedUrl, + name: item.split("/").pop() || t.defaultDoc, + }; + } + const url = (item.url || item.path || "") as string; + const name = + (item.name || url.split("/").pop() || t.defaultDoc) as string; const resolvedUrl = - item.startsWith("http") || - item.startsWith("blob:") || - item.startsWith("data:") - ? item - : getApiRequestUrl(item); - return { - url: item, - previewUrl: resolvedUrl, - name: item.split("/").pop() || "document", - }; - } - const url = (item.url || item.path || "") as string; - const name = (item.name || url.split("/").pop() || "document") as string; - const resolvedUrl = - url.startsWith("http") || - url.startsWith("blob:") || - url.startsWith("data:") - ? url - : getApiRequestUrl(url); - return { url, name, previewUrl: resolvedUrl }; - }); - } - if (typeof stored === "string" && stored.trim().length > 0) { - const resolvedUrl = - stored.startsWith("http") || - stored.startsWith("blob:") || - stored.startsWith("data:") - ? stored - : getApiRequestUrl(stored); - return [ - { - url: stored, - name: stored.split("/").pop() || "document", - previewUrl: resolvedUrl, - }, - ]; - } - return []; - }, []); + url.startsWith("http") || + url.startsWith("blob:") || + url.startsWith("data:") + ? url + : getApiRequestUrl(url); + return { url, name, previewUrl: resolvedUrl }; + }); + } + if (typeof stored === "string" && stored.trim().length > 0) { + const resolvedUrl = + stored.startsWith("http") || + stored.startsWith("blob:") || + stored.startsWith("data:") + ? stored + : getApiRequestUrl(stored); + return [ + { + url: stored, + name: stored.split("/").pop() || t.defaultDoc, + previewUrl: resolvedUrl, + }, + ]; + } + return []; + }, + [t.defaultDoc], + ); const [uploadedDocs, setUploadedDocs] = useState(() => parseInitialDocs(storedValue), @@ -139,7 +249,7 @@ export function QuestionFile({ : getApiRequestUrl(response.path); const newDoc: UploadedDoc = { url: response.path, - name: response.name ?? response.path.split("/").pop() ?? "uploaded", + name: response.name ?? response.path.split("/").pop() ?? t.defaultDoc, previewUrl: resolved, }; setUploadedDocs((prev) => { @@ -198,7 +308,7 @@ export function QuestionFile({ : getApiRequestUrl(remoteUrl); newDocs.push({ url: remoteUrl, - name: f?.name || remoteUrl.split("/").pop() || "document", + name: f?.name || remoteUrl.split("/").pop() || t.defaultDoc, previewUrl: resolved, }); } else if (f?.base64) { @@ -239,7 +349,7 @@ export function QuestionFile({ return () => { unsubscribe?.(); }; - }, [question, setAnswerValue, uploadTmpMediaMutation]); + }, [question, setAnswerValue, t.defaultDoc, uploadTmpMediaMutation]); /** Handle file pick in Flutter WebView via upload_file action. */ const handleFlutterPick = useCallback(() => { @@ -363,7 +473,22 @@ export function QuestionFile({ )}
handleTriggerPick() : undefined} + onKeyDown={ + !hasDocuments + ? (e) => { + if (e.key === "Enter" || e.key === " ") handleTriggerPick(); + } + : undefined + } + className={[ + "relative flex w-full flex-col items-center justify-center rounded-[29px] border-2 border-dashed border-[#8D8D8D] bg-[#F7F7F7] text-center transition-all duration-200 focus-within:outline-2 focus-within:outline-offset-4 focus-within:outline-[#6F6F6F] hover:border-[#777777] active:border-[#111111] overflow-hidden p-4", + hasDocuments + ? "min-h-[156px]" + : "aspect-[727/330] min-h-[156px] cursor-pointer", + ].join(" ")} > {hasDocuments ? ( /* ────── UPLOADED MULTI-DOCUMENT STATE ────── */ @@ -415,7 +540,7 @@ export function QuestionFile({
) : ( /* ────── DEFAULT EMPTY STATE ────── */ -
handleTriggerPick()} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") handleTriggerPick(); - }} - className="flex w-full cursor-pointer flex-col items-center justify-center py-4" - > +
Upload - بارگذاری مدارک (کارت شناسایی، پاسپورت و...) + {t.uploadPrompt} - {acceptedFiles ? ( + {uploadTmpMediaMutation.isError ? ( + + {t.uploadFailed} + + ) : acceptedFiles ? ( {acceptedFiles} @@ -491,7 +612,9 @@ export function QuestionFile({ {isPending && (
- در حال آپلود در تمپ... + + {t.uploading} +
)}