Skip to content

Commit 52281db

Browse files
committed
feat: Hebrew Romanizer
1 parent 7a17f43 commit 52281db

6 files changed

Lines changed: 213 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## [next]
22

33
- Japanese Kanji support added using [`kuromoji`](https://pub.dev/packages/kuromoji).
4+
- Hebrew romanization support added based on ISO 259 / DIN 31636.
45

56
## 0.0.1
67

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ This initializes all the necessary resources, such as the Japanese and Chinese d
101101
- **Chinese** (中文) - Using [`pinyin`](https://pub.dev/packages/pinyin) for Pinyin conversion (Simplified and Traditional)
102102
- **Cyrillic** (Кириллица) - Custom transliteration for Russian, Ukrainian, Serbian, and more
103103
- **Arabic** (العربية) - Custom transliteration based on ISO 233 and DIN 31635
104+
- **Hebrew** (עברית) - Custom transliteration based on ISO 259-2
104105

105106
## API Reference
106107

@@ -110,6 +111,7 @@ Main class for romanizing text.
110111

111112
#### Static Methods
112113

114+
- `ensureInitialized()` - Ensures that all resources are loaded and initialized.
113115
- `romanize(String input)` - Processes each word separately, auto-detecting and romanizing each word. Perfect for multi-language text.
114116
- `detectLanguage(String input)` - Detects the first matching language and returns the corresponding `Romanizer`. Returns `EmptyRomanizer` if no match is found.
115117
- `detectLanguages(String input)` - Detects all matching languages and returns a `Set<Romanizer>`. Returns empty set if no matches are found.

example/bin/romanize_example.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ void main() async {
77
_arabic();
88
_cyrillic();
99
_chinese();
10+
_hebrew();
1011
_multiLanguages();
1112
}
1213

@@ -76,6 +77,17 @@ Ain't it poppin' love, poppin' love, poppin' love, yeah
7677
print('Chinese Romanization: \n$chineseOutput');
7778
}
7879

80+
void _hebrew() {
81+
final hebrewText = '''
82+
בכפוף אירועים אל מלא. ויקי שאלות את אחד. הרוח ויקי אל בדף,
83+
ב אחד לערוך ומהימנה. על סדר בלשנות סוציולוגיה, מה מתוך מדריכים קרן. זאת או
84+
ברית תרבות פולנית, תיבת חשמל גיאוגרפיה מלא אם. גם ויש מונחים מועמדים גיאוגרפיה,
85+
שנתי בארגז על מדע, בהבנה העריכהגירסאות גם שכל. על כדי רביעי לחיבור.
86+
''';
87+
final hebrewOutput = TextRomanizer.romanize(hebrewText);
88+
print('Hebrew Romanization: \n$hebrewOutput');
89+
}
90+
7991
void _multiLanguages() {
8092
// from home nct u
8193
final multiLanguagesText = '''

lib/romanize.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:romanize/src/romanize_base.dart';
44
import 'package:romanize/src/romanizers/arabic.dart';
55
import 'package:romanize/src/romanizers/chinese.dart';
66
import 'package:romanize/src/romanizers/cyrillic.dart';
7+
import 'package:romanize/src/romanizers/hebrew.dart';
78
import 'package:romanize/src/romanizers/japanese.dart';
89
import 'package:romanize/src/romanizers/korean.dart';
910

@@ -49,6 +50,7 @@ class TextRomanizer {
4950
ChineseRomanizer(),
5051
CyrillicRomanizer(),
5152
ArabicRomanizer(),
53+
HebrewRomanizer(),
5254
};
5355

5456
/// Automatically detects the language of the input text.
@@ -117,6 +119,9 @@ class TextRomanizer {
117119
/// final result2 = TextRomanizer.romanizeWords('你好世界');
118120
/// print(result2); // ni hao shi jie
119121
/// ```
122+
///
123+
/// Uses a cache to avoid redundant language detection for repeated words.
124+
/// This improves performance for long texts.
120125
static String romanize(String input) {
121126
final languages = detectLanguages(input);
122127
if (languages.length == 1) {

lib/src/romanizers/hebrew.dart

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import 'package:romanize/romanize.dart';
2+
3+
/// A romanizer for Hebrew script.
4+
///
5+
/// Supports transliteration of Hebrew characters into Latin script using
6+
/// a standard transliteration table (aligned with ISO 259 and DIN 31636).
7+
class HebrewRomanizer extends Romanizer {
8+
const HebrewRomanizer() : super(language: 'hebrew');
9+
10+
/// Transliteration map for Hebrew to Latin characters.
11+
///
12+
/// Based on ISO 259 / DIN 31636.
13+
static const Map<String, String> _transliterationMap = {
14+
// Special combinations
15+
'וֹ': 'ō', // Holam Male (Vav + Holam)
16+
'וּ': 'u', // Shuruq (Vav + Dagesh)
17+
// Consonants
18+
'א': 'ʾ', // Alef
19+
'ב': 'b', // Bet
20+
'ג': 'g', // Gimel
21+
'ד': 'd', // Dalet
22+
'ה': 'h', // He
23+
'ו': 'v', // Vav
24+
'ז': 'z', // Zayin
25+
'ח': 'ḥ', // Het
26+
'ט': 'ṭ', // Tet
27+
'י': 'y', // Yod
28+
'כ': 'k', // Kaf
29+
'ך': 'k', // Final Kaf
30+
'ל': 'l', // Lamed
31+
'מ': 'm', // Mem
32+
'ם': 'm', // Final Mem
33+
'נ': 'n', // Nun
34+
'ן': 'n', // Final Nun
35+
'ס': 's', // Samekh
36+
'ע': 'ʿ', // Ayin
37+
'פ': 'p', // Pe
38+
'ף': 'p', // Final Pe
39+
'צ': 'ṣ', // Tsadi
40+
'ץ': 'ṣ', // Final Tsadi
41+
'ק': 'q', // Qof
42+
'ר': 'r', // Resh
43+
'ש': 'sh', // Shin (Base)
44+
'ת': 't', // Tav
45+
// Vowels (Niqqud)
46+
'ַ': 'a', // Patah
47+
'ָ': 'ā', // Qamats
48+
'ֶ': 'e', // Segol
49+
'ֵ': 'ē', // Tsere
50+
'ִ': 'i', // Hiriq
51+
'ֹ': 'ō', // Holam
52+
'ֻ': 'u', // Qibbuts
53+
'ְ': 'e', // Sheva
54+
'ֲ': 'a', // Hataf Patah
55+
'ֱ': 'e', // Hataf Segol
56+
'ֳ': 'o', // Hataf Qamats
57+
// Marks/Punctuation
58+
'ּ': '', // Dagesh
59+
'ֿ': '', // Rafe
60+
'ׁ': '', // Shin Dot
61+
'ׂ': '', // Sin Dot
62+
'׳': '\'', // Geresh
63+
'״': '"', // Gershayim
64+
};
65+
66+
static final _hebrewPattern = RegExp(r'[\u0590-\u05FF\uFB1D-\uFB4F]');
67+
68+
/// Converts a given Hebrew string to its Romanized form.
69+
@override
70+
String romanize(String input) {
71+
final buffer = StringBuffer();
72+
final runes = input.runes.toList();
73+
74+
for (int i = 0; i < runes.length; i++) {
75+
final char = runes[i];
76+
final charString = String.fromCharCode(char);
77+
78+
// Check for 2-character sequences (ligatures/combinations)
79+
if (i + 1 < runes.length) {
80+
final nextChar = runes[i + 1];
81+
final bigram = charString + String.fromCharCode(nextChar);
82+
if (_transliterationMap.containsKey(bigram)) {
83+
buffer.write(_transliterationMap[bigram]);
84+
i++; // Skip the next character as it was part of the bigram
85+
continue;
86+
}
87+
}
88+
89+
buffer.write(_transliterationMap[charString] ?? charString);
90+
}
91+
return buffer.toString();
92+
}
93+
94+
/// Validates if the input string contains Hebrew characters.
95+
@override
96+
bool isValid(String input) {
97+
return _hebrewPattern.hasMatch(input);
98+
}
99+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import 'package:romanize/romanize.dart';
2+
import 'package:romanize/src/romanizers/hebrew.dart';
3+
import 'package:test/test.dart';
4+
5+
void main() {
6+
setUp(() async {
7+
await TextRomanizer.ensureInitialized();
8+
});
9+
10+
group('HebrewRomanizer', () {
11+
const romanizer = HebrewRomanizer();
12+
13+
test('should have correct language name', () {
14+
expect(romanizer.language, equals('hebrew'));
15+
});
16+
17+
group('isValid', () {
18+
test('should return true for Hebrew text', () {
19+
expect(romanizer.isValid('שָׁלוֹם'), isTrue); // Shalom
20+
expect(romanizer.isValid('עִבְרִית'), isTrue); // Ivrit
21+
expect(romanizer.isValid('אני'), isTrue); // Ani (unpointed)
22+
});
23+
24+
test('should return false for non-Hebrew text', () {
25+
expect(romanizer.isValid('Hello'), isFalse);
26+
expect(romanizer.isValid('こんにちは'), isFalse);
27+
expect(romanizer.isValid('مرحبا'), isFalse); // Arabic
28+
expect(romanizer.isValid('123'), isFalse);
29+
});
30+
31+
test('should return true for mixed Hebrew and other text', () {
32+
expect(romanizer.isValid('שָׁלוֹם Hello'), isTrue);
33+
expect(romanizer.isValid('Hello שָׁלוֹם'), isTrue);
34+
});
35+
36+
test('should return false for empty string', () {
37+
expect(romanizer.isValid(''), isFalse);
38+
});
39+
40+
test('should return false for whitespace-only string', () {
41+
expect(romanizer.isValid(' '), isFalse);
42+
});
43+
});
44+
45+
group('romanize', () {
46+
test('should romanize Hebrew text', () {
47+
// Shalom: Shin, Qamats, Lamed, Holam Male, Final Mem
48+
final result = romanizer.romanize('שָׁלוֹם');
49+
expect(result, isNotEmpty);
50+
expect(result, isA<String>());
51+
expect(result, equals('shālōm'));
52+
});
53+
54+
test('should romanize simple unpointed text', () {
55+
// Ani: Alef, Nun, Yod
56+
final result = romanizer.romanize('אני');
57+
expect(result, equals('ʾny'));
58+
});
59+
60+
test('should handle mixed content', () {
61+
final result = romanizer.romanize('שָׁלוֹם Hello');
62+
expect(result, equals('shālōm Hello'));
63+
});
64+
65+
test('should handle empty string', () {
66+
final result = romanizer.romanize('');
67+
expect(result, isEmpty);
68+
expect(result, equals(''));
69+
});
70+
71+
test('should preserve non-Hebrew characters', () {
72+
final result = romanizer.romanize('שָׁלוֹם Hello 123');
73+
expect(result, contains('Hello'));
74+
expect(result, contains('123'));
75+
expect(result, equals('shālōm Hello 123'));
76+
});
77+
78+
test('should handle longer Hebrew text', () {
79+
// Bereshit bara Elohim (In the beginning God created)
80+
// בְּרֵאשִׁית בָּרָא אֱלֹהִים
81+
// Note: Our romanizer is character-based and might be verbose with Alefs/Yods
82+
const hebrewText = 'בְּרֵאשִׁית בָּרָא אֱלֹהִים';
83+
final result = romanizer.romanize(hebrewText);
84+
expect(result, isNotEmpty);
85+
86+
// Expected derivation based on map:
87+
// בְּרֵאשִׁית -> b e r ē ʾ sh i y t -> berēʾshiyt
88+
// בָּרָא -> b ā r ā ʾ -> bārāʾ
89+
// אֱלֹהִים -> ʾ e l ō h i y m -> ʾelōhiym
90+
expect(result, equals('berēʾshiyt bārāʾ ʾelōhiym'));
91+
});
92+
});
93+
});
94+
}

0 commit comments

Comments
 (0)