lyric-romanizer 0.2.0 → 0.3.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.
@@ -0,0 +1,296 @@
1
+ # lyric-romanizer
2
+
3
+ [![npm version](https://img.shields.io/npm/v/lyric-romanizer.svg)](https://www.npmjs.com/package/lyric-romanizer)
4
+ [![license](https://img.shields.io/npm/l/lyric-romanizer.svg)](https://github.com/thedavidweng/lyric-romanizer/blob/main/LICENSE)
5
+
6
+ > **दर्शन: पहिया दोबारा न इजाद करो।**
7
+ > इस परियोजना में जानबूझकर रोमनाइज़ेशन तर्क को शुरू से बनाने से बचा गया है। इसके बजाय, यह प्रत्येक लिपि के लिए समुदाय-अनुरक्षित बेहतरीन लाइब्रेरियों को जोड़ता है — ऑर्केस्ट्रेशन परत पर ध्यान केंद्रित करते हुए: लिपि पहचान, इंजन रूटिंग, बोली प्रसंस्करण, और एकीकृत API। निर्भता सूची में प्रत्येक रोमनाइज़ेशन इंजन डोमेन विशेषज्ञों द्वारा अनुरक्षित, परीक्षित लाइब्रेरी है। यही इस परियोजना का मूल विचार है।
8
+
9
+ गीतों के लिए लिपि पहचान और स्थानीय रोमनाइज़ेशन इंजन। जापानी, चीनी (मंदारिन और कैंटोनीज़), कोरियाई, सिरिलिक, भारतीय लिपियाँ, तमिल, और थाई सहित 12 लिपियों का समर्थन करता है — सब स्थानीय रूप से, बिना किसी API कॉल के।
10
+
11
+ [Spotify Karaoke](https://github.com/haroldalan/spotify-karaoke) से निकाला गया। [OpenKara](https://github.com/thedavidweng/openkara) द्वारा उपयोग किया जाता है।
12
+
13
+ [English](https://github.com/thedavidweng/lyric-romanizer#readme) | [日本語](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ja.md) | [中文(简体)](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.zh-CN.md) | [中文(粵語)](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.zh-yue.md) | [한국어](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ko.md) | [Русский](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ru.md) | [தமிழ்](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ta.md) | [ไทย](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.th.md)
14
+
15
+ ## विशेषताएँ
16
+
17
+ - **शून्य API कॉल** — सभी रोमनाइज़ेशन स्थानीय रूप से चलता है
18
+ - **स्वचालित लिपि पहचान** — पाठ दें, उपयोग की गई लिपि मिलेगी
19
+ - **12+ लिपियाँ** — जापानी, चीनी, कोरियाई, सिरिलिक, 6 भारतीय लिपियाँ, तमिल, थाई
20
+ - **कैंटोनीज़ समर्थन** — डिफ़ॉल्ट मंदारिन पिनयिन के साथ-साथ कैंटोनीज़ ज्युटपिंग (Jyutping)
21
+ - **हल्का पहचान उपपथ** — रोमनाइज़ेशन इंजन शामिल किए बिना केवल लिपि पहचान (और बाहरी-लिपि वर्गीकरण) आयात करें
22
+ - **आलसी इंजन** — प्रत्येक इंजन पहले उपयोग पर लोड होता है; जब तक आप रोमनाइज़ न करें, मुख्य प्रविष्टि आयात करने की कोई लागत नहीं
23
+ - **प्लग-योग्य इंजन** — किसी भी अंतर्निर्मित इंजन को ओवरराइड करें, या बाहरी रूप से रोमनाइज़ की जाने वाली लिपियों के लिए अपना स्वयं का एडाप्टर प्लग करें
24
+ - **अवलोकनीय फ़ॉलबैक** — प्रति-पंक्ति फ़्लैग बताते हैं कि कब कोई इंजन विफल हुआ और किसी पंक्ति को अंतिम उपाय के रूप में लिप्यंतरित किया गया
25
+ - **यूक्रेनी-सचेत सिरिलिक** — यूक्रेनी विशिष्ट वर्णों की स्वचालित पहचान और सही लिप्यंतरण प्रीसेट लागू करें
26
+ - **सादे Node ESM में चलता है** — किसी बंडलर की आवश्यकता नहीं
27
+
28
+ ## स्थापना
29
+
30
+ ```bash
31
+ npm install lyric-romanizer
32
+ ```
33
+
34
+ ```bash
35
+ yarn add lyric-romanizer
36
+ ```
37
+
38
+ ```bash
39
+ pnpm add lyric-romanizer
40
+ ```
41
+
42
+ ## त्वरित प्रारंभ
43
+
44
+ ```ts
45
+ import { createRomanizer, detectScript } from 'lyric-romanizer';
46
+
47
+ const romanizer = createRomanizer();
48
+
49
+ // लिपि स्वचालित पहचान और रोमनाइज़ेशन
50
+ const result = await romanizer.romanizeLines(['你好世界', '很高兴认识你']);
51
+ // { script: 'chinese', lines: ['nǐ hǎo shì jiè', 'hěn gāo xìng rèn shi nǐ'], fallbacks: [false, false] }
52
+
53
+ // एक पंक्ति का रोमनाइज़ेशन
54
+ const line = await romanizer.romanizeLine('안녕하세요');
55
+ // 'annyeonghaseyo'
56
+ ```
57
+
58
+ > **पहचान की सूक्ष्मता** — `romanizeLines` **सभी पंक्तियों में एक बार** प्रमुख लिपि का पता लगाता है और उसे हर पंक्ति के लिए पिन कर देता है (जानबूझकर: किसी जापानी गीत के भीतर केवल-कांजी वाली पंक्तियों को जापानी इंजन तक पहुँचना ज़रूरी है, और सरणी में कोई भी काना `japanese` को पिन कर देता है)। इसके बजाय `romanizeLine` को लूप में कॉल करने पर **प्रति पंक्ति** पहचान होती है और यह प्रत्येक पंक्ति को अलग इंजन पर रूट कर सकता है। पिन की गई लिपि के अंतर्गत शुद्ध-लैटिन पंक्तियाँ जैसी की वैसी लौटाई जाती हैं। देखें **मिश्रित-लिपि सरणियाँ**।
59
+
60
+ ## API
61
+
62
+ ### आयात
63
+
64
+ ```ts
65
+ // मुख्य प्रविष्टि — पूर्ण रोमनाइज़ेशन इंजन
66
+ import {
67
+ createRomanizer,
68
+ detectScript,
69
+ isLatinScript,
70
+ requiresExternalRomanization,
71
+ UnsupportedRomanizationError,
72
+ } from 'lyric-romanizer';
73
+
74
+ // केवल पहचान उपपथ — हल्का, रोमनाइज़ेशन निर्भता रहित
75
+ import {
76
+ detectScript,
77
+ isLatinScript,
78
+ requiresExternalRomanization,
79
+ NON_LATIN_SCRIPT_RE,
80
+ } from 'lyric-romanizer/detector';
81
+ ```
82
+
83
+ ### प्रकार
84
+
85
+ ```ts
86
+ type ScriptType =
87
+ | 'japanese' | 'chinese' | 'korean' | 'cyrillic'
88
+ | 'devanagari' | 'gujarati' | 'gurmukhi' | 'telugu'
89
+ | 'kannada' | 'odia' | 'tamil' | 'malayalam'
90
+ | 'bengali' | 'arabic' | 'hebrew' | 'thai'
91
+ | 'latin' | 'other';
92
+
93
+ interface Romanizer {
94
+ romanizeLine(line: string, options?: RomanizeOptions): Promise<string>;
95
+ romanizeLines(lines: readonly string[], options?: RomanizeOptions): Promise<RomanizeResult>;
96
+ }
97
+
98
+ // `dialect` केवल 'chinese' के लिए लागू होता है; बाकी हर लिपि इसे अनदेखा करती है।
99
+ type RomanizeOptions = { script?: ScriptType; dialect?: 'mandarin' | 'cantonese' };
100
+
101
+ // `fallbacks` `lines` के साथ संरेखित है: true वहाँ जहाँ इंजन विफल हुआ और
102
+ // पंक्ति को अंतिम उपाय के रूप में यूनिवर्सल रूप से लिप्यंतरित किया गया।
103
+ type RomanizeResult = { script: ScriptType; lines: string[]; fallbacks?: boolean[] };
104
+
105
+ // एक इंजन एडाप्टर: अपनी लिपि की एक पंक्ति को रोमनाइज़ करता है। फेंकना (या
106
+ // अस्वीकार करना) यूनिवर्सल लिप्यंतरण फ़ॉलबैक को ट्रिगर करता है।
107
+ type RomanizeEngine = (line: string, context: { dialect: 'mandarin' | 'cantonese' }) => string | Promise<string>;
108
+
109
+ type RomanizerOptions = {
110
+ japaneseDictPath?: string;
111
+ engines?: Partial<Record<ScriptType, RomanizeEngine>>;
112
+ };
113
+ ```
114
+
115
+ ### `createRomanizer(options?)`
116
+
117
+ फ़ैक्टरी जो `Romanizer` इंस्टेंस लौटाता है। प्रत्येक इंजन पहले उपयोग पर आलसी रूप से लोड होता है और कैश होता है — विफल लोड अगली कॉल पर पुनः प्रयास करता है।
118
+
119
+ ```ts
120
+ const romanizer = createRomanizer();
121
+
122
+ // Kuromoji शब्दकोश CDN पथ ओवरराइड (सेल्फ-होस्टिंग आदि के लिए)
123
+ const romanizer = createRomanizer({
124
+ japaneseDictPath: 'https://my-cdn.com/kuromoji/dict',
125
+ });
126
+ ```
127
+
128
+ #### इंजन एडाप्टर
129
+
130
+ `options.engines` किसी लिपि के लिए अंतर्निर्मित इंजन को ओवरराइड करता है — या ऐसी लिपि में इंजन प्लग करता है जिसमें कोई अंतर्निर्मित इंजन नहीं है (`arabic`, `hebrew`, `malayalam`, `bengali`, `other`), ताकि `romanizeLines` हर लिपि को एक ही इंटरफ़ेस के माध्यम से संभाल सके। डिफ़ॉल्ट रूप से लाइब्रेरी **शून्य नेटवर्क I/O** करती है; रिमोट एडाप्टर प्लग करना कॉलर का स्पष्ट निर्णय है।
131
+
132
+ ```ts
133
+ const romanizer = createRomanizer({
134
+ engines: {
135
+ // स्थानीय इंजन रहित लिपियों के लिए अपना स्वयं का बाहरी रोमनाइज़ेशन लाएँ।
136
+ arabic: async (line) => myTransliterationApi(line),
137
+ // या किसी अंतर्निर्मित इंजन को बदलें (उदा. परीक्षणों में नकली इंजन से)।
138
+ korean: (line) => myKoreanRomanizer(line),
139
+ },
140
+ });
141
+
142
+ await romanizer.romanizeLines(['مرحبا']);
143
+ // { script: 'arabic', lines: [...], fallbacks: [false] } — अब और नहीं फेंकता
144
+ ```
145
+
146
+ बिना किसी इंजन वाली लिपियाँ — अंतर्निर्मित या इंजेक्ट की गई — अब भी `UnsupportedRomanizationError` फेंकती हैं।
147
+
148
+ ### `detectScript(lines)`
149
+
150
+ दी गई पाठ पंक्तियों में प्रमुख लिपि का पता लगाता है। पहले जापानी काना की जाँच करता है (निर्धारक), फिर अन्य सभी लिपियों को वर्ण गणना से स्कोर करता है।
151
+
152
+ ```ts
153
+ detectScript(['こんにちは']); // 'japanese'
154
+ detectScript(['你好世界']); // 'chinese'
155
+ detectScript(['Привет']); // 'cyrillic'
156
+ detectScript(['Hello world']); // 'latin'
157
+ detectScript(['123 ???']); // 'other'
158
+ ```
159
+
160
+ ### `isLatinScript(lines)`
161
+
162
+ त्वरित जाँच — यदि पाठ में केवल लैटिन अक्षर हैं (CJK, सिरिलिक, भारतीय लिपियाँ आदि के बिना) तो `true` लौटाता है। रोमनाइज़ेशन को पूरी तरह छोड़ने के लिए उपयोगी।
163
+
164
+ ```ts
165
+ isLatinScript(['Hello world']); // true
166
+ isLatinScript(['안녕하세요']); // false
167
+ isLatinScript(['♪♪♪']); // false (कोई अक्षर नहीं)
168
+ ```
169
+
170
+ ### `requiresExternalRomanization(script)`
171
+
172
+ उन लिपियों के लिए `true` लौटाता है जिनमें कोई अंतर्निर्मित इंजन नहीं है और जिन्हें बाहरी API की आवश्यकता है। हल्के `lyric-romanizer/detector` उपपथ से आयात करने योग्य, इसलिए "क्या मुझे किसी बाहरी सेवा की ओर शाखा बनानी चाहिए?" का उत्तर देने में शून्य इंजन पेलोड लगता है।
173
+
174
+ ```ts
175
+ requiresExternalRomanization('chinese'); // false
176
+ requiresExternalRomanization('arabic'); // true
177
+ requiresExternalRomanization('malayalam'); // true
178
+ ```
179
+
180
+ ### `romanizer.romanizeLine(line, options?)`
181
+
182
+ एक पंक्ति का रोमनाइज़ेशन करता है। यदि `script` छोड़ दिया जाए, तो `detectScript` के माध्यम से **प्रति पंक्ति** स्वचालित रूप से पहचाना जाता है — `romanizeLine` पर लूप करने से प्रत्येक पंक्ति अलग इंजन पर रूट हो सकती है, `romanizeLines` के विपरीत, जो पूरी सरणी के लिए एक ही लिपि पिन करता है। लैटिन पाठ या बिना अक्षर वाली सामग्री के लिए मूल पंक्ति जैसी की वैसी लौटाता है।
183
+
184
+ चीनी पाठ के लिए `dialect` विकल्प रोमनाइज़ेशन प्रणाली को नियंत्रित करता है: `'mandarin'` (डिफ़ॉल्ट) पिनयिन का उपयोग करता है, `'cantonese'` [Jyutping](https://github.com/CanCLID/to-jyutping) का उपयोग करता है। अन्य लिपियाँ `dialect` को अनदेखा करती हैं।
185
+
186
+ बिना किसी इंजन (अंतर्निर्मित या इंजेक्ट की गई) वाली लिपियों के लिए `UnsupportedRomanizationError` **फेंकता है**।
187
+
188
+ ```ts
189
+ await romanizer.romanizeLine('你好世界');
190
+ // 'nǐ hǎo shì jiè' (डिफ़ॉल्ट: मंदारिन/पिनयिन)
191
+
192
+ await romanizer.romanizeLine('你好', { dialect: 'cantonese' });
193
+ // 'nei5 hou2' (Jyutping)
194
+
195
+ await romanizer.romanizeLine('Привет мир');
196
+ // 'Privet mir'
197
+
198
+ await romanizer.romanizeLine('Hello world');
199
+ // 'Hello world' (जैसी की वैसी)
200
+
201
+ await romanizer.romanizeLine('مرحبا');
202
+ // throws UnsupportedRomanizationError { script: 'arabic' }
203
+ ```
204
+
205
+ ### `romanizer.romanizeLines(lines, options?)`
206
+
207
+ कई पंक्तियों का समानांतर में रोमनाइज़ेशन करता है। **सभी पंक्तियों में एक बार** प्रमुख लिपि का पता लगाता है और उसे हर पंक्ति के लिए पिन करता है (देखें **मिश्रित-लिपि सरणियाँ**)। लिपि, रोमनाइज़ की गई पंक्तियाँ, और प्रति-पंक्ति `fallbacks` फ़्लैग लौटाता है — `true` वहाँ जहाँ इंजन विफल हुआ और पंक्ति को अंतिम उपाय के रूप में यूनिवर्सल रूप से लिप्यंतरित किया गया।
208
+
209
+ ```ts
210
+ const { script, lines, fallbacks } = await romanizer.romanizeLines([
211
+ 'สวัสดี',
212
+ 'ชาวโลก',
213
+ ]);
214
+ // { script: 'thai', lines: ['swasdi', 'chaolok'], fallbacks: [false, false] }
215
+ ```
216
+
217
+ ### `UnsupportedRomanizationError`
218
+
219
+ जब ऐसी लिपि का रोमनाइज़ेशन करने का प्रयास किया जाता है जिसमें कोई इंजन नहीं है — न अंतर्निर्मित और न ही `options.engines` के माध्यम से इंजेक्ट किया गया — तब फेंका जाता है। प्रोग्रामेटिक प्रसंस्करण के लिए `script` गुण है।
220
+
221
+ ```ts
222
+ try {
223
+ await romanizer.romanizeLine('مرحبا');
224
+ } catch (err) {
225
+ if (err instanceof UnsupportedRomanizationError) {
226
+ console.log(err.script); // 'arabic'
227
+ // बाहरी API पर फ़ॉलबैक
228
+ }
229
+ }
230
+ ```
231
+
232
+ ## समर्थित लिपियाँ
233
+
234
+ ### स्थानीय (पूर्णतः ऑफ़लाइन)
235
+
236
+ | लिपि | इंजन | उदाहरण |
237
+ |------|------|--------|
238
+ | यूनिवर्सल *(fallback)* | [transliteration](https://github.com/nickclaw/transliteration) | `Привет` → `Privet` |
239
+ | जापानी | [kuroshiro](https://github.com/sglkc/kuroshiro-ts) + [kuromoji](https://github.com/takuyaa/kuromoji.js) | `こんにちは` → `konnichiha` |
240
+ | मंदारिन | [pinyin-pro](https://github.com/zh-lx/pinyin-pro) | `你好` → `nǐ hǎo` |
241
+ | कैंटोनीज़ | [to-jyutping](https://github.com/CanCLID/to-jyutping) | `佢冇` → `keoi5 mou5` |
242
+ | कोरियाई | [@romanize/korean](https://github.com/kntng/romanize) | `안녕` → `annyeong` |
243
+ | सिरिलिक | [cyrillic-to-translit-js](https://github.com/greybax/CyrillicToTranslitJS) | `Привет` → `Privet` |
244
+ | देवनागरी | [sanscript](https://github.com/indic-transliteration/sanscript) | `नमस्ते` → `namaste` |
245
+ | गुजराती | [sanscript](https://github.com/indic-transliteration/sanscript) | `નમસ્તે` → `namaste` |
246
+ | गुरमुखी | [sanscript](https://github.com/indic-transliteration/sanscript) | `ਨਮਸਤੇ` → `namasate` |
247
+ | तेलुगु | [sanscript](https://github.com/indic-transliteration/sanscript) | `నమస్తే` → `namaste` |
248
+ | कन्नड़ | [sanscript](https://github.com/indic-transliteration/sanscript) | `ನಮಸ್ತೇ` → `namaste` |
249
+ | ओडिया | [sanscript](https://github.com/indic-transliteration/sanscript) | `ନମସ୍ତେ` → `namaste` |
250
+ | तमिल | [tamil-romanizer](https://github.com/haroldalan/tamil-romanizer) | `வணக்கம்` → `vanakkam` |
251
+ | थाई | [@dehoist/romanize-thai](https://github.com/Dehoist/Open-Source) | `สวัสดี` → `swasdi` |
252
+
253
+ ### बाहरी API आवश्यक
254
+
255
+ | लिपि | विधि |
256
+ |------|------|
257
+ | मलयालम | Google Translate `dt=rm` |
258
+ | बंगाली | Google Translate `dt=rm` |
259
+ | अरबी | Google Translate `dt=rm` |
260
+ | हिब्रू | Google Translate `dt=rm` |
261
+ | अन्य | Google Translate `dt=rm` |
262
+
263
+ इनका पता लगाने और अपने पसंदीदा API पर स्विच करने के लिए `requiresExternalRomanization()` का उपयोग करें — या API को एक बार **इंजन एडाप्टर** के रूप में प्लग करें और `romanizeLines` को हर लिपि संभालने दें।
264
+
265
+ ## लिपि-विशिष्ट नोट्स
266
+
267
+ ### मिश्रित-लिपि सरणियाँ
268
+
269
+ `romanizeLines` पूरी सरणी की **प्रमुख** लिपि को हर पंक्ति पर पिन करता है। यह जानबूझकर है: किसी जापानी गीत के भीतर केवल-कांजी वाली पंक्ति अपने आप में चीनी से अप्रभेद्य होती है (कांजी और हान्ज़ी एक ही Unicode ब्लॉक साझा करते हैं), इसलिए केवल पूरी-सरणी का संदर्भ ही उसे सही इंजन पर रूट करता है। जानने योग्य परिणाम:
270
+
271
+ - सरणी में कहीं भी कोई भी काना पूरी सरणी को `japanese` पर पिन कर देता है — काना निर्णायक प्रमाण है।
272
+ - सरणी के भीतर किसी *भिन्न* गैर-लैटिन लिपि की पंक्ति को भी पिन किए गए इंजन को ही दिया जाता है।
273
+ - शुद्ध-लैटिन पंक्तियाँ (किसी CJK गीत के भीतर अंग्रेज़ी कोरस) को पिन किए गए इंजन को देने के बजाय **जैसी की वैसी लौटाया जाता है**।
274
+ - प्रति-पंक्ति `fallbacks` फ़्लैग बताते हैं कि कब कोई इंजन विफल हुआ और कोई पंक्ति यूनिवर्सल लिप्यंतरण पर फ़ॉलबैक हो गई।
275
+
276
+ यदि आपको सच्ची प्रति-पंक्ति इंजन रूटिंग चाहिए, तो `romanizeLine` को लूप में कॉल करें — यह प्रति पंक्ति पहचान करता है।
277
+
278
+ ### सिरिलिक पहचान
279
+
280
+ सिरिलिक स्वचालित रूप से यूक्रेनी विशिष्ट वर्णों (`і`, `ї`, `є`, `ґ`) की पहचान करता है और यूक्रेनी लिप्यंतरण प्रीसेट लागू करता है। अन्य सभी सिरिलिक पाठ डिफ़ॉल्ट रूप से रूसी के रूप में संसाधित होता है।
281
+
282
+ ### कैंटोनीज़ समर्थन
283
+
284
+ चीनी पाठ डिफ़ॉल्ट रूप से मंदारिन (पिनयिन) है। `RomanizeOptions` में `dialect: 'cantonese'` पास करें ताकि चीनी पाठ को [Jyutping](https://github.com/CanCLID/to-jyutping) में रोमनाइज़ किया जा सके।
285
+
286
+ ```ts
287
+ const { lines } = await romanizer.romanizeLines(['你好世界', '食飯'], {
288
+ script: 'chinese',
289
+ dialect: 'cantonese',
290
+ });
291
+ // ['nei5 hou2 sai3 gaai3', 'sik6 faan6']
292
+ ```
293
+
294
+ ## लाइसेंस
295
+
296
+ MIT
@@ -0,0 +1,296 @@
1
+ # lyric-romanizer
2
+
3
+ [![npm version](https://img.shields.io/npm/v/lyric-romanizer.svg)](https://www.npmjs.com/package/lyric-romanizer)
4
+ [![license](https://img.shields.io/npm/l/lyric-romanizer.svg)](https://github.com/thedavidweng/lyric-romanizer/blob/main/LICENSE)
5
+
6
+ > **哲学:車輪の再発明をしない。**
7
+ > このプロジェクトは、ゼロからローマ字化ロジックを構築するのではなく、各スクリプトに特化したコミュニティ主導のライブラリを組み合わせる設計思想を採用しています。スクリプト検出、エンジンルーティング、方言処理、統一APIというオーケストレーション層に集中しています。依存関係にあるすべてのローマ字化エンジンは、各分野の専門家によって維持管理されている実績あるライブラリです。それがこのプロジェクトの本質です。
8
+
9
+ 歌詞向けスクリプト検出・ローカルローマ字化エンジン。日本語、中国語(普通話・広東語)、韓国語、キリル文字、インド系文字、タミル語、タイ語の12以上のスクリプトをサポートし、すべてAPIコールなしでローカルで動作します。
10
+
11
+ [Spotify Karaoke](https://github.com/haroldalan/spotify-karaoke) から抽出。[OpenKara](https://github.com/thedavidweng/openkara) で使用されています。
12
+
13
+ [English](https://github.com/thedavidweng/lyric-romanizer#readme) | [中文(简体)](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.zh-CN.md) | [中文(粵語)](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.zh-yue.md) | [한국어](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ko.md) | [Русский](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ru.md) | [हिन्दी](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.hi.md) | [தமிழ்](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.ta.md) | [ไทย](https://github.com/thedavidweng/lyric-romanizer/blob/main/docs/README.th.md)
14
+
15
+ ## 特徴
16
+
17
+ - **APIコール不要** — すべてのローマ字化がローカルで実行される
18
+ - **自動スクリプト検出** — テキストを渡すだけで使用スクリプトを自動判定
19
+ - **12以上のスクリプト** — 日本語、中国語、韓国語、キリル文字、6種類のインド系文字、タミル語、タイ語
20
+ - **広東語サポート** — 普通話ピン音に加え、広東語粤拼(Jyutping)にも対応
21
+ - **軽量検出サブパス** — ローマ字化エンジンを取り込まずに、スクリプト検出(および外部スクリプトの分類)のみをインポート可能
22
+ - **遅延エンジン** — すべてのエンジンは初回使用時に読み込まれ、ローマ字化するまでメインエントリのインポートにコストはかかりません
23
+ - **差し込み可能なエンジン** — 任意の組み込みエンジンを上書きするか、外部でローマ字化するスクリプト向けに独自のアダプターを差し込めます
24
+ - **観測可能なフォールバック** — 行ごとのフラグにより、エンジンが失敗して行が最終手段として翻字されたタイミングがわかります
25
+ - **ウクライナ語対応キリル文字** — ウクライナ固有文字を自動検出し、適切な翻字プリセットを適用
26
+ - **素の Node ESM で動作** — バンドラー不要
27
+
28
+ ## インストール
29
+
30
+ ```bash
31
+ npm install lyric-romanizer
32
+ ```
33
+
34
+ ```bash
35
+ yarn add lyric-romanizer
36
+ ```
37
+
38
+ ```bash
39
+ pnpm add lyric-romanizer
40
+ ```
41
+
42
+ ## クイックスタート
43
+
44
+ ```ts
45
+ import { createRomanizer, detectScript } from 'lyric-romanizer';
46
+
47
+ const romanizer = createRomanizer();
48
+
49
+ // スクリプトを自動検出してローマ字化
50
+ const result = await romanizer.romanizeLines(['你好世界', '很高兴认识你']);
51
+ // { script: 'chinese', lines: ['nǐ hǎo shì jiè', 'hěn gāo xìng rèn shi nǐ'], fallbacks: [false, false] }
52
+
53
+ // 1行だけローマ字化
54
+ const line = await romanizer.romanizeLine('안녕하세요');
55
+ // 'annyeonghaseyo'
56
+ ```
57
+
58
+ > **検出の粒度** — `romanizeLines` は**全行を通して一度だけ**支配的なスクリプトを検出し、すべての行に固定します(意図的な設計です。日本語の曲の中の漢字のみの行も日本語エンジンに到達する必要があり、配列内にかながあれば `japanese` に固定されます)。一方、`romanizeLine` をループで呼び出すと**行ごとに**検出し、各行を異なるエンジンにルーティングする可能性があります。固定されたスクリプトの下では、純粋なラテン文字の行はそのまま返されます。**スクリプトが混在する配列**を参照してください。
59
+
60
+ ## API
61
+
62
+ ### インポート
63
+
64
+ ```ts
65
+ // メインエントリ — ローマ字化エンジン全体
66
+ import {
67
+ createRomanizer,
68
+ detectScript,
69
+ isLatinScript,
70
+ requiresExternalRomanization,
71
+ UnsupportedRomanizationError,
72
+ } from 'lyric-romanizer';
73
+
74
+ // 検出専用サブパス — 軽量、ローマ字化の依存関係なし
75
+ import {
76
+ detectScript,
77
+ isLatinScript,
78
+ requiresExternalRomanization,
79
+ NON_LATIN_SCRIPT_RE,
80
+ } from 'lyric-romanizer/detector';
81
+ ```
82
+
83
+ ### 型定義
84
+
85
+ ```ts
86
+ type ScriptType =
87
+ | 'japanese' | 'chinese' | 'korean' | 'cyrillic'
88
+ | 'devanagari' | 'gujarati' | 'gurmukhi' | 'telugu'
89
+ | 'kannada' | 'odia' | 'tamil' | 'malayalam'
90
+ | 'bengali' | 'arabic' | 'hebrew' | 'thai'
91
+ | 'latin' | 'other';
92
+
93
+ interface Romanizer {
94
+ romanizeLine(line: string, options?: RomanizeOptions): Promise<string>;
95
+ romanizeLines(lines: readonly string[], options?: RomanizeOptions): Promise<RomanizeResult>;
96
+ }
97
+
98
+ // `dialect` は 'chinese' でのみ有効。他のすべてのスクリプトでは無視されます。
99
+ type RomanizeOptions = { script?: ScriptType; dialect?: 'mandarin' | 'cantonese' };
100
+
101
+ // `fallbacks` は `lines` と対応し、エンジンが失敗して行が最終手段として
102
+ // ユニバーサル翻字された箇所が true になります。
103
+ type RomanizeResult = { script: ScriptType; lines: string[]; fallbacks?: boolean[] };
104
+
105
+ // エンジンアダプター:自身のスクリプトの1行をローマ字化します。スロー(または
106
+ // リジェクト)するとユニバーサル翻字フォールバックが発動します。
107
+ type RomanizeEngine = (line: string, context: { dialect: 'mandarin' | 'cantonese' }) => string | Promise<string>;
108
+
109
+ type RomanizerOptions = {
110
+ japaneseDictPath?: string;
111
+ engines?: Partial<Record<ScriptType, RomanizeEngine>>;
112
+ };
113
+ ```
114
+
115
+ ### `createRomanizer(options?)`
116
+
117
+ `Romanizer` インスタンスを返すファクトリ関数。すべてのエンジンは初回使用時に遅延読み込みされ、キャッシュされます。読み込みに失敗した場合は、次回の呼び出しで再試行されます。
118
+
119
+ ```ts
120
+ const romanizer = createRomanizer();
121
+
122
+ // Kuromoji 辞書の CDN パスを上書き(セルフホスティングなど)
123
+ const romanizer = createRomanizer({
124
+ japaneseDictPath: 'https://my-cdn.com/kuromoji/dict',
125
+ });
126
+ ```
127
+
128
+ #### エンジンアダプター
129
+
130
+ `options.engines` はスクリプトの組み込みエンジンを上書きします。あるいは、組み込みエンジンを持たないスクリプト(`arabic`、`hebrew`、`malayalam`、`bengali`、`other`)にエンジンを差し込むことで、`romanizeLines` が単一のインターフェースを通じてすべてのスクリプトを処理できるようになります。デフォルトでは、このライブラリは**ネットワークI/Oを一切行いません**。リモートアダプターを差し込むのは、呼び出し側の明示的な判断です。
131
+
132
+ ```ts
133
+ const romanizer = createRomanizer({
134
+ engines: {
135
+ // ローカルエンジンを持たないスクリプト向けに独自の外部ローマ字化を用意します。
136
+ arabic: async (line) => myTransliterationApi(line),
137
+ // または組み込みエンジンを置き換えます(例:テストでのフェイク)。
138
+ korean: (line) => myKoreanRomanizer(line),
139
+ },
140
+ });
141
+
142
+ await romanizer.romanizeLines(['مرحبا']);
143
+ // { script: 'arabic', lines: [...], fallbacks: [false] } — もうスローしません
144
+ ```
145
+
146
+ 組み込み・注入を問わず、エンジンを持たないスクリプトは、引き続き `UnsupportedRomanizationError` をスローします。
147
+
148
+ ### `detectScript(lines)`
149
+
150
+ 与えられたテキスト行の支配的なスクリプトを検出します。まず日本語のかなを判定し(確定的)、その後他のスクリプトを文字数でスコアリングします。
151
+
152
+ ```ts
153
+ detectScript(['こんにちは']); // 'japanese'
154
+ detectScript(['你好世界']); // 'chinese'
155
+ detectScript(['Привет']); // 'cyrillic'
156
+ detectScript(['Hello world']); // 'latin'
157
+ detectScript(['123 ???']); // 'other'
158
+ ```
159
+
160
+ ### `isLatinScript(lines)`
161
+
162
+ 高速チェック — テキストにラテン文字のみが含まれる場合に `true` を返します(CJK、キリル文字、インド系文字などは含まない)。ローマ字化を完全にスキップするのに便利です。
163
+
164
+ ```ts
165
+ isLatinScript(['Hello world']); // true
166
+ isLatinScript(['안녕하세요']); // false
167
+ isLatinScript(['♪♪♪']); // false(文字なし)
168
+ ```
169
+
170
+ ### `requiresExternalRomanization(script)`
171
+
172
+ 組み込みエンジンを持たず、外部APIが必要なスクリプトに対して `true` を返します。軽量な `lyric-romanizer/detector` サブパスからインポートできるため、「外部サービスに分岐すべきか?」の判定にエンジンのペイロードは一切かかりません。
173
+
174
+ ```ts
175
+ requiresExternalRomanization('chinese'); // false
176
+ requiresExternalRomanization('arabic'); // true
177
+ requiresExternalRomanization('malayalam'); // true
178
+ ```
179
+
180
+ ### `romanizer.romanizeLine(line, options?)`
181
+
182
+ 1行をローマ字化します。`script` を省略すると、`detectScript` によって**行ごとに**自動検出されます。`romanizeLine` をループで呼び出すと各行が異なるエンジンにルーティングされる可能性があり、配列全体に1つのスクリプトを固定する `romanizeLines` とは異なります。ラテン文字や文字を含まないコンテンツはそのまま返されます。
183
+
184
+ 中国語テキストの場合、`dialect` オプションでローマ字化方式を制御します:`'mandarin'`(デフォルト)はピン音、`'cantonese'` は [Jyutping](https://github.com/CanCLID/to-jyutping) を使用します。他のスクリプトは `dialect` を無視します。
185
+
186
+ エンジン(組み込み・注入を問わず)を持たないスクリプトの場合、`UnsupportedRomanizationError` を**スローします**。
187
+
188
+ ```ts
189
+ await romanizer.romanizeLine('你好世界');
190
+ // 'nǐ hǎo shì jiè'(デフォルト:普通話/ピン音)
191
+
192
+ await romanizer.romanizeLine('你好', { dialect: 'cantonese' });
193
+ // 'nei5 hou2' (Jyutping)
194
+
195
+ await romanizer.romanizeLine('Привет мир');
196
+ // 'Privet mir'
197
+
198
+ await romanizer.romanizeLine('Hello world');
199
+ // 'Hello world'(そのまま)
200
+
201
+ await romanizer.romanizeLine('مرحبا');
202
+ // throws UnsupportedRomanizationError { script: 'arabic' }
203
+ ```
204
+
205
+ ### `romanizer.romanizeLines(lines, options?)`
206
+
207
+ 複数行を並列でローマ字化します。**全行を通して一度だけ**支配的なスクリプトを検出し、すべての行に固定します(**スクリプトが混在する配列**を参照)。スクリプト、ローマ字化された行、そして行ごとの `fallbacks` フラグを返します。`fallbacks` は、エンジンが失敗して行が最終手段としてユニバーサル翻字された箇所で `true` になります。
208
+
209
+ ```ts
210
+ const { script, lines, fallbacks } = await romanizer.romanizeLines([
211
+ 'สวัสดี',
212
+ 'ชาวโลก',
213
+ ]);
214
+ // { script: 'thai', lines: ['swasdi', 'chaolok'], fallbacks: [false, false] }
215
+ ```
216
+
217
+ ### `UnsupportedRomanizationError`
218
+
219
+ エンジン(組み込み、または `options.engines` で注入)を持たないスクリプトをローマ字化しようとした場合にスローされます。プログラム処理用の `script` プロパティを持ちます。
220
+
221
+ ```ts
222
+ try {
223
+ await romanizer.romanizeLine('مرحبا');
224
+ } catch (err) {
225
+ if (err instanceof UnsupportedRomanizationError) {
226
+ console.log(err.script); // 'arabic'
227
+ // 外部APIにフォールバック
228
+ }
229
+ }
230
+ ```
231
+
232
+ ## サポートされているスクリプト
233
+
234
+ ### ローカル(完全オフライン)
235
+
236
+ | スクリプト | エンジン | 例 |
237
+ |-----------|---------|-----|
238
+ | ユニバーサル *(fallback)* | [transliteration](https://github.com/nickclaw/transliteration) | `Привет` → `Privet` |
239
+ | 日本語 | [kuroshiro](https://github.com/sglkc/kuroshiro-ts) + [kuromoji](https://github.com/takuyaa/kuromoji.js) | `こんにちは` → `konnichiha` |
240
+ | 中国語(普通話) | [pinyin-pro](https://github.com/zh-lx/pinyin-pro) | `你好` → `nǐ hǎo` |
241
+ | 中国語(広東語) | [to-jyutping](https://github.com/CanCLID/to-jyutping) | `佢冇` → `keoi5 mou5` |
242
+ | 韓国語 | [@romanize/korean](https://github.com/kntng/romanize) | `안녕` → `annyeong` |
243
+ | キリル文字 | [cyrillic-to-translit-js](https://github.com/greybax/CyrillicToTranslitJS) | `Привет` → `Privet` |
244
+ | デーバナーガリー | [sanscript](https://github.com/indic-transliteration/sanscript) | `नमस्ते` → `namaste` |
245
+ | グジャラート語 | [sanscript](https://github.com/indic-transliteration/sanscript) | `નમસ્તે` → `namaste` |
246
+ | グルムキー文字 | [sanscript](https://github.com/indic-transliteration/sanscript) | `ਨਮਸਤੇ` → `namasate` |
247
+ | テルグ語 | [sanscript](https://github.com/indic-transliteration/sanscript) | `నమస్తే` → `namaste` |
248
+ | カンナダ語 | [sanscript](https://github.com/indic-transliteration/sanscript) | `ನಮಸ್ತೇ` → `namaste` |
249
+ | オディア語 | [sanscript](https://github.com/indic-transliteration/sanscript) | `ନମସ୍ତେ` → `namaste` |
250
+ | タミル語 | [tamil-romanizer](https://github.com/haroldalan/tamil-romanizer) | `வணக்கம்` → `vanakkam` |
251
+ | タイ語 | [@dehoist/romanize-thai](https://github.com/Dehoist/Open-Source) | `สวัสดี` → `swasdi` |
252
+
253
+ ### 外部APIが必要
254
+
255
+ | スクリプト | 方法 |
256
+ |-----------|------|
257
+ | マラヤーラム語 | Google Translate `dt=rm` |
258
+ | ベンガル語 | Google Translate `dt=rm` |
259
+ | アラビア語 | Google Translate `dt=rm` |
260
+ | ヘブライ語 | Google Translate `dt=rm` |
261
+ | その他 | Google Translate `dt=rm` |
262
+
263
+ `requiresExternalRomanization()` でこれらを検出してお好みのAPIに分岐するか、あるいはAPIを一度**エンジンアダプター**として差し込んで、`romanizeLines` にすべてのスクリプトを処理させることもできます。
264
+
265
+ ## スクリプト別の注意事項
266
+
267
+ ### スクリプトが混在する配列
268
+
269
+ `romanizeLines` は配列全体の**支配的な**スクリプトをすべての行に固定します。これは意図的な設計です。日本語の曲の中の漢字のみの行は、それ単体では中国語と区別できません(漢字と中国語の漢字は同じUnicodeブロックを共有しているため)。そのため、配列全体の文脈だけが正しいエンジンへのルーティングを可能にします。知っておくべき影響は次のとおりです。
270
+
271
+ - 配列内のどこかにかなが1文字でもあれば、配列全体が `japanese` に固定されます — かなは決定的な証拠です。
272
+ - 配列内に*異なる*非ラテン文字の行があっても、固定されたエンジンに渡されます。
273
+ - 純粋なラテン文字の行(CJKの曲の中の英語のサビなど)は、固定されたエンジンに渡されず、**そのまま返されます**。
274
+ - 行ごとの `fallbacks` フラグは、エンジンが失敗して行がユニバーサル翻字に劣化した箇所を報告します。
275
+
276
+ 真に行ごとのエンジンルーティングが必要な場合は、`romanizeLine` をループで呼び出してください — こちらは行ごとに検出します。
277
+
278
+ ### キリル文字の検出
279
+
280
+ キリル文字はウクライナ固有の文字(`і`、`ї`、`є`、`ґ`)を自動検出し、ウクライナ語の翻字プリセットを適用します。その他のキリル文字はロシア語として処理されます。
281
+
282
+ ### 広東語サポート
283
+
284
+ 中国語はデフォルトで普通話(ピン音)になります。`RomanizeOptions` で `dialect: 'cantonese'` を渡すと、中国語テキストを [Jyutping](https://github.com/CanCLID/to-jyutping) でローマ字化します。
285
+
286
+ ```ts
287
+ const { lines } = await romanizer.romanizeLines(['你好世界', '食飯'], {
288
+ script: 'chinese',
289
+ dialect: 'cantonese',
290
+ });
291
+ // ['nei5 hou2 sai3 gaai3', 'sik6 faan6']
292
+ ```
293
+
294
+ ## ライセンス
295
+
296
+ MIT