Newer
Older
client_dwatch / lib / i18n.ts
export type Lang = "ja" | "en";

/**
 * モック(d-watch-mock.jsx)の対訳テーブル。
 * キーは日本語原文。EN時のみ差し替える。
 */
const EN: Record<string, string> = {
  "DFAロボット監視システム": "DFA Robot Monitoring System",
  "DFA運用担当": "DFA Operator",
  "📊 ダッシュボード": "📊 Dashboard",
  "📋 清掃詳細ログ": "📋 Cleaning Logs",
  "🔔 アラート": "🔔 Alerts",
  "📈 レポート": "📈 Reports",
  // ★清掃面積(指示書AN 段階5)★ — メニューの並びに合わせてここに置く
  "📐 清掃面積": "📐 Cleaning Area",
  "🔧 点検記録": "🔧 Inspections",
  "🔒 D-Secログ": "🔒 D-Sec Logs",
  "⚠ D-Secアラート": "⚠ D-Sec Alerts",
  "🏢 施設ロボットマスタ": "🏢 Facility Master",
  "📅 スケジュール": "📅 Schedules",
  "⚙ システム管理": "⚙ System Admin",
  モニタリング: "MONITORING",
  設定: "SETTINGS",
  施設名: "Facility",
  機種: "Model",
  シリアル: "Serial",
  日付: "Date",
  予定: "Scheduled",
  出発: "Dep.",
  清掃結果: "Clean Result",
  完了率: "Completion",
  判定: "Grade",
  ダッシュボード: "Dashboard",
  清掃詳細ログ: "Cleaning Detail Log",
  "アラート(事前チェック)": "Alerts (Pre-check)",
  レポート: "Reports",
  "D-Sec ログ": "D-Sec Logs",
  "D-Security アラート": "D-Security Alerts",
  施設ロボットマスタ: "Facility Robot Master",
  スケジュール管理: "Schedule Mgmt",
  システム管理: "System Admin",
  点検記録: "Inspection Records",
  CSV: "CSV",
  // ---- 分類(ホールディングス / 顧客 / 店舗)。指示書AK §2-5 ----
  // ★第11回 P-15=B(`customers` は法人の単位)で確定。2026-10-05 に見出し語を「屋号」から「顧客」に変えた(D-158。指示書AU 段階4)★。
  //   ★キーごと改名した★(旧キーは残さない。片方だけ直した画面が「屋号」のまま出ても気づけなくなるため)。
  //   旧: 2026-09-15「屋号でよいです、ただ顧客によってはない場合もあります」で「屋号」にしていた(TeamWorks 第9回 Q7-1 の回答待ち)。
  // ★英語は仮★("Customer" / "All Customers"。大文字は既存の "All Holdings" にそろえた。§11 Q7)—
  //   ★英語は後でユーザーがまとめて見直す★。
  // ★見出し語をここ1箇所に集めてある★(呼び出し側は `t("顧客")` / `t("全顧客")`)。
  // ★旧キー「全グループ」「全会社」は削除した★(指示書AK 段階7 の判断)—
  //   モックの文言で、5画面すべてが「全ホールディングス」「全屋号」に差し替わり、
  //   ★アプリ側の参照が1件も無くなった★(残る出現箇所は `d-watch-mock.jsx` だけで、
  //   モックは自前の辞書を持つためこの定義には依存しない)。
  ホールディングス: "Holdings",
  顧客: "Customer",
  全ホールディングス: "All Holdings",
  全顧客: "All Customers",
  全件: "All",
  "△×のみ": "△× Only",
  "×のみ": "× Only",
  問題エリアのみ: "Problems Only",
  "エリア別詳細を見る →": "View Area Details →",
  エリア別清掃完了率: "Area Completion Rates",
  // ★エリア別完了率モーダル(F-02。指示書Z 段階6)★ — 件数行は画面が {n}/{m} を差し込む
  //   (`{n}日連続で出発失敗` 等と同じ方式A)。★キーは日本語原文と1文字も違わないこと★
  "問題エリア({n} / {m}エリア)": "Problem areas ({n} / {m})",
  "全{n}エリア(完了率昇順)": "All {n} areas (by completion rate)",
  "問題エリアはありません(全エリア◎○)": "No problem areas (all ◎○)",
  // ★モーダルのヘッダ(施設名 | 日付 | 全体 xx%)★ — ここだけ対訳が無く EN で和文が残っていた
  全体: "Overall",
  // ★「全部◎○だった」と区別するための文言★ — エリアが1件も無いのはデータ側の問題なので
  //   「問題エリアはありません」と同じ表示にしない
  "エリアのデータがありません": "No area data",
  "読み込み中…": "Loading…",
  閉じる: "Close",
  準備中: "Coming soon",

  // ---- レポート(F-04)。指示書F 5-8 ----
  // モックの T 辞書(d-watch-mock.jsx:138-147)からの移植。
  // ★`完了率`(= "Completion")は上に既にあるため再掲しない★
  CSVエクスポート: "CSV Export",
  検索: "Search",
  全ステータス: "All Status",
  週次: "Weekly",
  月次: "Monthly",
  // ★レポート画面の粒度タブ3つ目(2026-09-30。指示書AQ 決定1)★
  年次: "Yearly",
  // ★清掃詳細ログの事前チェック列の見出し(ユーザー判断 2026-10-16「案1」。D-178)★ —
  //   列は「その清掃の何回目の事前チェックか」(早い方が1回目)。何分前かはセルの中に出す
  //   (事前チェックの時刻は設定で変わるため、見出しに分数を書かない)。
  //   ★旧キー「1h前ST」「30分前ST」は削除した★(画面の参照が無くなった。CSV の見出しは `lib/clean-logs/csv.ts` が直書きで、辞書を通らない)
  事前チェック1回目: "Pre-check 1",
  事前チェック2回目: "Pre-check 2",
  稼働率: "Uptime",
  出発成功率: "Dep. Rate",
  清掃完了率: "Completion",
  エラー発生率: "Error Rate",
  連続問題施設: "Recurring Issues",
  エラー種別の内訳: "Error Breakdown",
  "施設別パフォーマンス(稼働率ワースト順)": "Facility Performance (Worst Uptime)",
  要改善: "Needs Fix",
  注意: "Caution",
  良好: "Good",
  // ★施設別パフォーマンスの4値目(D-109 決定1')★ — 当期に実施済みの清掃が1件も無く
  //   稼働率が算出できない施設。★週の初め・月の初めにだけ現れる★ので見落としやすい。
  //   ★`translate()` は未登録キーを日本語のまま返すため、足さないと EN で1つだけ和文が混ざる★
  判定なし: "No Data",
  前週: "Prev Week",
  前月: "Prev Month",
  // ★年次の解禁に伴って追加(2026-09-30。指示書AQ 決定1)★ —
  //   `ReportsScreen` は `t(prevPeriodLabel(period))` と、★関数の戻り値をそのまま辞書キーにする★
  //   (`lib/format/period.ts` の `prevPeriodLabel()`)。★ここに無いと EN で「前年」だけ和文で残る★
  前年: "Prev Year",
  該当施設なし: "No matching facilities",
  上位10件に戻す: "Back to top 10",
  エラー率: "Error Rate",
  変化: "Change",
  ステータス: "Status",
  // ★指示書O 段階3★: エラー種別の内訳ラベルが空の場合の代替文言(`lib/format/raw-label.ts`)
  "(値なし)": "(no value)",

  // ---- 数値が文中に入るラベル(指示書F 5-5 / 5-7 のフッタ)----
  // `translate()` はキーの完全一致でしか引けないため、`他5件を表示 →` のように
  // 数値が混ざったラベルはそのままではキーにできない。
  // 画面担当が選べるよう **2方式ぶんのキーを用意している**。どちらか一方だけを使い、
  // 使わなかった方は画面実装が固まった時点で削除してよい。
  //
  //   方式A(プレースホルダ + replace)★推奨★
  //     t("他{n}件を表示 →").replace("{n}", String(n))
  //     → ja: 「他5件を表示 →」 / en: "View 5 more →"
  //     語順が言語ごとに違っても対訳側で吸収できるため、こちらを推す。
  //
  //   方式B(前後を分割して連結)
  //     t("他") + n + t("件を表示 →")
  //     → ja: 「他5件を表示 →」 / en: "View 5 more →"
  //     ★注意★ `他` / `全` のような1文字キーは他画面の文言と衝突しやすい
  //     (将来 `全` 単体を別の意味で使うと巻き込まれる)。方式Aなら衝突しない。
  "他{n}件を表示 →": "View {n} more →",
  "全{n}施設を表示 →": "View all {n} facilities →",
  他: "View ",
  "件を表示 →": " more →",
  全: "View all ",
  "施設を表示 →": " facilities →",

  // ---- D-Secログ(F-06)の「備考」(編集モード。D-183)----
  備考を入力: "Enter a note",
  "保存中…": "Saving…",
  // 画面側の既定文言(サーバーが `{ error: { message } }` を返さなかった場合に出る)
  保存に失敗しました: "Failed to save",
  // PATCH /ds-devices/{serial_number}/note がエラー時に返す文言。
  // セル下の赤字にはサーバーの文言がそのまま入るため、ここに載せた分だけ EN になる
  // (`translate()` はキーが無ければ原文を返すので、未収録でも表示は壊れない)
  デバイスが見つかりません: "Device not found",
  備考の保存に失敗しました: "Failed to save the note",
  "noteが長すぎます": "Note is too long",

  // ---- アラート(F-03)の対応状況(指示書AT §3-8。D-105 で「キーが無い」と申し送られていたもの)----
  // ★足すと既存のバッジ・サマリカードの「未解消」も英語になる★
  未解消: "Unresolved",
  対応中: "In progress",
  解消: "Resolved",
  対応状況の更新に失敗しました: "Failed to update the status",

  // ---- 施設ロボットマスタ(F-08)----
  // ★`施設名` / `シリアル` / `機種` は上に既にあるため再掲しない★
  "施設名 / シリアル / 住所": "Facility / Serial / Address",
  該当する施設はありません: "No matching facilities",
  都道府県: "Prefecture",
  住所: "Address",
  電話番号: "Phone",
  責任者名: "Manager",
  担当者名: "Contact",
  メール: "Email",
  監視: "Monitor",
  // 製品名なので EN でも同じ。`translate()` は未収録でも原文を返すため表示は変わらないが、
  // 「訳し漏れ」と「意図的に同じ」を区別できるように載せておく
  "D-Sec": "D-Sec",
  "ロボット(メーカー / 機種 / シリアル)": "Robot (Maker / Model / Serial)",
  // 対応機種レジストリ(lib/config/index.ts の ROBOT_MODELS)に無い機種のバッジ(指示書N)
  未対応: "Unsupported",
  // ★旧「全{n}施設」は削除した(指示書AA 段階5)★ — 施設一覧のフッタを
  //   共通部品 `components/ui/Pagination` に置き換え、件数表示を
  //   「全{total}件中 {from}〜{to}件」に6画面で統一したため使われなくなった。
  //   ★`"全{n}施設を表示 →"` は別のキー★(レポート画面の
  //   `FacilityPerformanceTable` が使用中)なので消さないこと。

  // ---- スケジュール管理(F-09)----
  // ★`シリアル` / `施設名` は上に既にあるため再掲しない★
  "施設名 / シリアル": "Facility / Serial",
  該当するスケジュールはありません: "No matching schedules",
  曜日: "Days",
  時間: "Time",
  有効: "Active",

  // ---- システム管理(F-10)運用設定 ----
  "清掃監視の運用パラメータ。変更はDFAシステム管理者のみ":
    "Cleaning monitoring parameters. Changes by DFA system administrators only",
  運用設定: "Operation Settings",
  // ★旧「この画面は現在 参照のみ です。」「設定値の編集はP1後半で対応します。」は削除した
  //   (指示書AF 段階7-3)★ — 連続日数の表示上限だけが編集できるようになり、
  //   文言が事実と食い違うようになったため。他に使っている箇所が無いことを確認済み。
  "この画面は一部の設定のみ編集できます。":
    "Only some settings on this screen can be edited.",
  "編集できない設定には「編集不可」と表示します。":
    "Settings that cannot be edited are marked “Read-only”.",
  事前チェック設定: "Pre-check Settings",
  清掃開始予定時刻の何分前にロボット状態をチェックするか:
    "How many minutes before the scheduled cleaning start time the robot status is checked",
  // 数値混じり(方式A: プレースホルダ + replace)。1文字キー(第/分前/日以上連続)にしない
  "第{n}回チェック": "Check #{n}",
  "{n}分前": "{n} min before",
  // ★アラート(F-03)の種別ラベル(指示書S 2-3 / D-105)★
  //   分数を enum に持てないため、画面が `precheck_minutes` から組み立てる(方式A)。
  //   ★従来 `lib/i18n.ts` にキーが無く、EN でも日本語のまま出ていた★
  //   (`AlertsScreen.tsx` は `t()` を通していたが対訳が無かった)。
  //   ★旧「事前チェック異常」(分数が null のときの退避)は 2026-10-29 に消した(アラートは事前チェック異常だけ)★
  "{n}分前異常": "Error {n} min before",
  // ★「出発失敗」は清掃詳細ログの清掃結果列(`CLEAN_RESULT_NOT_DEPARTED_LABEL`)が使う★(アラートは 2026-10-29 から使わない)
  出発失敗: "Launch failed",
  // ★清掃結果(`clean_task_reports.status`)の表示名(第10回 N-2。D-155 項2)★ —
  //   ★英語は仮。ユーザーが後でまとめて見直す★。表に無い値(6 など)は数字のまま出る(辞書に無いので)
  清掃開始前: "Not started",
  清掃中: "Cleaning",
  清掃中断: "Interrupted",
  清掃キャンセル: "Cancelled",
  清掃正常終了: "Completed",
  清掃異常終了: "Ended abnormally",
  // ★結果待ち(ユーザー判断 2026-10-07。D-173)★ — 詳細ログの清掃結果列(`CLEAN_RESULT_PENDING_LABEL`)と
  //   ダッシュボードの「結果待ち」カード(副題「レポートの到着待ち」)が共有する。★英語は仮★
  結果待ち: "Pending",
  // ★予定外タスクの行の「予定」列(`CLEAN_LOG_UNSCHEDULED_LABEL`。指示書BG §2-1)★ — 時刻の代わりに出す
  予定外: "Unscheduled",
  レポートの到着待ち: "Waiting for report",
  // ★同じ画面(`AlertsScreen.tsx`)でキーが無かったもの(指示書S 2-3)★
  残量: "Battery",
  検知: "Detected",
  "{n}日以上連続": "{n}+ consecutive days",
  // ★連続問題施設(F-04)の summary(指示書W)★ — サーバはテンプレート文字列のまま返し
  //   (方式A)、画面が {n}/{m} を差し込む。lib/db/report-mapping.ts の
  //   SUMMARY_TEMPLATE_COMPLETION / SUMMARY_TEMPLATE_LAUNCH_FAILED / SUMMARY_TEMPLATE_MIXED
  //   と★1文字も違わないこと★(違うと translate() が未登録キーとして日本語のまま返る)
  "{n}日連続で完了率が△以下": "Completion rate △ or below for {n} consecutive days",
  "{n}日連続で出発失敗": "Launch failed for {n} consecutive days",
  "{n}日連続で△以下(うち{m}日は出発失敗)":
    "△ or below for {n} consecutive days ({m} with launch failure)",
  // ★連続問題施設(F-04)のバッジ(指示書AF 段階5-3)★ — 従来 `日` を t() に通しておらず
  //   EN でも「5日」と出ていた(指示書AF §H-4)。既存キー `"{n}日以上連続"`(上)は
  //   システム管理画面が使っているので★書き換えない★。バッジには短い方を使う。
  "{n}日": "{n} days",
  "{n}日以上": "{n}+ days",
  // ★連続問題施設の summary が表示上限で頭打ちになったとき(指示書AF 段階5-3)★ —
  //   `lib/db/report-mapping.ts` の SUMMARY_TEMPLATE_CAPPED_COMPLETION /
  //   _CAPPED_LAUNCH_FAILED / _CAPPED_MIXED と★1文字も違わないこと★。
  //   ★混在の上限用テンプレートには {m} が無い★(指示書AF §B-5)
  "{n}日以上連続で完了率が△以下":
    "Completion rate △ or below for {n}+ consecutive days",
  "{n}日以上連続で出発失敗": "Launch failed for {n}+ consecutive days",
  "{n}日以上連続で△以下(出発失敗を含む)":
    "△ or below for {n}+ consecutive days (including launch failures)",

  // ---- ダッシュボードの見出し(F-01。D-159 / 指示書AV 段階5-2)----
  // ★数値カードと清掃詳細ログ(コンパクト版)は「各清掃エリアの直近の清掃」★(7日以内)。
  //   {n} には `DASHBOARD_LATEST_RUN.lookbackDays` を差し込む(方式A。日数を直書きしない)。
  //   ★モック(`d-watch-mock.jsx:476`。旧見出し「昨夜の…」)とは文言が違う★(D-159 でユーザーが変えた)。
  //   ★EN は暫定★(ユーザーが後でまとめて見直す)
  "直近の清掃結果サマリー(各清掃エリアの最後の清掃・{n}日以内)":
    "Latest cleaning results (last run per area, within {n} days)",
  // ★スケジュール進捗カード(ユーザー判断 2026-10-29「案B」)★ — {date} には API の `schedule_progress.date` を
  //   `formatMd()`(10/28 の形)にして差し込む(方式A)。直近モードでは JST の前日。★EN は暫定★
  "スケジュール進捗({date})": "Schedule progress ({date})",
  "{date} の清掃予定はありません": "No cleaning scheduled for {date}",
  "/ {n} 完了": "/ {n} departed",

  // ---- 要レスキュー店舗(F-01。ユーザー指示 2026-09-15 / 指示書AI 段階6)----
  // ★API が返すのは識別子(reasons)と数値だけ★(D-111)。文言を作るのは
  //   `components/features/dashboard/RescueRequiredCard.tsx` の1箇所で、
  //   数値は方式A(プレースホルダ + replace)で差し込む。
  // ★`完了率` は上に既にあるため再掲しない★(重複キーは lint が落とす)
  要レスキュー店舗: "Facilities Needing Rescue",
  // ★0件でもカードを消さないので、この文言が必ず要る★(理由はカード側の docstring)
  要レスキューの店舗はありません: "No facilities need rescue",
  // ★リクエストした日とは限らない★(予定の無い施設はそれ以前の最新スケジュール存在日)
  判定日: "Evaluated on",
  前回: "Previous",
  "{n}件": "{n} facilities",
  "{n}日連続で未実施": "Not executed for {n} consecutive days",
  "連続問題レベル3({n}日連続)": "Escalation level 3 ({n} consecutive days)",
  "完了率が前回比{n}%低下": "Completion rate down {n}% vs. previous",
  // ★低下の比が算出できないのに急落と判定された場合の保険★(0% と書かないため)
  完了率が前回比で急落: "Completion rate dropped sharply vs. previous",
  // ★初期表示は5件で、超えるぶんはこのボタンで開く★(指示書AJ 段階1 / §4-4)—
  //   ★API は件数で切らない★ので、{n} には「まだ見えていない件数」が入る(方式A。D-111)。
  //   ★閉じる側は既存キー(モーダルの `閉じる`)を使い回す★ — 同じ語・同じ意味なので
  //   キーを増やすと対訳が二重管理になる(重複キーは lint が落とす)。
  "もっと表示(あと{n}件)": "Show all ({n} more)",
  完了率の判定閾値: "Completion Rate Thresholds",
  "清掃詳細ログの◎○△×判定に使用する閾値。上から順に評価します":
    "Thresholds for the ◎○△× grade in the cleaning detail log. Evaluated from the top",
  // ★境界は「上のランクに入る」(D-139。2026-09-16)★ — 90.0 は ◎、70.0 は ○、10.0 は △。
  //   旧規則(下位に落ちる)の文言に戻さないこと。`lib/judge/grade.ts` の比較は `>=`。
  "判定は丸める前の生値で行います(表示は小数第1位を四捨五入した整数)。境界値ちょうどは上位の判定になります。":
    "Grading uses the raw value before rounding (the display is rounded to the nearest integer). A value exactly on a boundary is included in the higher grade.",
  "アドバイス エスカレーション閾値": "Advice Escalation Thresholds",
  "同一施設で問題(出発失敗 または △以下)が連続した場合のレベル引き上げ基準":
    "Criteria for raising the level when problems (departure failure or △ and below) continue at the same facility",
  "「連続」はカレンダー日ではなく、清掃スケジュールが存在する日の連続で数えます。清掃予定のない日は連続を途切れさせません。":
    "“Consecutive” is counted over days that have a cleaning schedule, not calendar days. Days with no cleaning scheduled do not break the streak.",
  // ★連続日数の表示上限(指示書AF 段階7)★ — この画面で唯一編集できる設定。
  //   `日` は単位のラベル(入力欄の右)で、`t("日")` を呼ぶのはこの1箇所だけ。
  //   数値と連結する文言(`{n}日` / `{n}日以上` / `{n}日以上連続`)は別キー(方式A)。
  連続日数の表示上限: "Consecutive Days Display Cap",
  "連続問題施設などで、連続日数がこの日数を超えたとき「{n}日以上」と表示します":
    "When a streak exceeds this number of days, it is shown as “{n}+ days” (e.g. in Facilities with Continuous Problems)",
  日: "days",
  保存: "Save",
  既定値に戻す: "Reset to default",
  保存しました: "Saved",
  // 空欄のまま保存した場合の画面側の文言(`Number("") === 0` を送らないための足切り)
  日数を入力してください: "Enter the number of days",
  表示上限の取得に失敗しました: "Failed to load the display cap",
  // PATCH /settings/display-limits がエラー時に返す文言。
  // 入力欄の下の赤字にはサーバーの文言がそのまま入るため、ここに載せた分だけ EN になる
  // (`translate()` はキーが無ければ原文を返すので、未収録でも表示は壊れない)
  "JSONの形式が不正です": "Invalid JSON format",
  "keyの値が不正です": "Invalid key",
  "valueの値が不正です": "Invalid value",
  設定の保存に失敗しました: "Failed to save the setting",
  アドバイス文言: "Advice Text",
  清掃詳細ログに表示される推定原因と推奨アクション:
    "Estimated causes and recommended actions shown in the cleaning detail log",
  "発火条件(どのルールがいつ出るか)は仕様のため編集不可です。":
    "Trigger conditions (which rule fires when) are part of the specification and cannot be edited.",
  アドバイス文言は登録されていません: "No advice text registered",
  // アドバイスのレベル表示(`LEVEL_LABEL`)。★`注意` は上に既にあるため再掲しない★
  警告: "Warning",
  重大: "Critical",
  // アドバイスのフェーズ表示(`PHASE_LABEL`)
  "Phase A(完了率・連続日数)": "Phase A (Completion rate / Consecutive days)",
  "Phase B(状態・エラー種別)": "Phase B (Status / Error type)",
  編集済み: "Customized",
  推奨アクション: "Recommended Actions",
  発火条件: "Trigger",
  編集不可: "Read-only",

  // ---- 一覧のページ送り(指示書AA)----
  // ★プレースホルダが3つある(既存の {n} 1つより多い)。replace を3回つなげる★
  //   t("全{total}件中 {from}〜{to}件")
  //     .replace("{total}", String(total))
  //     .replace("{from}", String(from))
  //     .replace("{to}", String(to))
  // ★この文言を組み立てるのは components/ui/Pagination.tsx の1箇所だけ★
  //   (各画面で組み立てると .replace() の連鎖が6箇所に増える)
  "全{total}件中 {from}〜{to}件": "{from}–{to} of {total}",
  前へ: "Prev",
  次へ: "Next",
  // ★<nav> の aria-label★(画面には出ない。スクリーンリーダー向け)
  ページ送り: "Pagination",
  // ★CSVは表示中のページではなく全件★
  //   (app/api/clean-logs/export/route.ts が page / per_page を無視する契約)
  "CSVは絞り込み条件に一致する全件を出力します(表示中のページだけではありません)":
    "CSV exports all rows matching the filters, not just the current page",
  // ---- CSVの行数上限(ユーザー指示 2026-09-15 / 指示書AI 段階8)----
  // ★プレースホルダは {total} と {max} の2つ。数値は画面側で差し込む(D-111)★
  //   t("この期間だとCSV出力できません({total}件/上限{max}件)。期間を短くするか、店舗で絞り込んでください。")
  //     .replace("{total}", String(total))
  //     .replace("{max}", String(MAX_CSV_ROWS))
  // ★★`disabled` だけにして理由を出さないことはしない★★ —
  //   押せないボタンは、理由が書いていないと不具合と区別がつかない(D-053 と同じ理由)。
  "この期間だとCSV出力できません({total}件/上限{max}件)。期間を短くするか、店舗で絞り込んでください。":
    "CSV export is unavailable for this range ({total} rows / limit {max}). Narrow the date range or filter by facility.",
  // ---- 清掃詳細ログの期間が空のとき(D-159 追記 / 指示書AV 段階5b-5)----
  // ★既定は「期間を指定しない(全期間)」★(ユーザー指示 2026-10-06「C」)。
  //   `DetailLogScreen.tsx` の日付入力の placeholder と、両方空のときに出す表示で使う。★EN は暫定★
  "指定なし(全期間)": "Not set (all)",
  // ---- レポート画面を1本ずつ出す(指示書AL 段階2 / §3-4)----
  // ★KPIだけが 422(対象顧客が1社に定まらない)のとき、そのブロックにだけ出す誘導★。
  //   ★件数 {n} は `details.customer_ids` の長さ★ — ★文言を読ませるだけにしない★ため、
  //   「何社の中から選ぶのか」を数で見せる(`lib/reports/block-visibility.ts`)。
  KPI: "KPI",
  "稼働率・出発成功率・清掃完了率・エラー発生率":
    "Uptime / Launch / Completion / Error rates",
  顧客を選んでください: "Select a customer",
  "選べる顧客が{n}件あります": "{n} customers to choose from",
  顧客を選ぶ: "Choose a customer",
  この条件では選べる顧客がありません: "No customers available for this filter",
  データの取得に失敗しました: "Failed to load data",

  // ---- 清掃面積(指示書AN 段階5。ユーザー指示 2026-09-16「新画面」)----
  // ★`完了率` / `検索` / `ホールディングス` / `顧客` / `施設名` / `読み込み中…` /
  //   `閉じる` / `もっと表示(あと{n}件)` / `{n}件` は上に既にあるため再掲しない★
  //   (重複キーは lint が落とす)。
  // ★単位(㎡)は辞書に入れない★ — `lib/config` の `AREA_UNIT` が持つ(指示書AN §2-4)。
  //   ★対訳が要るのは見出しの語だけ★で、単位は言語で変わらない。
  清掃面積: "Cleaning Area",
  清掃対象面積: "Target Area",
  清掃実施回数: "Cleanings",
  // ★日付入力の aria-label★(画面には出ない。スクリーンリーダー向け)
  開始日: "From",
  終了日: "To",
  // ★粒度タブ★。`ホールディングス` / `顧客` は分類の見出し語(上)を使い回す
  店舗: "Stores",
  // ★★ホールディングスに属さない顧客をまとめる行★★(`CLEAN_AREA_GROUP_NONE_LABEL`)—
  //   ★「グループ0」を作らない★(D-011)。★API が返す `name` は使わず、画面が
  //   `scope === "group" && id === null` を見てこのキーを引く★ — 生文字列を
  //   そのまま `t()` に通すと、同名の実在する屋号名があったときに誤訳するため。
  グループなし: "No Holdings",
  該当データがありません: "No data",
  // ★「直近2年ぶん」の明記★(指示書AN §2-2 / 落とし穴10)—
  //   ★消さないこと★。書かないと3年目に数字が減ったときに問い合わせが来る。
  "期間を指定しないときは全期間の合計です。ただしデータの保持は2年なので、実際には直近2年ぶんの合計になります。":
    "With no date range, this is the total for all stored data. Data is retained for 2 years, so in practice it covers the most recent 2 years.",
  // ★総清掃面積の母集団の注記(D-156 論点7。指示書BG §2-5 で完了率・実施回数にも予定外を含めた)。★英訳は暫定★(ユーザーが後でまとめて見直す)
  "清掃面積・完了率・実施回数には予定外の清掃も含みます":
    "Area, completion rate and run count include unscheduled runs.",

  // ---- エラー一覧(D-160 / 指示書AW 段階5-5)----
  // ★EN は暫定★(ユーザーが後でまとめて見直す)。
  // ★`施設名` / `シリアル` / `未解消` / `対応中` / `解消` / `備考を入力` /
  //   `保存中…` / `保存に失敗しました` / `対応状況の更新に失敗しました` / `読み込み中…` /
  //   `指定なし(全期間)` / `ホールディングス` / `顧客` / `データの取得に失敗しました` は上に既にあるため再掲しない★
  //   (重複キーは lint が落とす)。
  // ★件数は `全{n}件` を使う★ — `{n}件` は EN が "{n} facilities"(施設数の文言)なので件数表示に使えない(指示書AW 4-18)。
  // ★`error_type` / `error_level` / `serial_number` の値は辞書に入れない★(生値のまま出す。D-077)。
  "🚨 エラー一覧": "🚨 Errors",
  エラー一覧: "Errors",
  "未対応のエラー(ロボット・制御盤・セキュリティ・清掃開始失敗)":
    "Open errors (robots, control panels, security, start failures)",
  "全{n}件": "{n} total",
  検知時刻: "Detected at",
  種類: "Type",
  レベル: "Level",
  紐づく予定: "Schedule",
  対応状況: "Status",
  備考: "Note",
  // ★「詳細」の列(エラー内容。指示書BE §12。2026-10-27 ユーザー指示で種類の2行目から独立した列にした)★
  詳細: "Details",
  // ★「紐づく予定」のラベル(`errorScheduleLabel()`。指示書AW 表3)★ — `{n}` は replace(`"{n}分前"` と同じ方式A)
  本清掃: "Main run",
  "事前チェック({n}分前)": "Pre-check ({n} min before)",
  事前チェック: "Pre-check",
  予定なし: "No schedule",
  予定不明: "Unknown schedule",
  解消済みも表示: "Show resolved",
  該当するエラーはありません: "No matching errors",
  エラーが見つかりません: "Error not found",
  // ★エラー内容の選択(指示書BE。D-185)★
  // ★分類名・詳細の文字列は辞書に入れない★(`t()` に通すと「該当するエラーはありません」等が誤訳される)。
  // ★EN は暫定★。
  未選択: "Not selected",
  "不明({id})": "Unknown ({id})",
  選択: "Select",
  エラー内容を選ぶ: "Choose an error description",
  すべての分類: "All categories",
  分類: "Category",
  キーワード: "Keyword",
  "{n}件 / {total}件": "{n} / {total}",
  クリア: "Clear",
  備考必須: "Note required",
  該当するエラー内容がありません: "No matching error descriptions",
  エラー内容の選択肢を読み込めませんでした: "Could not load error descriptions",
  その他を選んだときは備考を入力してください: "Enter a note when you choose Other",
  "備考を入力(必須)": "Enter a note (required)",
  // ★保存できなかった原因(指示書BF §1。`lib/errors/save-error-message.ts`)★ — EN は暫定。
  // ★`保存に失敗しました` / `その他を選んだときは備考を入力してください` は上に既にあるため再掲しない★
  この行ではエラー内容を選べません: "An error description cannot be chosen for this row",
  "選んだエラー内容は今は使えません。選び直してください":
    "The chosen error description is no longer available. Please choose again",
  "備考は{n}文字以内で入力してください": "Enter a note of {n} characters or fewer",
  入力内容を確かめてください: "Please check your input",
  "このエラーは削除されたため保存できません。画面を読み込み直してください":
    "This error was deleted and cannot be saved. Please reload the page",
  ログインし直してください: "Please sign in again",
  "サーバーで保存に失敗しました(DB への書き込みエラー)。時間をおいて保存し直し、続くときは管理者に連絡してください":
    "The server failed to save (database write error). Please try again later, and contact an administrator if it continues",
  "通信に失敗しました。接続を確かめて保存し直してください":
    "Network error. Please check your connection and save again",

  // ---- 対応状況・備考の編集モード(エラー一覧・アラート。ユーザー指示 2026-10-06。D-165)----
  // ★EN は暫定★。★`保存` / `保存中…` は上に既にあるため再掲しない★(重複キーは lint が落とす)。
  編集: "Edit",
  キャンセル: "Cancel",
  "保存({n}件)": "Save ({n})",
  "変更を破棄しますか?": "Discard changes?",
  "{n}件を保存できませんでした": "{n} could not be saved",
  編集中は絞り込みとページ送りを使えません: "Filters and paging are disabled while editing",

  // ---- アラート・D-Sec アラート: 状態ボタンと数値カード(D-181。D-Sec はアラートの文言に揃えた)----
  // ★EN はモック(d-watch-mock.jsx:140)の対訳★。★`未解消` / `対応中` / `解消` は上に既にあるため再掲しない★。
  // ★足すとアラート画面のボタン・数値カードも EN になる(いままで日本語のまま出ていた)★
  未解消のみ: "Unresolved",
  解消済み: "Resolved",

  // ---- アラート: 本清掃が成功した事前チェック異常(ユーザー指示 2026-10-07。D-171)----
  // ★EN は暫定★
  本清掃は成功: "Main run succeeded",

  // ---- 画面の失敗・空の表示(指示書BC 段階2。D-183)----
  // ★EN は暫定★
  アドバイスの取得に失敗しました: "Failed to load advice",
  該当するアドバイスはありません: "No advice for this run",
  該当するログはありません: "No matching logs",
  "全{n}施設のうち上位{m}施設を表示しています(表示の上限)": "Showing the top {m} of {n} facilities (display limit)",
  "全{n}件のうち{m}件を表示しています(表示の上限)": "Showing {m} of {n} (display limit)",

  // ---- 文言・a11y(指示書BC 段階5。D-183)----
  // ★EN は暫定★
  "D-Secログ": "D-Sec Logs",
  "施設名・シリアルで検索": "Search by facility or serial",
  "1行=1ロボット×1スケジュール実行": "1 row = 1 robot × 1 scheduled run",
  "← 戻る": "← Back",
  "シリアルNo.": "Serial No.",
  前の期間: "Previous period",
  次の期間: "Next period",
  "{n}日以上連続で△以下または出発失敗の施設": "Facilities rated △ or worse, or failing to launch, for {n}+ consecutive days",
  "この期間のエラー発生件数(種別ごと)": "Errors in this period (by type)",
  該当なし: "None",
  "{n}施設": "{n} facilities",
  判定基準: "Grading criteria",
  // ログイン画面(ログイン画面も ThemeProvider の中。段階3 の後は保存した言語で出る)
  アクセストークン: "Access token",
  ログイン: "Sign in",
  "確認中…": "Checking…",
  "社内利用のみ。接続元IPも制限されています。": "Internal use only. Source IP addresses are restricted.",
  トークンが違います: "Invalid token",
  トークンを入力してください: "Please enter the token",
  このIPアドレスからは利用できません: "Access from this IP address is not allowed",
  "試行回数が多すぎます。しばらくしてから再度お試しください": "Too many attempts. Please try again later",
  "ログインできませんでした。時間をおいて再度お試しください": "Could not sign in. Please try again later",
  "通信に失敗しました。時間をおいて再度お試しください": "Network error. Please try again later",
};

export function translate(lang: Lang, text: string): string {
  if (lang === "en" && EN[text]) return EN[text];
  return text;
}