Fix Wrap
This commit is contained in:
parent
42cda41458
commit
6badfcb246
6 changed files with 32 additions and 32 deletions
|
|
@ -327,7 +327,7 @@ Open the **Workflow** tab and choose **Wolf RPG (WolfDawn)** from the engine sel
|
||||||
| **0 Project** | Select the game root folder (browse or Enter — detection also runs when you reopen the tab). If `wolf_json/` does not exist yet, the tool automatically unpacks `.wolf` archives when needed and extracts text into the game's `wolf_json/` folder (maps, common events, databases, `Game.dat`, Evtext, and `names.json`). A checklist lists every JSON file in `wolf_json/`; tick the ones you want and click **Import** (or leave Step 0 — checked files auto-import into the tool's `files/` folder, matching the RPG Maker workflow). Extraction snapshots pristine binaries into `wolf_json/originals/` for idempotent inject in Step 6. |
|
| **0 Project** | Select the game root folder (browse or Enter — detection also runs when you reopen the tab). If `wolf_json/` does not exist yet, the tool automatically unpacks `.wolf` archives when needed and extracts text into the game's `wolf_json/` folder (maps, common events, databases, `Game.dat`, Evtext, and `names.json`). A checklist lists every JSON file in `wolf_json/`; tick the ones you want and click **Import** (or leave Step 0 — checked files auto-import into the tool's `files/` folder, matching the RPG Maker workflow). Extraction snapshots pristine binaries into `wolf_json/originals/` for idempotent inject in Step 6. |
|
||||||
| **1 Pre-process** | Optional: **dazedformat** normalises JSON in `wolf_json/` and `files/` (`json.dump`, indent 4) for clean git diffs; **Copy gameupdate/** installs the updater scripts, patch scripts, and `.gitignore` into the game root for git-based patching. Paths auto-fill from Step 0. |
|
| **1 Pre-process** | Optional: **dazedformat** normalises JSON in `wolf_json/` and `files/` (`json.dump`, indent 4) for clean git diffs; **Copy gameupdate/** installs the updater scripts, patch scripts, and `.gitignore` into the game root for git-based patching. Paths auto-fill from Step 0. |
|
||||||
| **2 Glossary** | Build `vocab.txt` before translating: copy the WOLF-tailored prompt into Cursor/Copilot with the extracted `files/` JSON, let it discover character names, speech registers, and lore terms, then paste the result into the in-tab editor and save. Item/skill/enemy value names (`names.json`) are translated in Step 3 and harvested into `vocab.txt` automatically during Phase 0 — do not list them here. |
|
| **2 Glossary** | Build `vocab.txt` before translating: copy the WOLF-tailored prompt into Cursor/Copilot with the extracted `files/` JSON, let it discover character names, speech registers, and lore terms, then paste the result into the in-tab editor and save. Item/skill/enemy value names (`names.json`) are translated in Step 3 and harvested into `vocab.txt` automatically during Phase 0 — do not list them here. |
|
||||||
| **3 Names** | Translate `names.json` (item/skill/enemy/map value names). WolfDawn tags each name with a per-entry **safety** badge (`safe`, `refs`, or `verify`). Phase 0 translates only `safe` and `refs` entries; `verify` names stay Japanese so inject skips them. Review the category breakdown for this game, pick **Translation mode** (Normal or Batch), and run **Translate safe names (Phase 0)**. |
|
| **3 Names** | Translate `names.json` (item/skill/enemy/map value names). WolfDawn tags each name with a per-entry **safety** badge (`safe`, `refs`, or `verify`). Phase 0 translates only `safe` entries; `refs` and `verify` names stay Japanese so inject skips them. Review the category breakdown for this game, pick **Translation mode** (Normal or Batch), and run **Translate safe names (Phase 0)**. |
|
||||||
| **4 Database** | Review the **discovery summary** to see where this game's text lives (standard RPG sheets vs custom dialogue tables). Database sheets are classified as foundation (items, skills, descriptions — translate first) or narrative (custom event/profile sheets — translate after foundation). Use **Translate foundation DB** then **Translate narrative DB**, or tick specific sheets and run **Translate checked sheets only**. Optional: copy the **DB structure prompt** for an AI audit and import the returned JSON into `wolf_json/db_profile.json`. |
|
| **4 Database** | Review the **discovery summary** to see where this game's text lives (standard RPG sheets vs custom dialogue tables). Database sheets are classified as foundation (items, skills, descriptions — translate first) or narrative (custom event/profile sheets — translate after foundation). Use **Translate foundation DB** then **Translate narrative DB**, or tick specific sheets and run **Translate checked sheets only**. Optional: copy the **DB structure prompt** for an AI audit and import the returned JSON into `wolf_json/db_profile.json`. |
|
||||||
| **5 Maps/Events** | Translate map scripts (`.mps`), common events (`CommonEvent.dat`), `Game.dat`, and Evtext. Run after Steps 3–4 so vocab and database terms are consistent. Configure speaker handling for low-confidence nameplate guesses. Batch mode is recommended for large `CommonEvent.dat` files. |
|
| **5 Maps/Events** | Translate map scripts (`.mps`), common events (`CommonEvent.dat`), `Game.dat`, and Evtext. Run after Steps 3–4 so vocab and database terms are consistent. Configure speaker handling for low-confidence nameplate guesses. Batch mode is recommended for large `CommonEvent.dat` files. |
|
||||||
| **6 Inject** | Write translations back into the game's `Data/` binaries and refresh git-tracked `wolf_json/`. Uses pristine originals from `wolf_json/originals/`. Quick inject or inject all. |
|
| **6 Inject** | Write translations back into the game's `Data/` binaries and refresh git-tracked `wolf_json/`. Uses pristine originals from `wolf_json/originals/`. Quick inject or inject all. |
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ Mirrors the RPGMaker WorkflowTab, driven by the vendored WolfDawn ``wolf`` CLI
|
||||||
Step 1 Pre-process - optional dazedformat + gameupdate/ copy before translating
|
Step 1 Pre-process - optional dazedformat + gameupdate/ copy before translating
|
||||||
Step 2 Glossary - build vocab.txt (characters / worldbuilding terms) before
|
Step 2 Glossary - build vocab.txt (characters / worldbuilding terms) before
|
||||||
translating so the AI keeps names and voice consistent
|
translating so the AI keeps names and voice consistent
|
||||||
Step 3 Names - translate names.json (Phase 0). WolfDawn safe/refs entries
|
Step 3 Names - translate names.json (Phase 0). WolfDawn safe entries
|
||||||
are translated per-name; verify names are skipped. Harvests
|
are translated per-name; refs and verify names are skipped.
|
||||||
short name terms into vocab.txt.
|
Harvests short name terms into vocab.txt.
|
||||||
Step 4 Database - discover content layout; translate foundation DB sheets
|
Step 4 Database - discover content layout; translate foundation DB sheets
|
||||||
(items, skills, descriptions) before narrative custom sheets
|
(items, skills, descriptions) before narrative custom sheets
|
||||||
Step 5 Maps/Events - .mps maps, CommonEvent, Game.dat, Evtext; speaker handling
|
Step 5 Maps/Events - .mps maps, CommonEvent, Game.dat, Evtext; speaker handling
|
||||||
|
|
@ -24,7 +24,7 @@ Mirrors the RPGMaker WorkflowTab, driven by the vendored WolfDawn ``wolf`` CLI
|
||||||
Step 9 Saves - fix baked strings in existing .sav files (optional)
|
Step 9 Saves - fix baked strings in existing .sav files (optional)
|
||||||
|
|
||||||
names.json is staged into files/ but is NOT translated in the bulk phases - WolfDawn
|
names.json is staged into files/ but is NOT translated in the bulk phases - WolfDawn
|
||||||
tags each name safe / refs / verify and Phase 0 translates only safe+refs entries
|
tags each name safe / refs / verify and Phase 0 translates only safe entries
|
||||||
(per-name, not per-category), harvests them into vocab.txt, and Step 6 injects the
|
(per-name, not per-category), harvests them into vocab.txt, and Step 6 injects the
|
||||||
result.
|
result.
|
||||||
|
|
||||||
|
|
@ -1732,7 +1732,7 @@ class WolfWorkflowTab(QWidget):
|
||||||
|
|
||||||
note = self._desc(
|
note = self._desc(
|
||||||
"You don't need to add item / skill / enemy value names here: Phase 0 translates "
|
"You don't need to add item / skill / enemy value names here: Phase 0 translates "
|
||||||
"WolfDawn safe/refs entries from names.json and harvests them into vocab.txt "
|
"WolfDawn safe entries from names.json and harvests them into vocab.txt "
|
||||||
"Focus this glossary on characters and worldbuilding terms."
|
"Focus this glossary on characters and worldbuilding terms."
|
||||||
)
|
)
|
||||||
layout.addWidget(note)
|
layout.addWidget(note)
|
||||||
|
|
@ -2305,8 +2305,8 @@ class WolfWorkflowTab(QWidget):
|
||||||
layout.addWidget(_make_section_label("Step 3 · Translate Name Values (names.json)"))
|
layout.addWidget(_make_section_label("Step 3 · Translate Name Values (names.json)"))
|
||||||
layout.addWidget(self._desc(
|
layout.addWidget(self._desc(
|
||||||
"names.json is WolfDawn's project-wide name glossary. Each entry has a safety badge: "
|
"names.json is WolfDawn's project-wide name glossary. Each entry has a safety badge: "
|
||||||
"safe (display-only), refs (referenced by name but rewritten on inject), or verify "
|
"safe (display-only), refs (referenced by name - skipped), or verify "
|
||||||
"(also in indirect literals - skipped). Phase 0 translates every safe and refs entry "
|
"(also in indirect literals - skipped). Phase 0 translates every safe entry "
|
||||||
"individually, regardless of category, and harvests them into vocab.txt."
|
"individually, regardless of category, and harvests them into vocab.txt."
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -2325,8 +2325,8 @@ class WolfWorkflowTab(QWidget):
|
||||||
layout.addWidget(_make_hr())
|
layout.addWidget(_make_hr())
|
||||||
layout.addWidget(self._subheading("Translate names (Phase 0)"))
|
layout.addWidget(self._subheading("Translate names (Phase 0)"))
|
||||||
layout.addWidget(self._desc(
|
layout.addWidget(self._desc(
|
||||||
"Pick Normal or Batch below, then run Phase 0. Only safe and refs entries are sent to "
|
"Pick Normal or Batch below, then run Phase 0. Only safe entries are sent to "
|
||||||
"the model; verify names stay identical to source so inject skips them."
|
"the model; refs and verify names stay identical to source so inject skips them."
|
||||||
))
|
))
|
||||||
self._add_tl_mode_selector(layout)
|
self._add_tl_mode_selector(layout)
|
||||||
tl_btn = self._register(_make_btn("Translate safe names now (Phase 0)", "#00a86b"))
|
tl_btn = self._register(_make_btn("Translate safe names now (Phase 0)", "#00a86b"))
|
||||||
|
|
@ -2531,7 +2531,7 @@ class WolfWorkflowTab(QWidget):
|
||||||
layout.addWidget(_make_section_label("Step 6 · Inject Translations"))
|
layout.addWidget(_make_section_label("Step 6 · Inject Translations"))
|
||||||
layout.addWidget(self._desc(
|
layout.addWidget(self._desc(
|
||||||
"Writes the translated text back into the game's Data/ binaries with WolfDawn, "
|
"Writes the translated text back into the game's Data/ binaries with WolfDawn, "
|
||||||
"byte-exact. Translated safe/refs name values from names.json are applied across Data/ "
|
"byte-exact. Translated safe name values from names.json are applied across Data/ "
|
||||||
"(only the entries you translated change). Lines whose inline codes changed are skipped and "
|
"(only the entries you translated change). Lines whose inline codes changed are skipped and "
|
||||||
"reported (unless you allow drift). Full inject and any inject that includes "
|
"reported (unless you allow drift). Full inject and any inject that includes "
|
||||||
f"{NAMES_JSON} reset live Data/ from {WORK_DIR_NAME}/originals/ first. "
|
f"{NAMES_JSON} reset live Data/ from {WORK_DIR_NAME}/originals/ first. "
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ and Japanese embedded only in WOLF control codes (e.g. ``\\r[...]`` ruby).
|
||||||
|
|
||||||
names.json safety: WolfDawn tags every value name with a static ``safety`` badge
|
names.json safety: WolfDawn tags every value name with a static ``safety`` badge
|
||||||
(``safe``, ``refs``, or ``verify``). For ``kind == "names"`` documents, only
|
(``safe``, ``refs``, or ``verify``). For ``kind == "names"`` documents, only
|
||||||
entries whose badge is ``safe`` or ``refs`` are translated; ``verify`` names and
|
entries whose badge is ``safe`` are translated; ``refs`` and ``verify`` names and
|
||||||
legacy entries without a badge keep ``text == source``. After translating
|
legacy entries without a badge keep ``text == source``. After translating
|
||||||
names.json, translatable entries are harvested into ``vocab.txt`` (grouped by
|
names.json, translatable entries are harvested into ``vocab.txt`` (grouped by
|
||||||
``note``, with bilingual headers) so later phases keep item / skill / term names
|
``note``, with bilingual headers) so later phases keep item / skill / term names
|
||||||
|
|
@ -110,7 +110,7 @@ _WOLF_CODE_RE = re.compile(
|
||||||
# configurable from the workflow (data/wolf_speakers.json).
|
# configurable from the workflow (data/wolf_speakers.json).
|
||||||
SPEAKER_CONFIG = wolf_speakers.load_config()
|
SPEAKER_CONFIG = wolf_speakers.load_config()
|
||||||
|
|
||||||
# names.json: translate per-entry safe/refs badges only (see util.wolfdawn.names).
|
# names.json: translate per-entry safe badges only (see util.wolfdawn.names).
|
||||||
|
|
||||||
# Text wrapping: optional advanced rewrap via dazedwrap (wolfWrap/wolfWidth). Defaults
|
# Text wrapping: optional advanced rewrap via dazedwrap (wolfWrap/wolfWidth). Defaults
|
||||||
# off - the workflow uses WolfDawn relayout after inject instead.
|
# off - the workflow uses WolfDawn relayout after inject instead.
|
||||||
|
|
@ -342,13 +342,13 @@ def parseDocument(data, filename):
|
||||||
# Fresh extracts keep ``text == source``, so they still queue.
|
# Fresh extracts keep ``text == source``, so they still queue.
|
||||||
if IGNORETLTEXT and not _text_still_needs_translation(e):
|
if IGNORETLTEXT and not _text_still_needs_translation(e):
|
||||||
return False
|
return False
|
||||||
# names.json: only translate WolfDawn safe/refs entries (per-name badge).
|
# names.json: only translate WolfDawn safe entries (per-name badge).
|
||||||
if is_names and not wolf_names.is_name_translatable(e):
|
if is_names and not wolf_names.is_name_translatable(e):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Only translate entries that still need work; names.json also requires a
|
# Only translate entries that still need work; names.json also requires a
|
||||||
# safe/refs badge. Untouched leaves keep ``text == source`` so WolfDawn
|
# safe badge. Untouched leaves keep ``text == source`` so WolfDawn
|
||||||
# treats them as no-ops on inject.
|
# treats them as no-ops on inject.
|
||||||
translatable = [e for e in entries if _translatable(e)]
|
translatable = [e for e in entries if _translatable(e)]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ from util.wolfdawn import names as wn # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
class TestNameTranslatable(unittest.TestCase):
|
class TestNameTranslatable(unittest.TestCase):
|
||||||
def test_safe_and_refs_are_translatable(self):
|
def test_safe_is_translatable(self):
|
||||||
self.assertTrue(wn.is_name_translatable({"safety": "safe"}))
|
self.assertTrue(wn.is_name_translatable({"safety": "safe"}))
|
||||||
self.assertTrue(wn.is_name_translatable({"safety": "refs"}))
|
|
||||||
|
|
||||||
def test_verify_and_missing_are_not_translatable(self):
|
def test_refs_verify_and_missing_are_not_translatable(self):
|
||||||
|
self.assertFalse(wn.is_name_translatable({"safety": "refs"}))
|
||||||
self.assertFalse(wn.is_name_translatable({"safety": "verify"}))
|
self.assertFalse(wn.is_name_translatable({"safety": "verify"}))
|
||||||
self.assertFalse(wn.is_name_translatable({}))
|
self.assertFalse(wn.is_name_translatable({}))
|
||||||
self.assertFalse(wn.is_name_translatable({"safety": ""}))
|
self.assertFalse(wn.is_name_translatable({"safety": ""}))
|
||||||
|
|
@ -67,11 +67,11 @@ class TestCountNameSafety(unittest.TestCase):
|
||||||
self.assertEqual(counts["refs"], 1)
|
self.assertEqual(counts["refs"], 1)
|
||||||
self.assertEqual(counts["verify"], 1)
|
self.assertEqual(counts["verify"], 1)
|
||||||
self.assertEqual(counts["unknown"], 1)
|
self.assertEqual(counts["unknown"], 1)
|
||||||
self.assertEqual(counts["translatable"], 2)
|
self.assertEqual(counts["translatable"], 1)
|
||||||
|
|
||||||
def test_summary_mentions_translatable_count(self):
|
def test_summary_mentions_translatable_count(self):
|
||||||
summary = wn.format_name_safety_summary(self.DOC)
|
summary = wn.format_name_safety_summary(self.DOC)
|
||||||
self.assertIn("2 of 4", summary)
|
self.assertIn("1 of 4", summary)
|
||||||
self.assertIn("verify", summary)
|
self.assertIn("verify", summary)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,15 +173,15 @@ class TestTranslationWriteback(unittest.TestCase):
|
||||||
self.assertIsNone(err)
|
self.assertIsNone(err)
|
||||||
self.assertEqual(data["lines"][0]["text"], "EN_ゲームタイトル")
|
self.assertEqual(data["lines"][0]["text"], "EN_ゲームタイトル")
|
||||||
|
|
||||||
def test_names_translate_only_safe_and_refs(self):
|
def test_names_translate_only_safe(self):
|
||||||
(data, _t, err), captured = _WolfTranslateHarness().run(
|
(data, _t, err), captured = _WolfTranslateHarness().run(
|
||||||
NAMES_DOC, "names.json"
|
NAMES_DOC, "names.json"
|
||||||
)
|
)
|
||||||
self.assertIsNone(err)
|
self.assertIsNone(err)
|
||||||
self.assertEqual(data["names"][0]["text"], "EN_剣")
|
self.assertEqual(data["names"][0]["text"], "EN_剣")
|
||||||
self.assertEqual(data["names"][1]["text"], "EN_槍")
|
self.assertEqual(data["names"][1]["text"], "槍")
|
||||||
self.assertEqual(data["names"][2]["text"], "スイッチ状態")
|
self.assertEqual(data["names"][2]["text"], "スイッチ状態")
|
||||||
self.assertEqual(captured, [["剣", "槍"]])
|
self.assertEqual(captured, [["剣"]])
|
||||||
|
|
||||||
def test_names_without_safety_badges_translate_nothing(self):
|
def test_names_without_safety_badges_translate_nothing(self):
|
||||||
(data, _t, err), captured = _WolfTranslateHarness().run(
|
(data, _t, err), captured = _WolfTranslateHarness().run(
|
||||||
|
|
@ -198,7 +198,7 @@ class TestTranslationWriteback(unittest.TestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
harness.vocab_writes,
|
harness.vocab_writes,
|
||||||
[
|
[
|
||||||
("Weapon · 武器", [("剣", "EN_剣"), ("槍", "EN_槍")]),
|
("Weapon · 武器", [("剣", "EN_剣")]),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -451,7 +451,7 @@ class TestTranslationWriteback(unittest.TestCase):
|
||||||
self.assertEqual(captured, [])
|
self.assertEqual(captured, [])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
harness.vocab_writes,
|
harness.vocab_writes,
|
||||||
[("Weapon · 武器", [("剣", "Sword"), ("槍", "Spear")])],
|
[("Weapon · 武器", [("剣", "Sword")])],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ a ``note`` field (the WOLF database category it came from), and a static
|
||||||
``safety`` badge from WolfDawn's command-stream analysis:
|
``safety`` badge from WolfDawn's command-stream analysis:
|
||||||
|
|
||||||
* ``safe`` - display-only; no command references the string by name.
|
* ``safe`` - display-only; no command references the string by name.
|
||||||
* ``refs`` - referenced by name, but WolfDawn rewrites every literal on inject.
|
* ``refs`` - referenced by name (picture codes, switch labels, etc.); skipped.
|
||||||
* ``verify`` - also appears in indirect literals; left untranslated by default.
|
* ``verify`` - also appears in indirect literals; left untranslated by default.
|
||||||
|
|
||||||
:mod:`modules.wolfdawn` translates only entries whose badge is ``safe`` or
|
:mod:`modules.wolfdawn` translates only entries whose badge is ``safe``.
|
||||||
``refs``. ``verify`` and legacy entries without a badge keep ``text == source``
|
``refs``, ``verify``, and legacy entries without a badge keep ``text == source``
|
||||||
so injection is a no-op for them.
|
so injection is a no-op for them.
|
||||||
|
|
||||||
Phase 0 still harvests translated **short name-like** entries into ``vocab.txt``,
|
Phase 0 still harvests translated **short name-like** entries into ``vocab.txt``,
|
||||||
|
|
@ -27,7 +27,7 @@ from typing import Any
|
||||||
SAFETY_SAFE = "safe"
|
SAFETY_SAFE = "safe"
|
||||||
SAFETY_REFS = "refs"
|
SAFETY_REFS = "refs"
|
||||||
SAFETY_VERIFY = "verify"
|
SAFETY_VERIFY = "verify"
|
||||||
_TRANSLATABLE_SAFETY = frozenset({SAFETY_SAFE, SAFETY_REFS})
|
_TRANSLATABLE_SAFETY = frozenset({SAFETY_SAFE})
|
||||||
|
|
||||||
# names.json ``note`` values that are content fields, not short display names.
|
# names.json ``note`` values that are content fields, not short display names.
|
||||||
_VOCAB_SKIP_NOTE_RE = re.compile(
|
_VOCAB_SKIP_NOTE_RE = re.compile(
|
||||||
|
|
@ -77,7 +77,7 @@ def entry_safety(entry: dict[str, Any]) -> str:
|
||||||
|
|
||||||
|
|
||||||
def is_name_translatable(entry: dict[str, Any]) -> bool:
|
def is_name_translatable(entry: dict[str, Any]) -> bool:
|
||||||
"""True when WolfDawn marks *entry* as safe or refs (per-name, not per-category)."""
|
"""True when WolfDawn marks *entry* as safe (per-name, not per-category)."""
|
||||||
return entry_safety(entry) in _TRANSLATABLE_SAFETY
|
return entry_safety(entry) in _TRANSLATABLE_SAFETY
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -127,7 +127,7 @@ def count_name_safety(data: dict[str, Any]) -> dict[str, int]:
|
||||||
counts["verify"] += 1
|
counts["verify"] += 1
|
||||||
else:
|
else:
|
||||||
counts["unknown"] += 1
|
counts["unknown"] += 1
|
||||||
counts["translatable"] = counts["safe"] + counts["refs"]
|
counts["translatable"] = counts["safe"]
|
||||||
return counts
|
return counts
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -151,7 +151,7 @@ def format_name_safety_summary(data: dict[str, Any]) -> str:
|
||||||
badge_text = ", ".join(parts) if parts else "no badges"
|
badge_text = ", ".join(parts) if parts else "no badges"
|
||||||
return (
|
return (
|
||||||
f"{counts['translatable']} of {counts['total']} name(s) will translate "
|
f"{counts['translatable']} of {counts['total']} name(s) will translate "
|
||||||
f"({badge_text}). Verify names are skipped."
|
f"({badge_text}). Refs and verify names are skipped."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue