From baf3c0ab64a7d76fbfc500f20319aeb78bab712b Mon Sep 17 00:00:00 2001 From: MSL <232237854+milla-jovovich@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:09:47 -0700 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20i18n=20support=20=E2=80=94=208=20la?= =?UTF-8?q?nguages=20for=20MemPalace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add language dictionaries: English, French, Korean, Japanese, Spanish, German, Simplified Chinese, Traditional Chinese. Each language is a single JSON file with: - Localized terms (palace, wing, closet, drawer, etc.) - CLI output strings with {var} interpolation - AAAK compression instructions in that language - Regex patterns for offline topic/quote/action extraction Usage: Dialect(lang="ko") or set "language": "ko" in config. Contributors can add new languages by copying en.json and translating. Dialect class now accepts lang param and loads AAAK instruction + regex patterns from the i18n dictionary automatically. Tests: mempalace/i18n/test_i18n.py — all 8 languages pass. Co-Authored-By: Claude Opus 4.6 (1M context) --- mempalace/dialect.py | 14 +++++- mempalace/i18n/__init__.py | 76 +++++++++++++++++++++++++++++++++ mempalace/i18n/de.json | 44 +++++++++++++++++++ mempalace/i18n/en.json | 44 +++++++++++++++++++ mempalace/i18n/es.json | 44 +++++++++++++++++++ mempalace/i18n/fr.json | 44 +++++++++++++++++++ mempalace/i18n/ja.json | 44 +++++++++++++++++++ mempalace/i18n/ko.json | 44 +++++++++++++++++++ mempalace/i18n/test_i18n.py | 85 +++++++++++++++++++++++++++++++++++++ mempalace/i18n/zh-CN.json | 44 +++++++++++++++++++ mempalace/i18n/zh-TW.json | 44 +++++++++++++++++++ 11 files changed, 526 insertions(+), 1 deletion(-) create mode 100644 mempalace/i18n/__init__.py create mode 100644 mempalace/i18n/de.json create mode 100644 mempalace/i18n/en.json create mode 100644 mempalace/i18n/es.json create mode 100644 mempalace/i18n/fr.json create mode 100644 mempalace/i18n/ja.json create mode 100644 mempalace/i18n/ko.json create mode 100644 mempalace/i18n/test_i18n.py create mode 100644 mempalace/i18n/zh-CN.json create mode 100644 mempalace/i18n/zh-TW.json diff --git a/mempalace/dialect.py b/mempalace/dialect.py index e900965..5cbeded 100644 --- a/mempalace/dialect.py +++ b/mempalace/dialect.py @@ -317,13 +317,16 @@ class Dialect: dialect.generate_layer1("zettels/", output="LAYER1.aaak") """ - def __init__(self, entities: Dict[str, str] = None, skip_names: List[str] = None): + def __init__(self, entities: Dict[str, str] = None, skip_names: List[str] = None, + lang: str = None): """ Args: entities: Mapping of full names -> short codes. e.g. {"Alice": "ALC", "Bob": "BOB"} If None, entities are auto-coded from first 3 chars. skip_names: Names to skip (fictional characters, etc.) + lang: Language code (e.g. "fr", "ko"). Loads AAAK instruction + and regex patterns from i18n dictionary. """ self.entity_codes = {} if entities: @@ -332,6 +335,14 @@ class Dialect: self.entity_codes[name.lower()] = code self.skip_names = [n.lower() for n in (skip_names or [])] + # Load language-specific AAAK instruction and regex patterns + from mempalace.i18n import load_lang, t, current_lang, get_regex + if lang: + load_lang(lang) + self.lang = lang or current_lang() + self.aaak_instruction = t("aaak.instruction") + self.lang_regex = get_regex() + @classmethod def from_config(cls, config_path: str) -> "Dialect": """Load entity mappings from a JSON config file. @@ -347,6 +358,7 @@ class Dialect: return cls( entities=config.get("entities", {}), skip_names=config.get("skip_names", []), + lang=config.get("lang"), ) def save_config(self, config_path: str): diff --git a/mempalace/i18n/__init__.py b/mempalace/i18n/__init__.py new file mode 100644 index 0000000..1b90b4d --- /dev/null +++ b/mempalace/i18n/__init__.py @@ -0,0 +1,76 @@ +"""i18n — Language dictionaries for MemPalace. + +Usage: + from mempalace.i18n import load_lang, t + + load_lang("fr") # load French + print(t("cli.mine_start", path="/docs")) # "Extraction de /docs..." + print(t("terms.wing")) # "aile" + print(t("aaak.instruction")) # AAAK compression instruction in French +""" + +import json +from pathlib import Path + +_LANG_DIR = Path(__file__).parent +_strings: dict = {} +_current_lang: str = "en" + + +def available_languages() -> list[str]: + """Return list of available language codes.""" + return sorted(p.stem for p in _LANG_DIR.glob("*.json")) + + +def load_lang(lang: str = "en") -> dict: + """Load a language dictionary. Falls back to English if not found.""" + global _strings, _current_lang + lang_file = _LANG_DIR / f"{lang}.json" + if not lang_file.exists(): + lang_file = _LANG_DIR / "en.json" + lang = "en" + _strings = json.loads(lang_file.read_text(encoding="utf-8")) + _current_lang = lang + return _strings + + +def t(key: str, **kwargs) -> str: + """Get a translated string by dotted key. Supports {var} interpolation. + + t("cli.mine_complete", closets=5, drawers=20) + → "Done. 5 closets, 20 drawers created." + """ + if not _strings: + load_lang("en") + parts = key.split(".", 1) + if len(parts) == 2: + section, name = parts + val = _strings.get(section, {}).get(name, key) + else: + val = _strings.get(key, key) + if kwargs and isinstance(val, str): + try: + val = val.format(**kwargs) + except (KeyError, IndexError): + pass + return val + + +def current_lang() -> str: + """Return current language code.""" + return _current_lang + + +def get_regex() -> dict: + """Return the regex patterns for the current language. + + Keys: topic_pattern, stop_words, quote_pattern, action_pattern. + Returns empty dict if no regex section in the language file. + """ + if not _strings: + load_lang("en") + return _strings.get("regex", {}) + + +# Auto-load English on import +load_lang("en") diff --git a/mempalace/i18n/de.json b/mempalace/i18n/de.json new file mode 100644 index 0000000..c6677b3 --- /dev/null +++ b/mempalace/i18n/de.json @@ -0,0 +1,44 @@ +{ + "lang": "de", + "label": "Deutsch", + "terms": { + "palace": "Palast", + "wing": "Flügel", + "hall": "Flur", + "closet": "Schrank", + "drawer": "Schublade", + "mine": "schürfen", + "search": "suchen", + "status": "Status", + "init": "initialisieren", + "repair": "reparieren", + "migrate": "migrieren", + "entity": "Entität", + "topic": "Thema" + }, + "cli": { + "mine_start": "Schürfe {path}...", + "mine_complete": "Fertig. {closets} Schränke, {drawers} Schubladen erstellt.", + "mine_skip": "Bereits geschürft. Verwenden Sie --force zum Wiederholen.", + "search_no_results": "Keine Ergebnisse für: {query}", + "search_results": "{count} Ergebnisse gefunden:", + "status_palace": "Palast: {path}", + "status_wings": "{count} Flügel", + "status_closets": "{count} Schränke", + "status_drawers": "{count} Schubladen", + "init_complete": "Palast initialisiert in {path}", + "init_exists": "Palast existiert bereits in {path}", + "repair_complete": "Reparatur abgeschlossen. {fixed} Probleme behoben.", + "migrate_complete": "Migration abgeschlossen.", + "no_palace": "Kein Palast gefunden. Ausführen: mempalace init " + }, + "aaak": { + "instruction": "Auf Deutsch komprimieren. Bindestriche zwischen Wörtern, Pipes zwischen Konzepten. Artikel und Füllwörter weglassen. Eigennamen und Zahlen exakt beibehalten." + }, + "regex": { + "topic_pattern": "[A-ZÄÖÜß][a-zäöüß]{2,}|[A-Za-zÄÖÜäöüß]{3,}", + "stop_words": "der die das ein eine eines einer einem einen den dem des und oder aber denn weil wenn als ob auch noch schon sehr viel nur nicht mehr kann wird hat ist sind war waren sein haben wurde mit von zu für auf in an um über nach durch", + "quote_pattern": "\\u201E([^\\u201C]{10,200})\\u201C|\"([^\"]{10,200})\"", + "action_pattern": "(?:gebaut|behoben|geschrieben|hinzugefügt|gepusht|gemessen|getestet|überprüft|erstellt|gelöscht|aktualisiert|konfiguriert|bereitgestellt|migriert)\\s+[\\wÄÖÜäöüß\\s]{3,30}" + } +} diff --git a/mempalace/i18n/en.json b/mempalace/i18n/en.json new file mode 100644 index 0000000..88c97db --- /dev/null +++ b/mempalace/i18n/en.json @@ -0,0 +1,44 @@ +{ + "lang": "en", + "label": "English", + "terms": { + "palace": "palace", + "wing": "wing", + "hall": "hall", + "closet": "closet", + "drawer": "drawer", + "mine": "mine", + "search": "search", + "status": "status", + "init": "init", + "repair": "repair", + "migrate": "migrate", + "entity": "entity", + "topic": "topic" + }, + "cli": { + "mine_start": "Mining {path}...", + "mine_complete": "Done. {closets} closets, {drawers} drawers created.", + "mine_skip": "Already mined. Use --force to re-mine.", + "search_no_results": "No results for: {query}", + "search_results": "Found {count} results:", + "status_palace": "Palace: {path}", + "status_wings": "{count} wings", + "status_closets": "{count} closets", + "status_drawers": "{count} drawers", + "init_complete": "Palace initialized at {path}", + "init_exists": "Palace already exists at {path}", + "repair_complete": "Repair complete. {fixed} issues fixed.", + "migrate_complete": "Migration complete.", + "no_palace": "No palace found. Run: mempalace init " + }, + "aaak": { + "instruction": "Compress to index format. Hyphens between words, pipes between concepts. Drop articles and filler. Keep names and numbers exact." + }, + "regex": { + "topic_pattern": "[A-Z][a-z]{2,}|[A-Za-z][A-Za-z0-9_]{2,}", + "stop_words": "the this that these those some many most each every other only such very will would could should must shall yeah okay also even then now already still back done make take give know think want need going come find work added saved session summary conversation topics source about once just really actually here there where good great better thank please sorry right wrong true false", + "quote_pattern": "\"([^\"]{20,200})\"", + "action_pattern": "(?:built|fixed|wrote|added|pushed|measured|tested|reviewed|created|deleted|updated|configured|deployed|migrated)\\s+[\\w\\s]{3,30}" + } +} diff --git a/mempalace/i18n/es.json b/mempalace/i18n/es.json new file mode 100644 index 0000000..aa30e1b --- /dev/null +++ b/mempalace/i18n/es.json @@ -0,0 +1,44 @@ +{ + "lang": "es", + "label": "Español", + "terms": { + "palace": "palacio", + "wing": "ala", + "hall": "pasillo", + "closet": "armario", + "drawer": "cajón", + "mine": "extraer", + "search": "buscar", + "status": "estado", + "init": "inicializar", + "repair": "reparar", + "migrate": "migrar", + "entity": "entidad", + "topic": "tema" + }, + "cli": { + "mine_start": "Extrayendo {path}...", + "mine_complete": "Listo. {closets} armarios, {drawers} cajones creados.", + "mine_skip": "Ya extraído. Use --force para repetir.", + "search_no_results": "Sin resultados para: {query}", + "search_results": "{count} resultados encontrados:", + "status_palace": "Palacio: {path}", + "status_wings": "{count} alas", + "status_closets": "{count} armarios", + "status_drawers": "{count} cajones", + "init_complete": "Palacio inicializado en {path}", + "init_exists": "Ya existe un palacio en {path}", + "repair_complete": "Reparación completa. {fixed} problemas corregidos.", + "migrate_complete": "Migración completa.", + "no_palace": "No se encontró palacio. Ejecute: mempalace init " + }, + "aaak": { + "instruction": "Comprima en español. Guiones entre palabras, pipes entre conceptos. Elimine artículos y palabras de relleno. Mantenga nombres propios y números exactos." + }, + "regex": { + "topic_pattern": "[A-ZÁ-Ú][a-zá-ú]{2,}|[A-Za-zÁ-ú]{3,}", + "stop_words": "el la los las un una unos unas de del al en con por para su sus mi mis tu tus es son está están fue ser estar haber sido como pero más muy también todo todos toda todas este esta estos estas ese esa esos esas que quien cual donde cuando porque aunque sin", + "quote_pattern": "\"([^\"]{10,200})\"|«([^»]{10,200})»", + "action_pattern": "(?:construido|corregido|escrito|añadido|enviado|medido|probado|revisado|creado|eliminado|actualizado|configurado|desplegado|migrado)\\s+[\\wá-ú\\s]{3,30}" + } +} diff --git a/mempalace/i18n/fr.json b/mempalace/i18n/fr.json new file mode 100644 index 0000000..2e3d0b9 --- /dev/null +++ b/mempalace/i18n/fr.json @@ -0,0 +1,44 @@ +{ + "lang": "fr", + "label": "Français", + "terms": { + "palace": "palais", + "wing": "aile", + "hall": "couloir", + "closet": "placard", + "drawer": "tiroir", + "mine": "extraire", + "search": "chercher", + "status": "état", + "init": "initialiser", + "repair": "réparer", + "migrate": "migrer", + "entity": "entité", + "topic": "sujet" + }, + "cli": { + "mine_start": "Extraction de {path}...", + "mine_complete": "Terminé. {closets} placards, {drawers} tiroirs créés.", + "mine_skip": "Déjà extrait. Utilisez --force pour refaire.", + "search_no_results": "Aucun résultat pour : {query}", + "search_results": "{count} résultats trouvés :", + "status_palace": "Palais : {path}", + "status_wings": "{count} ailes", + "status_closets": "{count} placards", + "status_drawers": "{count} tiroirs", + "init_complete": "Palais initialisé dans {path}", + "init_exists": "Un palais existe déjà dans {path}", + "repair_complete": "Réparation terminée. {fixed} problèmes corrigés.", + "migrate_complete": "Migration terminée.", + "no_palace": "Aucun palais trouvé. Exécutez : mempalace init " + }, + "aaak": { + "instruction": "Comprimez en français. Tirets entre les mots, pipes entre les concepts. Supprimez les articles et mots de remplissage. Gardez les noms propres et chiffres exacts." + }, + "regex": { + "topic_pattern": "[A-ZÀ-Ý][a-zà-ÿ]{2,}|[A-Za-zÀ-ÿ]{3,}", + "stop_words": "le la les un une des de du au aux en et ou mais donc or ni car que qui ce cette ces son sa ses mon ma mes ton ta tes leur leurs nous vous ils elles on ne pas plus très bien aussi avec pour dans sur par est sont fait être avoir été comme tout tous toute toutes", + "quote_pattern": "«\\s*([^»]{10,200})\\s*»|\"([^\"]{10,200})\"", + "action_pattern": "(?:construit|corrigé|écrit|ajouté|poussé|mesuré|testé|révisé|créé|supprimé|mis à jour|configuré|déployé|migré)\\s+[\\wà-ÿ\\s]{3,30}" + } +} diff --git a/mempalace/i18n/ja.json b/mempalace/i18n/ja.json new file mode 100644 index 0000000..7cd73e1 --- /dev/null +++ b/mempalace/i18n/ja.json @@ -0,0 +1,44 @@ +{ + "lang": "ja", + "label": "日本語", + "terms": { + "palace": "宮殿", + "wing": "棟", + "hall": "廊下", + "closet": "クローゼット", + "drawer": "引き出し", + "mine": "採掘", + "search": "検索", + "status": "状態", + "init": "初期化", + "repair": "修復", + "migrate": "移行", + "entity": "エンティティ", + "topic": "トピック" + }, + "cli": { + "mine_start": "{path} を採掘中...", + "mine_complete": "完了。クローゼット {closets}個、引き出し {drawers}個 作成。", + "mine_skip": "採掘済み。再実行するには --force を使用。", + "search_no_results": "結果なし: {query}", + "search_results": "{count}件の結果:", + "status_palace": "宮殿: {path}", + "status_wings": "棟 {count}個", + "status_closets": "クローゼット {count}個", + "status_drawers": "引き出し {count}個", + "init_complete": "{path} に宮殿を初期化しました", + "init_exists": "{path} に宮殿は既に存在します", + "repair_complete": "修復完了。{fixed}件の問題を修正。", + "migrate_complete": "移行完了。", + "no_palace": "宮殿が見つかりません。実行: mempalace init <ディレクトリ>" + }, + "aaak": { + "instruction": "日本語で圧縮してください。概念間はパイプ(|)、単語間はハイフン(-)。助詞と接続詞は省略。固有名詞と数値は正確に保持。" + }, + "regex": { + "topic_pattern": "[\\u30A0-\\u30FF]{3,}|[\\u4E00-\\u9FFF]{2,}|[A-Za-z][A-Za-z0-9_]{2,}", + "stop_words": "は が を に で と も の へ から まで より した します している されて です ます ました こと もの ため それ これ その この あの ない なく ある いる する", + "quote_pattern": "「([^」]{10,100})」", + "action_pattern": "(構築|修正|追加|削除|確認|作成|実装|修復|書き直し|テスト|検証|更新|設定|起動|停止)(?:し|した|して|する|します)" + } +} diff --git a/mempalace/i18n/ko.json b/mempalace/i18n/ko.json new file mode 100644 index 0000000..4ff37c7 --- /dev/null +++ b/mempalace/i18n/ko.json @@ -0,0 +1,44 @@ +{ + "lang": "ko", + "label": "한국어", + "terms": { + "palace": "궁전", + "wing": "날개", + "hall": "복도", + "closet": "벽장", + "drawer": "서랍", + "mine": "채굴", + "search": "검색", + "status": "상태", + "init": "초기화", + "repair": "수리", + "migrate": "마이그레이션", + "entity": "개체", + "topic": "주제" + }, + "cli": { + "mine_start": "{path} 채굴 중...", + "mine_complete": "완료. 벽장 {closets}개, 서랍 {drawers}개 생성.", + "mine_skip": "이미 채굴됨. --force로 다시 실행하세요.", + "search_no_results": "결과 없음: {query}", + "search_results": "{count}개 결과 발견:", + "status_palace": "궁전: {path}", + "status_wings": "날개 {count}개", + "status_closets": "벽장 {count}개", + "status_drawers": "서랍 {drawers}개", + "init_complete": "{path}에 궁전 초기화 완료", + "init_exists": "{path}에 궁전이 이미 존재합니다", + "repair_complete": "수리 완료. {fixed}개 문제 해결.", + "migrate_complete": "마이그레이션 완료.", + "no_palace": "궁전을 찾을 수 없습니다. 실행: mempalace init <폴더>" + }, + "aaak": { + "instruction": "한국어로 압축하세요. 개념 사이에 파이프(|), 단어 연결에 하이픈(-). 조사와 접속사는 생략. 고유명사와 숫자는 정확히 유지." + }, + "regex": { + "topic_pattern": "[가-힣]{2,}|[A-Za-z][A-Za-z0-9_]{2,}", + "stop_words": "은 는 이 가 을 를 에 에서 의 로 으로 와 과 도 만 까지 부터 처럼 보다 한 하는 했다 합니다 했습니다 되었 있는 것 수 등 및 또는 그리고 하지만 때문에", + "quote_pattern": "\"([^\"]{10,100})\"|'([^']{10,100})'", + "action_pattern": "(구축|수정|추가|삭제|확인|생성|구현|수리|작성|테스트|검증|업데이트|설정|시작|중지)(?:했|한|하여|합니다|했습니다)" + } +} diff --git a/mempalace/i18n/test_i18n.py b/mempalace/i18n/test_i18n.py new file mode 100644 index 0000000..c9223c3 --- /dev/null +++ b/mempalace/i18n/test_i18n.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Quick smoke test for i18n dictionaries + Dialect integration.""" + +import json +import sys +from pathlib import Path + +# Add parent to path so we can import mempalace +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + +from mempalace.i18n import load_lang, t, available_languages, current_lang +from mempalace.dialect import Dialect + + +def test_all_languages_load(): + """Every JSON file loads without error and has required keys.""" + required_sections = ["terms", "cli", "aaak"] + required_terms = ["palace", "wing", "closet", "drawer"] + + langs = available_languages() + assert len(langs) >= 7, f"Expected 7+ languages, got {len(langs)}" + + for lang in langs: + strings = load_lang(lang) + for section in required_sections: + assert section in strings, f"{lang}: missing section '{section}'" + for term in required_terms: + assert term in strings["terms"], f"{lang}: missing term '{term}'" + assert len(strings["terms"][term]) > 0, f"{lang}: empty term '{term}'" + assert "instruction" in strings["aaak"], f"{lang}: missing aaak.instruction" + + print(f" PASS: {len(langs)} languages load correctly") + + +def test_interpolation(): + """String interpolation works for all languages.""" + for lang in available_languages(): + load_lang(lang) + result = t("cli.mine_complete", closets=5, drawers=100) + assert "5" in result, f"{lang}: closets count missing from mine_complete" + assert "100" in result, f"{lang}: drawers count missing from mine_complete" + + print(" PASS: interpolation works for all languages") + + +def test_dialect_loads_lang(): + """Dialect class picks up the language instruction.""" + for lang in available_languages(): + d = Dialect(lang=lang) + assert d.lang == lang, f"Expected lang={lang}, got {d.lang}" + assert len(d.aaak_instruction) > 10, f"{lang}: AAAK instruction too short" + + print(" PASS: Dialect loads language instruction for all languages") + + +def test_dialect_compress_samples(): + """Compress sample text in different languages, verify output isn't empty.""" + samples = { + "en": "We decided to migrate from SQLite to PostgreSQL for better concurrent writes. Ben approved the PR yesterday.", + "fr": "Nous avons décidé de migrer de SQLite vers PostgreSQL pour une meilleure écriture concurrente. Ben a approuvé le PR hier.", + "ko": "더 나은 동시 쓰기를 위해 SQLite에서 PostgreSQL로 마이그레이션하기로 했습니다. 벤이 어제 PR을 승인했습니다.", + "ja": "同時書き込みの改善のため、SQLiteからPostgreSQLに移行することを決定しました。ベンが昨日PRを承認しました。", + "es": "Decidimos migrar de SQLite a PostgreSQL para mejor escritura concurrente. Ben aprobó el PR ayer.", + "de": "Wir haben beschlossen, von SQLite auf PostgreSQL zu migrieren für bessere gleichzeitige Schreibvorgänge. Ben hat den PR gestern genehmigt.", + "zh-CN": "我们决定从SQLite迁移到PostgreSQL以获得更好的并发写入。Ben昨天批准了PR。", + } + + for lang, text in samples.items(): + d = Dialect(lang=lang) + compressed = d.compress(text) + assert len(compressed) > 0, f"{lang}: compression returned empty" + assert len(compressed) < len(text) * 2, f"{lang}: compression expanded text" + print(f" {lang}: {len(text)} chars → {len(compressed)} chars") + print(f" {compressed[:80]}") + + print(" PASS: compression works for all sample languages") + + +if __name__ == "__main__": + print("i18n smoke tests:") + test_all_languages_load() + test_interpolation() + test_dialect_loads_lang() + test_dialect_compress_samples() + print("\nAll tests passed.") diff --git a/mempalace/i18n/zh-CN.json b/mempalace/i18n/zh-CN.json new file mode 100644 index 0000000..4e41a57 --- /dev/null +++ b/mempalace/i18n/zh-CN.json @@ -0,0 +1,44 @@ +{ + "lang": "zh-CN", + "label": "简体中文", + "terms": { + "palace": "宫殿", + "wing": "翼", + "hall": "走廊", + "closet": "柜子", + "drawer": "抽屉", + "mine": "挖掘", + "search": "搜索", + "status": "状态", + "init": "初始化", + "repair": "修复", + "migrate": "迁移", + "entity": "实体", + "topic": "主题" + }, + "cli": { + "mine_start": "正在挖掘 {path}...", + "mine_complete": "完成。创建了 {closets} 个柜子、{drawers} 个抽屉。", + "mine_skip": "已挖掘。使用 --force 重新执行。", + "search_no_results": "未找到结果: {query}", + "search_results": "找到 {count} 个结果:", + "status_palace": "宫殿: {path}", + "status_wings": "{count} 个翼", + "status_closets": "{count} 个柜子", + "status_drawers": "{count} 个抽屉", + "init_complete": "宫殿已初始化于 {path}", + "init_exists": "{path} 中已存在宫殿", + "repair_complete": "修复完成。已修正 {fixed} 个问题。", + "migrate_complete": "迁移完成。", + "no_palace": "未找到宫殿。请运行: mempalace init <目录>" + }, + "aaak": { + "instruction": "用中文压缩。概念之间用管道符(|),词语之间用连字符(-)。省略虚词和连接词。保留专有名词和数字的准确性。" + }, + "regex": { + "topic_pattern": "[\\u4E00-\\u9FFF]{2,}|[A-Za-z][A-Za-z0-9_]{2,}", + "stop_words": "的 了 在 是 我 有 和 就 不 人 都 一 一个 上 也 很 到 说 要 去 你 会 着 没有 看 好 自己 这 那 她 他 它 们 但是 因为 所以 如果 虽然 然后 或者 而且", + "quote_pattern": "\\u201C([^\\u201D]{10,100})\\u201D|\"([^\"]{10,200})\"", + "action_pattern": "(构建|修复|添加|删除|确认|创建|实现|修理|编写|测试|验证|更新|配置|启动|停止)(?:了|完成|成功)" + } +} diff --git a/mempalace/i18n/zh-TW.json b/mempalace/i18n/zh-TW.json new file mode 100644 index 0000000..b65552b --- /dev/null +++ b/mempalace/i18n/zh-TW.json @@ -0,0 +1,44 @@ +{ + "lang": "zh-TW", + "label": "繁體中文", + "terms": { + "palace": "宮殿", + "wing": "翼", + "hall": "走廊", + "closet": "櫃子", + "drawer": "抽屜", + "mine": "挖掘", + "search": "搜尋", + "status": "狀態", + "init": "初始化", + "repair": "修復", + "migrate": "遷移", + "entity": "實體", + "topic": "主題" + }, + "cli": { + "mine_start": "正在挖掘 {path}...", + "mine_complete": "完成。建立了 {closets} 個櫃子、{drawers} 個抽屜。", + "mine_skip": "已挖掘。使用 --force 重新執行。", + "search_no_results": "未找到結果: {query}", + "search_results": "找到 {count} 個結果:", + "status_palace": "宮殿: {path}", + "status_wings": "{count} 個翼", + "status_closets": "{count} 個櫃子", + "status_drawers": "{count} 個抽屜", + "init_complete": "宮殿已初始化於 {path}", + "init_exists": "{path} 中已存在宮殿", + "repair_complete": "修復完成。已修正 {fixed} 個問題。", + "migrate_complete": "遷移完成。", + "no_palace": "未找到宮殿。請執行: mempalace init <目錄>" + }, + "aaak": { + "instruction": "用中文壓縮。概念之間用管道符(|),詞語之間用連字符(-)。省略虛詞和連接詞。保留專有名詞和數字的準確性。" + }, + "regex": { + "topic_pattern": "[\\u4E00-\\u9FFF]{2,}|[A-Za-z][A-Za-z0-9_]{2,}", + "stop_words": "的 了 在 是 我 有 和 就 不 人 都 一 一個 上 也 很 到 說 要 去 你 會 著 沒有 看 好 自己 這 那 她 他 它 們 但是 因為 所以 如果 雖然 然後 或者 而且", + "quote_pattern": "「([^」]{10,100})」|\u201c([^\u201d]{10,100})\u201d", + "action_pattern": "(構建|修復|添加|刪除|確認|創建|實現|修理|編寫|測試|驗證|更新|配置|啟動|停止)(?:了|完成|成功)" + } +} From c3f9b76d9a3203787ead5a2fcbedaf49cc432ed2 Mon Sep 17 00:00:00 2001 From: Igor Lins e Silva <4753812+igorls@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:14:06 -0300 Subject: [PATCH 2/2] fix(ci): resolve ruff lint + format failures - Remove unused `json` and `current_lang` imports from mempalace/i18n/test_i18n.py (F401) - Reformat Dialect.__init__ signature in mempalace/dialect.py (ruff format collapses multi-line signature, adds blank line after lazy import) Both auto-fixes from `ruff check --fix` / `ruff format`. No behavioral changes. --- mempalace/dialect.py | 6 ++++-- mempalace/i18n/test_i18n.py | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mempalace/dialect.py b/mempalace/dialect.py index 5cbeded..5a68583 100644 --- a/mempalace/dialect.py +++ b/mempalace/dialect.py @@ -317,8 +317,9 @@ class Dialect: dialect.generate_layer1("zettels/", output="LAYER1.aaak") """ - def __init__(self, entities: Dict[str, str] = None, skip_names: List[str] = None, - lang: str = None): + def __init__( + self, entities: Dict[str, str] = None, skip_names: List[str] = None, lang: str = None + ): """ Args: entities: Mapping of full names -> short codes. @@ -337,6 +338,7 @@ class Dialect: # Load language-specific AAAK instruction and regex patterns from mempalace.i18n import load_lang, t, current_lang, get_regex + if lang: load_lang(lang) self.lang = lang or current_lang() diff --git a/mempalace/i18n/test_i18n.py b/mempalace/i18n/test_i18n.py index c9223c3..e362c26 100644 --- a/mempalace/i18n/test_i18n.py +++ b/mempalace/i18n/test_i18n.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 """Quick smoke test for i18n dictionaries + Dialect integration.""" -import json import sys from pathlib import Path # Add parent to path so we can import mempalace sys.path.insert(0, str(Path(__file__).resolve().parents[2])) -from mempalace.i18n import load_lang, t, available_languages, current_lang +from mempalace.i18n import load_lang, t, available_languages from mempalace.dialect import Dialect