Skip to content

Commit 196ea3d

Browse files
committed
fix: Japanese punctuation problem
1 parent 2d9f1bb commit 196ea3d

4 files changed

Lines changed: 69 additions & 86 deletions

File tree

example/bin/romanize_example.dart

Lines changed: 32 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -89,70 +89,38 @@ void _hebrew() {
8989
}
9090

9191
void _multiLanguages() {
92-
// from home nct u
93-
final multiLanguagesText = '''
94-
Ooh, yeah-yeah-yeah
95-
I remember like it's yesterday, oh-no
96-
외로움에 힘들던 그때 oh-ooh
97-
낯설기만 하던 이 공기도
98-
두렵기만 하던 이 떨림도
99-
Now I know
100-
그 어렸던 마음까지 모두
101-
추억이 되게 해준 너
102-
Cause of us I'm feeling strong again
103-
서로 믿어줄 때면
104-
이곳에 날 당연하게 해
105-
When we shine bright
106-
I'm alive in the CT 날 노래해
107-
이 조명 아래 서로를 바라보면
108-
나도 몰래 웃게 돼 다 잊게 돼 yeah
109-
Cause I'm not alone
110-
내게 따듯한 집이 돼준 너
111-
어제와 지금의 나 또 다가올 내일 우리
112-
It all starts from home
113-
彩虹是和你再见的誓言
114-
飘过初雪到仲夏的夜
115-
直到和你眼神交会
116-
拥抱让我遗落寂寞 ooh
117-
随时能和你连结 为你回应
118-
見つけたよ ココロが安らぐ
119-
My home, my own (My own)
120-
映照着自己 感受到笑意都一往如初
121-
Once again (Ooh)
122-
僕ら強くなれる
123-
活成彼此的阳光
124-
我的存在自然而耀眼
125-
When we shine bright
126-
I'm alive in the CT 날 노래해
127-
이 조명 아래 서로를 바라보면
128-
나도 몰래 웃게 돼 다 잊게 돼 yeah
129-
Cause I'm not alone
130-
내게 따듯한 집이 돼준 너
131-
어제와 지금의 나 또 다가올 내일 우리
132-
It all starts from home
133-
이젠 길을 잃을 두려움도
134-
겁내기 바빴던 날들도
135-
Now It's all gone and I
136-
Found a reason to be myself
137-
Know that you are not alone anymore
138-
When we shine bright
139-
I'm alive in the CT 날 노래해
140-
이 조명 아래 서로를 바라보면
141-
나도 몰래 웃게 돼 다 잊게 돼 yeah
142-
Cause I'm not alone
143-
내게 따듯한 집이 돼준 너 (어제와 지금의 나)
144-
어제와 지금의 나 또 다가올 내일 우리
145-
It all starts from home
146-
Na-na-na, na-na-na, na-na-na
147-
From home (From home)
148-
Na-na-na, na-na-na, from home
149-
Yeah-yeah-yeah
150-
Na-na-na (Na-na-na)
151-
Na-na-na (Na-na-na)
152-
Na-na-na
153-
From home
154-
And we start from here our home
155-
''';
92+
final multiLanguagesText = '''Mixed Script Stress Test:
93+
-------------------------
94+
1. CJK Ambiguity (Should detect Chinese vs Japanese context):
95+
中文 (Chinese) vs 日本語 (Japanese)
96+
你好世界 (Hello World - CN) mixed with こんにちは (Hello - JP)
97+
東京 (Tokyo - JP/CN chars) vs 北京 (Beijing - CN)
98+
99+
2. RTL/LTR Alternation (Arabic/Hebrew/English):
100+
English -> العربية -> English -> עִבְרִית -> English
101+
Start: مرحبا (Marhaban) -> Middle: שָׁלוֹם (Shalom) -> End.
102+
Complex: "The letter 'ا' (Alif) and 'א' (Alef) start alphabets."
103+
104+
3. Diacritic Heavy (Vowelization Stress):
105+
Arabic: كَتَبَ الْوَلَدُ الرِّسَالَةَ (Kataba al-waladu ar-risalata)
106+
Hebrew: בְּרֵאשִׁית בָּרָא אֱלֹהִים אֵת הַשָּׁמַיִם וְאֵת הָאָרֶץ (Genesis 1:1)
107+
108+
4. Cyrillic & Extended Latin:
109+
Russian: Съешь же ещё этих мягких французских булок, да выпей чаю.
110+
Mixed: "Privet (Привет) means Hello."
111+
112+
5. Rapid Switching (Tokenization Stress):
113+
KR:안녕하세요_JP:こんにちは_CN:你好_RU:Привет_AR:مرحبا_HE:שָׁלוֹם
114+
123٤٥٦(Numbers)abc가나다(Hangul)カキク(Katakana)
115+
116+
6. Long Paragraph (Performance):
117+
Lorem ipsum dolor sit amet. 但是,如果我们切换到中文。
118+
Then back to English. そして日本語に切り替えます。
119+
Suddenly, Cyrillic appears: Внезапно появляется кириллица.
120+
Followed by Arabic: ويتبع ذلك العربية.
121+
And finally Hebrew: ולבסוף עברית.
122+
123+
End of Stress Test.''';
156124
final multiLanguagesOutput = TextRomanizer.romanize(multiLanguagesText);
157125
print('Multi Languages Romanization: \n$multiLanguagesOutput');
158126
}

