816 lines
84 KiB
JavaScript
816 lines
84 KiB
JavaScript
(() => {
|
||
"use strict";
|
||
|
||
const STORAGE_KEY = "arr:locale:v1";
|
||
const COOKIE_KEY = "arr_locale";
|
||
const DEFAULT_LOCALE = "zh-CN";
|
||
const LOCALES = {
|
||
"zh-CN": { label: "中文", intl: "zh-CN-u-ca-gregory-nu-latn" },
|
||
en: { label: "English", intl: "en-GB-u-ca-gregory-nu-latn" },
|
||
th: { label: "ไทย", intl: "th-TH-u-ca-gregory-nu-latn" },
|
||
};
|
||
|
||
// The first value is the current product copy. Keeping the source copy here
|
||
// gives the legacy static markup a safe Chinese fallback while all new
|
||
// dynamic strings use the semantic key.
|
||
const CATALOG = {
|
||
"common.language": ["语言", "Language", "ภาษา"],
|
||
"common.skip_main": ["跳到主要内容", "Skip to main content", "ข้ามไปยังเนื้อหาหลัก"],
|
||
"common.skip_login": ["跳到登录表单", "Skip to login form", "ข้ามไปยังแบบฟอร์มเข้าสู่ระบบ"],
|
||
"common.cancel": ["取消", "Cancel", "ยกเลิก"],
|
||
"common.close": ["关闭", "Close", "ปิด"],
|
||
"common.confirm": ["确认", "Confirm", "ยืนยัน"],
|
||
"common.save": ["保存", "Save", "บันทึก"],
|
||
"common.delete": ["删除", "Delete", "ลบ"],
|
||
"common.select": ["选择", "Select", "เลือก"],
|
||
"common.view": ["查看", "View", "ดู"],
|
||
"common.download": ["下载", "Download", "ดาวน์โหลด"],
|
||
"common.refresh": ["刷新", "Refresh", "รีเฟรช"],
|
||
"common.previous": ["上一页", "Previous", "ก่อนหน้า"],
|
||
"common.next": ["下一页", "Next", "ถัดไป"],
|
||
"common.status": ["状态", "Status", "สถานะ"],
|
||
"common.company": ["公司", "Company", "บริษัท"],
|
||
"common.room_type": ["房型", "Room type", "ประเภทห้อง"],
|
||
"common.quantity": ["数量", "Quantity", "จำนวน"],
|
||
"common.operation": ["操作", "Actions", "การดำเนินการ"],
|
||
"common.total": ["合计", "Total", "รวม"],
|
||
"common.details": ["详情", "Details", "รายละเอียด"],
|
||
"common.file": ["文件", "File", "ไฟล์"],
|
||
"common.submitted_at": ["提交时间", "Submitted", "เวลาส่ง"],
|
||
"common.submitted_at_bangkok": ["提交时间(曼谷)", "Submitted (Bangkok)", "เวลาส่ง (กรุงเทพฯ)"],
|
||
"common.generated_at": ["生成时间", "Generated", "เวลาสร้าง"],
|
||
"common.none": ["暂无", "None", "ไม่มีข้อมูล"],
|
||
"common.unknown": ["未知", "Unknown", "ไม่ทราบ"],
|
||
"common.not_selected": ["未选择", "Not selected", "ยังไม่ได้เลือก"],
|
||
"common.pending": ["待生成", "Pending", "รอสร้าง"],
|
||
"common.no_result": ["无结果", "No result", "ไม่มีผลลัพธ์"],
|
||
"common.not_recorded": ["未记录", "Not recorded", "ไม่ได้บันทึก"],
|
||
"common.not_available": ["未完成", "Not completed", "ยังไม่เสร็จ"],
|
||
"common.year_month_unselected": ["未选择", "Not selected", "ยังไม่ได้เลือก"],
|
||
"common.loading": ["正在读取…", "Loading…", "กำลังโหลด…"],
|
||
"common.reading": ["正在读取", "Reading", "กำลังอ่าน"],
|
||
"common.retrying": ["重试中", "Retrying", "กำลังลองอีกครั้ง"],
|
||
"common.choose_file": ["选择文件", "Choose file", "เลือกไฟล์"],
|
||
"common.download_short": ["下载", "Download", "ดาวน์โหลด"],
|
||
"common.select_month": ["月份", "Month", "เดือน"],
|
||
"history.business_month": ["业务月份", "Business month", "เดือนธุรกิจ"],
|
||
"history.view_month": ["查看月份", "View month", "เดือนที่ดู"],
|
||
"history.generation_month": ["生成月份", "Generation month", "เดือนที่สร้าง"],
|
||
"history.generation_month_title": ["选择 C/O 所属的生成月份", "Choose the generation month for C/O", "เลือกเดือนที่สร้างสำหรับ C/O"],
|
||
"history.previous_month": ["上一个月", "Previous month", "เดือนก่อนหน้า"],
|
||
"history.next_month": ["下一个月", "Next month", "เดือนถัดไป"],
|
||
"history.current_month": ["本月", "This month", "เดือนนี้"],
|
||
"common.locale_zh": ["中文", "Chinese", "ภาษาจีน"],
|
||
"nav.primary": ["主要模块", "Main modules", "โมดูลหลัก"],
|
||
"nav.daily": ["日报处理", "Daily processing", "ประมวลผลรายวัน"],
|
||
"nav.monthly": ["月报", "Monthly report", "รายงานประจำเดือน"],
|
||
"nav.bi": ["渠道BI", "Channel BI", "Channel BI"],
|
||
"nav.company_detail": ["公司渠道明细", "Company channel detail", "รายละเอียดช่องทางบริษัท"],
|
||
"brand.home": ["ARR Report 首页", "ARR Report home", "หน้าหลัก ARR Report"],
|
||
"status.connecting_service": ["正在连接数据服务", "Connecting to data service", "กำลังเชื่อมต่อบริการข้อมูล"],
|
||
"status.service_ready": ["服务正常", "Service ready", "บริการพร้อมใช้งาน"],
|
||
"status.service_disconnected": ["数据服务未连接", "Data service unavailable", "บริการข้อมูลไม่พร้อมใช้งาน"],
|
||
"status.connecting": ["正在连接", "Connecting", "กำลังเชื่อมต่อ"],
|
||
"status.received": ["已接收", "Received", "รับแล้ว"],
|
||
"status.uploaded": ["已上传", "Uploaded", "อัปโหลดแล้ว"],
|
||
"status.waiting_processing": ["等待处理", "Waiting for processing", "รอประมวลผล"],
|
||
"status.processing": ["处理中", "Processing", "กำลังประมวลผล"],
|
||
"status.waiting_validation": ["等待验收", "Waiting for validation", "รอตรวจสอบผล"],
|
||
"status.validating": ["正在验收", "Validating", "กำลังตรวจสอบผล"],
|
||
"status.completed": ["已完成", "Completed", "เสร็จแล้ว"],
|
||
"status.validation_failed": ["验收失败", "Validation failed", "ตรวจสอบผลไม่ผ่าน"],
|
||
"status.failed": ["处理失败", "Processing failed", "ประมวลผลไม่สำเร็จ"],
|
||
"status.cancelled": ["已取消", "Cancelled", "ยกเลิกแล้ว"],
|
||
"status.generating": ["生成中", "Generating", "กำลังสร้าง"],
|
||
"status.validated": ["已验收", "Validated", "ตรวจสอบแล้ว"],
|
||
"status.current_version": ["当前版本", "Current version", "เวอร์ชันปัจจุบัน"],
|
||
"status.history_version": ["历史版本", "Historical version", "เวอร์ชันประวัติ"],
|
||
"status.generation_failed": ["生成失败", "Generation failed", "สร้างไม่สำเร็จ"],
|
||
"auth.logout": ["退出登录", "Log out", "ออกจากระบบ"],
|
||
"auth.logout_short": ["退出", "Log out", "ออก"],
|
||
"auth.logout_failed": ["退出登录失败,请重试", "Could not log out. Please try again.", "ออกจากระบบไม่สำเร็จ โปรดลองอีกครั้ง"],
|
||
"auth.session_expired": ["登录状态已失效", "Your session has expired.", "เซสชันหมดอายุแล้ว"],
|
||
"auth.session_init_failed": ["页面会话初始化失败", "Could not initialize the page session.", "เริ่มเซสชันของหน้าไม่สำเร็จ"],
|
||
"auth.username": ["username", "username", "ชื่อผู้ใช้"],
|
||
"auth.password": ["password", "password", "รหัสผ่าน"],
|
||
"auth.show": ["显示", "Show", "แสดง"],
|
||
"auth.hide": ["隐藏", "Hide", "ซ่อน"],
|
||
"auth.show_password": ["显示密码", "Show password", "แสดงรหัสผ่าน"],
|
||
"auth.hide_password": ["隐藏密码", "Hide password", "ซ่อนรหัสผ่าน"],
|
||
"auth.submit": ["登录 ARR Report", "Log in to ARR Report", "เข้าสู่ระบบ ARR Report"],
|
||
"auth.validating": ["正在验证…", "Verifying…", "กำลังตรวจสอบ…"],
|
||
"auth.unavailable": ["登录暂时不可用,请稍后重试", "Login is temporarily unavailable. Please try again later.", "การเข้าสู่ระบบไม่พร้อมใช้งานชั่วคราว โปรดลองอีกครั้งภายหลัง"],
|
||
"auth.required_username": ["请输入用户名", "Enter your username.", "กรุณากรอกชื่อผู้ใช้"],
|
||
"auth.required_password": ["请输入密码", "Enter your password.", "กรุณากรอกรหัสผ่าน"],
|
||
"time.business": ["页面日期时间统一按泰国曼谷(UTC+7)显示。", "All page dates and times use Bangkok time (UTC+7).", "วันที่และเวลาทั้งหมดใช้เวลาที่กรุงเทพฯ (UTC+7)"],
|
||
"time.business_short": ["业务时间统一按泰国曼谷(UTC+7)显示", "Business time uses Bangkok time (UTC+7)", "เวลาธุรกิจใช้เวลาที่กรุงเทพฯ (UTC+7)"],
|
||
"upload.xml.title": ["上传 ARR.XML", "Upload ARR.XML", "อัปโหลด ARR.XML"],
|
||
"upload.xml.drop": ["拖放 XML 文件到这里", "Drop an XML file here", "ลากไฟล์ XML มาที่นี่"],
|
||
"upload.xml.choose": ["或点击选择文件,单个文件最大 25 MB", "or click to choose a file; maximum 25 MB", "หรือคลิกเพื่อเลือกไฟล์ ขนาดไม่เกิน 25 MB"],
|
||
"upload.file_not_selected": ["尚未选择文件", "No file selected", "ยังไม่ได้เลือกไฟล์"],
|
||
"upload.start": ["开始处理", "Start processing", "เริ่มประมวลผล"],
|
||
"upload.progress": ["ARR.XML 处理进度", "ARR.XML processing progress", "ความคืบหน้าการประมวลผล ARR.XML"],
|
||
"upload.waiting": ["等待开始", "Waiting to start", "รอเริ่ม"],
|
||
"upload.uploading": ["正在上传 ARR.XML", "Uploading ARR.XML", "กำลังอัปโหลด ARR.XML"],
|
||
"upload.processing": ["正在处理ARR.XML文件", "Processing ARR.XML", "กำลังประมวลผล ARR.XML"],
|
||
"upload.processing_detail": ["正在安全上传、运行固定处理器、独立验收并提交数据库。", "Uploading securely, running the fixed processor, validating independently and committing to the database.", "กำลังอัปโหลดอย่างปลอดภัย เรียกใช้ตัวประมวลผล ตรวจสอบผล และบันทึกลงฐานข้อมูล"],
|
||
"upload.after_processing": ["处理完成后将立即读取服务端全流程日志…", "The server trace will be read as soon as processing finishes…", "ระบบจะอ่านบันทึกกระบวนการจากเซิร์ฟเวอร์เมื่อเสร็จสิ้น…"],
|
||
"upload.creating_task": ["正在创建任务", "Creating task", "กำลังสร้างงาน"],
|
||
"upload.processing_done": ["处理完成", "Processing complete", "ประมวลผลเสร็จแล้ว"],
|
||
"upload.processing_failed": ["处理失败", "Processing failed", "ประมวลผลไม่สำเร็จ"],
|
||
"upload.success": ["ARR.XML 已处理并完成入库", "ARR.XML was processed and committed.", "ประมวลผลและบันทึก ARR.XML แล้ว"],
|
||
"upload.failure_log": ["ARR.XML 处理失败,请查看任务日志", "ARR.XML processing failed. Check the task log.", "การประมวลผล ARR.XML ไม่สำเร็จ โปรดดูบันทึกงาน"],
|
||
"upload.service_unready": ["文件接收服务尚未完成生产接线。", "The file intake service is not ready for production.", "บริการรับไฟล์ยังไม่พร้อมใช้งานจริง"],
|
||
"upload.stage_processor": ["正在运行固定处理器", "Running the fixed processor", "กำลังเรียกใช้ตัวประมวลผล"],
|
||
"upload.stage_validate": ["正在独立验收结果", "Independently validating the result", "กำลังตรวจสอบผลอย่างอิสระ"],
|
||
"upload.stage_commit": ["正在提交数据库", "Committing to the database", "กำลังบันทึกลงฐานข้อมูล"],
|
||
"metric.arrival_date": ["最近成功处理日期", "Latest successful processing date", "วันที่ประมวลผลสำเร็จล่าสุด"],
|
||
"metric.duration": ["处理耗时", "Processing time", "เวลาประมวลผล"],
|
||
"metric.duration_from_submit": ["从提交至完成", "From submission to completion", "ตั้งแต่ส่งจนเสร็จ"],
|
||
"metric.rooms": ["保留记录的售出房数", "Sold rooms in retained records", "จำนวนห้องที่ขายในรายการที่เก็บไว้"],
|
||
"daily.history": ["Daily Report", "Daily Report", "Daily Report"],
|
||
"daily.refresh": ["刷新", "Refresh", "รีเฟรช"],
|
||
"daily.download": ["日报", "Daily report", "รายงานรายวัน"],
|
||
"daily.empty": ["本月暂无 Daily Report 记录", "No Daily Report records this month", "เดือนนี้ยังไม่มีรายการ Daily Report"],
|
||
"daily.loading": ["正在读取本月记录…", "Loading this month’s records…", "กำลังโหลดรายการของเดือนนี้…"],
|
||
"daily.unavailable": ["日报记录暂时无法读取", "Daily records are temporarily unavailable", "ไม่สามารถอ่านรายการรายวันได้ชั่วคราว"],
|
||
"daily.no_selected_log": ["请上传文件,或从下方选择一个历史任务。", "Upload a file or select a historical task below.", "อัปโหลดไฟล์หรือเลือกงานเก่าด้านล่าง"],
|
||
"daily.overview": ["日报处理概览", "Daily processing overview", "ภาพรวมการประมวลผลรายวัน"],
|
||
"daily.pagination_label": ["Daily Report 分页", "Daily Report pagination", "การแบ่งหน้า Daily Report"],
|
||
"daily.file": ["文件", "File", "ไฟล์"],
|
||
"daily.submitted_at": ["提交时间", "Submitted", "เวลาส่ง"],
|
||
"daily.output": ["日报", "Daily report", "รายงานรายวัน"],
|
||
"monthly.processing": ["月报处理", "Monthly processing", "ประมวลผลรายงานประจำเดือน"],
|
||
"monthly.auto_update": ["自动更新 · 4 秒", "Auto-refresh · 4 sec", "อัปเดตอัตโนมัติ · 4 วินาที"],
|
||
"monthly.loading": ["正在读取月报记录…", "Loading monthly report records…", "กำลังโหลดรายการรายงานประจำเดือน…"],
|
||
"monthly.empty": ["该月份暂无月报处理记录", "No monthly processing records for this month", "เดือนนี้ยังไม่มีรายการประมวลผลรายเดือน"],
|
||
"monthly.updated_to": ["更新至", "Updated through", "อัปเดตถึง"],
|
||
"monthly.rows": ["数据行", "Data rows", "แถวข้อมูล"],
|
||
"monthly.pagination_label": ["月报记录分页", "Monthly report pagination", "การแบ่งหน้ารายงานประจำเดือน"],
|
||
"monthly.unavailable": ["月报记录暂时无法读取", "Monthly records are temporarily unavailable", "ไม่สามารถอ่านรายการรายเดือนได้ชั่วคราว"],
|
||
"monthly.new": ["新月报已自动加入列表", "A new monthly report was added automatically", "เพิ่มรายงานประจำเดือนใหม่ในรายการแล้ว"],
|
||
"monthly.retry": ["自动更新重试中", "Auto-refresh retrying", "กำลังลองอัปเดตอัตโนมัติอีกครั้ง"],
|
||
"bi.title": ["渠道BI", "Channel BI", "Channel BI"],
|
||
"bi.month": ["查看月份", "Month", "เดือน"],
|
||
"bi.metrics": ["本月核心指标", "This month’s key metrics", "ตัวชี้วัดหลักของเดือนนี้"],
|
||
"bi.rooms": ["售出房数", "Rooms sold", "จำนวนห้องที่ขาย"],
|
||
"bi.revenue": ["销售金额", "Sales amount", "ยอดขาย"],
|
||
"bi.nights": ["间夜", "Room nights", "คืนห้องพัก"],
|
||
"bi.companies": ["公司数", "Companies", "จำนวนบริษัท"],
|
||
"bi.rooms_total": ["NO. OF ROOM 合计", "Total NO. OF ROOM", "รวม NO. OF ROOM"],
|
||
"bi.revenue_total": ["销售金额合计", "Total sales amount", "ยอดขายรวม"],
|
||
"bi.nights_formula": ["房数 × 入住晚数", "Rooms × nights", "ห้อง × จำนวนคืน"],
|
||
"bi.channel_count": ["月报子表数量", "Monthly report worksheet count", "จำนวนชีตรายงานประจำเดือน"],
|
||
"bi.sales_by_company": ["各公司本月销售情况", "Monthly sales by company", "ยอดขายรายเดือนของแต่ละบริษัท"],
|
||
"bi.sales_ranking": ["公司销售金额排名", "Company sales ranking", "อันดับยอดขายบริษัท"],
|
||
"bi.sales_share": ["公司销售金额占比", "Company sales share", "สัดส่วนยอดขายบริษัท"],
|
||
"bi.matrix": ["渠道 × 房型售出房数", "Rooms sold by channel × room type", "จำนวนห้องที่ขายตามช่องทาง × ประเภทห้อง"],
|
||
"bi.channel": ["渠道", "Channel", "ช่องทาง"],
|
||
"bi.empty": ["暂无渠道与房型数据", "No channel or room-type data", "ไม่มีข้อมูลช่องทางหรือประเภทห้อง"],
|
||
"bi.empty_sales": ["暂无公司销售数据", "No company sales data", "ไม่มีข้อมูลยอดขายบริษัท"],
|
||
"bi.loading": ["正在读取看板数据…", "Loading dashboard data…", "กำลังโหลดข้อมูลแดชบอร์ด…"],
|
||
"company.title": ["公司渠道明细", "Company channel detail", "รายละเอียดช่องทางบริษัท"],
|
||
"company.fixed": ["固定生成:LianTai、QBD、DY-AI-Easy-KB、FengRun、HanaTour", "Fixed scope: LianTai, QBD, DY-AI-Easy-KB, FengRun, HanaTour", "ขอบเขตคงที่: LianTai, QBD, DY-AI-Easy-KB, FengRun, HanaTour"],
|
||
"company.refresh_title": ["重新读取服务状态、Excel 来源和生成记录", "Reload service status, Excel source and job history", "โหลดสถานะบริการ แหล่ง Excel และประวัติงานใหม่"],
|
||
"company.refresh_jobs": ["刷新任务", "Refresh jobs", "รีเฟรชงาน"],
|
||
"company.service_unready": ["公司渠道明细服务暂未就绪", "Company channel detail service is not ready", "บริการรายละเอียดช่องทางบริษัทยังไม่พร้อม"],
|
||
"company.service_unready_detail": ["请检查本机数据库和 Excel 生成服务后刷新。", "Check the local database and Excel generation service, then refresh.", "ตรวจสอบฐานข้อมูลและบริการสร้าง Excel ในเครื่อง แล้วรีเฟรช"],
|
||
"company.generate": ["生成报表", "Generate reports", "สร้างรายงาน"],
|
||
"company.report_month": ["报表月份", "Report month", "เดือนรายงาน"],
|
||
"company.month_title": ["选择 C/O 所属报表月份,并筛选下方生成记录", "Choose the report month for C/O and filter the job history below", "เลือกเดือนรายงานของ C/O และกรองประวัติงานด้านล่าง"],
|
||
"company.upload_title": ["上传 Excel 报表", "Upload Excel report", "อัปโหลดรายงาน Excel"],
|
||
"company.choose_excel": ["选择或拖放 Excel 文件", "Choose or drop an Excel file", "เลือกหรือลากไฟล์ Excel"],
|
||
"company.xlsx_limit": [".xlsx 格式,单个文件最大 25 MB", ".xlsx format, maximum 25 MB", "รูปแบบ .xlsx ขนาดไม่เกิน 25 MB"],
|
||
"company.extract": ["提取并核对", "Extract and review", "แยกข้อมูลและตรวจสอบ"],
|
||
"company.checking": ["核对中", "Checking", "กำลังตรวจสอบ"],
|
||
"company.period_release_10": ["周期结束:当月 11 日 00:00(曼谷)", "Period ends: 00:00 on the 11th (Bangkok)", "รอบสิ้นสุด: 00:00 วันที่ 11 (กรุงเทพฯ)"],
|
||
"company.period_release_20": ["周期结束:当月 21 日 00:00(曼谷)", "Period ends: 00:00 on the 21st (Bangkok)", "รอบสิ้นสุด: 00:00 วันที่ 21 (กรุงเทพฯ)"],
|
||
"company.period_release_month_end": ["周期结束:次月 1 日 00:00(曼谷)", "Period ends: 00:00 on the first day of the next month (Bangkok)", "รอบสิ้นสุด: 00:00 วันที่ 1 ของเดือนถัดไป (กรุงเทพฯ)"],
|
||
"company.generate_short": ["生成", "Generate", "สร้าง"],
|
||
"company.review_title": ["Booking记录提取", "Booking record extraction", "การแยกรายการ Booking"],
|
||
"company.extracted_file": ["提取文件", "Source file", "ไฟล์ต้นทาง"],
|
||
"company.review_stats": ["提取统计", "Extraction summary", "สรุปการแยกข้อมูล"],
|
||
"company.confirmed": ["已确认", "Confirmed", "ยืนยันแล้ว"],
|
||
"company.pending_human": ["待人工", "Needs review", "รอตรวจสอบ"],
|
||
"company.deleted": ["已删除", "Deleted", "ลบแล้ว"],
|
||
"company.delete_selected": ["删除所选", "Delete selected", "ลบรายการที่เลือก"],
|
||
"company.discard": ["放弃本次提取", "Discard this extraction", "ยกเลิกการแยกข้อมูลครั้งนี้"],
|
||
"company.activate": ["确认并启用", "Confirm and activate", "ยืนยันและเปิดใช้งาน"],
|
||
"company.extract_rooms": ["提取房型记录", "Extracted room records", "รายการประเภทห้องที่แยกได้"],
|
||
"company.raw_identifier": ["原始标识", "Raw identifier", "ตัวระบุดิบ"],
|
||
"company.empty_review": ["没有可显示的提取记录。", "No extraction records to display.", "ไม่มีรายการแยกข้อมูลให้แสดง"],
|
||
"company.task_status": ["TASK STATUS", "TASK STATUS", "TASK STATUS"],
|
||
"company.preparing": ["正在准备公司渠道明细任务", "Preparing company channel detail job", "กำลังเตรียมงานรายละเอียดช่องทางบริษัท"],
|
||
"company.reading_status": ["正在读取任务状态。", "Reading job status.", "กำลังอ่านสถานะงาน"],
|
||
"company.queued": ["已排队", "Queued", "เข้าคิวแล้ว"],
|
||
"company.generating": ["生成中", "Generating", "กำลังสร้าง"],
|
||
"company.all_success": ["全部成功", "All succeeded", "สำเร็จทั้งหมด"],
|
||
"company.partial_success": ["部分成功", "Partially succeeded", "สำเร็จบางส่วน"],
|
||
"company.incomplete": ["未完成", "Incomplete", "ยังไม่เสร็จ"],
|
||
"company.waiting_generation": ["等待生成", "Waiting to generate", "รอสร้าง"],
|
||
"company.success": ["成功", "Success", "สำเร็จ"],
|
||
"company.success_review": ["成功 · 待复核", "Success · review needed", "สำเร็จ · ต้องตรวจสอบ"],
|
||
"company.failed": ["失败", "Failed", "ล้มเหลว"],
|
||
"company.not_generated": ["未生成", "Not generated", "ยังไม่สร้าง"],
|
||
"company.warning_count": ["需复核", "Needs review", "ต้องตรวจสอบ"],
|
||
"company.cutoff_10": ["截至 10 日", "Through the 10th", "ถึงวันที่ 10"],
|
||
"company.cutoff_20": ["截至 20 日", "Through the 20th", "ถึงวันที่ 20"],
|
||
"company.cutoff_month_end": ["截至月末", "Through month-end", "ถึงสิ้นเดือน"],
|
||
"company.history": ["生成记录", "Generation history", "ประวัติการสร้าง"],
|
||
"company.per_page": ["每页 50 条", "50 per page", "หน้าละ 50 รายการ"],
|
||
"company.history_loading": ["正在读取公司渠道明细任务…", "Loading company channel detail jobs…", "กำลังโหลดงานรายละเอียดช่องทางบริษัท…"],
|
||
"company.history_empty": ["该月份还没有公司渠道明细任务", "No company channel detail jobs for this month", "เดือนนี้ยังไม่มีงานรายละเอียดช่องทางบริษัท"],
|
||
"company.processing_title": ["公司渠道明细处理", "Company channel detail processing", "การประมวลผลรายละเอียดช่องทางบริษัท"],
|
||
"company.review_pagination": ["提取记录分页", "Extraction record pagination", "การแบ่งหน้ารายการแยกข้อมูล"],
|
||
"company.history_pagination": ["生成记录分页", "Generation history pagination", "การแบ่งหน้าประวัติการสร้าง"],
|
||
"company.result_caption": ["本次任务中五家公司的生成结果", "Generation results for the five companies in this job", "ผลการสร้างของห้าบริษัทในงานนี้"],
|
||
"company.select_all_page": ["选择本页全部记录", "Select all records on this page", "เลือกทุกรายการในหน้านี้"],
|
||
"company.generated": ["已生成", "Generated", "สร้างแล้ว"],
|
||
"company.as_of": ["截止日期", "As-of date", "วันที่ข้อมูล"],
|
||
"company.task_duration": ["任务耗时", "Job duration", "ระยะเวลางาน"],
|
||
"company.cutoff_period": ["截止期间", "Cutoff period", "ช่วงเวลาสิ้นสุด"],
|
||
"company.review_problem": ["复核 / 异常", "Review / issues", "ตรวจสอบ / ปัญหา"],
|
||
"company.warning_short": ["需复核", "Needs review", "ต้องตรวจสอบ"],
|
||
"company.report_default_description": ["2026年07月,01-10", "July 2026, 01-10", "กรกฎาคม 2026, 01-10"],
|
||
"company.progress_waiting": ["等待生成", "Waiting to generate", "รอสร้าง"],
|
||
"company.progress_failed": ["生成未完成", "Generation incomplete", "การสร้างไม่เสร็จ"],
|
||
"company.progress_loading_finance": ["正在读取已校验的 finance 数据", "Loading validated finance data", "กำลังโหลดข้อมูล finance ที่ตรวจสอบแล้ว"],
|
||
"company.progress_generating_excel": ["正在生成 5 家公司 Excel", "Generating Excel files for 5 companies", "กำลังสร้างไฟล์ Excel สำหรับ 5 บริษัท"],
|
||
"company.progress_complete": ["生成完成", "Generation complete", "สร้างเสร็จแล้ว"],
|
||
"company.progress_ended": ["生成已结束", "Generation ended", "การสร้างสิ้นสุดแล้ว"],
|
||
"company.job_queued_message": ["任务已进入生成队列。", "The job entered the generation queue.", "งานเข้าสู่คิวการสร้างแล้ว"],
|
||
"company.job_generating_message": ["正在按公司生成渠道明细表。", "Generating channel detail workbooks by company.", "กำลังสร้างตารางรายละเอียดช่องทางแยกตามบริษัท"],
|
||
"company.job_success_message": ["5 家公司渠道明细表已生成。", "Channel detail workbooks for 5 companies were generated.", "สร้างตารางรายละเอียดช่องทางสำหรับ 5 บริษัทแล้ว"],
|
||
"company.job_partial_message": ["部分公司已生成,失败公司可按错误代码复核后重跑。", "Some companies were generated. Review failed companies by error code and rerun.", "สร้างบางบริษัทแล้ว ตรวจสอบบริษัทที่ล้มเหลวตามรหัสข้อผิดพลาดแล้วลองใหม่"],
|
||
"company.job_execution_failed_message": ["渠道明细生成未完成;未成功发布的公司不会提供下载。", "Channel detail generation did not complete; companies not published successfully will not be available for download.", "การสร้างรายละเอียดช่องทางไม่เสร็จ บริษัทที่เผยแพร่ไม่สำเร็จจะไม่พร้อมให้ดาวน์โหลด"],
|
||
"company.discarding": ["正在放弃", "Discarding", "กำลังยกเลิก"],
|
||
"company.deleting": ["正在删除", "Deleting", "กำลังลบ"],
|
||
"company.confirm_delete_title": ["删除房型记录", "Delete room record", "ลบรายการประเภทห้อง"],
|
||
"company.confirm_delete_description": ["确认删除所选房型记录?", "Delete the selected room record?", "ยืนยันการลบรายการประเภทห้องที่เลือกหรือไม่"],
|
||
"company.confirm_discard_title": ["放弃本次提取", "Discard this extraction", "ยกเลิกการแยกข้อมูลครั้งนี้"],
|
||
"company.confirm_discard_description": ["确认放弃本次提取?", "Discard this extraction?", "ยืนยันการยกเลิกการแยกข้อมูลครั้งนี้หรือไม่"],
|
||
"company.confirm_discard_note": ["当前已做的人工修改和删除将被丢弃,原 Booking 数据源不会改变。", "Your edits and deletions will be discarded; the original Booking source will not change.", "การแก้ไขและการลบจะถูกทิ้ง แหล่งข้อมูล Booking เดิมจะไม่เปลี่ยนแปลง"],
|
||
"company.confirm_delete_note": ["此操作只影响本次提取结果,确认启用前原数据源不会改变。", "This affects only this extraction; the original source will not change until activation.", "การดำเนินการนี้มีผลเฉพาะผลการแยกครั้งนี้ แหล่งข้อมูลเดิมจะไม่เปลี่ยนจนกว่าจะเปิดใช้งาน"],
|
||
"company.confirm_delete": ["确认删除", "Confirm delete", "ยืนยันการลบ"],
|
||
"company.confirm_discard": ["确认放弃", "Confirm discard", "ยืนยันการยกเลิก"],
|
||
"company.confirm_generate": ["确认生成", "Confirm generation", "ยืนยันการสร้าง"],
|
||
"company.generate_five": ["将一次生成五家公司的正式 Excel。", "This will generate the official Excel files for five companies.", "ระบบจะสร้างไฟล์ Excel อย่างเป็นทางการสำหรับห้าบริษัท"],
|
||
"company.generate_early": ["周期尚未结束,将按当前已入库数据生成;后续新数据不会自动补入,可再次生成新版。", "The period is not complete. The current committed data will be used; later data will not be added automatically. Generate again for a new version.", "รอบยังไม่สิ้นสุด ระบบจะใช้ข้อมูลที่บันทึกแล้ว ข้อมูลใหม่จะไม่ถูกเพิ่มอัตโนมัติ สามารถสร้างเวอร์ชันใหม่ได้"],
|
||
"company.no_valid_month": ["请先选择有效的报表月份", "Choose a valid report month first", "เลือกเดือนรายงานที่ถูกต้องก่อน"],
|
||
"company.review_open": ["请先确认或放弃当前 Excel 提取结果", "Confirm or discard the current Excel extraction first", "ยืนยันหรือยกเลิกผลการแยก Excel ปัจจุบันก่อน"],
|
||
"company.source_required": ["请先提取并启用 Excel 报表", "Extract and activate the Excel report first", "แยกและเปิดใช้งานรายงาน Excel ก่อน"],
|
||
"company.service_required": ["公司渠道明细服务尚未就绪", "Company channel detail service is not ready", "บริการรายละเอียดช่องทางบริษัทยังไม่พร้อม"],
|
||
"company.future_month": ["未来报表月份暂不可生成", "Future report months cannot be generated yet", "ยังไม่สามารถสร้างเดือนรายงานในอนาคตได้"],
|
||
"company.select_month": ["选择月份", "Choose a month", "เลือกเดือน"],
|
||
"company.period_not_finished": ["周期未结束", "Period not complete", "รอบยังไม่สิ้นสุด"],
|
||
"company.period_finished": ["周期已结束", "Period complete", "รอบสิ้นสุดแล้ว"],
|
||
"company.source_error": ["当前 Excel 数据源暂时无法读取", "The current Excel source is temporarily unavailable", "ไม่สามารถอ่านแหล่ง Excel ปัจจุบันได้ชั่วคราว"],
|
||
"company.current_source": ["当前启用来源", "Active source", "แหล่งข้อมูลที่เปิดใช้งาน"],
|
||
"company.source_active": ["已启用", "Active", "เปิดใช้งานแล้ว"],
|
||
"company.source_meta": ["批次 #{batch} · {rows} 条记录 · {rooms} 间 · 启用于 {activated}", "Batch #{batch} · {rows} records · {rooms} rooms · Activated {activated}", "ชุดข้อมูล #{batch} · {rows} รายการ · {rooms} ห้อง · เปิดใช้งานเมื่อ {activated}"],
|
||
"company.source_file_missing": ["未记录文件名", "File name not recorded", "ไม่ได้บันทึกชื่อไฟล์"],
|
||
"company.source_already_active": ["这份 Excel 已是当前启用来源;请在下方按月份查看或生成对应报表", "This Excel is already the active source. Review or generate the corresponding report by month below.", "Excel นี้เป็นแหล่งข้อมูลที่เปิดใช้งานอยู่แล้ว ตรวจสอบหรือสร้างรายงานตามเดือนได้ด้านล่าง"],
|
||
"company.job_source": ["来源 Excel(提交时)", "Source Excel (at submission)", "แหล่ง Excel (ขณะส่งงาน)"],
|
||
"company.history_source": ["来源 Excel", "Source Excel", "แหล่ง Excel"],
|
||
"company.job_source_missing": ["历史任务未记录来源", "Source not recorded for this historical job", "งานย้อนหลังนี้ไม่ได้บันทึกแหล่งข้อมูล"],
|
||
"company.job_source_meta": ["批次 #{batch} · 启用于 {activated}", "Batch #{batch} · Activated {activated}", "ชุดข้อมูล #{batch} · เปิดใช้งานเมื่อ {activated}"],
|
||
"company.draft_error": ["当前提取结果暂时无法读取", "The current extraction is temporarily unavailable", "ไม่สามารถอ่านผลการแยกปัจจุบันได้ชั่วคราว"],
|
||
"company.file_xlsx": ["请选择 .xlsx 格式的 Excel 文件", "Choose an .xlsx Excel file", "เลือกไฟล์ Excel รูปแบบ .xlsx"],
|
||
"company.file_empty": ["Excel 文件为空", "The Excel file is empty", "ไฟล์ Excel ว่างเปล่า"],
|
||
"company.file_too_large": ["Excel 文件超过 25 MB", "The Excel file exceeds 25 MB", "ไฟล์ Excel มีขนาดเกิน 25 MB"],
|
||
"company.file_extract_failed": ["Excel 文件未能完成提取", "The Excel file could not be extracted", "แยกข้อมูลจากไฟล์ Excel ไม่สำเร็จ"],
|
||
"company.fill_room_type": ["请填写房型后再保存", "Enter a room type before saving", "กรอกประเภทห้องก่อนบันทึก"],
|
||
"company.quantity_invalid": ["房间数量必须是 1 至 9999 的整数", "Room quantity must be an integer from 1 to 9999", "จำนวนห้องต้องเป็นจำนวนเต็มตั้งแต่ 1 ถึง 9999"],
|
||
"company.review_saved": ["房型记录已保存", "Room record saved", "บันทึกรายการประเภทห้องแล้ว"],
|
||
"company.review_deleted": ["房型记录已删除", "Room record deleted", "ลบรายการประเภทห้องแล้ว"],
|
||
"company.review_discarded": ["本次提取已放弃,原数据源未改变", "Extraction discarded; the original source was not changed", "ยกเลิกการแยกแล้ว แหล่งข้อมูลเดิมไม่เปลี่ยนแปลง"],
|
||
"company.review_activated": ["人工核对完成,Booking 数据源已启用", "Review complete; the Booking source is active", "ตรวจสอบเสร็จแล้ว เปิดใช้งานแหล่งข้อมูล Booking"],
|
||
"company.review_discard_failed": ["本次提取未能放弃", "The extraction could not be discarded", "ยกเลิกการแยกข้อมูลไม่สำเร็จ"],
|
||
"company.review_delete_failed": ["房型记录未能删除", "The room record could not be deleted", "ลบรายการประเภทห้องไม่สำเร็จ"],
|
||
"company.review_save_failed": ["房型记录未能保存", "The room record could not be saved", "บันทึกรายการประเภทห้องไม่สำเร็จ"],
|
||
"company.source_activate_failed": ["Booking 数据源未能启用", "The Booking source could not be activated", "เปิดใช้งานแหล่งข้อมูล Booking ไม่สำเร็จ"],
|
||
"company.extract_done": ["提取完成,可以确认并启用", "Extraction complete. Confirm and activate.", "แยกข้อมูลเสร็จแล้ว ยืนยันและเปิดใช้งานได้"],
|
||
"company.extract_pending": ["提取完成,{count} 条需要人工确认", "Extraction complete. {count} records need review.", "แยกข้อมูลเสร็จแล้ว ต้องตรวจสอบ {count} รายการ"],
|
||
"company.file_name_missing": ["未记录文件名", "File name not recorded", "ไม่ได้บันทึกชื่อไฟล์"],
|
||
"company.review_pending_confirm": ["待人工确认", "Pending human review", "รอยืนยันโดยเจ้าหน้าที่"],
|
||
"company.review_save": ["保存", "Save", "บันทึก"],
|
||
"company.review_delete": ["删除", "Delete", "ลบ"],
|
||
"company.review_select": ["选择", "Select", "เลือก"],
|
||
"company.review_no_record": ["暂无", "None", "ไม่มีข้อมูล"],
|
||
"company.period_pending": ["待生成", "Pending", "รอสร้าง"],
|
||
"company.period_waiting": ["等待生成", "Waiting to generate", "รอสร้าง"],
|
||
"company.period_none": ["无结果", "No result", "ไม่มีผลลัพธ์"],
|
||
"company.period_not_generated": ["未生成", "Not generated", "ยังไม่สร้าง"],
|
||
"company.period_not_recorded": ["未记录", "Not recorded", "ไม่ได้บันทึก"],
|
||
"company.report_prepare": ["正在准备公司渠道明细任务", "Preparing company channel detail job", "กำลังเตรียมงานรายละเอียดช่องทางบริษัท"],
|
||
"company.report_waiting": ["等待生成", "Waiting to generate", "รอสร้าง"],
|
||
"company.report_warning_count": ["{count} 项", "{count} items", "{count} รายการ"],
|
||
"company.report_no_result": ["暂无", "None", "ไม่มีข้อมูล"],
|
||
"company.this_record": ["这条记录", "this record", "รายการนี้"],
|
||
"company.job_submitted": ["五家公司渠道明细任务已提交", "The five-company channel detail job was submitted", "ส่งงานรายละเอียดช่องทางของห้าบริษัทแล้ว"],
|
||
"company.jobs_refreshed": ["公司渠道明细任务已刷新", "Company channel detail jobs refreshed", "รีเฟรชงานรายละเอียดช่องทางบริษัทแล้ว"],
|
||
"company.job_submit_failed": ["公司渠道明细任务未能提交", "The company channel detail job could not be submitted", "ส่งงานรายละเอียดช่องทางบริษัทไม่สำเร็จ"],
|
||
"task.title": ["任务日志", "Task log", "บันทึกงาน"],
|
||
"task.description": ["显示当前选中的日报处理任务从上传到下游通知的处理日志。", "Shows the selected daily task from upload through downstream notification.", "แสดงบันทึกของงานรายวันที่เลือกตั้งแต่อัปโหลดจนถึงการแจ้งเตือนปลายทาง"],
|
||
"task.kicker": ["DAILY JOB / LOG", "DAILY JOB / LOG", "DAILY JOB / LOG"],
|
||
"task.raw_log": ["任务原始日志", "Raw task log", "บันทึกงานดิบ"],
|
||
"task.refresh": ["刷新日志", "Refresh log", "รีเฟรชบันทึก"],
|
||
"task.copy": ["复制全部日志", "Copy all logs", "คัดลอกบันทึกทั้งหมด"],
|
||
"task.copying": ["复制中…", "Copying…", "กำลังคัดลอก…"],
|
||
"task.copied": ["已复制", "Copied", "คัดลอกแล้ว"],
|
||
"task.copy_failed": ["复制失败", "Copy failed", "คัดลอกไม่สำเร็จ"],
|
||
"task.copy_none": ["暂无可复制的任务日志", "No task log to copy", "ไม่มีบันทึกงานให้คัดลอก"],
|
||
"task.copy_denied": ["无法复制日志,请检查浏览器剪贴板权限", "Could not copy the log. Check browser clipboard permission.", "คัดลอกบันทึกไม่ได้ โปรดตรวจสอบสิทธิ์คลิปบอร์ดของเบราว์เซอร์"],
|
||
"task.clipboard_denied": ["浏览器未允许访问剪贴板", "The browser did not allow clipboard access", "เบราว์เซอร์ไม่อนุญาตให้เข้าถึงคลิปบอร์ด"],
|
||
"task.reading": ["正在读取任务日志", "Reading task log", "กำลังอ่านบันทึกงาน"],
|
||
"task.reading_detail": ["正在聚合数据库中的处理证据…", "Aggregating processing evidence from the database…", "กำลังรวมหลักฐานการประมวลผลจากฐานข้อมูล…"],
|
||
"task.unavailable": ["任务日志暂时无法读取", "The task log is temporarily unavailable", "ไม่สามารถอ่านบันทึกงานได้ชั่วคราว"],
|
||
"task.no_server_log": ["服务端没有返回可用日志。", "The server returned no usable log.", "เซิร์ฟเวอร์ไม่ส่งบันทึกที่ใช้ได้กลับมา"],
|
||
"task.not_selected": ["未选择任务", "No task selected", "ยังไม่ได้เลือกงาน"],
|
||
"task.waiting_upload": ["等待上传 ARR.XML", "Waiting for ARR.XML upload", "รอการอัปโหลด ARR.XML"],
|
||
"task.after_create": ["新任务创建后,这里会显示服务端保存的全流程日志。", "The server-side trace will appear here after a new task is created.", "บันทึกจากเซิร์ฟเวอร์จะแสดงที่นี่หลังสร้างงานใหม่"],
|
||
"task.inline_prompt": ["请上传文件,或从 Daily Report 中选择一个任务。", "Upload a file or select a task from Daily Report.", "อัปโหลดไฟล์หรือเลือกงานจาก Daily Report"],
|
||
"task.refresh_failed": ["日志刷新失败", "Log refresh failed", "รีเฟรชบันทึกไม่สำเร็จ"],
|
||
"task.auto_refresh_failed": ["自动刷新失败", "Auto-refresh failed", "อัปเดตอัตโนมัติไม่สำเร็จ"],
|
||
"task.created": ["任务已登记到 ARR 程序化处理队列。", "The task was registered in the ARR processing queue.", "ลงทะเบียนงานในคิวประมวลผล ARR แล้ว"],
|
||
"task.processor_started": ["ARR 已开始处理已登记的源 XML。", "ARR started processing the registered source XML.", "ARR เริ่มประมวลผล XML ต้นทางที่ลงทะเบียนแล้ว"],
|
||
"task.processor_failed": ["程序化处理失败", "Programmatic processing failed", "การประมวลผลแบบโปรแกรมไม่สำเร็จ"],
|
||
"task.processor_cancelled": ["程序化处理已取消", "Programmatic processing cancelled", "ยกเลิกการประมวลผลแบบโปรแกรมแล้ว"],
|
||
"task.processor_complete": ["处理尝试已闭环", "Processing attempt completed", "การประมวลผลเสร็จสิ้น"],
|
||
"task.accepted": ["业务任务已完成", "Business task completed", "งานธุรกิจเสร็จสิ้น"],
|
||
"task.upload_registered": ["上传已安全登记", "Upload safely registered", "ลงทะเบียนการอัปโหลดอย่างปลอดภัยแล้ว"],
|
||
"task.trace_processing": ["处理已结束", "Processing ended", "การประมวลผลสิ้นสุดแล้ว"],
|
||
"task.trace_failed": ["处理未能完成", "Processing could not complete", "การประมวลผลไม่เสร็จสมบูรณ์"],
|
||
"task.refreshed": ["任务日志已刷新", "Task log refreshed", "รีเฟรชบันทึกงานแล้ว"],
|
||
"task.no_job_id": ["任务编号暂不可用,请刷新 Daily Report。", "The task ID is not available yet. Refresh Daily Report.", "รหัสงานยังไม่พร้อม โปรดรีเฟรช Daily Report"],
|
||
"task.failed_refresh_hint": ["请刷新 Daily Report,确认是否已登记失败任务。", "Refresh Daily Report to confirm whether a failed task was recorded.", "รีเฟรช Daily Report เพื่อตรวจสอบว่าบันทึกงานที่ล้มเหลวแล้วหรือไม่"],
|
||
"task.error_status": ["处理失败", "Processing failed", "ประมวลผลไม่สำเร็จ"],
|
||
"task.live_failed": ["日志刷新失败", "Log refresh failed", "รีเฟรชบันทึกไม่สำเร็จ"],
|
||
"task.live_auto_failed": ["自动刷新失败", "Auto-refresh failed", "อัปเดตอัตโนมัติไม่สำเร็จ"],
|
||
"task.live_reading": ["正在读取", "Reading", "กำลังอ่าน"],
|
||
"task.live_unselected": ["未选择任务", "No task selected", "ยังไม่ได้เลือกงาน"],
|
||
"task.trace_failed_status": ["FAILED", "FAILED", "FAILED"],
|
||
"task.trace_running_status": ["RUNNING / auto refresh {seconds}s", "RUNNING / auto refresh {seconds}s", "RUNNING / อัปเดตทุก {seconds}s"],
|
||
"task.trace_succeeded_status": ["SUCCEEDED", "SUCCEEDED", "SUCCEEDED"],
|
||
"task.trace_unknown_status": ["UNKNOWN", "UNKNOWN", "UNKNOWN"],
|
||
"task.no_events": ["0 events", "0 events", "0 เหตุการณ์"],
|
||
"task.events": ["{count} events", "{count} events", "{count} เหตุการณ์"],
|
||
"task.raw_log_count": ["0 lines", "0 lines", "0 บรรทัด"],
|
||
"task.raw_log_aria": ["任务原始日志", "Raw task log", "บันทึกงานดิบ"],
|
||
"task.browser_clipboard": ["浏览器未允许访问剪贴板", "The browser did not allow clipboard access", "เบราว์เซอร์ไม่อนุญาตให้เข้าถึงคลิปบอร์ด"],
|
||
"task.none_to_copy": ["暂无可复制的任务日志", "No task log to copy", "ไม่มีบันทึกงานให้คัดลอก"],
|
||
"task.copying_lines": ["已复制全部任务日志({count} 行)", "Copied all task logs ({count} lines)", "คัดลอกบันทึกงานทั้งหมดแล้ว ({count} บรรทัด)"],
|
||
"task.copy_button": ["复制全部日志", "Copy all logs", "คัดลอกบันทึกทั้งหมด"],
|
||
"task.copying_button": ["复制中…", "Copying…", "กำลังคัดลอก…"],
|
||
"task.copied_button": ["已复制", "Copied", "คัดลอกแล้ว"],
|
||
"task.copy_failed_button": ["复制失败", "Copy failed", "คัดลอกไม่สำเร็จ"],
|
||
"task.close_button": ["关闭", "Close", "ปิด"],
|
||
"error.generic": ["请求未完成", "The request could not be completed", "ดำเนินการตามคำขอไม่สำเร็จ"],
|
||
"error.unrecognized_response": ["服务返回了无法识别的结果", "The service returned an unrecognized response", "บริการส่งผลลัพธ์ที่ไม่รู้จักกลับมา"],
|
||
"error.database_unavailable": ["数据库连接失败", "Database connection failed", "เชื่อมต่อฐานข้อมูลไม่สำเร็จ"],
|
||
"error.database_read_unavailable": ["数据库读取服务暂不可用", "Database read service is temporarily unavailable", "บริการอ่านฐานข้อมูลไม่พร้อมใช้งานชั่วคราว"],
|
||
"error.query_invalid": ["查询参数无效", "Invalid query parameters", "พารามิเตอร์การค้นหาไม่ถูกต้อง"],
|
||
"error.auth_required": ["请先登录", "Please log in first", "กรุณาเข้าสู่ระบบก่อน"],
|
||
"error.login_failed": ["用户名或密码不正确", "Incorrect username or password", "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง"],
|
||
"error.login_rate_limited": ["登录尝试过于频繁,请稍后再试", "Too many login attempts. Try again later.", "พยายามเข้าสู่ระบบบ่อยเกินไป โปรดลองอีกครั้งภายหลัง"],
|
||
"error.request_invalid": ["请求内容不是有效 JSON", "The request is not valid JSON", "คำขอไม่ใช่ JSON ที่ถูกต้อง"],
|
||
"error.request_too_large": ["请求内容过大", "The request is too large", "คำขอมีขนาดใหญ่เกินไป"],
|
||
"error.upload_empty": ["XML 文件为空", "The XML file is empty", "ไฟล์ XML ว่างเปล่า"],
|
||
"error.upload_too_large": ["XML 文件超过 25 MB", "The XML file exceeds 25 MB", "ไฟล์ XML มีขนาดเกิน 25 MB"],
|
||
"error.upload_invalid": ["文件内容不是 XML", "The file content is not XML", "เนื้อหาไฟล์ไม่ใช่ XML"],
|
||
"error.upload_filename": ["请选择 XML 文件", "Choose an XML file", "เลือกไฟล์ XML"],
|
||
"error.job_id": ["任务编号无效", "Invalid task ID", "รหัสงานไม่ถูกต้อง"],
|
||
"error.route_not_found": ["页面或接口不存在", "The page or endpoint does not exist", "ไม่พบหน้าหรือปลายทาง"],
|
||
"error.static_not_found": ["页面资源不存在", "The page resource does not exist", "ไม่พบทรัพยากรของหน้า"],
|
||
"error.session_invalid": ["页面会话已失效", "The page session is no longer valid", "เซสชันของหน้าไม่ถูกต้องแล้ว"],
|
||
"error.download_not_found": ["文件不存在或尚未生成", "The file does not exist or has not been generated", "ไม่พบไฟล์หรือยังไม่ได้สร้าง"],
|
||
"error.download_invalid": ["文件身份无效", "Invalid file identity", "ข้อมูลระบุไฟล์ไม่ถูกต้อง"],
|
||
"error.download_unavailable": ["文件读取服务暂不可用", "The file read service is temporarily unavailable", "บริการอ่านไฟล์ไม่พร้อมใช้งานชั่วคราว"],
|
||
"error.download_integrity": ["文件完整性校验失败", "File integrity check failed", "ตรวจสอบความสมบูรณ์ของไฟล์ไม่ผ่าน"],
|
||
"error.company_source_required": ["请先上传并校验 Excel 报表", "Upload and validate the Excel report first", "อัปโหลดและตรวจสอบรายงาน Excel ก่อน"],
|
||
"error.company_review_open": ["请先完成或放弃当前 Excel 提取草稿", "Complete or discard the current Excel draft first", "ดำเนินการหรือยกเลิกร่าง Excel ปัจจุบันก่อน"],
|
||
"error.company_future": ["未来报表月份暂不可生成", "Future report months cannot be generated yet", "ยังไม่สามารถสร้างเดือนรายงานในอนาคตได้"],
|
||
"error.company_running": ["同一月份和期间已有生成任务", "A job already exists for this month and period", "มีงานสำหรับเดือนและช่วงเวลานี้อยู่แล้ว"],
|
||
"error.company_unavailable": ["公司渠道明细生成服务暂不可用", "Company channel detail generation is temporarily unavailable", "บริการสร้างรายละเอียดช่องทางบริษัทไม่พร้อมใช้งานชั่วคราว"],
|
||
"error.company_download": ["该公司的正式表格尚不可下载", "The official workbook for this company is not ready to download", "ไฟล์อย่างเป็นทางการของบริษัทนี้ยังไม่พร้อมดาวน์โหลด"],
|
||
"error.booking_unavailable": ["Excel 数据源服务暂未就绪", "The Excel source service is not ready", "บริการแหล่ง Excel ยังไม่พร้อม"],
|
||
"error.booking_extract_failed": ["Excel 文件未能完成提取", "The Excel file could not be extracted", "แยกข้อมูลจากไฟล์ Excel ไม่สำเร็จ"],
|
||
"error.processing_failed": ["文件已安全接收,但程序化处理未能完成", "The file was received safely, but processing did not complete", "รับไฟล์อย่างปลอดภัยแล้ว แต่การประมวลผลไม่เสร็จ"],
|
||
"error.invalid_request": ["请求字段无效", "Invalid request fields", "ฟิลด์คำขอไม่ถูกต้อง"],
|
||
"error.invalid_period": ["期间无效", "Invalid period", "ช่วงเวลาไม่ถูกต้อง"],
|
||
"error.invalid_company": ["公司名称无效", "Invalid company name", "ชื่อบริษัทไม่ถูกต้อง"],
|
||
"error.not_found": ["任务或数据不存在", "The task or data was not found", "ไม่พบงานหรือข้อมูล"],
|
||
"error.service_unavailable": ["服务暂时不可用", "The service is temporarily unavailable", "บริการไม่พร้อมใช้งานชั่วคราว"],
|
||
"error.internal": ["服务暂时无法完成请求", "The service could not complete the request", "บริการไม่สามารถดำเนินการตามคำขอได้"],
|
||
"error.month_invalid": ["报表月份无效", "Invalid report month", "เดือนรายงานไม่ถูกต้อง"],
|
||
"error.monthly_busy": ["该服务正在生成月报", "The monthly report service is already running", "บริการรายงานประจำเดือนกำลังทำงาน"],
|
||
"error.monthly_failed": ["月报生成未完成", "Monthly report generation did not complete", "การสร้างรายงานประจำเดือนไม่เสร็จ"],
|
||
"error.monthly_not_found": ["找不到该月份的正式月报。", "The published monthly report for this month was not found.", "ไม่พบรายงานประจำเดือนที่เผยแพร่ของเดือนนี้"],
|
||
"error.channel_invalid": ["渠道名称无效", "Invalid channel name", "ชื่อช่องทางไม่ถูกต้อง"],
|
||
"error.login_request_invalid": ["登录请求格式或字段无效", "The login request is invalid", "คำขอเข้าสู่ระบบไม่ถูกต้อง"],
|
||
"error.content_length_required": ["请求长度缺失", "Request length is missing", "ไม่มีความยาวคำขอ"],
|
||
"error.booking_filename": ["请选择 .xlsx 格式的 Excel 文件", "Choose an .xlsx Excel file", "เลือกไฟล์ Excel รูปแบบ .xlsx"],
|
||
"error.review_request_invalid": ["人工确认请求无效", "The review request is invalid", "คำขอตรวจสอบไม่ถูกต้อง"],
|
||
"error.review_item_invalid": ["待确认记录编号无效", "Invalid review item ID", "รหัสรายการตรวจสอบไม่ถูกต้อง"],
|
||
"error.company_request_invalid": ["渠道明细请求字段无效", "Invalid company detail request fields", "ฟิลด์คำขอรายละเอียดบริษัทไม่ถูกต้อง"],
|
||
"error.company_source_already_active": ["这份 Excel 已经启用,无需重复提取", "This Excel is already active; no duplicate extraction is needed", "Excel นี้เปิดใช้งานอยู่แล้ว ไม่จำเป็นต้องแยกข้อมูลซ้ำ"],
|
||
"error.company_period_invalid": ["期间必须是 01-10、11-20 或 21-month-end", "Period must be 01-10, 11-20 or 21-month-end", "ช่วงเวลาต้องเป็น 01-10, 11-20 หรือ 21-month-end"],
|
||
"error.company_job_not_found": ["任务不存在", "The job does not exist", "ไม่พบงาน"],
|
||
"error.download_too_large": ["文件超过下载限制", "The file exceeds the download limit", "ไฟล์มีขนาดเกินขีดจำกัดการดาวน์โหลด"],
|
||
"problem.multi_price": ["同房型存在不同静态价格,需人工复核", "The same room type has different static prices; review required", "ประเภทห้องเดียวกันมีราคาคงที่ต่างกัน ต้องตรวจสอบ"],
|
||
"problem.source_missing": ["数据源版本缺失", "Source version is missing", "ไม่มีเวอร์ชันแหล่งข้อมูล"],
|
||
"problem.group_missing": ["Group Code 缺失", "Group Code is missing", "ไม่มี Group Code"],
|
||
"problem.group_not_found": ["Group Code 未找到", "Group Code not found", "ไม่พบ Group Code"],
|
||
"problem.booking_parse": ["预订解析异常", "Booking parsing error", "เกิดข้อผิดพลาดในการแยก Booking"],
|
||
"problem.room_items_missing": ["房型明细缺失", "Room details are missing", "ไม่มีรายละเอียดประเภทห้อง"],
|
||
"problem.stay_date": ["住宿日期异常", "Invalid stay date", "วันที่เข้าพักไม่ถูกต้อง"],
|
||
"problem.nights_conflict": ["间夜数据冲突", "Room-night data conflict", "ข้อมูลคืนห้องพักขัดแย้งกัน"],
|
||
"problem.total_price": ["静态价格异常", "Invalid static price", "ราคาคงที่ไม่ถูกต้อง"],
|
||
"problem.output_validation": ["Excel 输出校验失败", "Excel output validation failed", "ตรวจสอบผลลัพธ์ Excel ไม่ผ่าน"],
|
||
"problem.publish_failed": ["正式表格发布失败", "Official workbook publication failed", "เผยแพร่ไฟล์อย่างเป็นทางการไม่สำเร็จ"],
|
||
"problem.internal": ["处理服务异常", "Processing service error", "บริการประมวลผลขัดข้อง"],
|
||
"problem.execution": ["任务执行未完成", "Job execution did not complete", "การทำงานยังไม่เสร็จ"],
|
||
"problem.interrupted": ["服务重启中断任务", "The service restart interrupted the job", "การรีสตาร์ตบริการขัดจังหวะงาน"],
|
||
"problem.review_result": ["需复核处理结果", "Review the processing result", "โปรดตรวจสอบผลการประมวลผล"],
|
||
"units.second": ["秒", "sec", "วินาที"],
|
||
"units.seconds": ["秒", "sec", "วินาที"],
|
||
"units.minute": ["分", "min", "นาที"],
|
||
"units.minutes": ["分", "min", "นาที"],
|
||
"units.rows": ["条", "records", "รายการ"],
|
||
"units.items": ["项", "items", "รายการ"],
|
||
"pagination.zero": ["共 0 条", "0 records", "0 รายการ"],
|
||
"pagination.summary": ["共 {total} 条 · 本页 {start} 至 {end}", "{total} records · Showing {start}–{end}", "{total} รายการ · แสดง {start}–{end}"],
|
||
"pagination.page": ["第 {page} / {pages} 页", "Page {page} of {pages}", "หน้า {page} จาก {pages}"],
|
||
"history.pagination": ["{month} · {summary}", "{month} · {summary}", "{month} · {summary}"],
|
||
"history.daily_empty": ["{month} · 暂无 Daily Report 记录", "{month} · No Daily Report records", "{month} · ไม่มีรายการ Daily Report"],
|
||
"history.monthly_empty": ["{month} · 暂无月报处理记录", "{month} · No monthly processing records", "{month} · ไม่มีรายการประมวลผลรายเดือน"],
|
||
"history.company_empty": ["{month} · 暂无公司渠道明细任务", "{month} · No company channel detail jobs", "{month} · ไม่มีงานรายละเอียดช่องทางบริษัท"],
|
||
"history.view_recent": ["查看 {month}", "View {month}", "ดู {month}"],
|
||
"progress.auto_update": ["自动更新 · {seconds} 秒", "Auto-refresh · {seconds} sec", "อัปเดตอัตโนมัติ · {seconds} วินาที"],
|
||
"period.release": ["周期结束:{date} 00:00(曼谷)", "Period ends: {date} 00:00 (Bangkok)", "รอบสิ้นสุด: {date} 00:00 (กรุงเทพฯ)"],
|
||
"period.release_hint": ["周期尚未结束;可按当前已入库数据生成,周期结束时间为曼谷 {date} 00:00", "The period is not complete; current committed data can be used. The Bangkok completion time is {date} 00:00.", "รอบยังไม่สิ้นสุด ใช้ข้อมูลที่บันทึกแล้วสร้างได้ เวลาสิ้นสุดที่กรุงเทพฯ คือ {date} 00:00"],
|
||
"company.month_end": ["21-月末", "21-month-end", "21-สิ้นเดือน"],
|
||
"company.range_month_end": ["21-{day}", "21-{day}", "21-{day}"],
|
||
"company.range_co": ["C/O:{range}", "C/O:{range}", "C/O:{range}"],
|
||
"company.month_label": ["{year}年{month}月", "{monthName} {year}", "{monthName} {year}"],
|
||
"company.at_period": ["{month},{period}", "{month}, {period}", "{month}, {period}"],
|
||
"company.selected_count": ["已选 {count} 条", "{count} selected", "เลือก {count} รายการ"],
|
||
"company.deleted_count": ["已删除 {count} 条房型记录", "Deleted {count} room records", "ลบรายการประเภทห้อง {count} รายการแล้ว"],
|
||
"company.delete_count": ["删除 {count} 条", "Delete {count}", "ลบ {count} รายการ"],
|
||
"company.confirm_single_delete": ["确认删除 {tourCode} 的这条房型记录?", "Delete this room record for {tourCode}?", "ลบรายการประเภทห้องของ {tourCode} หรือไม่"],
|
||
"company.confirm_multi_delete": ["确认删除已选择的 {count} 条房型记录?", "Delete the {count} selected room records?", "ลบรายการประเภทห้องที่เลือก {count} รายการหรือไม่"],
|
||
"company.selected_file": ["{name} · {size} KB", "{name} · {size} KB", "{name} · {size} KB"],
|
||
"task.selected_job": ["任务 {jobId}", "Task {jobId}", "งาน {jobId}"],
|
||
"task.view_job": ["查看任务 {jobId} 的全流程日志", "View the full trace for task {jobId}", "ดูบันทึกทั้งหมดของงาน {jobId}"],
|
||
"task.copied_lines": ["已复制全部任务日志({count} 行)", "Copied all task logs ({count} lines)", "คัดลอกบันทึกงานทั้งหมดแล้ว ({count} บรรทัด)"],
|
||
"bi.data_range": ["数据范围:{start} – {end}", "Data coverage: {start} – {end}", "ช่วงข้อมูล: {start} – {end}"],
|
||
"company.warning_items": ["{count} 项", "{count} items", "{count} รายการ"],
|
||
"company.period_rows": ["✓ {count} 条", "✓ {count} records", "✓ {count} รายการ"],
|
||
"company.period_end_display": ["周期结束:{date} 00:00(曼谷)", "Period ends: {date} 00:00 (Bangkok)", "รอบสิ้นสุด: {date} 00:00 (กรุงเทพฯ)"],
|
||
"trace.events": ["0 events", "0 events", "0 เหตุการณ์"],
|
||
"trace.events_count": ["{count} events", "{count} events", "{count} เหตุการณ์"],
|
||
"trace.failed": ["ERROR", "ERROR", "ERROR"],
|
||
"trace.loading": ["LOADING", "LOADING", "LOADING"],
|
||
"trace.idle": ["IDLE", "IDLE", "IDLE"],
|
||
"trace.failed_status": ["FAILED / {stage} / {code}", "FAILED / {stage} / {code}", "FAILED / {stage} / {code}"],
|
||
"trace.running_status": ["RUNNING / auto refresh {seconds}s", "RUNNING / auto refresh {seconds}s", "RUNNING / อัปเดตทุก {seconds}s"],
|
||
"trace.succeeded_status": ["SUCCEEDED", "SUCCEEDED", "SUCCEEDED"],
|
||
"trace.unknown_status": ["{status}", "{status}", "{status}"],
|
||
"h5.snapshot": ["基于已发布月报快照", "Based on the published monthly snapshot", "อ้างอิงสแนปช็อตรายเดือนที่เผยแพร่แล้ว"],
|
||
"h5.nights_formula": ["房数 × 晚数", "Rooms × nights", "ห้อง × จำนวนคืน"],
|
||
"h5.monthly_sheet": ["月报子表", "Monthly worksheets", "ชีตรายงานประจำเดือน"],
|
||
"h5.loading": ["正在读取数据…", "Loading data…", "กำลังโหลดข้อมูล…"],
|
||
"h5.login_title": ["ARR Report 登录", "ARR Report login", "เข้าสู่ระบบ ARR Report"],
|
||
"login.title": ["登录 · ARR Report", "Log in · ARR Report", "เข้าสู่ระบบ · ARR Report"],
|
||
"h5.title": ["渠道BI · ARR Report", "Channel BI · ARR Report", "Channel BI · ARR Report"],
|
||
};
|
||
|
||
const ERROR_KEYS = {
|
||
LOGIN_FAILED: "error.login_failed",
|
||
LOGIN_RATE_LIMITED: "error.login_rate_limited",
|
||
AUTH_REQUIRED: "error.auth_required",
|
||
SESSION_INVALID: "error.session_invalid",
|
||
REQUEST_JSON_INVALID: "error.request_invalid",
|
||
REQUEST_TOO_LARGE: "error.request_too_large",
|
||
UPLOAD_FILENAME_INVALID: "error.upload_filename",
|
||
UPLOAD_EMPTY: "error.upload_empty",
|
||
UPLOAD_TOO_LARGE: "error.upload_too_large",
|
||
UPLOAD_CONTENT_INVALID: "error.upload_invalid",
|
||
JOB_ID_INVALID: "error.job_id",
|
||
ROUTE_NOT_FOUND: "error.route_not_found",
|
||
STATIC_NOT_FOUND: "error.static_not_found",
|
||
QUERY_INVALID: "error.query_invalid",
|
||
DATABASE_UNAVAILABLE: "error.database_unavailable",
|
||
DATABASE_DRIVER_UNAVAILABLE: "error.database_read_unavailable",
|
||
DATABASE_CONFIG_MISSING: "error.database_read_unavailable",
|
||
DATABASE_TARGET_INVALID: "error.database_read_unavailable",
|
||
DATABASE_QUERY_FAILED: "error.database_read_unavailable",
|
||
DOWNLOAD_NOT_FOUND: "error.download_not_found",
|
||
DOWNLOAD_REQUEST_INVALID: "error.download_invalid",
|
||
DOWNLOAD_REFERENCE_INVALID: "error.download_invalid",
|
||
DOWNLOAD_UNAVAILABLE: "error.download_unavailable",
|
||
DOWNLOAD_IDENTITY_MISMATCH: "error.download_integrity",
|
||
BOOKING_EXCEL_SERVICE_UNAVAILABLE: "error.booking_unavailable",
|
||
BOOKING_EXCEL_PROCESSING_FAILED: "error.booking_extract_failed",
|
||
BOOKING_EXCEL_REVIEW_OPEN: "error.company_review_open",
|
||
BOOKING_EXCEL_SOURCE_REQUIRED: "error.company_source_required",
|
||
BOOKING_EXCEL_SOURCE_ALREADY_ACTIVATED: "error.company_source_already_active",
|
||
COMPANY_REPORT_MONTH_IN_FUTURE: "error.company_future",
|
||
COMPANY_REPORT_ALREADY_RUNNING: "error.company_running",
|
||
COMPANY_REPORT_GENERATOR_UNAVAILABLE: "error.company_unavailable",
|
||
COMPANY_REPORT_DOWNLOAD_NOT_FOUND: "error.company_download",
|
||
PROGRAMMATIC_PROCESSING_FAILED: "error.processing_failed",
|
||
MONTH_INVALID: "error.month_invalid",
|
||
MONTH_KEY_INVALID: "error.month_invalid",
|
||
CHANNEL_INVALID: "error.channel_invalid",
|
||
LOGIN_REQUEST_INVALID: "error.login_request_invalid",
|
||
CONTENT_LENGTH_REQUIRED: "error.content_length_required",
|
||
INTERNAL_ERROR: "error.internal",
|
||
PROCESSING_GATEWAY_UNAVAILABLE: "error.service_unavailable",
|
||
MONTHLY_GENERATOR_UNAVAILABLE: "error.service_unavailable",
|
||
MONTHLY_REQUEST_INVALID: "error.invalid_request",
|
||
MONTHLY_ALREADY_RUNNING: "error.monthly_busy",
|
||
MONTHLY_GENERATION_FAILED: "error.monthly_failed",
|
||
MONTHLY_NOT_FOUND: "error.monthly_not_found",
|
||
BOOKING_EXCEL_FILENAME_INVALID: "error.booking_filename",
|
||
BOOKING_EXCEL_REVIEW_REQUEST_INVALID: "error.review_request_invalid",
|
||
BOOKING_EXCEL_REVIEW_ITEM_INVALID: "error.review_item_invalid",
|
||
COMPANY_REPORT_REQUEST_INVALID: "error.company_request_invalid",
|
||
COMPANY_REPORT_PERIOD_INVALID: "error.company_period_invalid",
|
||
COMPANY_REPORT_COMPANY_INVALID: "error.invalid_company",
|
||
COMPANY_REPORT_JOB_NOT_FOUND: "error.company_job_not_found",
|
||
COMPANY_REPORT_DOWNLOAD_INVALID: "error.download_invalid",
|
||
DOWNLOAD_TOO_LARGE: "error.download_too_large",
|
||
};
|
||
|
||
const sourceToKey = new Map();
|
||
Object.entries(CATALOG).forEach(([key, values]) => {
|
||
if (!sourceToKey.has(values[0])) sourceToKey.set(values[0], key);
|
||
});
|
||
|
||
let locale = DEFAULT_LOCALE;
|
||
let translating = false;
|
||
const sourceTextValues = new WeakMap();
|
||
const sourceAttributeValues = new WeakMap();
|
||
|
||
function validLocale(value) {
|
||
return Object.prototype.hasOwnProperty.call(LOCALES, value) ? value : null;
|
||
}
|
||
|
||
function readCookie(name) {
|
||
const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
|
||
return match ? decodeURIComponent(match[1]) : "";
|
||
}
|
||
|
||
function writeLocaleCookie(value) {
|
||
document.cookie = `${COOKIE_KEY}=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Strict`;
|
||
}
|
||
|
||
function resolveLocale() {
|
||
let candidate = null;
|
||
try { candidate = validLocale(window.localStorage.getItem(STORAGE_KEY)); } catch (_) { /* private mode */ }
|
||
return candidate || validLocale(readCookie(COOKIE_KEY)) || DEFAULT_LOCALE;
|
||
}
|
||
|
||
function currentMeta() {
|
||
return LOCALES[locale] || LOCALES[DEFAULT_LOCALE];
|
||
}
|
||
|
||
function interpolate(value, params = {}) {
|
||
return String(value).replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => String(params[name] ?? ""));
|
||
}
|
||
|
||
function t(key, params = {}) {
|
||
const entry = CATALOG[key];
|
||
if (!entry) return interpolate(key, params);
|
||
const index = locale === "en" ? 1 : locale === "th" ? 2 : 0;
|
||
return interpolate(entry[index] || entry[0], params);
|
||
}
|
||
|
||
function keyForSource(value) {
|
||
const normalized = String(value ?? "").trim();
|
||
return sourceToKey.get(normalized) || null;
|
||
}
|
||
|
||
function patternText(value) {
|
||
const source = String(value ?? "");
|
||
const trimmed = source.trim();
|
||
const exact = keyForSource(trimmed);
|
||
if (exact) return t(exact);
|
||
|
||
let match = /^共\s*([\d,]+)\s*条\s*·\s*本页\s*([\d,]+)\s*至\s*([\d,]+)$/.exec(trimmed);
|
||
if (match) return t("pagination.summary", { total: match[1], start: match[2], end: match[3] });
|
||
match = /^第\s*([\d,]+)\s*\/\s*([\d,]+)\s*页$/.exec(trimmed);
|
||
if (match) return t("pagination.page", { page: match[1], pages: match[2] });
|
||
match = /^已选\s*([\d,]+)\s*条$/.exec(trimmed);
|
||
if (match) return t("company.selected_count", { count: match[1] });
|
||
match = /^已删除\s*([\d,]+)\s*条房型记录$/.exec(trimmed);
|
||
if (match) return t("company.deleted_count", { count: match[1] });
|
||
match = /^删除\s*([\d,]+)\s*条$/.exec(trimmed);
|
||
if (match) return t("company.delete_count", { count: match[1] });
|
||
match = /^提取完成,\s*([\d,]+)\s*条需要人工确认$/.exec(trimmed);
|
||
if (match) return t("company.extract_pending", { count: match[1] });
|
||
match = /^✓\s*([\d,]+)\s*条$/.exec(trimmed);
|
||
if (match) return t("company.period_rows", { count: match[1] });
|
||
match = /^([\d,]+)\s*项$/.exec(trimmed);
|
||
if (match) return t("company.warning_items", { count: match[1] });
|
||
match = /^自动更新\s*·\s*([\d,]+)\s*秒$/.exec(trimmed);
|
||
if (match) return t("progress.auto_update", { seconds: match[1] });
|
||
match = /^周期结束:(.+?)\s*00:00(曼谷)$/.exec(trimmed);
|
||
if (match) return t("company.period_end_display", { date: match[1] });
|
||
match = /^周期尚未结束;可按当前已入库数据生成,周期结束时间为曼谷\s*(.+?)\s*00:00$/.exec(trimmed);
|
||
if (match) return t("period.release_hint", { date: match[1] });
|
||
match = /^(\d{4})年(\d{2})月,(.+)$/.exec(trimmed);
|
||
if (match) return t("company.at_period", { month: `${match[1]}年${match[2]}月`, period: match[3] });
|
||
match = /^(\d{4})年(\d{2})月$/.exec(trimmed);
|
||
if (match) return t("company.month_label", { year: match[1], month: match[2], monthName: `${match[1]}年${match[2]}月` });
|
||
match = /^任务\s+(.+)$/.exec(trimmed);
|
||
if (match) return t("task.selected_job", { jobId: match[1] });
|
||
match = /^查看任务\s+(.+?)\s+的全流程日志$/.exec(trimmed);
|
||
if (match) return t("task.view_job", { jobId: match[1] });
|
||
match = /^([\d,]+)\s*秒$/.exec(trimmed);
|
||
if (match) return `${match[1]} ${t("units.seconds")}`;
|
||
match = /^([\d,]+)\s*分\s*([\d,]+)\s*秒$/.exec(trimmed);
|
||
if (match) return `${match[1]} ${t("units.minutes")} ${match[2]} ${t("units.seconds")}`;
|
||
return source;
|
||
}
|
||
|
||
function translateValue(value) {
|
||
if (typeof value !== "string") return value;
|
||
if (value.includes("\n")) {
|
||
return value.split("\n").map((line) => patternText(line)).join("\n");
|
||
}
|
||
const leading = value.match(/^\s*/)?.[0] || "";
|
||
const trailing = value.match(/\s*$/)?.[0] || "";
|
||
return `${leading}${patternText(value)}${trailing}`;
|
||
}
|
||
|
||
function translateAttribute(value) {
|
||
const translated = translateValue(value);
|
||
if (translated !== value) return translated;
|
||
let match = /^ARR Report 首页$/.exec(value);
|
||
if (match) return t("brand.home");
|
||
match = /^查看任务\s+(.+?)\s+的全流程日志$/.exec(value);
|
||
if (match) return t("task.view_job", { jobId: match[1] });
|
||
match = /^选择\s+(.+)$/.exec(value);
|
||
if (match) return `${t("common.select")} ${match[1]}`;
|
||
match = /^(.+?)\s+房型$/.exec(value);
|
||
if (match) return `${match[1]} ${t("common.room_type")}`;
|
||
match = /^(.+?)\s+数量$/.exec(value);
|
||
if (match) return `${match[1]} ${t("common.quantity")}`;
|
||
match = /^(.+?)\s*·\s*退出登录$/.exec(value);
|
||
if (match) return `${match[1]} · ${t("auth.logout")}`;
|
||
return value;
|
||
}
|
||
|
||
function translateDom(root = document) {
|
||
if (translating || !root) return;
|
||
translating = true;
|
||
try {
|
||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
||
const nodes = [];
|
||
let node;
|
||
while ((node = walker.nextNode())) {
|
||
const parent = node.parentElement;
|
||
if (!parent || ["SCRIPT", "STYLE"].includes(parent.tagName)) continue;
|
||
nodes.push(node);
|
||
}
|
||
nodes.forEach((textNode) => {
|
||
const source = sourceTextValues.has(textNode) ? sourceTextValues.get(textNode) : textNode.nodeValue;
|
||
sourceTextValues.set(textNode, source);
|
||
const value = translateValue(source);
|
||
if (value !== textNode.nodeValue) textNode.nodeValue = value;
|
||
});
|
||
root.querySelectorAll?.("[aria-label], [title], [placeholder], [aria-valuetext]").forEach((element) => {
|
||
["aria-label", "title", "placeholder", "aria-valuetext"].forEach((attribute) => {
|
||
if (!element.hasAttribute(attribute)) return;
|
||
const values = sourceAttributeValues.get(element) || new Map();
|
||
const current = element.getAttribute(attribute);
|
||
const source = values.has(attribute) ? values.get(attribute) : current;
|
||
values.set(attribute, source);
|
||
sourceAttributeValues.set(element, values);
|
||
const next = translateAttribute(source);
|
||
if (next !== current) element.setAttribute(attribute, next);
|
||
});
|
||
});
|
||
root.querySelectorAll?.("[data-arr-locale-selector]").forEach((select) => {
|
||
select.value = locale;
|
||
select.setAttribute("aria-label", t("common.language"));
|
||
});
|
||
} finally {
|
||
translating = false;
|
||
}
|
||
}
|
||
|
||
function formatInteger(value) {
|
||
return new Intl.NumberFormat(currentMeta().intl, { maximumFractionDigits: 0 }).format(Number(value || 0));
|
||
}
|
||
|
||
function formatMoney(value, compact = false) {
|
||
const amount = Number(value || 0);
|
||
const formatter = new Intl.NumberFormat(currentMeta().intl, compact
|
||
? { notation: "compact", maximumFractionDigits: 2 }
|
||
: { maximumFractionDigits: 0 });
|
||
return `¥${formatter.format(amount)}`;
|
||
}
|
||
|
||
function formatDate(value, withTime = false) {
|
||
if (!value) return "—";
|
||
const date = new Date(value);
|
||
if (Number.isNaN(date.getTime())) return translateValue(String(value));
|
||
return new Intl.DateTimeFormat(currentMeta().intl, withTime
|
||
? { timeZone: "Asia/Bangkok", calendar: "gregory", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23" }
|
||
: { timeZone: "Asia/Bangkok", calendar: "gregory", year: "numeric", month: "2-digit", day: "2-digit" }
|
||
).format(date);
|
||
}
|
||
|
||
function formatMonth(value) {
|
||
const match = /^(\d{4})-(\d{2})$/.exec(value || "");
|
||
if (!match) return t("common.year_month_unselected");
|
||
const date = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, 1));
|
||
return new Intl.DateTimeFormat(currentMeta().intl, { timeZone: "UTC", calendar: "gregory", year: "numeric", month: "long" }).format(date);
|
||
}
|
||
|
||
function formatDurationSeconds(value) {
|
||
const seconds = Number(value);
|
||
if (!Number.isFinite(seconds)) return t("common.none");
|
||
if (seconds < 60) return `${Math.max(1, Math.round(seconds))} ${t("units.seconds")}`;
|
||
return `${Math.floor(seconds / 60)} ${t("units.minutes")} ${Math.round(seconds % 60)} ${t("units.seconds")}`;
|
||
}
|
||
|
||
function errorMessage(code, fallback = "") {
|
||
const normalizedCode = String(code || "");
|
||
const key = ERROR_KEYS[normalizedCode] || (
|
||
normalizedCode.endsWith("_NOT_FOUND") ? "error.not_found"
|
||
: normalizedCode.includes("UNAVAILABLE") || normalizedCode.endsWith("_TIMEOUT") ? "error.service_unavailable"
|
||
: normalizedCode.endsWith("_INVALID") || normalizedCode.includes("REQUEST") ? "error.invalid_request"
|
||
: normalizedCode.endsWith("_FAILED") || normalizedCode.endsWith("_ERROR") ? "error.generic"
|
||
: null
|
||
);
|
||
if (key) return t(key);
|
||
const translated = fallback ? translateValue(fallback) : "";
|
||
const containsCjk = /[\u4e00-\u9fff]/.test(translated);
|
||
const containsThai = /[\u0e00-\u0e7f]/.test(translated);
|
||
if (translated && locale === DEFAULT_LOCALE) return translated;
|
||
if (translated && locale === "en" && !containsCjk && !containsThai) return translated;
|
||
if (translated && locale === "th" && !containsCjk && containsThai) return translated;
|
||
return t("error.generic");
|
||
}
|
||
|
||
function setLocale(next) {
|
||
const resolved = validLocale(next) || DEFAULT_LOCALE;
|
||
locale = resolved;
|
||
try { window.localStorage.setItem(STORAGE_KEY, resolved); } catch (_) { /* private mode */ }
|
||
writeLocaleCookie(resolved);
|
||
document.documentElement.lang = resolved;
|
||
translateDom();
|
||
document.dispatchEvent(new CustomEvent("arr:locale-change", { detail: { locale: resolved } }));
|
||
}
|
||
|
||
function bindSelectors() {
|
||
document.querySelectorAll("[data-arr-locale-selector]").forEach((select) => {
|
||
select.value = locale;
|
||
select.addEventListener("change", (event) => setLocale(event.currentTarget.value));
|
||
});
|
||
}
|
||
|
||
function init() {
|
||
locale = resolveLocale();
|
||
document.documentElement.lang = locale;
|
||
bindSelectors();
|
||
translateDom();
|
||
const observer = new MutationObserver((mutations) => {
|
||
if (locale === DEFAULT_LOCALE || translating) return;
|
||
mutations.forEach((mutation) => {
|
||
mutation.addedNodes.forEach((node) => {
|
||
if (node.nodeType === Node.ELEMENT_NODE) translateDom(node);
|
||
else if (node.nodeType === Node.TEXT_NODE) {
|
||
sourceTextValues.set(node, node.nodeValue);
|
||
node.nodeValue = translateValue(node.nodeValue);
|
||
}
|
||
});
|
||
});
|
||
});
|
||
observer.observe(document.body, { childList: true, subtree: true });
|
||
}
|
||
|
||
window.ARRI18n = {
|
||
CATALOG,
|
||
LOCALES,
|
||
get locale() { return locale; },
|
||
t,
|
||
text: translateValue,
|
||
translateDom,
|
||
errorMessage,
|
||
formatInteger,
|
||
formatMoney,
|
||
formatDate,
|
||
formatMonth,
|
||
formatDurationSeconds,
|
||
setLocale,
|
||
};
|
||
|
||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init, { once: true });
|
||
else init();
|
||
})();
|