From 7bfa1e5265064f98745bc06f198a551d35c672d1 Mon Sep 17 00:00:00 2001 From: Eotel <49769982+Eotel@users.noreply.github.com> Date: Sun, 25 Oct 2026 21:30:27 +0900 Subject: [PATCH] fix: share dictionary limits and count Unicode characters consistently --- ARCHITECTURE.md | 8 ++++ backend/app/models.py | 15 ++++--- backend/tests/test_dictionary.py | 11 +++++ .../rejected-81.png | Bin 0 -> 24403 bytes .../saved-80.png | Bin 0 -> 26665 bytes .../verification.json | 20 +++++++++ docs/dictionary.md | 2 + .../active/2026-10-25-unicode-form-limits.md | 39 ++++++++++++++++++ docs/generated/README.md | 4 +- frontend/src/dictionary.ts | 11 ++--- ...ry-case-folding.ts => dictionary-rules.ts} | 1 + frontend/src/machines/operator.ts | 4 +- frontend/src/presenters/operator.ts | 6 ++- frontend/src/roots.test.tsx | 25 +++++++++++ .../views/operator/dictionary-settings.tsx | 6 +-- scripts/export_openapi.py | 20 +++++---- 16 files changed, 143 insertions(+), 29 deletions(-) create mode 100644 docs/assets/dictionary-contract-2026-10-25/rejected-81.png create mode 100644 docs/assets/dictionary-contract-2026-10-25/saved-80.png create mode 100644 docs/assets/dictionary-contract-2026-10-25/verification.json create mode 100644 docs/exec-plans/active/2026-10-25-unicode-form-limits.md rename frontend/src/generated/{dictionary-case-folding.ts => dictionary-rules.ts} (99%) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index cf2bc68..cb09eb0 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -87,6 +87,14 @@ SSE は購読登録と初期スナップショットの取得を中断なしに [API の型生成](docs/generated/README.md)でバックエンドのモデルから OpenAPI と TypeScript 型を生成する。フロントエンドの Adapter は生成した型を使用し、API モデルを独自に再定義しない。 +## 辞書の制限と検証 + +入力上限は [models.py](backend/app/models.py) の `DICTIONARY_LIMITS` に置き、Pydanticモデルと、[生成した辞書規則](frontend/src/generated/dictionary-rules.ts)で共有する。`check:api` が上限値とUnicode case foldingの一致を検査する。APIの型は従来どおりOpenAPIから生成し、画面の下書きや操作イベントとは分ける。 + +画面の [dictionary.ts](frontend/src/dictionary.ts) は、入力中の下書きを保持しながら保存前にエラーを示す。APIのPydantic検証は画面以外の呼出しも受け付けるため残す。HTMLのUTF-16文字数による切り詰めは使わず、画面とAPIがUnicodeコードポイントで上限を判定する。 + +辞書の保存・改版照合は `WordDictionary` が担当する。音声認識は次の開始時に有効な表記を取得し、AI校正は入力を受け取った時点の辞書を保持する。手修正の保護と公開可否は `CaptionRelay`、提供元通信と処理の取消は各Adapterと `CaptionWorkflow` に保つ。この整理では入力・応答の形式、上限値、辞書の保存形式を変更しない。 + ## 画面の責務 画面と共有の Button・Textarea はそれぞれの Scope を持って Root の配下に置き、Passive View、Presenter、Mediator、Adapter に責務を分ける。親へ伝播する操作イベントと XState の状態遷移の詳細は[フロントエンドの構成](docs/frontend-architecture.md)を参照する。 diff --git a/backend/app/models.py b/backend/app/models.py index 0ceb118..5693c5d 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -8,13 +8,16 @@ class Model(BaseModel): model_config = ConfigDict(extra="forbid", strict=True, validate_assignment=True) -WordText = Annotated[str, Field(min_length=1, max_length=80, pattern=r"^[^\x00-\x1f\x7f<>]+$")] +DICTIONARY_LIMITS = {"maxWordLength": 80, "maxVariants": 5, "maxEntries": 100, "maxTotalLength": 10000} + + +WordText = Annotated[str, Field(min_length=1, max_length=DICTIONARY_LIMITS["maxWordLength"], pattern=r"^[^\x00-\x1f\x7f<>]+$")] class DictionaryWord(Model): term: WordText - reading: str = Field(default="", max_length=80, pattern=r"^[^\x00-\x1f\x7f<>]*$") - variants: list[WordText] = Field(default_factory=list, max_length=5) + reading: str = Field(default="", max_length=DICTIONARY_LIMITS["maxWordLength"], pattern=r"^[^\x00-\x1f\x7f<>]*$") + variants: list[WordText] = Field(default_factory=list, max_length=DICTIONARY_LIMITS["maxVariants"]) enabled: bool = True @field_validator("term", "reading") @@ -33,15 +36,15 @@ def valid_word(self) -> "DictionaryWord": class DictionarySettings(Model): - entries: list[DictionaryWord] = Field(default_factory=lambda: list[DictionaryWord](), max_length=100) + entries: list[DictionaryWord] = Field(default_factory=lambda: list[DictionaryWord](), max_length=DICTIONARY_LIMITS["maxEntries"]) @model_validator(mode="after") def unique_terms(self) -> "DictionarySettings": keys = [unicodedata.normalize("NFKC", entry.term).casefold() for entry in self.entries] if len(set(keys)) != len(keys): raise ValueError("同じ表記は一度だけ登録してください。") - if sum(len(entry.term) + len(entry.reading) + sum(map(len, entry.variants)) for entry in self.entries) > 10000: - raise ValueError("辞書は合計10,000文字以内で登録してください。") + if sum(len(entry.term) + len(entry.reading) + sum(map(len, entry.variants)) for entry in self.entries) > DICTIONARY_LIMITS["maxTotalLength"]: + raise ValueError(f"辞書は合計{DICTIONARY_LIMITS['maxTotalLength']:,}文字以内で登録してください。") return self diff --git a/backend/tests/test_dictionary.py b/backend/tests/test_dictionary.py index 74b60e8..feb30e6 100644 --- a/backend/tests/test_dictionary.py +++ b/backend/tests/test_dictionary.py @@ -58,6 +58,17 @@ def test_dictionary_rejects_invalid_or_ambiguous_registration(entries): assert client.get("/api/dictionary").json()["entries"] == [] +def test_dictionary_counts_supplementary_characters_and_keeps_saved_words_on_rejection(): + text = "𠮷" * 80 + with TestClient(create_app(environment={})) as client: + response = client.put("/api/dictionary", json={"base_revision": 0, "entries": [{"term": text, "reading": text}]}) + assert response.status_code == 200 + assert response.json()["entries"][0]["term"] == text + response = client.put("/api/dictionary", json={"base_revision": 1, "entries": [{"term": text + "𠮷"}]}) + assert response.status_code == 422 + assert client.get("/api/dictionary").json()["entries"][0]["term"] == text + + def test_failed_atomic_save_preserves_memory_and_file(tmp_path: Path, monkeypatch): path = tmp_path / "words" / "dictionary.json" dictionary = WordDictionary(path) diff --git a/docs/assets/dictionary-contract-2026-10-25/rejected-81.png b/docs/assets/dictionary-contract-2026-10-25/rejected-81.png new file mode 100644 index 0000000000000000000000000000000000000000..ae651fd8e2b6dbe5da3f8ccbbaccb363b679c6d0 GIT binary patch literal 24403 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、重複する表記を拒否する。表記の重複は全角/半角や大文字/小文字の差を無視して確認する。「Straße / STRASSE」のようなUnicodeの表記も保存前に重複と判断する。画面の文字変換表はサーバーのPythonから生成し、[生成結果の検証](generated/README.md)で一致を確認する。 +文字数はUnicodeコードポイントで数え、「𠮷」のような補助漢字も1文字とする。上限を超えた入力は保存せず、下書きを残してエラーを表示する。上限値と文字変換表は[バックエンドから生成](generated/README.md)し、画面とAPIで同じ値を使う。 + 「有効」を外すと、保存後の認識・校正では参照しなくなる。削除や編集は保存するまで適用しない。パネルを閉じたり字幕を更新取得したりしても下書きを保つ。「保存済みに戻す」は入力中の辞書を破棄し、接続先から読み直す。 別の操作画面が保存した場合は、古い辞書での上書きを拒否する。入力は保持するため、必要な内容を確認してから「保存済みに戻す」で最新の辞書を読み直す。保存先に書き込めない場合も、保存済みの辞書と下書きを保持する。 diff --git a/docs/exec-plans/active/2026-10-25-unicode-form-limits.md b/docs/exec-plans/active/2026-10-25-unicode-form-limits.md new file mode 100644 index 0000000..5f636ae --- /dev/null +++ b/docs/exec-plans/active/2026-10-25-unicode-form-limits.md @@ -0,0 +1,39 @@ +# ルーム名と背景の文字数をAPIへそろえる + +## Goal + +ルーム名とトークの背景でも、辞書と同じくUnicodeコードポイントで上限を判定する。 + +## Context + +#21のNode更新はPR #34でマージ済み。#19の辞書の制限値はバックエンドから生成する。調査ではhome.tsxのルーム名maxLength=120、背景maxLength=4000、ai-settings.tsxの背景maxLength=4000にも同じUTF-16による切り詰めが残っている。APIのPydanticはUnicode文字数で判定する。 + +## Scope and Non-Goals + +上記3入力の文字数、上限超過時の下書き保持と保存拒否、制限値の生成を対象にする。ルーム切替、API形式、認識Adapter、AIの要求内容、公開方針は変えない。 + +## Constraints From Decisions + +- 既存の未コミット6項目と別作業ツリーを保持する。 +- Viewは描画と通知のみ。入力検証はRoot/Mediatorなど既存の担当層へ置く。 +- 生成値はPydanticのOpenAPIから作り、手で生成物を編集しない。 +- 物理マイクと実音声サービスは使用しない。 + +## Remaining Work + +1. 3入力の所有者と保存前の検査を確認し、補助漢字の切り詰めを再現する。 +2. OpenAPIの上限値を生成し、入力の保存判定をUnicodeへそろえる。 +3. 実APIとブラウザで上限以内の保存と上限超過時の保持を確認する。 +4. 全チェックと別セッション監査、PR・CI・マージを完了する。 + +## Now And Next + +homeのRootとOperator Mediatorの保存処理を読み、既存の辞書の修正と同じ考え方で回帰テストを追加する。 + +## Blockers And Human Tasks + +なし。 + +## Verification + +ルーム名120文字、背景4000文字の補助漢字を入力でき、APIが受け付ける。超過時は明確なエラーを示し、下書きと保存済み設定を保持する。API型と既存のルーム保護を維持する。 diff --git a/docs/generated/README.md b/docs/generated/README.md index e0d24a2..dbba51a 100644 --- a/docs/generated/README.md +++ b/docs/generated/README.md @@ -7,7 +7,7 @@ API モデルを変更するときに参照する。生成の流れは次のと | 生成元 | [backend/app/models.py](../../backend/app/models.py) の Pydantic モデルと [main.py](../../backend/app/main.py) のルート・応答モデル | | OpenAPI の生成 | [scripts/export_openapi.py](../../scripts/export_openapi.py) → [openapi.json](openapi.json) | | TypeScript の生成 | `openapi-typescript` → [frontend/src/generated/api.d.ts](../../frontend/src/generated/api.d.ts) | -| 辞書の文字変換表 | [scripts/export_openapi.py](../../scripts/export_openapi.py) → [dictionary-case-folding.ts](../../frontend/src/generated/dictionary-case-folding.ts)。サーバーのPythonと同じUnicode case foldingを使う | +| 辞書の入力制限と文字変換表 | [scripts/export_openapi.py](../../scripts/export_openapi.py) → [dictionary-rules.ts](../../frontend/src/generated/dictionary-rules.ts)。Pydanticモデルと同じ上限値、サーバーのPythonと同じUnicode case foldingを使う | 入力、操作画面用字幕、観客用字幕、セッション、表示切り替えの型をフロントエンドへ渡す。公開モデルの境界は[システム構成](../../ARCHITECTURE.md#api-と公開データの境界)を参照する。 @@ -18,6 +18,6 @@ npm run check:api npm run build ``` -生成ファイルは手で編集しない。モデル変更と再生成結果を同じ変更に含める。`check:api` は現在の FastAPI モデル、保存された OpenAPI、TypeScript の生成結果を照合し、辞書の文字変換表がサーバーのPythonと一致することも確認する。全体の検証はルートで `bash scripts/check.sh` を実行する。 +生成ファイルは手で編集しない。モデル変更と再生成結果を同じ変更に含める。`check:api` は現在の FastAPI モデル、保存された OpenAPI、TypeScript の生成結果を照合し、辞書の入力制限と文字変換表がサーバーのPythonと一致することも確認する。全体の検証はルートで `bash scripts/check.sh` を実行する。 TypeScript の型はブラウザで JSON の実行時検証を行うものではない。型生成ツールの対応範囲に合わせ、TypeScript は 5.9 系を使用する。 diff --git a/frontend/src/dictionary.ts b/frontend/src/dictionary.ts index 022f16a..fd0659c 100644 --- a/frontend/src/dictionary.ts +++ b/frontend/src/dictionary.ts @@ -1,5 +1,6 @@ import type { DictionarySnapshot, DictionaryUpdate, DictionaryWord } from "./domain"; -import { dictionaryCaseFolding } from "./generated/dictionary-case-folding"; +import { dictionaryCaseFolding, dictionaryLimits } from "./generated/dictionary-rules"; +export { dictionaryLimits } from "./generated/dictionary-rules"; export const emptyDictionary: DictionarySnapshot = { revision: 0, entries: [] }; export interface DictionaryDraft { @@ -22,21 +23,21 @@ export function dictionaryUpdate(draft: DictionaryDraft): DictionaryUpdate { export function dictionaryValidation(draft: DictionaryDraft): string { const { entries } = dictionaryUpdate(draft); - if (entries.length > 100) return "単語は100件まで登録できます。"; + if (entries.length > dictionaryLimits.maxEntries) return `単語は${dictionaryLimits.maxEntries}件まで登録できます。`; const terms = new Set(); let total = 0; for (const [index, word] of entries.entries()) { const label = `${index + 1}件目`; if (!word.term) return `${label}の表記を入力してください。`; const values = [word.term, word.reading, ...word.variants]; - if (values.some(value => Array.from(value).length > 80)) return `${label}は各欄80文字以内で入力してください。`; + if (values.some(value => Array.from(value).length > dictionaryLimits.maxWordLength)) return `${label}は各欄${dictionaryLimits.maxWordLength}文字以内で入力してください。`; if (values.some(value => /[\x00-\x1f\x7f<>]/.test(value))) return `${label}に改行や < > は使えません。誤認識の表記は1行ずつ入力してください。`; - if (word.variants.length > 5) return `${label}の誤認識の表記は5件以内で入力してください。`; + if (word.variants.length > dictionaryLimits.maxVariants) return `${label}の誤認識の表記は${dictionaryLimits.maxVariants}件以内で入力してください。`; if (new Set(word.variants).size !== word.variants.length) return `${label}の誤認識の表記が重複しています。`; const key = Array.from(word.term.normalize("NFKC"), character => dictionaryCaseFolding[character] ?? character).join(""); if (terms.has(key)) return `${label}の表記は登録済みです。同じ表記は一度だけ登録してください。`; terms.add(key); total += values.reduce((sum, value) => sum + Array.from(value).length, 0); } - return total > 10000 ? "辞書は合計10,000文字以内で登録してください。" : ""; + return total > dictionaryLimits.maxTotalLength ? `辞書は合計${dictionaryLimits.maxTotalLength.toLocaleString("ja-JP")}文字以内で登録してください。` : ""; } diff --git a/frontend/src/generated/dictionary-case-folding.ts b/frontend/src/generated/dictionary-rules.ts similarity index 99% rename from frontend/src/generated/dictionary-case-folding.ts rename to frontend/src/generated/dictionary-rules.ts index aebff5d..a38a177 100644 --- a/frontend/src/generated/dictionary-case-folding.ts +++ b/frontend/src/generated/dictionary-rules.ts @@ -1,2 +1,3 @@ // Generated by scripts/export_openapi.py with Unicode 15.0.0; do not edit. +export const dictionaryLimits = {"maxWordLength":80,"maxVariants":5,"maxEntries":100,"maxTotalLength":10000} as const; export const dictionaryCaseFolding: Readonly> = {"A":"a","B":"b","C":"c","D":"d","E":"e","F":"f","G":"g","H":"h","I":"i","J":"j","K":"k","L":"l","M":"m","N":"n","O":"o","P":"p","Q":"q","R":"r","S":"s","T":"t","U":"u","V":"v","W":"w","X":"x","Y":"y","Z":"z","\u00b5":"\u03bc","\u00c0":"\u00e0","\u00c1":"\u00e1","\u00c2":"\u00e2","\u00c3":"\u00e3","\u00c4":"\u00e4","\u00c5":"\u00e5","\u00c6":"\u00e6","\u00c7":"\u00e7","\u00c8":"\u00e8","\u00c9":"\u00e9","\u00ca":"\u00ea","\u00cb":"\u00eb","\u00cc":"\u00ec","\u00cd":"\u00ed","\u00ce":"\u00ee","\u00cf":"\u00ef","\u00d0":"\u00f0","\u00d1":"\u00f1","\u00d2":"\u00f2","\u00d3":"\u00f3","\u00d4":"\u00f4","\u00d5":"\u00f5","\u00d6":"\u00f6","\u00d8":"\u00f8","\u00d9":"\u00f9","\u00da":"\u00fa","\u00db":"\u00fb","\u00dc":"\u00fc","\u00dd":"\u00fd","\u00de":"\u00fe","\u00df":"ss","\u0100":"\u0101","\u0102":"\u0103","\u0104":"\u0105","\u0106":"\u0107","\u0108":"\u0109","\u010a":"\u010b","\u010c":"\u010d","\u010e":"\u010f","\u0110":"\u0111","\u0112":"\u0113","\u0114":"\u0115","\u0116":"\u0117","\u0118":"\u0119","\u011a":"\u011b","\u011c":"\u011d","\u011e":"\u011f","\u0120":"\u0121","\u0122":"\u0123","\u0124":"\u0125","\u0126":"\u0127","\u0128":"\u0129","\u012a":"\u012b","\u012c":"\u012d","\u012e":"\u012f","\u0130":"i\u0307","\u0132":"\u0133","\u0134":"\u0135","\u0136":"\u0137","\u0139":"\u013a","\u013b":"\u013c","\u013d":"\u013e","\u013f":"\u0140","\u0141":"\u0142","\u0143":"\u0144","\u0145":"\u0146","\u0147":"\u0148","\u0149":"\u02bcn","\u014a":"\u014b","\u014c":"\u014d","\u014e":"\u014f","\u0150":"\u0151","\u0152":"\u0153","\u0154":"\u0155","\u0156":"\u0157","\u0158":"\u0159","\u015a":"\u015b","\u015c":"\u015d","\u015e":"\u015f","\u0160":"\u0161","\u0162":"\u0163","\u0164":"\u0165","\u0166":"\u0167","\u0168":"\u0169","\u016a":"\u016b","\u016c":"\u016d","\u016e":"\u016f","\u0170":"\u0171","\u0172":"\u0173","\u0174":"\u0175","\u0176":"\u0177","\u0178":"\u00ff","\u0179":"\u017a","\u017b":"\u017c","\u017d":"\u017e","\u017f":"s","\u0181":"\u0253","\u0182":"\u0183","\u0184":"\u0185","\u0186":"\u0254","\u0187":"\u0188","\u0189":"\u0256","\u018a":"\u0257","\u018b":"\u018c","\u018e":"\u01dd","\u018f":"\u0259","\u0190":"\u025b","\u0191":"\u0192","\u0193":"\u0260","\u0194":"\u0263","\u0196":"\u0269","\u0197":"\u0268","\u0198":"\u0199","\u019c":"\u026f","\u019d":"\u0272","\u019f":"\u0275","\u01a0":"\u01a1","\u01a2":"\u01a3","\u01a4":"\u01a5","\u01a6":"\u0280","\u01a7":"\u01a8","\u01a9":"\u0283","\u01ac":"\u01ad","\u01ae":"\u0288","\u01af":"\u01b0","\u01b1":"\u028a","\u01b2":"\u028b","\u01b3":"\u01b4","\u01b5":"\u01b6","\u01b7":"\u0292","\u01b8":"\u01b9","\u01bc":"\u01bd","\u01c4":"\u01c6","\u01c5":"\u01c6","\u01c7":"\u01c9","\u01c8":"\u01c9","\u01ca":"\u01cc","\u01cb":"\u01cc","\u01cd":"\u01ce","\u01cf":"\u01d0","\u01d1":"\u01d2","\u01d3":"\u01d4","\u01d5":"\u01d6","\u01d7":"\u01d8","\u01d9":"\u01da","\u01db":"\u01dc","\u01de":"\u01df","\u01e0":"\u01e1","\u01e2":"\u01e3","\u01e4":"\u01e5","\u01e6":"\u01e7","\u01e8":"\u01e9","\u01ea":"\u01eb","\u01ec":"\u01ed","\u01ee":"\u01ef","\u01f0":"j\u030c","\u01f1":"\u01f3","\u01f2":"\u01f3","\u01f4":"\u01f5","\u01f6":"\u0195","\u01f7":"\u01bf","\u01f8":"\u01f9","\u01fa":"\u01fb","\u01fc":"\u01fd","\u01fe":"\u01ff","\u0200":"\u0201","\u0202":"\u0203","\u0204":"\u0205","\u0206":"\u0207","\u0208":"\u0209","\u020a":"\u020b","\u020c":"\u020d","\u020e":"\u020f","\u0210":"\u0211","\u0212":"\u0213","\u0214":"\u0215","\u0216":"\u0217","\u0218":"\u0219","\u021a":"\u021b","\u021c":"\u021d","\u021e":"\u021f","\u0220":"\u019e","\u0222":"\u0223","\u0224":"\u0225","\u0226":"\u0227","\u0228":"\u0229","\u022a":"\u022b","\u022c":"\u022d","\u022e":"\u022f","\u0230":"\u0231","\u0232":"\u0233","\u023a":"\u2c65","\u023b":"\u023c","\u023d":"\u019a","\u023e":"\u2c66","\u0241":"\u0242","\u0243":"\u0180","\u0244":"\u0289","\u0245":"\u028c","\u0246":"\u0247","\u0248":"\u0249","\u024a":"\u024b","\u024c":"\u024d","\u024e":"\u024f","\u0345":"\u03b9","\u0370":"\u0371","\u0372":"\u0373","\u0376":"\u0377","\u037f":"\u03f3","\u0386":"\u03ac","\u0388":"\u03ad","\u0389":"\u03ae","\u038a":"\u03af","\u038c":"\u03cc","\u038e":"\u03cd","\u038f":"\u03ce","\u0390":"\u03b9\u0308\u0301","\u0391":"\u03b1","\u0392":"\u03b2","\u0393":"\u03b3","\u0394":"\u03b4","\u0395":"\u03b5","\u0396":"\u03b6","\u0397":"\u03b7","\u0398":"\u03b8","\u0399":"\u03b9","\u039a":"\u03ba","\u039b":"\u03bb","\u039c":"\u03bc","\u039d":"\u03bd","\u039e":"\u03be","\u039f":"\u03bf","\u03a0":"\u03c0","\u03a1":"\u03c1","\u03a3":"\u03c3","\u03a4":"\u03c4","\u03a5":"\u03c5","\u03a6":"\u03c6","\u03a7":"\u03c7","\u03a8":"\u03c8","\u03a9":"\u03c9","\u03aa":"\u03ca","\u03ab":"\u03cb","\u03b0":"\u03c5\u0308\u0301","\u03c2":"\u03c3","\u03cf":"\u03d7","\u03d0":"\u03b2","\u03d1":"\u03b8","\u03d5":"\u03c6","\u03d6":"\u03c0","\u03d8":"\u03d9","\u03da":"\u03db","\u03dc":"\u03dd","\u03de":"\u03df","\u03e0":"\u03e1","\u03e2":"\u03e3","\u03e4":"\u03e5","\u03e6":"\u03e7","\u03e8":"\u03e9","\u03ea":"\u03eb","\u03ec":"\u03ed","\u03ee":"\u03ef","\u03f0":"\u03ba","\u03f1":"\u03c1","\u03f4":"\u03b8","\u03f5":"\u03b5","\u03f7":"\u03f8","\u03f9":"\u03f2","\u03fa":"\u03fb","\u03fd":"\u037b","\u03fe":"\u037c","\u03ff":"\u037d","\u0400":"\u0450","\u0401":"\u0451","\u0402":"\u0452","\u0403":"\u0453","\u0404":"\u0454","\u0405":"\u0455","\u0406":"\u0456","\u0407":"\u0457","\u0408":"\u0458","\u0409":"\u0459","\u040a":"\u045a","\u040b":"\u045b","\u040c":"\u045c","\u040d":"\u045d","\u040e":"\u045e","\u040f":"\u045f","\u0410":"\u0430","\u0411":"\u0431","\u0412":"\u0432","\u0413":"\u0433","\u0414":"\u0434","\u0415":"\u0435","\u0416":"\u0436","\u0417":"\u0437","\u0418":"\u0438","\u0419":"\u0439","\u041a":"\u043a","\u041b":"\u043b","\u041c":"\u043c","\u041d":"\u043d","\u041e":"\u043e","\u041f":"\u043f","\u0420":"\u0440","\u0421":"\u0441","\u0422":"\u0442","\u0423":"\u0443","\u0424":"\u0444","\u0425":"\u0445","\u0426":"\u0446","\u0427":"\u0447","\u0428":"\u0448","\u0429":"\u0449","\u042a":"\u044a","\u042b":"\u044b","\u042c":"\u044c","\u042d":"\u044d","\u042e":"\u044e","\u042f":"\u044f","\u0460":"\u0461","\u0462":"\u0463","\u0464":"\u0465","\u0466":"\u0467","\u0468":"\u0469","\u046a":"\u046b","\u046c":"\u046d","\u046e":"\u046f","\u0470":"\u0471","\u0472":"\u0473","\u0474":"\u0475","\u0476":"\u0477","\u0478":"\u0479","\u047a":"\u047b","\u047c":"\u047d","\u047e":"\u047f","\u0480":"\u0481","\u048a":"\u048b","\u048c":"\u048d","\u048e":"\u048f","\u0490":"\u0491","\u0492":"\u0493","\u0494":"\u0495","\u0496":"\u0497","\u0498":"\u0499","\u049a":"\u049b","\u049c":"\u049d","\u049e":"\u049f","\u04a0":"\u04a1","\u04a2":"\u04a3","\u04a4":"\u04a5","\u04a6":"\u04a7","\u04a8":"\u04a9","\u04aa":"\u04ab","\u04ac":"\u04ad","\u04ae":"\u04af","\u04b0":"\u04b1","\u04b2":"\u04b3","\u04b4":"\u04b5","\u04b6":"\u04b7","\u04b8":"\u04b9","\u04ba":"\u04bb","\u04bc":"\u04bd","\u04be":"\u04bf","\u04c0":"\u04cf","\u04c1":"\u04c2","\u04c3":"\u04c4","\u04c5":"\u04c6","\u04c7":"\u04c8","\u04c9":"\u04ca","\u04cb":"\u04cc","\u04cd":"\u04ce","\u04d0":"\u04d1","\u04d2":"\u04d3","\u04d4":"\u04d5","\u04d6":"\u04d7","\u04d8":"\u04d9","\u04da":"\u04db","\u04dc":"\u04dd","\u04de":"\u04df","\u04e0":"\u04e1","\u04e2":"\u04e3","\u04e4":"\u04e5","\u04e6":"\u04e7","\u04e8":"\u04e9","\u04ea":"\u04eb","\u04ec":"\u04ed","\u04ee":"\u04ef","\u04f0":"\u04f1","\u04f2":"\u04f3","\u04f4":"\u04f5","\u04f6":"\u04f7","\u04f8":"\u04f9","\u04fa":"\u04fb","\u04fc":"\u04fd","\u04fe":"\u04ff","\u0500":"\u0501","\u0502":"\u0503","\u0504":"\u0505","\u0506":"\u0507","\u0508":"\u0509","\u050a":"\u050b","\u050c":"\u050d","\u050e":"\u050f","\u0510":"\u0511","\u0512":"\u0513","\u0514":"\u0515","\u0516":"\u0517","\u0518":"\u0519","\u051a":"\u051b","\u051c":"\u051d","\u051e":"\u051f","\u0520":"\u0521","\u0522":"\u0523","\u0524":"\u0525","\u0526":"\u0527","\u0528":"\u0529","\u052a":"\u052b","\u052c":"\u052d","\u052e":"\u052f","\u0531":"\u0561","\u0532":"\u0562","\u0533":"\u0563","\u0534":"\u0564","\u0535":"\u0565","\u0536":"\u0566","\u0537":"\u0567","\u0538":"\u0568","\u0539":"\u0569","\u053a":"\u056a","\u053b":"\u056b","\u053c":"\u056c","\u053d":"\u056d","\u053e":"\u056e","\u053f":"\u056f","\u0540":"\u0570","\u0541":"\u0571","\u0542":"\u0572","\u0543":"\u0573","\u0544":"\u0574","\u0545":"\u0575","\u0546":"\u0576","\u0547":"\u0577","\u0548":"\u0578","\u0549":"\u0579","\u054a":"\u057a","\u054b":"\u057b","\u054c":"\u057c","\u054d":"\u057d","\u054e":"\u057e","\u054f":"\u057f","\u0550":"\u0580","\u0551":"\u0581","\u0552":"\u0582","\u0553":"\u0583","\u0554":"\u0584","\u0555":"\u0585","\u0556":"\u0586","\u0587":"\u0565\u0582","\u10a0":"\u2d00","\u10a1":"\u2d01","\u10a2":"\u2d02","\u10a3":"\u2d03","\u10a4":"\u2d04","\u10a5":"\u2d05","\u10a6":"\u2d06","\u10a7":"\u2d07","\u10a8":"\u2d08","\u10a9":"\u2d09","\u10aa":"\u2d0a","\u10ab":"\u2d0b","\u10ac":"\u2d0c","\u10ad":"\u2d0d","\u10ae":"\u2d0e","\u10af":"\u2d0f","\u10b0":"\u2d10","\u10b1":"\u2d11","\u10b2":"\u2d12","\u10b3":"\u2d13","\u10b4":"\u2d14","\u10b5":"\u2d15","\u10b6":"\u2d16","\u10b7":"\u2d17","\u10b8":"\u2d18","\u10b9":"\u2d19","\u10ba":"\u2d1a","\u10bb":"\u2d1b","\u10bc":"\u2d1c","\u10bd":"\u2d1d","\u10be":"\u2d1e","\u10bf":"\u2d1f","\u10c0":"\u2d20","\u10c1":"\u2d21","\u10c2":"\u2d22","\u10c3":"\u2d23","\u10c4":"\u2d24","\u10c5":"\u2d25","\u10c7":"\u2d27","\u10cd":"\u2d2d","\u13f8":"\u13f0","\u13f9":"\u13f1","\u13fa":"\u13f2","\u13fb":"\u13f3","\u13fc":"\u13f4","\u13fd":"\u13f5","\u1c80":"\u0432","\u1c81":"\u0434","\u1c82":"\u043e","\u1c83":"\u0441","\u1c84":"\u0442","\u1c85":"\u0442","\u1c86":"\u044a","\u1c87":"\u0463","\u1c88":"\ua64b","\u1c90":"\u10d0","\u1c91":"\u10d1","\u1c92":"\u10d2","\u1c93":"\u10d3","\u1c94":"\u10d4","\u1c95":"\u10d5","\u1c96":"\u10d6","\u1c97":"\u10d7","\u1c98":"\u10d8","\u1c99":"\u10d9","\u1c9a":"\u10da","\u1c9b":"\u10db","\u1c9c":"\u10dc","\u1c9d":"\u10dd","\u1c9e":"\u10de","\u1c9f":"\u10df","\u1ca0":"\u10e0","\u1ca1":"\u10e1","\u1ca2":"\u10e2","\u1ca3":"\u10e3","\u1ca4":"\u10e4","\u1ca5":"\u10e5","\u1ca6":"\u10e6","\u1ca7":"\u10e7","\u1ca8":"\u10e8","\u1ca9":"\u10e9","\u1caa":"\u10ea","\u1cab":"\u10eb","\u1cac":"\u10ec","\u1cad":"\u10ed","\u1cae":"\u10ee","\u1caf":"\u10ef","\u1cb0":"\u10f0","\u1cb1":"\u10f1","\u1cb2":"\u10f2","\u1cb3":"\u10f3","\u1cb4":"\u10f4","\u1cb5":"\u10f5","\u1cb6":"\u10f6","\u1cb7":"\u10f7","\u1cb8":"\u10f8","\u1cb9":"\u10f9","\u1cba":"\u10fa","\u1cbd":"\u10fd","\u1cbe":"\u10fe","\u1cbf":"\u10ff","\u1e00":"\u1e01","\u1e02":"\u1e03","\u1e04":"\u1e05","\u1e06":"\u1e07","\u1e08":"\u1e09","\u1e0a":"\u1e0b","\u1e0c":"\u1e0d","\u1e0e":"\u1e0f","\u1e10":"\u1e11","\u1e12":"\u1e13","\u1e14":"\u1e15","\u1e16":"\u1e17","\u1e18":"\u1e19","\u1e1a":"\u1e1b","\u1e1c":"\u1e1d","\u1e1e":"\u1e1f","\u1e20":"\u1e21","\u1e22":"\u1e23","\u1e24":"\u1e25","\u1e26":"\u1e27","\u1e28":"\u1e29","\u1e2a":"\u1e2b","\u1e2c":"\u1e2d","\u1e2e":"\u1e2f","\u1e30":"\u1e31","\u1e32":"\u1e33","\u1e34":"\u1e35","\u1e36":"\u1e37","\u1e38":"\u1e39","\u1e3a":"\u1e3b","\u1e3c":"\u1e3d","\u1e3e":"\u1e3f","\u1e40":"\u1e41","\u1e42":"\u1e43","\u1e44":"\u1e45","\u1e46":"\u1e47","\u1e48":"\u1e49","\u1e4a":"\u1e4b","\u1e4c":"\u1e4d","\u1e4e":"\u1e4f","\u1e50":"\u1e51","\u1e52":"\u1e53","\u1e54":"\u1e55","\u1e56":"\u1e57","\u1e58":"\u1e59","\u1e5a":"\u1e5b","\u1e5c":"\u1e5d","\u1e5e":"\u1e5f","\u1e60":"\u1e61","\u1e62":"\u1e63","\u1e64":"\u1e65","\u1e66":"\u1e67","\u1e68":"\u1e69","\u1e6a":"\u1e6b","\u1e6c":"\u1e6d","\u1e6e":"\u1e6f","\u1e70":"\u1e71","\u1e72":"\u1e73","\u1e74":"\u1e75","\u1e76":"\u1e77","\u1e78":"\u1e79","\u1e7a":"\u1e7b","\u1e7c":"\u1e7d","\u1e7e":"\u1e7f","\u1e80":"\u1e81","\u1e82":"\u1e83","\u1e84":"\u1e85","\u1e86":"\u1e87","\u1e88":"\u1e89","\u1e8a":"\u1e8b","\u1e8c":"\u1e8d","\u1e8e":"\u1e8f","\u1e90":"\u1e91","\u1e92":"\u1e93","\u1e94":"\u1e95","\u1e96":"h\u0331","\u1e97":"t\u0308","\u1e98":"w\u030a","\u1e99":"y\u030a","\u1e9a":"a\u02be","\u1e9b":"\u1e61","\u1e9e":"ss","\u1ea0":"\u1ea1","\u1ea2":"\u1ea3","\u1ea4":"\u1ea5","\u1ea6":"\u1ea7","\u1ea8":"\u1ea9","\u1eaa":"\u1eab","\u1eac":"\u1ead","\u1eae":"\u1eaf","\u1eb0":"\u1eb1","\u1eb2":"\u1eb3","\u1eb4":"\u1eb5","\u1eb6":"\u1eb7","\u1eb8":"\u1eb9","\u1eba":"\u1ebb","\u1ebc":"\u1ebd","\u1ebe":"\u1ebf","\u1ec0":"\u1ec1","\u1ec2":"\u1ec3","\u1ec4":"\u1ec5","\u1ec6":"\u1ec7","\u1ec8":"\u1ec9","\u1eca":"\u1ecb","\u1ecc":"\u1ecd","\u1ece":"\u1ecf","\u1ed0":"\u1ed1","\u1ed2":"\u1ed3","\u1ed4":"\u1ed5","\u1ed6":"\u1ed7","\u1ed8":"\u1ed9","\u1eda":"\u1edb","\u1edc":"\u1edd","\u1ede":"\u1edf","\u1ee0":"\u1ee1","\u1ee2":"\u1ee3","\u1ee4":"\u1ee5","\u1ee6":"\u1ee7","\u1ee8":"\u1ee9","\u1eea":"\u1eeb","\u1eec":"\u1eed","\u1eee":"\u1eef","\u1ef0":"\u1ef1","\u1ef2":"\u1ef3","\u1ef4":"\u1ef5","\u1ef6":"\u1ef7","\u1ef8":"\u1ef9","\u1efa":"\u1efb","\u1efc":"\u1efd","\u1efe":"\u1eff","\u1f08":"\u1f00","\u1f09":"\u1f01","\u1f0a":"\u1f02","\u1f0b":"\u1f03","\u1f0c":"\u1f04","\u1f0d":"\u1f05","\u1f0e":"\u1f06","\u1f0f":"\u1f07","\u1f18":"\u1f10","\u1f19":"\u1f11","\u1f1a":"\u1f12","\u1f1b":"\u1f13","\u1f1c":"\u1f14","\u1f1d":"\u1f15","\u1f28":"\u1f20","\u1f29":"\u1f21","\u1f2a":"\u1f22","\u1f2b":"\u1f23","\u1f2c":"\u1f24","\u1f2d":"\u1f25","\u1f2e":"\u1f26","\u1f2f":"\u1f27","\u1f38":"\u1f30","\u1f39":"\u1f31","\u1f3a":"\u1f32","\u1f3b":"\u1f33","\u1f3c":"\u1f34","\u1f3d":"\u1f35","\u1f3e":"\u1f36","\u1f3f":"\u1f37","\u1f48":"\u1f40","\u1f49":"\u1f41","\u1f4a":"\u1f42","\u1f4b":"\u1f43","\u1f4c":"\u1f44","\u1f4d":"\u1f45","\u1f50":"\u03c5\u0313","\u1f52":"\u03c5\u0313\u0300","\u1f54":"\u03c5\u0313\u0301","\u1f56":"\u03c5\u0313\u0342","\u1f59":"\u1f51","\u1f5b":"\u1f53","\u1f5d":"\u1f55","\u1f5f":"\u1f57","\u1f68":"\u1f60","\u1f69":"\u1f61","\u1f6a":"\u1f62","\u1f6b":"\u1f63","\u1f6c":"\u1f64","\u1f6d":"\u1f65","\u1f6e":"\u1f66","\u1f6f":"\u1f67","\u1f80":"\u1f00\u03b9","\u1f81":"\u1f01\u03b9","\u1f82":"\u1f02\u03b9","\u1f83":"\u1f03\u03b9","\u1f84":"\u1f04\u03b9","\u1f85":"\u1f05\u03b9","\u1f86":"\u1f06\u03b9","\u1f87":"\u1f07\u03b9","\u1f88":"\u1f00\u03b9","\u1f89":"\u1f01\u03b9","\u1f8a":"\u1f02\u03b9","\u1f8b":"\u1f03\u03b9","\u1f8c":"\u1f04\u03b9","\u1f8d":"\u1f05\u03b9","\u1f8e":"\u1f06\u03b9","\u1f8f":"\u1f07\u03b9","\u1f90":"\u1f20\u03b9","\u1f91":"\u1f21\u03b9","\u1f92":"\u1f22\u03b9","\u1f93":"\u1f23\u03b9","\u1f94":"\u1f24\u03b9","\u1f95":"\u1f25\u03b9","\u1f96":"\u1f26\u03b9","\u1f97":"\u1f27\u03b9","\u1f98":"\u1f20\u03b9","\u1f99":"\u1f21\u03b9","\u1f9a":"\u1f22\u03b9","\u1f9b":"\u1f23\u03b9","\u1f9c":"\u1f24\u03b9","\u1f9d":"\u1f25\u03b9","\u1f9e":"\u1f26\u03b9","\u1f9f":"\u1f27\u03b9","\u1fa0":"\u1f60\u03b9","\u1fa1":"\u1f61\u03b9","\u1fa2":"\u1f62\u03b9","\u1fa3":"\u1f63\u03b9","\u1fa4":"\u1f64\u03b9","\u1fa5":"\u1f65\u03b9","\u1fa6":"\u1f66\u03b9","\u1fa7":"\u1f67\u03b9","\u1fa8":"\u1f60\u03b9","\u1fa9":"\u1f61\u03b9","\u1faa":"\u1f62\u03b9","\u1fab":"\u1f63\u03b9","\u1fac":"\u1f64\u03b9","\u1fad":"\u1f65\u03b9","\u1fae":"\u1f66\u03b9","\u1faf":"\u1f67\u03b9","\u1fb2":"\u1f70\u03b9","\u1fb3":"\u03b1\u03b9","\u1fb4":"\u03ac\u03b9","\u1fb6":"\u03b1\u0342","\u1fb7":"\u03b1\u0342\u03b9","\u1fb8":"\u1fb0","\u1fb9":"\u1fb1","\u1fba":"\u1f70","\u1fbb":"\u1f71","\u1fbc":"\u03b1\u03b9","\u1fbe":"\u03b9","\u1fc2":"\u1f74\u03b9","\u1fc3":"\u03b7\u03b9","\u1fc4":"\u03ae\u03b9","\u1fc6":"\u03b7\u0342","\u1fc7":"\u03b7\u0342\u03b9","\u1fc8":"\u1f72","\u1fc9":"\u1f73","\u1fca":"\u1f74","\u1fcb":"\u1f75","\u1fcc":"\u03b7\u03b9","\u1fd2":"\u03b9\u0308\u0300","\u1fd3":"\u03b9\u0308\u0301","\u1fd6":"\u03b9\u0342","\u1fd7":"\u03b9\u0308\u0342","\u1fd8":"\u1fd0","\u1fd9":"\u1fd1","\u1fda":"\u1f76","\u1fdb":"\u1f77","\u1fe2":"\u03c5\u0308\u0300","\u1fe3":"\u03c5\u0308\u0301","\u1fe4":"\u03c1\u0313","\u1fe6":"\u03c5\u0342","\u1fe7":"\u03c5\u0308\u0342","\u1fe8":"\u1fe0","\u1fe9":"\u1fe1","\u1fea":"\u1f7a","\u1feb":"\u1f7b","\u1fec":"\u1fe5","\u1ff2":"\u1f7c\u03b9","\u1ff3":"\u03c9\u03b9","\u1ff4":"\u03ce\u03b9","\u1ff6":"\u03c9\u0342","\u1ff7":"\u03c9\u0342\u03b9","\u1ff8":"\u1f78","\u1ff9":"\u1f79","\u1ffa":"\u1f7c","\u1ffb":"\u1f7d","\u1ffc":"\u03c9\u03b9","\u2126":"\u03c9","\u212a":"k","\u212b":"\u00e5","\u2132":"\u214e","\u2160":"\u2170","\u2161":"\u2171","\u2162":"\u2172","\u2163":"\u2173","\u2164":"\u2174","\u2165":"\u2175","\u2166":"\u2176","\u2167":"\u2177","\u2168":"\u2178","\u2169":"\u2179","\u216a":"\u217a","\u216b":"\u217b","\u216c":"\u217c","\u216d":"\u217d","\u216e":"\u217e","\u216f":"\u217f","\u2183":"\u2184","\u24b6":"\u24d0","\u24b7":"\u24d1","\u24b8":"\u24d2","\u24b9":"\u24d3","\u24ba":"\u24d4","\u24bb":"\u24d5","\u24bc":"\u24d6","\u24bd":"\u24d7","\u24be":"\u24d8","\u24bf":"\u24d9","\u24c0":"\u24da","\u24c1":"\u24db","\u24c2":"\u24dc","\u24c3":"\u24dd","\u24c4":"\u24de","\u24c5":"\u24df","\u24c6":"\u24e0","\u24c7":"\u24e1","\u24c8":"\u24e2","\u24c9":"\u24e3","\u24ca":"\u24e4","\u24cb":"\u24e5","\u24cc":"\u24e6","\u24cd":"\u24e7","\u24ce":"\u24e8","\u24cf":"\u24e9","\u2c00":"\u2c30","\u2c01":"\u2c31","\u2c02":"\u2c32","\u2c03":"\u2c33","\u2c04":"\u2c34","\u2c05":"\u2c35","\u2c06":"\u2c36","\u2c07":"\u2c37","\u2c08":"\u2c38","\u2c09":"\u2c39","\u2c0a":"\u2c3a","\u2c0b":"\u2c3b","\u2c0c":"\u2c3c","\u2c0d":"\u2c3d","\u2c0e":"\u2c3e","\u2c0f":"\u2c3f","\u2c10":"\u2c40","\u2c11":"\u2c41","\u2c12":"\u2c42","\u2c13":"\u2c43","\u2c14":"\u2c44","\u2c15":"\u2c45","\u2c16":"\u2c46","\u2c17":"\u2c47","\u2c18":"\u2c48","\u2c19":"\u2c49","\u2c1a":"\u2c4a","\u2c1b":"\u2c4b","\u2c1c":"\u2c4c","\u2c1d":"\u2c4d","\u2c1e":"\u2c4e","\u2c1f":"\u2c4f","\u2c20":"\u2c50","\u2c21":"\u2c51","\u2c22":"\u2c52","\u2c23":"\u2c53","\u2c24":"\u2c54","\u2c25":"\u2c55","\u2c26":"\u2c56","\u2c27":"\u2c57","\u2c28":"\u2c58","\u2c29":"\u2c59","\u2c2a":"\u2c5a","\u2c2b":"\u2c5b","\u2c2c":"\u2c5c","\u2c2d":"\u2c5d","\u2c2e":"\u2c5e","\u2c2f":"\u2c5f","\u2c60":"\u2c61","\u2c62":"\u026b","\u2c63":"\u1d7d","\u2c64":"\u027d","\u2c67":"\u2c68","\u2c69":"\u2c6a","\u2c6b":"\u2c6c","\u2c6d":"\u0251","\u2c6e":"\u0271","\u2c6f":"\u0250","\u2c70":"\u0252","\u2c72":"\u2c73","\u2c75":"\u2c76","\u2c7e":"\u023f","\u2c7f":"\u0240","\u2c80":"\u2c81","\u2c82":"\u2c83","\u2c84":"\u2c85","\u2c86":"\u2c87","\u2c88":"\u2c89","\u2c8a":"\u2c8b","\u2c8c":"\u2c8d","\u2c8e":"\u2c8f","\u2c90":"\u2c91","\u2c92":"\u2c93","\u2c94":"\u2c95","\u2c96":"\u2c97","\u2c98":"\u2c99","\u2c9a":"\u2c9b","\u2c9c":"\u2c9d","\u2c9e":"\u2c9f","\u2ca0":"\u2ca1","\u2ca2":"\u2ca3","\u2ca4":"\u2ca5","\u2ca6":"\u2ca7","\u2ca8":"\u2ca9","\u2caa":"\u2cab","\u2cac":"\u2cad","\u2cae":"\u2caf","\u2cb0":"\u2cb1","\u2cb2":"\u2cb3","\u2cb4":"\u2cb5","\u2cb6":"\u2cb7","\u2cb8":"\u2cb9","\u2cba":"\u2cbb","\u2cbc":"\u2cbd","\u2cbe":"\u2cbf","\u2cc0":"\u2cc1","\u2cc2":"\u2cc3","\u2cc4":"\u2cc5","\u2cc6":"\u2cc7","\u2cc8":"\u2cc9","\u2cca":"\u2ccb","\u2ccc":"\u2ccd","\u2cce":"\u2ccf","\u2cd0":"\u2cd1","\u2cd2":"\u2cd3","\u2cd4":"\u2cd5","\u2cd6":"\u2cd7","\u2cd8":"\u2cd9","\u2cda":"\u2cdb","\u2cdc":"\u2cdd","\u2cde":"\u2cdf","\u2ce0":"\u2ce1","\u2ce2":"\u2ce3","\u2ceb":"\u2cec","\u2ced":"\u2cee","\u2cf2":"\u2cf3","\ua640":"\ua641","\ua642":"\ua643","\ua644":"\ua645","\ua646":"\ua647","\ua648":"\ua649","\ua64a":"\ua64b","\ua64c":"\ua64d","\ua64e":"\ua64f","\ua650":"\ua651","\ua652":"\ua653","\ua654":"\ua655","\ua656":"\ua657","\ua658":"\ua659","\ua65a":"\ua65b","\ua65c":"\ua65d","\ua65e":"\ua65f","\ua660":"\ua661","\ua662":"\ua663","\ua664":"\ua665","\ua666":"\ua667","\ua668":"\ua669","\ua66a":"\ua66b","\ua66c":"\ua66d","\ua680":"\ua681","\ua682":"\ua683","\ua684":"\ua685","\ua686":"\ua687","\ua688":"\ua689","\ua68a":"\ua68b","\ua68c":"\ua68d","\ua68e":"\ua68f","\ua690":"\ua691","\ua692":"\ua693","\ua694":"\ua695","\ua696":"\ua697","\ua698":"\ua699","\ua69a":"\ua69b","\ua722":"\ua723","\ua724":"\ua725","\ua726":"\ua727","\ua728":"\ua729","\ua72a":"\ua72b","\ua72c":"\ua72d","\ua72e":"\ua72f","\ua732":"\ua733","\ua734":"\ua735","\ua736":"\ua737","\ua738":"\ua739","\ua73a":"\ua73b","\ua73c":"\ua73d","\ua73e":"\ua73f","\ua740":"\ua741","\ua742":"\ua743","\ua744":"\ua745","\ua746":"\ua747","\ua748":"\ua749","\ua74a":"\ua74b","\ua74c":"\ua74d","\ua74e":"\ua74f","\ua750":"\ua751","\ua752":"\ua753","\ua754":"\ua755","\ua756":"\ua757","\ua758":"\ua759","\ua75a":"\ua75b","\ua75c":"\ua75d","\ua75e":"\ua75f","\ua760":"\ua761","\ua762":"\ua763","\ua764":"\ua765","\ua766":"\ua767","\ua768":"\ua769","\ua76a":"\ua76b","\ua76c":"\ua76d","\ua76e":"\ua76f","\ua779":"\ua77a","\ua77b":"\ua77c","\ua77d":"\u1d79","\ua77e":"\ua77f","\ua780":"\ua781","\ua782":"\ua783","\ua784":"\ua785","\ua786":"\ua787","\ua78b":"\ua78c","\ua78d":"\u0265","\ua790":"\ua791","\ua792":"\ua793","\ua796":"\ua797","\ua798":"\ua799","\ua79a":"\ua79b","\ua79c":"\ua79d","\ua79e":"\ua79f","\ua7a0":"\ua7a1","\ua7a2":"\ua7a3","\ua7a4":"\ua7a5","\ua7a6":"\ua7a7","\ua7a8":"\ua7a9","\ua7aa":"\u0266","\ua7ab":"\u025c","\ua7ac":"\u0261","\ua7ad":"\u026c","\ua7ae":"\u026a","\ua7b0":"\u029e","\ua7b1":"\u0287","\ua7b2":"\u029d","\ua7b3":"\uab53","\ua7b4":"\ua7b5","\ua7b6":"\ua7b7","\ua7b8":"\ua7b9","\ua7ba":"\ua7bb","\ua7bc":"\ua7bd","\ua7be":"\ua7bf","\ua7c0":"\ua7c1","\ua7c2":"\ua7c3","\ua7c4":"\ua794","\ua7c5":"\u0282","\ua7c6":"\u1d8e","\ua7c7":"\ua7c8","\ua7c9":"\ua7ca","\ua7d0":"\ua7d1","\ua7d6":"\ua7d7","\ua7d8":"\ua7d9","\ua7f5":"\ua7f6","\uab70":"\u13a0","\uab71":"\u13a1","\uab72":"\u13a2","\uab73":"\u13a3","\uab74":"\u13a4","\uab75":"\u13a5","\uab76":"\u13a6","\uab77":"\u13a7","\uab78":"\u13a8","\uab79":"\u13a9","\uab7a":"\u13aa","\uab7b":"\u13ab","\uab7c":"\u13ac","\uab7d":"\u13ad","\uab7e":"\u13ae","\uab7f":"\u13af","\uab80":"\u13b0","\uab81":"\u13b1","\uab82":"\u13b2","\uab83":"\u13b3","\uab84":"\u13b4","\uab85":"\u13b5","\uab86":"\u13b6","\uab87":"\u13b7","\uab88":"\u13b8","\uab89":"\u13b9","\uab8a":"\u13ba","\uab8b":"\u13bb","\uab8c":"\u13bc","\uab8d":"\u13bd","\uab8e":"\u13be","\uab8f":"\u13bf","\uab90":"\u13c0","\uab91":"\u13c1","\uab92":"\u13c2","\uab93":"\u13c3","\uab94":"\u13c4","\uab95":"\u13c5","\uab96":"\u13c6","\uab97":"\u13c7","\uab98":"\u13c8","\uab99":"\u13c9","\uab9a":"\u13ca","\uab9b":"\u13cb","\uab9c":"\u13cc","\uab9d":"\u13cd","\uab9e":"\u13ce","\uab9f":"\u13cf","\uaba0":"\u13d0","\uaba1":"\u13d1","\uaba2":"\u13d2","\uaba3":"\u13d3","\uaba4":"\u13d4","\uaba5":"\u13d5","\uaba6":"\u13d6","\uaba7":"\u13d7","\uaba8":"\u13d8","\uaba9":"\u13d9","\uabaa":"\u13da","\uabab":"\u13db","\uabac":"\u13dc","\uabad":"\u13dd","\uabae":"\u13de","\uabaf":"\u13df","\uabb0":"\u13e0","\uabb1":"\u13e1","\uabb2":"\u13e2","\uabb3":"\u13e3","\uabb4":"\u13e4","\uabb5":"\u13e5","\uabb6":"\u13e6","\uabb7":"\u13e7","\uabb8":"\u13e8","\uabb9":"\u13e9","\uabba":"\u13ea","\uabbb":"\u13eb","\uabbc":"\u13ec","\uabbd":"\u13ed","\uabbe":"\u13ee","\uabbf":"\u13ef","\ufb00":"ff","\ufb01":"fi","\ufb02":"fl","\ufb03":"ffi","\ufb04":"ffl","\ufb05":"st","\ufb06":"st","\ufb13":"\u0574\u0576","\ufb14":"\u0574\u0565","\ufb15":"\u0574\u056b","\ufb16":"\u057e\u0576","\ufb17":"\u0574\u056d","\uff21":"\uff41","\uff22":"\uff42","\uff23":"\uff43","\uff24":"\uff44","\uff25":"\uff45","\uff26":"\uff46","\uff27":"\uff47","\uff28":"\uff48","\uff29":"\uff49","\uff2a":"\uff4a","\uff2b":"\uff4b","\uff2c":"\uff4c","\uff2d":"\uff4d","\uff2e":"\uff4e","\uff2f":"\uff4f","\uff30":"\uff50","\uff31":"\uff51","\uff32":"\uff52","\uff33":"\uff53","\uff34":"\uff54","\uff35":"\uff55","\uff36":"\uff56","\uff37":"\uff57","\uff38":"\uff58","\uff39":"\uff59","\uff3a":"\uff5a","\ud801\udc00":"\ud801\udc28","\ud801\udc01":"\ud801\udc29","\ud801\udc02":"\ud801\udc2a","\ud801\udc03":"\ud801\udc2b","\ud801\udc04":"\ud801\udc2c","\ud801\udc05":"\ud801\udc2d","\ud801\udc06":"\ud801\udc2e","\ud801\udc07":"\ud801\udc2f","\ud801\udc08":"\ud801\udc30","\ud801\udc09":"\ud801\udc31","\ud801\udc0a":"\ud801\udc32","\ud801\udc0b":"\ud801\udc33","\ud801\udc0c":"\ud801\udc34","\ud801\udc0d":"\ud801\udc35","\ud801\udc0e":"\ud801\udc36","\ud801\udc0f":"\ud801\udc37","\ud801\udc10":"\ud801\udc38","\ud801\udc11":"\ud801\udc39","\ud801\udc12":"\ud801\udc3a","\ud801\udc13":"\ud801\udc3b","\ud801\udc14":"\ud801\udc3c","\ud801\udc15":"\ud801\udc3d","\ud801\udc16":"\ud801\udc3e","\ud801\udc17":"\ud801\udc3f","\ud801\udc18":"\ud801\udc40","\ud801\udc19":"\ud801\udc41","\ud801\udc1a":"\ud801\udc42","\ud801\udc1b":"\ud801\udc43","\ud801\udc1c":"\ud801\udc44","\ud801\udc1d":"\ud801\udc45","\ud801\udc1e":"\ud801\udc46","\ud801\udc1f":"\ud801\udc47","\ud801\udc20":"\ud801\udc48","\ud801\udc21":"\ud801\udc49","\ud801\udc22":"\ud801\udc4a","\ud801\udc23":"\ud801\udc4b","\ud801\udc24":"\ud801\udc4c","\ud801\udc25":"\ud801\udc4d","\ud801\udc26":"\ud801\udc4e","\ud801\udc27":"\ud801\udc4f","\ud801\udcb0":"\ud801\udcd8","\ud801\udcb1":"\ud801\udcd9","\ud801\udcb2":"\ud801\udcda","\ud801\udcb3":"\ud801\udcdb","\ud801\udcb4":"\ud801\udcdc","\ud801\udcb5":"\ud801\udcdd","\ud801\udcb6":"\ud801\udcde","\ud801\udcb7":"\ud801\udcdf","\ud801\udcb8":"\ud801\udce0","\ud801\udcb9":"\ud801\udce1","\ud801\udcba":"\ud801\udce2","\ud801\udcbb":"\ud801\udce3","\ud801\udcbc":"\ud801\udce4","\ud801\udcbd":"\ud801\udce5","\ud801\udcbe":"\ud801\udce6","\ud801\udcbf":"\ud801\udce7","\ud801\udcc0":"\ud801\udce8","\ud801\udcc1":"\ud801\udce9","\ud801\udcc2":"\ud801\udcea","\ud801\udcc3":"\ud801\udceb","\ud801\udcc4":"\ud801\udcec","\ud801\udcc5":"\ud801\udced","\ud801\udcc6":"\ud801\udcee","\ud801\udcc7":"\ud801\udcef","\ud801\udcc8":"\ud801\udcf0","\ud801\udcc9":"\ud801\udcf1","\ud801\udcca":"\ud801\udcf2","\ud801\udccb":"\ud801\udcf3","\ud801\udccc":"\ud801\udcf4","\ud801\udccd":"\ud801\udcf5","\ud801\udcce":"\ud801\udcf6","\ud801\udccf":"\ud801\udcf7","\ud801\udcd0":"\ud801\udcf8","\ud801\udcd1":"\ud801\udcf9","\ud801\udcd2":"\ud801\udcfa","\ud801\udcd3":"\ud801\udcfb","\ud801\udd70":"\ud801\udd97","\ud801\udd71":"\ud801\udd98","\ud801\udd72":"\ud801\udd99","\ud801\udd73":"\ud801\udd9a","\ud801\udd74":"\ud801\udd9b","\ud801\udd75":"\ud801\udd9c","\ud801\udd76":"\ud801\udd9d","\ud801\udd77":"\ud801\udd9e","\ud801\udd78":"\ud801\udd9f","\ud801\udd79":"\ud801\udda0","\ud801\udd7a":"\ud801\udda1","\ud801\udd7c":"\ud801\udda3","\ud801\udd7d":"\ud801\udda4","\ud801\udd7e":"\ud801\udda5","\ud801\udd7f":"\ud801\udda6","\ud801\udd80":"\ud801\udda7","\ud801\udd81":"\ud801\udda8","\ud801\udd82":"\ud801\udda9","\ud801\udd83":"\ud801\uddaa","\ud801\udd84":"\ud801\uddab","\ud801\udd85":"\ud801\uddac","\ud801\udd86":"\ud801\uddad","\ud801\udd87":"\ud801\uddae","\ud801\udd88":"\ud801\uddaf","\ud801\udd89":"\ud801\uddb0","\ud801\udd8a":"\ud801\uddb1","\ud801\udd8c":"\ud801\uddb3","\ud801\udd8d":"\ud801\uddb4","\ud801\udd8e":"\ud801\uddb5","\ud801\udd8f":"\ud801\uddb6","\ud801\udd90":"\ud801\uddb7","\ud801\udd91":"\ud801\uddb8","\ud801\udd92":"\ud801\uddb9","\ud801\udd94":"\ud801\uddbb","\ud801\udd95":"\ud801\uddbc","\ud803\udc80":"\ud803\udcc0","\ud803\udc81":"\ud803\udcc1","\ud803\udc82":"\ud803\udcc2","\ud803\udc83":"\ud803\udcc3","\ud803\udc84":"\ud803\udcc4","\ud803\udc85":"\ud803\udcc5","\ud803\udc86":"\ud803\udcc6","\ud803\udc87":"\ud803\udcc7","\ud803\udc88":"\ud803\udcc8","\ud803\udc89":"\ud803\udcc9","\ud803\udc8a":"\ud803\udcca","\ud803\udc8b":"\ud803\udccb","\ud803\udc8c":"\ud803\udccc","\ud803\udc8d":"\ud803\udccd","\ud803\udc8e":"\ud803\udcce","\ud803\udc8f":"\ud803\udccf","\ud803\udc90":"\ud803\udcd0","\ud803\udc91":"\ud803\udcd1","\ud803\udc92":"\ud803\udcd2","\ud803\udc93":"\ud803\udcd3","\ud803\udc94":"\ud803\udcd4","\ud803\udc95":"\ud803\udcd5","\ud803\udc96":"\ud803\udcd6","\ud803\udc97":"\ud803\udcd7","\ud803\udc98":"\ud803\udcd8","\ud803\udc99":"\ud803\udcd9","\ud803\udc9a":"\ud803\udcda","\ud803\udc9b":"\ud803\udcdb","\ud803\udc9c":"\ud803\udcdc","\ud803\udc9d":"\ud803\udcdd","\ud803\udc9e":"\ud803\udcde","\ud803\udc9f":"\ud803\udcdf","\ud803\udca0":"\ud803\udce0","\ud803\udca1":"\ud803\udce1","\ud803\udca2":"\ud803\udce2","\ud803\udca3":"\ud803\udce3","\ud803\udca4":"\ud803\udce4","\ud803\udca5":"\ud803\udce5","\ud803\udca6":"\ud803\udce6","\ud803\udca7":"\ud803\udce7","\ud803\udca8":"\ud803\udce8","\ud803\udca9":"\ud803\udce9","\ud803\udcaa":"\ud803\udcea","\ud803\udcab":"\ud803\udceb","\ud803\udcac":"\ud803\udcec","\ud803\udcad":"\ud803\udced","\ud803\udcae":"\ud803\udcee","\ud803\udcaf":"\ud803\udcef","\ud803\udcb0":"\ud803\udcf0","\ud803\udcb1":"\ud803\udcf1","\ud803\udcb2":"\ud803\udcf2","\ud806\udca0":"\ud806\udcc0","\ud806\udca1":"\ud806\udcc1","\ud806\udca2":"\ud806\udcc2","\ud806\udca3":"\ud806\udcc3","\ud806\udca4":"\ud806\udcc4","\ud806\udca5":"\ud806\udcc5","\ud806\udca6":"\ud806\udcc6","\ud806\udca7":"\ud806\udcc7","\ud806\udca8":"\ud806\udcc8","\ud806\udca9":"\ud806\udcc9","\ud806\udcaa":"\ud806\udcca","\ud806\udcab":"\ud806\udccb","\ud806\udcac":"\ud806\udccc","\ud806\udcad":"\ud806\udccd","\ud806\udcae":"\ud806\udcce","\ud806\udcaf":"\ud806\udccf","\ud806\udcb0":"\ud806\udcd0","\ud806\udcb1":"\ud806\udcd1","\ud806\udcb2":"\ud806\udcd2","\ud806\udcb3":"\ud806\udcd3","\ud806\udcb4":"\ud806\udcd4","\ud806\udcb5":"\ud806\udcd5","\ud806\udcb6":"\ud806\udcd6","\ud806\udcb7":"\ud806\udcd7","\ud806\udcb8":"\ud806\udcd8","\ud806\udcb9":"\ud806\udcd9","\ud806\udcba":"\ud806\udcda","\ud806\udcbb":"\ud806\udcdb","\ud806\udcbc":"\ud806\udcdc","\ud806\udcbd":"\ud806\udcdd","\ud806\udcbe":"\ud806\udcde","\ud806\udcbf":"\ud806\udcdf","\ud81b\ude40":"\ud81b\ude60","\ud81b\ude41":"\ud81b\ude61","\ud81b\ude42":"\ud81b\ude62","\ud81b\ude43":"\ud81b\ude63","\ud81b\ude44":"\ud81b\ude64","\ud81b\ude45":"\ud81b\ude65","\ud81b\ude46":"\ud81b\ude66","\ud81b\ude47":"\ud81b\ude67","\ud81b\ude48":"\ud81b\ude68","\ud81b\ude49":"\ud81b\ude69","\ud81b\ude4a":"\ud81b\ude6a","\ud81b\ude4b":"\ud81b\ude6b","\ud81b\ude4c":"\ud81b\ude6c","\ud81b\ude4d":"\ud81b\ude6d","\ud81b\ude4e":"\ud81b\ude6e","\ud81b\ude4f":"\ud81b\ude6f","\ud81b\ude50":"\ud81b\ude70","\ud81b\ude51":"\ud81b\ude71","\ud81b\ude52":"\ud81b\ude72","\ud81b\ude53":"\ud81b\ude73","\ud81b\ude54":"\ud81b\ude74","\ud81b\ude55":"\ud81b\ude75","\ud81b\ude56":"\ud81b\ude76","\ud81b\ude57":"\ud81b\ude77","\ud81b\ude58":"\ud81b\ude78","\ud81b\ude59":"\ud81b\ude79","\ud81b\ude5a":"\ud81b\ude7a","\ud81b\ude5b":"\ud81b\ude7b","\ud81b\ude5c":"\ud81b\ude7c","\ud81b\ude5d":"\ud81b\ude7d","\ud81b\ude5e":"\ud81b\ude7e","\ud81b\ude5f":"\ud81b\ude7f","\ud83a\udd00":"\ud83a\udd22","\ud83a\udd01":"\ud83a\udd23","\ud83a\udd02":"\ud83a\udd24","\ud83a\udd03":"\ud83a\udd25","\ud83a\udd04":"\ud83a\udd26","\ud83a\udd05":"\ud83a\udd27","\ud83a\udd06":"\ud83a\udd28","\ud83a\udd07":"\ud83a\udd29","\ud83a\udd08":"\ud83a\udd2a","\ud83a\udd09":"\ud83a\udd2b","\ud83a\udd0a":"\ud83a\udd2c","\ud83a\udd0b":"\ud83a\udd2d","\ud83a\udd0c":"\ud83a\udd2e","\ud83a\udd0d":"\ud83a\udd2f","\ud83a\udd0e":"\ud83a\udd30","\ud83a\udd0f":"\ud83a\udd31","\ud83a\udd10":"\ud83a\udd32","\ud83a\udd11":"\ud83a\udd33","\ud83a\udd12":"\ud83a\udd34","\ud83a\udd13":"\ud83a\udd35","\ud83a\udd14":"\ud83a\udd36","\ud83a\udd15":"\ud83a\udd37","\ud83a\udd16":"\ud83a\udd38","\ud83a\udd17":"\ud83a\udd39","\ud83a\udd18":"\ud83a\udd3a","\ud83a\udd19":"\ud83a\udd3b","\ud83a\udd1a":"\ud83a\udd3c","\ud83a\udd1b":"\ud83a\udd3d","\ud83a\udd1c":"\ud83a\udd3e","\ud83a\udd1d":"\ud83a\udd3f","\ud83a\udd1e":"\ud83a\udd40","\ud83a\udd1f":"\ud83a\udd41","\ud83a\udd20":"\ud83a\udd42","\ud83a\udd21":"\ud83a\udd43"}; diff --git a/frontend/src/machines/operator.ts b/frontend/src/machines/operator.ts index ef58f6e..b5b347c 100644 --- a/frontend/src/machines/operator.ts +++ b/frontend/src/machines/operator.ts @@ -5,7 +5,7 @@ import { CaptionConflict, OperationError, disclosureIds, type DisclosureId, type import { changeDisplayField, defaultDisplay, selectDisplayPalette } from "../display"; import { editableAiSettings, defaultAi, knownAiModel } from "../ai"; import { defaultConnection } from "../connection"; -import { dictionaryUpdate, dictionaryValidation, draftDictionary, emptyDictionary, type DictionaryDraft } from "../dictionary"; +import { dictionaryLimits, dictionaryUpdate, dictionaryValidation, draftDictionary, emptyDictionary, type DictionaryDraft } from "../dictionary"; import type { DictionarySnapshot, DictionaryUpdate } from "../domain"; type StatusTarget = { captionId: string; kind: "source" | "publication" }; @@ -202,7 +202,7 @@ export const operatorMachine = setup({ connection: fromPromise(async ({ input, signal }: { input: { api: OperatorApi; settings: ConnectionSettings }; signal: AbortSignal }) => input.api.setConnection(input.settings, signal)), }, guards: { - canAddWord: ({ context }) => (context.dictionaryDraft ?? draftDictionary(context.dictionary)).entries.length < 100, + canAddWord: ({ context }) => (context.dictionaryDraft ?? draftDictionary(context.dictionary)).entries.length < dictionaryLimits.maxEntries, knownWord: ({ context, event }) => (event.type === "dictionary.word.changed" || event.type === "dictionary.word.enabled" || event.type === "dictionary.word.removed") && Number.isInteger(event.index) && event.index >= 0 && event.index < (context.dictionaryDraft ?? draftDictionary(context.dictionary)).entries.length, validDictionary: ({ context }) => !dictionaryValidation(context.dictionaryDraft ?? draftDictionary(context.dictionary)), diff --git a/frontend/src/presenters/operator.ts b/frontend/src/presenters/operator.ts index 1b3fd7d..74033a4 100644 --- a/frontend/src/presenters/operator.ts +++ b/frontend/src/presenters/operator.ts @@ -4,7 +4,7 @@ import { displayChoices, displayPalettes } from "../display"; import type { OperatorSnapshot } from "../machines/operator"; import { presentDisplayPreview, type DisplayCaption, type DisplayPreviewParameters } from "./display"; import { presentDisclosure, type DisclosureParameters } from "./disclosure"; -import { draftDictionary } from "../dictionary"; +import { dictionaryLimits, draftDictionary } from "../dictionary"; const receivedTime = new Intl.DateTimeFormat("ja-JP", { hour: "2-digit", minute: "2-digit" }); const receivedDateTime = new Intl.DateTimeFormat("ja-JP", { @@ -102,6 +102,7 @@ export interface AiParameters { } export interface DictionaryParameters { + variantsHint: string; entries: { index: number; term: string; reading: string; variantsText: string; enabled: boolean }[]; countLabel: string; empty: boolean; @@ -385,8 +386,9 @@ export function presentOperator(snapshot: OperatorSnapshot, roomId?: string): Op context.captions.map(caption => ({ id: caption.id, text: caption.public_text ?? caption.text, speaker: caption.published ? caption.public_speaker : caption.speaker })), context.openedDisclosures), dictionary: { + variantsHint: `任意・1行1件、${dictionaryLimits.maxVariants}件まで`, entries: dictionaryDraft.entries.map((word, index) => ({ ...word, index })), - countLabel: `${dictionaryDraft.entries.length} / 100件`, empty: dictionaryDraft.entries.length === 0, + countLabel: `${dictionaryDraft.entries.length} / ${dictionaryLimits.maxEntries}件`, empty: dictionaryDraft.entries.length === 0, disabled: dictionaryDisabled, addDisabled: !snapshot.can({ type: "dictionary.word.added" }), saveDisabled: dictionaryDisabled || !dictionaryDirty, diff --git a/frontend/src/roots.test.tsx b/frontend/src/roots.test.tsx index 8a11bdf..de345d5 100644 --- a/frontend/src/roots.test.tsx +++ b/frontend/src/roots.test.tsx @@ -46,6 +46,31 @@ it("edits a dictionary through Root and restores focus when the panel closes", a await waitFor(() => expect(document.activeElement).toBe(trigger)); }, 20000); +it("accepts eighty supplementary characters in dictionary fields and retains an oversized draft", async () => { + const api: OperatorApi = { + load: vi.fn().mockResolvedValue({ visible: true, captions: [] }), + save: vi.fn(), setVisible: vi.fn(), sendTest: vi.fn(), setPublicationMode: vi.fn(), setDisplay: vi.fn(), setAi: vi.fn(), setConnection: vi.fn(), + setDictionary: vi.fn().mockImplementation(update => Promise.resolve({ revision: update.base_revision + 1, entries: update.entries })), + }; + render(); + const user = userEvent.setup(); + await user.click(await screen.findByRole("button", { name: "単語辞書" })); + await user.click(screen.getByRole("button", { name: "単語を追加" })); + const term = screen.getByRole("textbox", { name: "1件目の表記" }); + const text = "𠮷".repeat(80); + await user.click(term); + await user.paste(text); + expect(term.value).toBe(text); + await user.click(screen.getByRole("button", { name: "辞書を保存" })); + await screen.findByText("辞書を保存しました。"); + expect(api.setDictionary).toHaveBeenLastCalledWith(expect.objectContaining({ entries: [expect.objectContaining({ term: text })] }), expect.any(AbortSignal)); + await user.type(term, "𠮷"); + await user.click(screen.getByRole("button", { name: "辞書を保存" })); + expect(term.value).toBe(text + "𠮷"); + expect(api.setDictionary).toHaveBeenCalledTimes(1); + expect(screen.getByRole("alert").textContent).toContain("80文字以内"); +}); + it("focuses an editable word when the dictionary is full and restores its trigger", async () => { const api: OperatorApi = { load: vi.fn().mockResolvedValue({ visible: true, captions: [], dictionary: { revision: 1, diff --git a/frontend/src/views/operator/dictionary-settings.tsx b/frontend/src/views/operator/dictionary-settings.tsx index d254238..b1780f0 100644 --- a/frontend/src/views/operator/dictionary-settings.tsx +++ b/frontend/src/views/operator/dictionary-settings.tsx @@ -25,11 +25,11 @@ export function DictionarySettingsView({ parameters }: { parameters: DictionaryP - - -