lib/romanize.dart

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,15 @@ class TextRomanizer {
4848
static final Set<Romanizer> romanizers = <Romanizer>{
4949
HangulRomanizer(),
5050

51+
CyrillicRomanizer(),
52+
ArabicRomanizer(),
53+
HebrewRomanizer(),
54+
5155
// Note: Chinese is placed before Japanese. Pure Kanji (e.g., "東京") will
5256
// default to Chinese. Mixed Japanese (Kanji + Kana) will be rejected by
5357
// ChineseRomanizer.isValid and fall through to JapaneseRomanizer.
5458
ChineseRomanizer(),
5559
JapaneseRomanizer(),
56-
CyrillicRomanizer(),
57-
ArabicRomanizer(),
58-
HebrewRomanizer(),
5960
};
6061

6162
/// Automatically detects the language of the input text.
@@ -75,7 +76,7 @@ class TextRomanizer {
7576
final String input, [
7677
Set<Romanizer>? romanizers,
7778
]) {
78-
if (input.isEmpty || !RegExp(r'\S').hasMatch(input)) {
79+
if (input.isEmpty) {
7980
return const EmptyRomanizer();
8081
}
8182

@@ -108,7 +109,7 @@ class TextRomanizer {
108109
return romanizers.where((romanizer) => romanizer.isValid(input)).toSet();
109110
}
110111

111-
static final _separatorPattern = RegExp(r'[\s\p{P}_]+');
112+
static final _separatorPattern = RegExp(r'[\s\p{P}_()]+');
112113

113114
/// Romanizes the input text by processing each word separately.
114115
///
@@ -135,7 +136,7 @@ class TextRomanizer {
135136
if (languages.length == 1) {
136137
return languages.first.romanize(input);
137138
}
138-
final wordCache = <String, Romanizer>{};
139+
final wordCache = <String, String>{};
139140

140141
return input.splitMapJoin(
141142
_separatorPattern,
@@ -144,13 +145,15 @@ class TextRomanizer {
144145
// Handle the content (words):
145146
onNonMatch: (String word) {
146147
if (word.isEmpty) return '';
147-
148-
final romanizer = wordCache.putIfAbsent(
148+
return wordCache.putIfAbsent(
149149
word,
150-
() => detectLanguage(word, languages),
150+
() => detectLanguage(
151+
word,
152+
// [detectLanguages] may not return all languages in the set, so
153+
// check for all languages.
154+
// languages,
155+
).romanize(word),
151156
);
152-
153-
return romanizer.romanize(word);
154157
},
155158
);
156159
}

lib/src/romanizers/chinese.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ class ChineseRomanizer extends Romanizer {
7676
return buffer.toString();
7777
}
7878

79-
static final _kanaPattern = RegExp(r'[\u3040-\u309F\u30A0-\u30FF]');
79+
static final _kanaPattern = RegExp(
80+
r'[\u3040-\u309F\u30A0-\u30FF\uFF66-\uFF9F]',
81+
);
8082

8183
/// Validates if the input string contains Chinese characters.
8284
///

lib/src/romanizers/japanese.dart

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,28 @@ class JapaneseRomanizer extends Romanizer {
3939
String romanize(String input) {
4040
if (input.trim().isEmpty) return input;
4141
if (_tokenizer != null) {
42-
final tokens = _tokenizer!.tokenize(input);
43-
final buffer = StringBuffer();
44-
for (final token in tokens) {
45-
if (token['reading'] != null &&
46-
token['reading'].isNotEmpty &&
47-
token['reading'] != '*') {
48-
buffer.write(token['reading']);
49-
} else {
50-
buffer.write(token['surface_form']);
42+
try {
43+
final tokens = _tokenizer!.tokenize(
44+
input
45+
// Kurumoji has a problem with full stop "。" characters.
46+
// Replace them with "."
47+
.replaceAll('。', '.'),
48+
);
49+
final buffer = StringBuffer();
50+
for (final token in tokens) {
51+
if (token.isEmpty) continue;
52+
if (token['reading'] != null &&
53+
token['reading'].isNotEmpty &&
54+
token['reading'] != '*') {
55+
buffer.write(kanaKit.toRomaji(token['reading']));
56+
} else {
57+
buffer.write(kanaKit.toRomaji(token['surface_form']));
58+
}
5159
}
60+
return buffer.toString();
61+
} catch (_) {
62+
// Fallback to direct romanization if tokenization fails
5263
}
53-
return kanaKit.toRomaji(buffer.toString());
5464
}
5565
return kanaKit.toRomaji(input);
5666
}

0 commit comments

Comments
 (0)