const translations = {
en: {
pageTitle: 'News Verification',
loginTitle: 'Sign in',
loginSubtitle: 'Use one of the sample accounts to continue.',
loginContact:
'Please contact quoc-nguyen@nict.go.jp for login information.',
usernameLabel: 'Username',
passwordLabel: 'Password',
usernamePlaceholder: 'Enter your username',
passwordPlaceholder: 'Enter your password',
loginButton: 'Continue',
loginError: 'Incorrect credentials. Please try again.',
fakeAccountsLabel: 'Sample accounts',
logoutButton: 'Log out',
languageLabel: 'Select Language',
languageJapanese: '日本語',
languageEnglish: 'English',
languageVietnamese: 'Tiếng Việt',
uploadMediaLabel: 'Upload media files',
uploadHint:
'Click here to upload your files or drag.',
supportedFormats:
'Supported Format: PNG, JPG, JPEG, GIF, WEBP, SVG, MOV, MP4',
additionalInformation: 'Additional Information',
socialMediaPostUrl: 'Social Media Post URL',
titleNewsArticle: 'Title of the News Article',
locationLabel: 'Location where the news took place',
categoryLabel: 'Category of the News (e.g., Politics, Health, Technology)',
violenceLevelLabel: 'Violence Level',
placeholderAdditionalText: 'Enter text here...',
placeholderSocialUrl: 'Enter URL here...',
placeholderNewsTitle: 'Enter title here...',
placeholderLocation: 'Enter location here...',
placeholderCategory: 'Enter category here...',
placeholderViolence: 'Enter violence level here...',
clearButton: 'Clear',
submitButton: 'Submit',
processingLabel: 'Processing...',
saveButton: 'Save',
caseSummaryTab: 'Case Summary',
contentClassificationTab: 'Content Classification',
verifiedEvidenceTab: 'Verified Evidence',
forensicAnalysisTab: 'Forensic Analysis',
otherEvidenceTab: 'Other Evidence & Findings',
sourceDetailsTab: 'Source Details',
whereTab: 'Where',
whenTab: 'When',
whoTab: 'Who',
whyTab: 'Why',
verificationPlaceholder: 'Verification result comes here...',
uploadRequirement: 'Please upload at least one media file',
languageChangeConfirmTitle: 'Change language?',
languageChangeConfirmMessage:
'Changing the language will reset the system to its initial state. Do you want to continue?',
languageChangeConfirm: 'Confirm',
languageChangeCancel: 'Cancel',
processingError:
'The server is currently experiencing high traffic. Please try again later.',
allowedFormats: 'Only the following formats are accepted: ',
noDataToSave: 'No data to save.',
downloadPrepareFailedNetwork:
'Unable to prepare the download. Please check your connection and try again.',
downloadPrepareFailed:
'Unable to prepare the download. Please try again later.',
notificationTitle: 'Notification',
movNotSupported: 'The system does not support preview for .mov files.',
videoTagNotSupported: 'Your browser does not support the video tag.',
previewUnavailable: 'Preview not available for this file type.',
imageLabel: 'Image',
feedbackHeader: 'Help us improve!',
sectionGeneralQuestions: '1. General Questions',
q0Title: '1.1 What is your profession?',
q0Option1: 'Journalist / Fact Checker',
q0Option2: 'Researcher / Student',
q0Option3: 'Law Enforcement / OSINT',
q0Option4: 'General User',
q1Title: '1.2 How accurate was the conclusion?',
q1Option1: 'Spot on',
q1Option2: 'Mostly correct',
q1Option3: 'Mixed results',
q1Option4: 'Missed key context',
q1Option5: 'Completely wrong',
q2Title: '1.3 Did the algorithm correctly classify this content?',
q2Option2: 'No - It was actually Real',
q2Option3: 'No - It was actually AI-Generated',
yesOption: 'Yes',
q3Title: '1.4 Were the provided links relevant?',
q3Option1: 'All links worked and were relevant',
q3Option2: 'Some links were broken/dead',
q3Option3: 'Links were unrelated to the topic',
q4Title: '1.5 Did the report contain any hallucinations (made-up facts)?',
q4Option1: 'No issues',
q4Option2: 'Invented dates/times',
q4Option3: 'Invented people/names',
q4Option4: 'Citations that do not exist',
sectionDetailedQuestions: '2. Detailed Questions',
q5Title:
'2.1 Did the app clearly identify the main people, organizations, or sources involved in the news item?',
veryClear: 'Very Clear',
clearOption: 'Clear',
moderatelyClear: 'Moderately Clear',
slightlyClear: 'Slightly Clear',
notClear: 'Not Clear',
q6Title:
'2.2 Did the app correctly identify the central claim or action of the news story?',
partially: 'Partially',
noOption: 'No',
q7Title:
'2.3 Did the app clearly show when the original event/information was first published or occurred?',
q8Title:
'2.4 Was the location of the event ("Where") specified and verified by the app\'s output?',
notApplicable: 'Not Applicable',
q9Title:
'2.5 How well did the app explain the motive or purpose behind the original claim (e.g., persuasion, profit, error)?',
clearlyExplained: 'Clearly Explained',
wellExplained: 'Well Explained',
moderatelyExplained: 'Moderately Explained',
poorlyExplained: 'Poorly Explained',
notExplained: 'Not Explained',
q10Title:
'2.6 Help us improve. If the verification failed, what did we miss?',
q10Placeholder: 'Share any details we missed...',
sendButton: 'Send',
sendingLabel: 'Sending...',
feedbackSuccess: 'Feedback sent successfully!',
liveChatText: 'Send your feedback',
statusHeaderCompleted: '{completed}/{total} completed',
statusPercent: '{percent}%',
stepTitle: 'Step {step}:',
waitingLabel: 'Waiting',
calculatingLabel: 'Calculating...',
minutesRemaining: '{minutes} minutes remaining.',
processingEstimate: 'Processing - Estimated time: {minutes} minutes.',
beginsAfterStep1:
'Begins after Step 1 is completed (Estimated time: {minutes} minutes).',
completedLabel: 'Completed',
serviceLabel: 'Service {index}/{total}: {name}',
serviceGeolocation: 'Location Analysis',
serviceTimestamp: 'Time Analysis',
serviceAIGVDetection: 'AI-Generated Content Detection',
serviceReport: 'Comprehensive Report',
statusProcessing: 'Processing',
statusCompleted: 'Completed',
statusPending: 'Pending',
},
ja: {
pageTitle: 'ニュース検証',
loginTitle: 'サインイン',
loginSubtitle: '以下のサンプルアカウントのいずれかでログインしてください。',
loginContact:
'ログイン情報については quoc-nguyen@nict.go.jp までご連絡ください。',
usernameLabel: 'ユーザー名またはメール',
passwordLabel: 'パスワード',
usernamePlaceholder: 'ユーザー名またはメールを入力',
passwordPlaceholder: 'パスワードを入力',
loginButton: '続行',
loginError: '認証情報が正しくありません。もう一度お試しください。',
fakeAccountsLabel: 'サンプルアカウント',
logoutButton: 'ログアウト',
languageLabel: '言語を選択',
languageJapanese: '日本語',
languageEnglish: 'English',
languageVietnamese: 'Tiếng Việt',
uploadMediaLabel: 'メディアファイルをアップロード',
uploadHint:
'ここをクリックしてファイルをアップロードするか、ドラッグしてください。',
supportedFormats: '対応形式: PNG, JPG, JPEG, GIF, WEBP, SVG, MOV, MP4',
additionalInformation: '追加情報',
socialMediaPostUrl: 'ソーシャルメディア投稿のURL',
titleNewsArticle: '記事のタイトル',
locationLabel: 'ニュースが起きた場所',
categoryLabel: 'ニュースのカテゴリー(例: 政治、健康、テクノロジー)',
violenceLevelLabel: '暴力レベル',
placeholderAdditionalText: 'ここに入力してください...',
placeholderSocialUrl: 'URLを入力してください...',
placeholderNewsTitle: 'タイトルを入力してください...',
placeholderLocation: '場所を入力してください...',
placeholderCategory: 'カテゴリーを入力してください...',
placeholderViolence: '暴力レベルを入力してください...',
clearButton: 'クリア',
submitButton: '送信',
processingLabel: '処理中...',
saveButton: '保存',
caseSummaryTab: '概要',
contentClassificationTab: 'コンテンツ分類',
verifiedEvidenceTab: '検証済みの証拠',
forensicAnalysisTab: 'フォレンジック分析',
otherEvidenceTab: 'その他の証拠・所見',
sourceDetailsTab: 'ソース詳細',
whereTab: 'どこ',
whenTab: 'いつ',
whoTab: 'だれ',
whyTab: 'なぜ',
verificationPlaceholder: '検証結果はここに表示されます...',
uploadRequirement: '少なくとも1つのファイルをアップロードしてください。',
languageChangeConfirmTitle: '言語を変更しますか?',
languageChangeConfirmMessage:
'言語を変更するとシステムが初期状態にリセットされます。続行しますか?',
languageChangeConfirm: '確認',
languageChangeCancel: 'キャンセル',
processingError:
'現在、サーバーが混雑しています。しばらくしてから再度お試しください。',
allowedFormats: '利用可能な形式: ',
noDataToSave: '保存するデータがありません。',
downloadPrepareFailedNetwork:
'ダウンロードの準備に失敗しました。接続を確認して再試行してください。',
downloadPrepareFailed:
'ダウンロードの準備に失敗しました。後でもう一度お試しください。',
notificationTitle: '通知',
movNotSupported: '.mov ファイルのプレビューには対応していません。',
videoTagNotSupported:
'お使いのブラウザは video タグをサポートしていません。',
previewUnavailable: 'このファイルタイプはプレビューできません。',
imageLabel: '画像',
feedbackHeader: '改善にご協力ください!',
sectionGeneralQuestions: '1. 一般的な質問',
q0Title: '1.1 ご職業は何ですか?',
q0Option1: 'ジャーナリスト / ファクトチェッカー',
q0Option2: '研究者 / 学生',
q0Option3: '法執行機関 / OSINT',
q0Option4: '一般ユーザー',
q1Title: '1.2 結論の正確さはいかがでしたか?',
q1Option1: 'ほぼ完璧',
q1Option2: 'だいたい正しい',
q1Option3: '結果が混在',
q1Option4: '重要な文脈を見落とした',
q1Option5: 'まったく違う',
q2Title: '1.3 アルゴリズムはこのコンテンツを正しく分類しましたか?',
q2Option2: 'いいえ - 実際には本物でした',
q2Option3: 'いいえ - 実際にはAI生成でした',
yesOption: 'はい',
q3Title: '1.4 提供されたリンクは有用でしたか?',
q3Option1: 'すべてのリンクが有効で関連していた',
q3Option2: '一部のリンクが無効/切れていた',
q3Option3: 'リンクがトピックと無関係だった',
q4Title: '1.5 レポートに事実と異なる内容(幻覚)はありましたか?',
q4Option1: '問題なし',
q4Option2: '日付/時間の誤り',
q4Option3: '人物名の誤り',
q4Option4: '存在しない引用/出典',
sectionDetailedQuestions: '2. 詳細な質問',
q5Title:
'2.1 ニュースに関わる主要な人物・組織・情報源を明確に示していましたか?',
veryClear: 'とても明確',
clearOption: '明確',
moderatelyClear: 'やや明確',
slightlyClear: '少し明確',
notClear: '不明瞭',
q6Title: '2.2 ストーリーの中心となる主張や行動を正しく特定していましたか?',
partially: '部分的に',
noOption: 'いいえ',
q7Title: '2.3 元の情報がいつ公開・発生したかを明確に示していましたか?',
q8Title: '2.4 イベントの場所(「どこ」)は特定・検証されていましたか?',
notApplicable: '該当なし',
q9Title:
'2.5 もとの主張の動機や目的(説得、利益、誤りなど)をどの程度説明していましたか?',
clearlyExplained: 'とても明確に説明した',
wellExplained: '十分説明した',
moderatelyExplained: 'ある程度説明した',
poorlyExplained: '説明が不十分',
notExplained: '説明なし',
q10Title:
'2.6 改善のため、検証が失敗した場合に不足していた点を教えてください。',
q10Placeholder: '見落としや改善点を入力してください...',
sendButton: '送信',
sendingLabel: '送信中...',
feedbackSuccess: 'フィードバックを送信しました!',
liveChatText: 'フィードバックを送る',
statusHeaderCompleted: '{completed}/{total} 完了',
statusPercent: '{percent}%',
stepTitle: 'ステップ {step}:',
waitingLabel: '待機中',
calculatingLabel: '計算中...',
minutesRemaining: '残り {minutes} 分。',
processingEstimate: '処理中 - 推定時間: {minutes} 分。',
beginsAfterStep1: 'ステップ1完了後に開始(推定時間: {minutes} 分)。',
completedLabel: '完了',
serviceLabel: 'サービス {index}/{total}: {name}',
serviceGeolocation: '位置情報分析',
serviceTimestamp: '時間分析',
serviceAIGVDetection: 'AI生成コンテンツ検出',
serviceReport: '総合レポート',
statusProcessing: '処理中',
statusCompleted: '完了',
statusPending: '待機中',
},
vi: {
pageTitle: 'Kiểm chứng tin tức',
loginTitle: 'Đăng nhập',
loginSubtitle: 'Dùng một trong các tài khoản mẫu để tiếp tục.',
loginContact:
'Vui lòng liên hệ quoc-nguyen@nict.go.jp để nhận thông tin đăng nhập.',
usernameLabel: 'Tên đăng nhập hoặc email',
passwordLabel: 'Mật khẩu',
usernamePlaceholder: 'Nhập tên đăng nhập hoặc email',
passwordPlaceholder: 'Nhập mật khẩu',
loginButton: 'Tiếp tục',
loginError: 'Sai thông tin đăng nhập, vui lòng thử lại.',
fakeAccountsLabel: 'Tài khoản mẫu',
logoutButton: 'Đăng xuất',
languageLabel: 'Chọn Ngôn Ngữ',
languageJapanese: '日本語',
languageEnglish: 'English',
languageVietnamese: 'Tiếng Việt',
uploadMediaLabel: 'Tải lên tệp phương tiện',
uploadHint:
'Nhấn vào đây để tải tệp lên hoặc kéo thả.',
supportedFormats:
'Định dạng hỗ trợ: PNG, JPG, JPEG, GIF, WEBP, SVG, MOV, MP4',
additionalInformation: 'Thông tin bổ sung',
socialMediaPostUrl: 'Đường dẫn bài đăng mạng xã hội',
titleNewsArticle: 'Tiêu đề bài báo',
locationLabel: 'Địa điểm xảy ra sự kiện',
categoryLabel: 'Chủ đề tin tức (ví dụ: Chính trị, Sức khỏe, Công nghệ)',
violenceLevelLabel: 'Mức độ bạo lực',
placeholderAdditionalText: 'Nhập nội dung tại đây...',
placeholderSocialUrl: 'Nhập đường dẫn tại đây...',
placeholderNewsTitle: 'Nhập tiêu đề tại đây...',
placeholderLocation: 'Nhập địa điểm tại đây...',
placeholderCategory: 'Nhập chủ đề tại đây...',
placeholderViolence: 'Nhập mức độ bạo lực tại đây...',
clearButton: 'Xóa',
submitButton: 'Gửi',
processingLabel: 'Đang xử lý...',
saveButton: 'Lưu',
caseSummaryTab: 'Tóm tắt',
contentClassificationTab: 'Phân loại nội dung',
verifiedEvidenceTab: 'Bằng chứng đã xác minh',
forensicAnalysisTab: 'Phân tích pháp chứng',
otherEvidenceTab: 'Bằng chứng & phát hiện khác',
sourceDetailsTab: 'Chi tiết nguồn',
whereTab: 'Ở đâu',
whenTab: 'Khi nào',
whoTab: 'Ai',
whyTab: 'Tại sao',
verificationPlaceholder: 'Kết quả kiểm chứng sẽ hiển thị tại đây...',
uploadRequirement: 'Vui lòng tải lên ít nhất một tệp.',
languageChangeConfirmTitle: 'Đổi ngôn ngữ?',
languageChangeConfirmMessage:
'Thay đổi ngôn ngữ sẽ đặt lại hệ thống về trạng thái ban đầu. Bạn có muốn tiếp tục?',
languageChangeConfirm: 'Xác nhận',
languageChangeCancel: 'Hủy',
processingError: 'Máy chủ hiện đang quá tải. Vui lòng thử lại sau.',
allowedFormats: 'Chỉ chấp nhận các định dạng: ',
noDataToSave: 'Không có dữ liệu để lưu.',
downloadPrepareFailedNetwork:
'Không thể chuẩn bị tệp tải xuống. Vui lòng kiểm tra kết nối và thử lại.',
downloadPrepareFailed:
'Không thể chuẩn bị tệp tải xuống. Vui lòng thử lại sau.',
notificationTitle: 'Thông báo',
movNotSupported: 'Hệ thống chưa hỗ trợ xem trước tệp .mov.',
videoTagNotSupported: 'Trình duyệt của bạn không hỗ trợ thẻ video.',
previewUnavailable: 'Không thể xem trước loại tệp này.',
imageLabel: 'Hình',
feedbackHeader: 'Giúp chúng tôi cải thiện!',
sectionGeneralQuestions: '1. Câu hỏi chung',
q0Title: '1.1 Nghề nghiệp của bạn là gì?',
q0Option1: 'Nhà báo / Kiểm chứng viên',
q0Option2: 'Nhà nghiên cứu / Sinh viên',
q0Option3: 'Cơ quan thực thi pháp luật / OSINT',
q0Option4: 'Người dùng thông thường',
q1Title: '1.2 Mức độ chính xác của kết luận?',
q1Option1: 'Rất chính xác',
q1Option2: 'Khá chính xác',
q1Option3: 'Kết quả lẫn lộn',
q1Option4: 'Thiếu ngữ cảnh quan trọng',
q1Option5: 'Hoàn toàn sai',
q2Title: '1.3 Thuật toán phân loại nội dung này có chính xác không?',
q2Option2: 'Không - Thực ra là thật',
q2Option3: 'Không - Thực ra là do AI tạo',
yesOption: 'Có',
q3Title: '1.4 Các liên kết được cung cấp có phù hợp không?',
q3Option1: 'Tất cả liên kết hoạt động và phù hợp',
q3Option2: 'Một số liên kết bị hỏng/không hoạt động',
q3Option3: 'Liên kết không liên quan chủ đề',
q4Title: '1.5 Báo cáo có thông tin bịa đặt (hallucination) không?',
q4Option1: 'Không có vấn đề',
q4Option2: 'Bịa ngày/giờ',
q4Option3: 'Bịa tên người',
q4Option4: 'Nguồn/citation không tồn tại',
sectionDetailedQuestions: '2. Câu hỏi chi tiết',
q5Title:
'2.1 Ứng dụng có xác định rõ người, tổ chức hoặc nguồn tin chính không?',
veryClear: 'Rất rõ ràng',
clearOption: 'Rõ ràng',
moderatelyClear: 'Khá rõ',
slightlyClear: 'Hơi rõ',
notClear: 'Không rõ',
q6Title:
'2.2 Ứng dụng có xác định đúng tuyên bố hoặc hành động chính của câu chuyện không?',
partially: 'Một phần',
noOption: 'Không',
q7Title:
'2.3 Ứng dụng có hiển thị rõ thời điểm sự kiện/thông tin gốc được công bố không?',
q8Title: '2.4 Địa điểm sự kiện ("Ở đâu") có được chỉ rõ và xác minh không?',
notApplicable: 'Không áp dụng',
q9Title:
'2.5 Ứng dụng giải thích động cơ/mục đích đằng sau tuyên bố gốc tốt thế nào?',
clearlyExplained: 'Giải thích rõ ràng',
wellExplained: 'Giải thích tốt',
moderatelyExplained: 'Giải thích vừa phải',
poorlyExplained: 'Giải thích kém',
notExplained: 'Không giải thích',
q10Title:
'2.6 Hãy giúp chúng tôi cải thiện. Nếu kiểm chứng sai, chúng tôi bỏ lỡ điều gì?',
q10Placeholder: 'Cho biết điều chúng tôi bỏ sót...',
sendButton: 'Gửi',
sendingLabel: 'Đang gửi...',
feedbackSuccess: 'Gửi phản hồi thành công!',
liveChatText: 'Gửi phản hồi của bạn',
statusHeaderCompleted: '{completed}/{total} đã xong',
statusPercent: '{percent}%',
stepTitle: 'Bước {step}:',
waitingLabel: 'Đang chờ',
calculatingLabel: 'Đang tính toán...',
minutesRemaining: 'Còn khoảng {minutes} phút.',
processingEstimate: 'Đang xử lý - Thời gian ước tính: {minutes} phút.',
beginsAfterStep1:
'Bắt đầu sau khi Bước 1 hoàn tất (Ước tính: {minutes} phút).',
completedLabel: 'Hoàn tất',
serviceLabel: 'Dịch vụ {index}/{total}: {name}',
serviceGeolocation: 'Phân tích vị trí',
serviceTimestamp: 'Phân tích thời gian',
serviceAIGVDetection: 'Phát hiện nội dung do AI tạo',
serviceReport: 'Báo cáo tổng hợp',
statusProcessing: 'Đang xử lý',
statusCompleted: 'Hoàn tất',
statusPending: 'Đang chờ',
},
};
let currentLanguage = 'en';
let initialized = false;
let languageChangeGuard = null;
const listeners = new Set();
export function t(key) {
const langPack = translations[currentLanguage] || translations.en;
return langPack?.[key] ?? translations.en?.[key] ?? key;
}
function applyTranslations() {
if (typeof document === 'undefined') return;
document.documentElement.lang = currentLanguage || 'en';
document.title = t('pageTitle');
const textNodes = document.querySelectorAll('[data-i18n]');
textNodes.forEach((el) => {
if (el.dataset.i18nDynamic === 'true') return;
const key = el.dataset.i18n;
const value = t(key);
if (value === undefined) return;
if (el.dataset.i18nTarget === 'html') {
el.innerHTML = value;
} else {
el.textContent = value;
}
});
const placeholders = document.querySelectorAll('[data-i18n-placeholder]');
placeholders.forEach((el) => {
const key = el.dataset.i18nPlaceholder;
const value = t(key);
if (value === undefined) return;
if ('placeholder' in el) {
el.placeholder = value;
}
});
const languageSelect = document.getElementById('language-select');
if (languageSelect) {
languageSelect.value = currentLanguage;
}
}
export function setLanguage(lang, options = {}) {
if (!translations[lang]) return;
if (
!options.force &&
typeof languageChangeGuard === 'function' &&
languageChangeGuard(lang, currentLanguage) === false
) {
return;
}
currentLanguage = lang;
localStorage.setItem('appLanguage', currentLanguage);
applyTranslations();
listeners.forEach((cb) => cb(currentLanguage));
}
export function setLanguageGuard(fn) {
languageChangeGuard = typeof fn === 'function' ? fn : null;
}
export function getCurrentLanguage() {
return currentLanguage;
}
export function bindLanguageSelector(selector) {
const selectElement =
typeof selector === 'string' ? document.querySelector(selector) : selector;
if (!selectElement) return;
selectElement.value = currentLanguage;
selectElement.addEventListener('change', (event) => {
setLanguage(event.target.value);
});
}
export function onLanguageChange(callback) {
if (typeof callback !== 'function') return () => {};
listeners.add(callback);
return () => listeners.delete(callback);
}
export function initI18n() {
if (initialized) {
applyTranslations();
return;
}
initialized = true;
const saved = localStorage.getItem('appLanguage');
if (saved && translations[saved]) {
currentLanguage = saved;
}
applyTranslations();
}