yanagi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +50 -0
- data/LICENSE +21 -0
- data/README.md +119 -0
- data/data/combinatorial.yml +59 -0
- data/data/exceptions.yml +15 -0
- data/data/exonyms.yml +14 -0
- data/data/mora.yml +790 -0
- data/exe/yanagi +7 -0
- data/lib/yanagi/audit.rb +301 -0
- data/lib/yanagi/cli.rb +345 -0
- data/lib/yanagi/cyrillic.rb +310 -0
- data/lib/yanagi/doc_sync.rb +194 -0
- data/lib/yanagi/lexicon.rb +211 -0
- data/lib/yanagi/mora.rb +81 -0
- data/lib/yanagi/normalize.rb +26 -0
- data/lib/yanagi/romaji.rb +55 -0
- data/lib/yanagi/rules.rb +89 -0
- data/lib/yanagi/version.rb +5 -0
- data/lib/yanagi.rb +15 -0
- data/yanagi.gemspec +34 -0
- metadata +94 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7d9c51329617c5af3c378c075b472bc750348149a804068ebe4fc83b70b43d43
|
|
4
|
+
data.tar.gz: a87fbe968fb608139db4c7cf4d59637fcb52d7e056f52b56d4ac4304798d34d2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 56b3ba1f958741308ad2ca52ac55a9965df0df4e101cd8f7af2e1725df4215f2f78081bc56608e7343593126eacd176e5f2b20515639802fd0123460217cf11f
|
|
7
|
+
data.tar.gz: 8b21ac53b568ab9a50fc71df6519cab2fcafbd945e4f40406d644b6632b1c99c10e779badbab953260bb16268e7c7eaad91641c4f1d6687764560047b0918408
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] — 2026-08-29
|
|
9
|
+
|
|
10
|
+
First release. Yanagi (柳) turns the Japanese→Ukrainian transliteration policy
|
|
11
|
+
from prose into executable rules, so a violation fails a build instead of
|
|
12
|
+
reaching print.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Mode A — generation.** `Yanagi.cyrillic` and `Yanagi.romaji` render kana
|
|
17
|
+
(or katakana, or a kanji + reading pair) into Ukrainian Cyrillic and Modified
|
|
18
|
+
Hepburn. Both derive from a single mora tokenizer, so they cannot disagree
|
|
19
|
+
about mora boundaries.
|
|
20
|
+
- **Mode B — audit.** `Yanagi::Audit` scans existing Ukrainian prose in three
|
|
21
|
+
tiers. Only tokens anchored to a known Japanese lexical item are ever
|
|
22
|
+
rewritten; pattern matches merely rank what a human reviews. Findings are
|
|
23
|
+
reported by default and applied only from an approved findings file.
|
|
24
|
+
- **`Yanagi::DocSync`.** Parses the authoritative policy document and asserts it
|
|
25
|
+
agrees with the rules data, so the doc and the engine cannot drift.
|
|
26
|
+
- **CLI** (`yanagi`): `romaji`, `cyrillic`, `audit`, `apply`, `doc-sync`,
|
|
27
|
+
`lexicon`, `verify-gold`.
|
|
28
|
+
- **Rules as data** in `data/`: the mora table with its forbidden variants,
|
|
29
|
+
combinatorial rules, exonyms, the lexicon, a native-Ukrainian allowlist, and
|
|
30
|
+
an exceptions table.
|
|
31
|
+
|
|
32
|
+
### Policy encoded
|
|
33
|
+
|
|
34
|
+
- Mora table rejecting Polivanov: ші, чі, цу, фу, джі, дз, ґ.
|
|
35
|
+
- Palatalised syllables (yōon), including the soft sign before о: шьо, чьо, джьо.
|
|
36
|
+
- い after a vowel renders as «і», never «ї».
|
|
37
|
+
- ん renders as «м» before п and б, «н» elsewhere.
|
|
38
|
+
- Long vowels are not doubled.
|
|
39
|
+
- Sokuon っ doubles the following consonant before the plosives п and к, and is
|
|
40
|
+
not rendered before sibilants and affricates.
|
|
41
|
+
- Established Ukrainian exonyms take precedence over derivation.
|
|
42
|
+
|
|
43
|
+
### Verification
|
|
44
|
+
|
|
45
|
+
- 377/377 hand-verified glossary pairs pass, with an empty exceptions table.
|
|
46
|
+
- Romaji output is byte-identical across 1496 dictionary readings, confirming
|
|
47
|
+
the engine was extracted from its predecessor without behaviour change.
|
|
48
|
+
- Zero runtime dependencies; stdlib only.
|
|
49
|
+
|
|
50
|
+
[0.1.0]: https://github.com/shogi-dojo/yanagi/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shogi-dojo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Yanagi (柳)
|
|
2
|
+
|
|
3
|
+
Deterministic Japanese → Ukrainian transliteration and policy enforcement gem.
|
|
4
|
+
|
|
5
|
+
柳 (*yanagi* = willow / верба) — a symbol of Ukraine and a tree equally at home in Japanese poetry.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
- **Zero runtime dependencies** — standard library only (`yaml`, `json`, `set`, `unicode_normalize`).
|
|
9
|
+
- **Mode A (Generation)**: Deterministic kana and romaji to Ukrainian Cyrillic rendering.
|
|
10
|
+
- **Mode B (Audit)**: Lexicon-anchored prose audit and policy violation detector:
|
|
11
|
+
- **Tier 1**: AUTOFIX-eligible (matches lexicon entry via forbidden Polivanov/anglicism substitution).
|
|
12
|
+
- **Tier 2**: High-confidence report for unknown Japanese terms (contains JP markers, unanchored in lexicon/allowlist).
|
|
13
|
+
- **Tier 3**: Polivanov markers check (off by default).
|
|
14
|
+
- **Policy Synchronization (`DocSync`)**: Asserts agreement between markdown policy documents (`shared/transliteration.md`) and executable YAML rules.
|
|
15
|
+
|
|
16
|
+
## Corpus data
|
|
17
|
+
|
|
18
|
+
The gem ships the **policy rules** only: the mora table, combinatorial rules,
|
|
19
|
+
exonyms and the exceptions table.
|
|
20
|
+
|
|
21
|
+
Mode B additionally needs two files generated from a specific translation
|
|
22
|
+
project — a lexicon of known Japanese terms and an allowlist of native
|
|
23
|
+
Ukrainian vocabulary. These are **not distributed**, because they are one
|
|
24
|
+
project's editorial research rather than transliteration policy.
|
|
25
|
+
|
|
26
|
+
Mode A (`cyrillic`, `romaji`) needs neither and works out of the box. Without
|
|
27
|
+
them Mode B simply reports nothing, since it only ever rewrites tokens anchored
|
|
28
|
+
to a known lexical item.
|
|
29
|
+
|
|
30
|
+
To use Mode B, generate the files and point the gem at them:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export YANAGI_DATA_DIR=/path/to/your/data
|
|
34
|
+
yanagi lexicon build --glossary /path/to/glossary.org
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Add this line to your application's Gemfile:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
gem 'yanagi'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or reference via git repository:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
gem 'yanagi', git: 'https://github.com/shogi-dojo/yanagi.git', tag: 'v0.1.0'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Ruby API Usage
|
|
53
|
+
|
|
54
|
+
### Mode A: Transliteration
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
require 'yanagi'
|
|
58
|
+
|
|
59
|
+
# Kana to Cyrillic
|
|
60
|
+
Yanagi.cyrillic("しゅうさい")
|
|
61
|
+
# => #<Yanagi::Result text="шюсай" source=:derived confidence=1.0>
|
|
62
|
+
|
|
63
|
+
Yanagi.cyrillic("シュウサイ")
|
|
64
|
+
# => #<Yanagi::Result text="шюсай" source=:derived confidence=1.0>
|
|
65
|
+
|
|
66
|
+
# Exonyms
|
|
67
|
+
Yanagi.cyrillic("とうきょう")
|
|
68
|
+
# => #<Yanagi::Result text="Токіо" source=:exonym confidence=1.0>
|
|
69
|
+
|
|
70
|
+
# Kana to Modified Hepburn Romaji
|
|
71
|
+
Yanagi.romaji("しゅうさい")
|
|
72
|
+
# => "shuusai"
|
|
73
|
+
|
|
74
|
+
# From Romaji to Cyrillic
|
|
75
|
+
Yanagi.from_romaji("shuusai")
|
|
76
|
+
# => "шюсай"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Mode B: Prose Audit & Correction
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
# Scan files or directories
|
|
83
|
+
findings = Yanagi.audit(["path/to/chapter.org"], tier2: true, tier3: false)
|
|
84
|
+
|
|
85
|
+
# Apply approved findings
|
|
86
|
+
Yanagi::Audit.apply("audit/findings.yml")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Policy Agreement Check
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
sync = Yanagi.doc_sync(path: "shared/transliteration.md")
|
|
93
|
+
sync.synced? # => true
|
|
94
|
+
sync.diff # => []
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## CLI Usage
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Romaji & Cyrillic generation
|
|
101
|
+
yanagi romaji しゅうさい
|
|
102
|
+
yanagi cyrillic しゅうさい
|
|
103
|
+
yanagi cyrillic しゅうさい --format json
|
|
104
|
+
|
|
105
|
+
# Prose auditing
|
|
106
|
+
yanagi audit books/meijin/translation/ua --format human
|
|
107
|
+
yanagi audit books/meijin/translation/ua --out audit/findings.yml
|
|
108
|
+
|
|
109
|
+
# Applying approved findings
|
|
110
|
+
yanagi apply audit/findings.yml
|
|
111
|
+
|
|
112
|
+
# Document synchronization & gold verification
|
|
113
|
+
yanagi doc-sync shared/transliteration.md
|
|
114
|
+
yanagi verify-gold
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT License. Copyright (c) 2026 shogi-dojo.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Combinatorial and contextual transliteration rules
|
|
2
|
+
---
|
|
3
|
+
sokuon:
|
|
4
|
+
rule: "っ (sokuon) doubles the following consonant before the plosives «п» and «к»; before sibilants and affricates it is not rendered"
|
|
5
|
+
geminating: ["п", "к"]
|
|
6
|
+
examples:
|
|
7
|
+
- { kana: "いっぺき", rendered: "іппекі" }
|
|
8
|
+
- { kana: "けっぺき", rendered: "кеппекі" }
|
|
9
|
+
- { kana: "ごじゅっぽ", rendered: "ґоджюппо" }
|
|
10
|
+
- { kana: "にっかい", rendered: "Ніккай" }
|
|
11
|
+
- { kana: "ほっけきょう", rendered: "Хоккекьо" }
|
|
12
|
+
- { kana: "がっかい", rendered: "Ґаккай" }
|
|
13
|
+
- { kana: "しっちゃく", rendered: "шічяку" }
|
|
14
|
+
- { kana: "てっしゅう", rendered: "Тешю" }
|
|
15
|
+
- { kana: "どうじんざっし", rendered: "доджін-дзаші" }
|
|
16
|
+
|
|
17
|
+
moraic_n:
|
|
18
|
+
rule: "ん before labials (p, b) renders as 'м', and as 'н' elsewhere (including before m)"
|
|
19
|
+
labials: ["п", "б"]
|
|
20
|
+
default: "н"
|
|
21
|
+
examples:
|
|
22
|
+
- { kana: "もんぷく", rendered: "момпуку" }
|
|
23
|
+
- { kana: "さんぼう", rendered: "самбо" }
|
|
24
|
+
- { kana: "しんぶん", rendered: "шімбун" }
|
|
25
|
+
- { kana: "さんまいきょう", rendered: "санмайкьо" }
|
|
26
|
+
- { kana: "らんま", rendered: "ранма" }
|
|
27
|
+
- { kana: "しんしゅう", rendered: "шіншю" }
|
|
28
|
+
|
|
29
|
+
i_after_vowel:
|
|
30
|
+
rule: "い (i) after any vowel within a Japanese stem renders as 'і', never 'ї'"
|
|
31
|
+
target: "і"
|
|
32
|
+
forbidden: "ї"
|
|
33
|
+
examples:
|
|
34
|
+
- { kana: "かるいざわ", rendered: "каруідзава" }
|
|
35
|
+
- { kana: "ごい", rendered: "ґоі" }
|
|
36
|
+
- { kana: "きいん", rendered: "кіін" }
|
|
37
|
+
- { kana: "ぬいもん", rendered: "нуімон" }
|
|
38
|
+
- { kana: "おてあい", rendered: "отеай" }
|
|
39
|
+
|
|
40
|
+
apostrophe_i:
|
|
41
|
+
rule: "After apostrophe in Japanese names/words, write 'і', not 'ї'"
|
|
42
|
+
target: "і"
|
|
43
|
+
forbidden: "ї"
|
|
44
|
+
examples:
|
|
45
|
+
- { romaji: "Jun'ichi", rendered: "Джюн'ічі" }
|
|
46
|
+
- { romaji: "Kan'ichi", rendered: "Кан'ічі" }
|
|
47
|
+
|
|
48
|
+
long_vowels:
|
|
49
|
+
rule: "Never double 'о'; double 'і' where morae genuinely collide; collapse long vowels always"
|
|
50
|
+
collapse: true
|
|
51
|
+
preserve_adjacent_i: true
|
|
52
|
+
examples:
|
|
53
|
+
- { kana: "おおさか", rendered: "осака" }
|
|
54
|
+
- { kana: "おおたけ", rendered: "отаке" }
|
|
55
|
+
- { kana: "おおしま", rendered: "ошіма" }
|
|
56
|
+
- { kana: "しゅうさい", rendered: "шюсай" }
|
|
57
|
+
- { kana: "きいん", rendered: "кіін" }
|
|
58
|
+
- { kana: "いいだ", rendered: "ііда" }
|
|
59
|
+
- { kana: "りいち", rendered: "ріічі" }
|
data/data/exceptions.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Exceptions table for underivable pairs and documented defects.
|
|
2
|
+
#
|
|
3
|
+
# status:
|
|
4
|
+
# accepted - a genuine, permanent exception to the regular rules
|
|
5
|
+
# pending - a data defect awaiting correction; FAILS `yanagi verify-gold`
|
|
6
|
+
# wontfix - an intentional divergence, deliberately not corrected
|
|
7
|
+
#
|
|
8
|
+
# A `pending` entry is a build failure by design, so unresolved defects stay
|
|
9
|
+
# visible instead of silently accumulating. Correct the source data, or
|
|
10
|
+
# reclassify the entry with a written reason.
|
|
11
|
+
#
|
|
12
|
+
# The 12 original entries (8 long-vowel `оо` violations plus 4 yōon/digraph
|
|
13
|
+
# defects) were corrected in books/meijin/glossary.org rather than excepted.
|
|
14
|
+
---
|
|
15
|
+
[]
|
data/data/exonyms.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Established Ukrainian exonyms (pre-empt regular phonetic transliteration)
|
|
2
|
+
---
|
|
3
|
+
とうきょう:
|
|
4
|
+
romaji: "tokyo"
|
|
5
|
+
cyrillic: "Токіо"
|
|
6
|
+
note: "Standard Ukrainian geographical exonym"
|
|
7
|
+
きょうと:
|
|
8
|
+
romaji: "kyoto"
|
|
9
|
+
cyrillic: "Кіото"
|
|
10
|
+
note: "Standard Ukrainian geographical exonym (pre-empts きょ -> кьо rule)"
|
|
11
|
+
おおさか:
|
|
12
|
+
romaji: "osaka"
|
|
13
|
+
cyrillic: "Осака"
|
|
14
|
+
note: "Standard Ukrainian geographical exonym"
|