prpm-dbp 0.15.0 → 0.17.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.
- package/SKILL.md +117 -0
- package/bin/cli.js +28 -2
- package/package.json +1 -1
- package/src/prpm.js +34 -1
- package/src/semak.js +50 -0
- package/src/tukar.js +65 -4
package/SKILL.md
CHANGED
|
@@ -9,6 +9,30 @@ Sumber kebenaran tunggal untuk ejaan BM dan Jawi ialah PRPM DBP, bukan ingatan m
|
|
|
9
9
|
|
|
10
10
|
Model boleh hasilkan Jawi yang nampak betul tetapi salah, dan pembaca tidak dapat kesan. Skill ini tukar tekaan jadi semakan.
|
|
11
11
|
|
|
12
|
+
## Prosedur wajib
|
|
13
|
+
|
|
14
|
+
Ikut ini setiap kali, jangan langkau langkah.
|
|
15
|
+
|
|
16
|
+
**Menulis Jawi daripada Rumi:**
|
|
17
|
+
|
|
18
|
+
1. `npx prpm-dbp jawi <fail> --luar-talian --homograf --json`
|
|
19
|
+
2. Baca `belumDisahkan` dan `namaKhas`. Jangan reka ejaan untuknya. Sahkan dengan `lookup`, atau tanya pengguna, atau tandakan dalam hasil akhir.
|
|
20
|
+
3. Baca `mencurigakan`. Setiap satu bermakna PRPM sendiri mungkin tersilap. Semak di pautan yang diberi sebelum menggunakannya.
|
|
21
|
+
4. Baca `homograf`. Beritahu pengguna perkataan mana yang akan taksa apabila dibaca.
|
|
22
|
+
5. Laporkan `semuanyaDbp`. Jika `false`, nyatakan bahagian mana bukan daripada sumber rasmi.
|
|
23
|
+
|
|
24
|
+
**Merumikan daripada Jawi:**
|
|
25
|
+
|
|
26
|
+
1. `npx prpm-dbp rumi <fail> --pilih --konteks --json`
|
|
27
|
+
2. Untuk **setiap** item dalam `kabur`: baca ayat yang mengandunginya, baca makna setiap calon, tentukan yang mana sesuai.
|
|
28
|
+
3. Jika `dipilih` salah bagi ayat itu, **gantikan** dalam hasil akhir. Kekerapan kalah kepada konteks.
|
|
29
|
+
4. Nyatakan kepada pengguna berapa banyak kekaburan wujud dan mana yang anda ubah.
|
|
30
|
+
5. `tidakDikenali` bermakna perkataan itu tiada dalam cache. Jalankan `warm` di luar talian, bukan tinggalkan bertanda.
|
|
31
|
+
|
|
32
|
+
**Berhenti dan tanya pengguna apabila:** ejaan `mencurigakan` mengubah makna, nama khas tiada rujukan bertulis, atau dua calon sama-sama munasabah dalam ayat itu.
|
|
33
|
+
|
|
34
|
+
Semua perintah menyokong `--json`, jadi langkah di atas boleh dibuat secara berulang tanpa menghurai teks.
|
|
35
|
+
|
|
12
36
|
## Peraturan
|
|
13
37
|
|
|
14
38
|
0. **Semak ejaan ikut DBP, bukan ikut ingatan.** Ini peraturan pertama kerana ia yang paling kerap dilanggar. Perkataan yang anda "tahu" ejaannya masih perlu disemak. Contoh sebenar daripada penggunaan: `sunah` ialah `سنة` tetapi `sunat` ialah `سونت`; `bena` langsung tiada ejaan Jawi dalam PRPM walaupun ia perkataan yang sah; `salat` hanyalah rujukan silang kepada `solat`. Tiada satu pun daripada perbezaan ini boleh diteka daripada bentuk Rumi. Jalankan `lookup` atau `jawi`, jangan tulis daripada ingatan.
|
|
@@ -30,6 +54,23 @@ Model boleh hasilkan Jawi yang nampak betul tetapi salah, dan pembaca tidak dapa
|
|
|
30
54
|
|
|
31
55
|
Bilangan rujukan itu datang daripada panel korpus DBP sendiri, bukan anggaran. Ia **mengisih** calon supaya yang lazim dilihat dahulu; ia **tidak memilih**. Perkataan jarang tetap betul apabila ayat menghendakinya.
|
|
32
56
|
|
|
57
|
+
0e. **Pembahagian kerja: alat mengemukakan, ANDA memutuskan.** CLI tidak boleh membaca konteks; anda boleh. Jangan sesekali menghantar `mengikut|mengekot` sebagai hasil akhir kepada pengguna.
|
|
58
|
+
|
|
59
|
+
Aliran yang betul:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx prpm-dbp rumi teks.txt --pilih --konteks
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`--pilih` menghasilkan teks bersih dengan memilih calon paling lazim, dan menyenaraikan setiap pilihan itu:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
itu mengikut sukatan pelajaran.
|
|
69
|
+
dipilih ikut kekerapan (SEMAK konteks): مڠيکوت -> mengikut [lain: mengekot]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Setiap baris `dipilih` ialah tekaan berasaskan kekerapan, **bukan pemahaman konteks**. Baca ayatnya. Dalam contoh di atas `mengikut sukatan pelajaran` jelas betul dan `mengekot` mustahil. Tetapi dalam ayat tentang orang kesejukan merapatkan tangan ke badan, `mengekot` yang betul walaupun kekerapannya 6 berbanding 2439. Kekerapan ialah tekaan terbaik tanpa konteks; ia kalah kepada konteks setiap kali.
|
|
73
|
+
|
|
33
74
|
1. **Jangan hantar Jawi yang belum disemak.** Setiap perkataan Jawi dalam output mesti datang dari PRPM atau ditanda belum disahkan.
|
|
34
75
|
2. **Tanda, jangan sekat.** Kalau satu perkataan tiada dalam PRPM, hantar kerja itu dan senaraikan perkataan berkenaan di hujung. Jangan tahan seluruh dokumen sebab satu nama khas.
|
|
35
76
|
3. **Sebut sumber.** PRPM ada beberapa kamus. Bila petik definisi, sebut kamus mana (Kamus Dewan Edisi Keempat, Kamus Pelajar Edisi Kedua, dan lain-lain).
|
|
@@ -143,6 +184,30 @@ Ejaan yang disimpan begini ditandakan datang daripada pengguna, bukan daripada D
|
|
|
143
184
|
|
|
144
185
|
Bagi nama berasal Arab, ejaan Jawinya memang ejaan Arab asal, jadi ia nampak seperti dikekalkan. Bagi nama bukan Arab ia ditransliterasi penuh: `Kuala Lumpur` menjadi `کوالا لومڤور`.
|
|
145
186
|
|
|
187
|
+
## Prosedur wajib
|
|
188
|
+
|
|
189
|
+
Ikut ini setiap kali, jangan langkau langkah.
|
|
190
|
+
|
|
191
|
+
**Menulis Jawi daripada Rumi:**
|
|
192
|
+
|
|
193
|
+
1. `npx prpm-dbp jawi <fail> --luar-talian --homograf --json`
|
|
194
|
+
2. Baca `belumDisahkan` dan `namaKhas`. Jangan reka ejaan untuknya. Sahkan dengan `lookup`, atau tanya pengguna, atau tandakan dalam hasil akhir.
|
|
195
|
+
3. Baca `mencurigakan`. Setiap satu bermakna PRPM sendiri mungkin tersilap. Semak di pautan yang diberi sebelum menggunakannya.
|
|
196
|
+
4. Baca `homograf`. Beritahu pengguna perkataan mana yang akan taksa apabila dibaca.
|
|
197
|
+
5. Laporkan `semuanyaDbp`. Jika `false`, nyatakan bahagian mana bukan daripada sumber rasmi.
|
|
198
|
+
|
|
199
|
+
**Merumikan daripada Jawi:**
|
|
200
|
+
|
|
201
|
+
1. `npx prpm-dbp rumi <fail> --pilih --konteks --json`
|
|
202
|
+
2. Untuk **setiap** item dalam `kabur`: baca ayat yang mengandunginya, baca makna setiap calon, tentukan yang mana sesuai.
|
|
203
|
+
3. Jika `dipilih` salah bagi ayat itu, **gantikan** dalam hasil akhir. Kekerapan kalah kepada konteks.
|
|
204
|
+
4. Nyatakan kepada pengguna berapa banyak kekaburan wujud dan mana yang anda ubah.
|
|
205
|
+
5. `tidakDikenali` bermakna perkataan itu tiada dalam cache. Jalankan `warm` di luar talian, bukan tinggalkan bertanda.
|
|
206
|
+
|
|
207
|
+
**Berhenti dan tanya pengguna apabila:** ejaan `mencurigakan` mengubah makna, nama khas tiada rujukan bertulis, atau dua calon sama-sama munasabah dalam ayat itu.
|
|
208
|
+
|
|
209
|
+
Semua perintah menyokong `--json`, jadi langkah di atas boleh dibuat secara berulang tanpa menghurai teks.
|
|
210
|
+
|
|
146
211
|
## Peraturan Pedoman DBP
|
|
147
212
|
|
|
148
213
|
Satu fail sahaja dalam pakej ini **menjana** ejaan dan bukan mencarinya: `src/pedoman.js`. Ia dibenarkan kerana setiap peraturan di dalamnya datang daripada *Pedoman Umum Ejaan Jawi Bahasa Melayu* terbitan DBP, dan dirujuk pada seksyennya.
|
|
@@ -254,6 +319,30 @@ Dua puluh dua kes kabur yang tinggal itu sah: `قلقله` boleh dibaca `qalqala
|
|
|
254
319
|
|
|
255
320
|
**Sifar perkataan hilang atau rosak.**
|
|
256
321
|
|
|
322
|
+
## Prosedur wajib
|
|
323
|
+
|
|
324
|
+
Ikut ini setiap kali, jangan langkau langkah.
|
|
325
|
+
|
|
326
|
+
**Menulis Jawi daripada Rumi:**
|
|
327
|
+
|
|
328
|
+
1. `npx prpm-dbp jawi <fail> --luar-talian --homograf --json`
|
|
329
|
+
2. Baca `belumDisahkan` dan `namaKhas`. Jangan reka ejaan untuknya. Sahkan dengan `lookup`, atau tanya pengguna, atau tandakan dalam hasil akhir.
|
|
330
|
+
3. Baca `mencurigakan`. Setiap satu bermakna PRPM sendiri mungkin tersilap. Semak di pautan yang diberi sebelum menggunakannya.
|
|
331
|
+
4. Baca `homograf`. Beritahu pengguna perkataan mana yang akan taksa apabila dibaca.
|
|
332
|
+
5. Laporkan `semuanyaDbp`. Jika `false`, nyatakan bahagian mana bukan daripada sumber rasmi.
|
|
333
|
+
|
|
334
|
+
**Merumikan daripada Jawi:**
|
|
335
|
+
|
|
336
|
+
1. `npx prpm-dbp rumi <fail> --pilih --konteks --json`
|
|
337
|
+
2. Untuk **setiap** item dalam `kabur`: baca ayat yang mengandunginya, baca makna setiap calon, tentukan yang mana sesuai.
|
|
338
|
+
3. Jika `dipilih` salah bagi ayat itu, **gantikan** dalam hasil akhir. Kekerapan kalah kepada konteks.
|
|
339
|
+
4. Nyatakan kepada pengguna berapa banyak kekaburan wujud dan mana yang anda ubah.
|
|
340
|
+
5. `tidakDikenali` bermakna perkataan itu tiada dalam cache. Jalankan `warm` di luar talian, bukan tinggalkan bertanda.
|
|
341
|
+
|
|
342
|
+
**Berhenti dan tanya pengguna apabila:** ejaan `mencurigakan` mengubah makna, nama khas tiada rujukan bertulis, atau dua calon sama-sama munasabah dalam ayat itu.
|
|
343
|
+
|
|
344
|
+
Semua perintah menyokong `--json`, jadi langkah di atas boleh dibuat secara berulang tanpa menghurai teks.
|
|
345
|
+
|
|
257
346
|
## Peraturan yang sengaja TIDAK dilaksanakan
|
|
258
347
|
|
|
259
348
|
| Seksyen | Sebab |
|
|
@@ -340,6 +429,34 @@ Empat kelas berasingan, kerana menggabungkannya menenggelamkan yang penting:
|
|
|
340
429
|
|
|
341
430
|
Penambahan huruf vokal **tidak** dikira ejaan lama. Kalau kehadiran `ي` diabaikan, `بينا` (bina) dan `بنا` (bena) akan kelihatan sama, sedangkan itu dua perkataan berlainan.
|
|
342
431
|
|
|
432
|
+
## Ejaan bukan piawai DBP
|
|
433
|
+
|
|
434
|
+
Dokumen kurikulum Malaysia kerap menggunakan ortografi Jawi yang berbeza daripada piawai DBP. Pasangan yang disahkan daripada DSKP Pendidikan Islam KPM sebenar:
|
|
435
|
+
|
|
436
|
+
| Rumi | DBP | DSKP | Beza |
|
|
437
|
+
|---|---|---|---|
|
|
438
|
+
| surah | `سورة` | `سوره` | ta marbutah jadi ha |
|
|
439
|
+
| iktibar | `اعتبار` | `اعتبر` | alif digugurkan |
|
|
440
|
+
| bersifat | `برصيفت` | `برصفة` | ya digugurkan, ta marbutah |
|
|
441
|
+
| dalil | `دليل` | `داليل` | alif ditambah |
|
|
442
|
+
| syirik | `شيريک` | `شرك` | ya digugurkan, kaf Arab |
|
|
443
|
+
|
|
444
|
+
Kesemuanya berbeza hanya pada huruf vokal dan bentuk huruf, bukan pada rangka konsonan. `--rangka` memadan rangka itu:
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
$ npx prpm-dbp rumi dskp.txt --luar-talian --rangka
|
|
448
|
+
ejaan bukan piawai DBP (209):
|
|
449
|
+
اعتبر -> iktibar [DBP eja: اعتبار]
|
|
450
|
+
سوره -> surah [DBP eja: سورة]
|
|
451
|
+
کتب -> kitab|katib|kutub (rangka sama, PILIH ikut konteks)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Diukur pada 168 baris DSKP sebenar: token yang langsung tidak dikenali turun daripada 100 kepada 47.
|
|
455
|
+
|
|
456
|
+
**Ini lapisan terakhir dan ia LOSSY.** 29% rangka dikongsi lebih daripada satu perkataan, purata 4.1 calon. Ia hanya digunakan selepas padanan tepat gagal, hasilnya sentiasa berlabel `ejaanBukanPiawai` berserta ejaan DBP untuk perbandingan, dan ia **tidak pernah memilih sendiri** apabila calon berbilang.
|
|
457
|
+
|
|
458
|
+
Kegunaannya bukan menjadikan alat lebih longgar. Ia membezakan **"ini surah, cuma dieja lain"** daripada **"saya tidak kenal benda ini"** — dua keadaan yang dahulu keluar sama sebagai `«...»`.
|
|
459
|
+
|
|
343
460
|
## Rantai kepercayaan
|
|
344
461
|
|
|
345
462
|
Setiap ejaan Jawi yang dikeluarkan membawa asal-usulnya. Jangan runtuhkan lapisan ini.
|
package/bin/cli.js
CHANGED
|
@@ -38,6 +38,9 @@ Pilihan: --refresh --had <n> --delay <ms> --json --cepat
|
|
|
38
38
|
--luar-talian jangan sentuh rangkaian langsung (wajib untuk laluan permintaan app)
|
|
39
39
|
--homograf tandakan perkataan yang ejaan Jawinya dikongsi perkataan lain
|
|
40
40
|
--konteks papar ayat dan makna setiap calon bagi kekaburan (rumi)
|
|
41
|
+
--rangka padan ejaan bukan piawai melalui rangka konsonan (berlabel)
|
|
42
|
+
--pilih pilih calon paling lazim supaya teks boleh terus dibaca;
|
|
43
|
+
setiap pilihan disenaraikan untuk disemak ikut konteks
|
|
41
44
|
|
|
42
45
|
Cache: ${CACHE_PATH}
|
|
43
46
|
`
|
|
@@ -55,6 +58,8 @@ function huraiArgs(argv) {
|
|
|
55
58
|
else if (a === '--luar-talian') pilihan.luarTalian = true
|
|
56
59
|
else if (a === '--homograf') pilihan.homograf = true
|
|
57
60
|
else if (a === '--konteks') pilihan.konteks = true
|
|
61
|
+
else if (a === '--pilih') pilihan.pilih = true
|
|
62
|
+
else if (a === '--rangka') pilihan.rangka = true
|
|
58
63
|
else if (a === '--jenis') pilihan.jenis = argv[++i]
|
|
59
64
|
else if (a === '--had') pilihan.had = Number(argv[++i])
|
|
60
65
|
else if (a === '--delay') pilihan.delay = Number(argv[++i])
|
|
@@ -220,7 +225,11 @@ async function main() {
|
|
|
220
225
|
|
|
221
226
|
case 'rumi': {
|
|
222
227
|
const teks = await readFile(baki[0], 'utf8')
|
|
223
|
-
const h = keRumi(teks, cache, {
|
|
228
|
+
const h = keRumi(teks, cache, {
|
|
229
|
+
dataset: await muatDataset(),
|
|
230
|
+
pilihLazim: pilihan.pilih,
|
|
231
|
+
padanRangka: pilihan.rangka,
|
|
232
|
+
})
|
|
224
233
|
if (pilihan.json) { keluar(h); break }
|
|
225
234
|
console.log(h.teks)
|
|
226
235
|
console.error(`\npeta songsang: ${h.saizPeta} ejaan`)
|
|
@@ -249,8 +258,25 @@ async function main() {
|
|
|
249
258
|
}
|
|
250
259
|
}
|
|
251
260
|
}
|
|
261
|
+
if (h.ejaanBukanPiawai?.length) {
|
|
262
|
+
const tunggal = h.ejaanBukanPiawai.filter((x) => x.calon.length === 1)
|
|
263
|
+
const banyak = h.ejaanBukanPiawai.filter((x) => x.calon.length > 1)
|
|
264
|
+
console.error(`\nejaan bukan piawai DBP (${h.ejaanBukanPiawai.length}):`)
|
|
265
|
+
for (const x of tunggal.slice(0, 20)) {
|
|
266
|
+
console.error(` ${x.jawi} -> ${x.calon[0].rumi} [DBP eja: ${x.calon[0].jawiDbp}]`)
|
|
267
|
+
}
|
|
268
|
+
for (const x of banyak.slice(0, 10)) {
|
|
269
|
+
console.error(` ${x.jawi} -> ${x.calon.map((c) => c.rumi).join('|')} (rangka sama, PILIH ikut konteks)`)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
252
272
|
if (h.arab.length) console.error(`teks Arab dibiarkan (${h.arab.length}): ${h.arab.join(' ')}`)
|
|
253
|
-
for (const k of h.kabur)
|
|
273
|
+
for (const k of h.kabur) {
|
|
274
|
+
console.error(
|
|
275
|
+
k.dipilih
|
|
276
|
+
? `dipilih ikut kekerapan (SEMAK konteks): ${k.jawi} -> ${k.dipilih} [lain: ${k.calon.slice(1).join(', ')}]`
|
|
277
|
+
: `kabur: ${k.jawi} -> ${k.calon.join(', ')}`,
|
|
278
|
+
)
|
|
279
|
+
}
|
|
254
280
|
break
|
|
255
281
|
}
|
|
256
282
|
|
package/package.json
CHANGED
package/src/prpm.js
CHANGED
|
@@ -127,10 +127,43 @@ export async function loadCache() {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Simpan cache dengan MENGGABUNGKAN keadaan di cakera terlebih dahulu.
|
|
132
|
+
*
|
|
133
|
+
* Menulis ganti secara membuta memusnahkan kerja proses lain. Dua sesi yang
|
|
134
|
+
* berjalan serentak akan memuat keadaan yang sama, masing-masing menambah
|
|
135
|
+
* perkataannya, dan yang menyimpan kemudian memadam tambahan yang awal.
|
|
136
|
+
* Ia diperhatikan secara langsung: entri yang baru dipanaskan hilang selepas
|
|
137
|
+
* sesi lain menyimpan.
|
|
138
|
+
*
|
|
139
|
+
* Rekod di cakera menang bagi kunci yang sama, kerana ia mungkin lebih baharu.
|
|
140
|
+
* Rekod benih (`ringkas`) tidak pernah menewaskan rekod penuh.
|
|
141
|
+
*/
|
|
130
142
|
export async function saveCache(cache) {
|
|
131
143
|
await mkdir(dirname(CACHE_PATH), { recursive: true })
|
|
144
|
+
|
|
145
|
+
let diCakera = {}
|
|
146
|
+
try {
|
|
147
|
+
diCakera = JSON.parse(await readFile(CACHE_PATH, 'utf8'))
|
|
148
|
+
} catch {
|
|
149
|
+
diCakera = {}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const gabung = { ...cache }
|
|
153
|
+
for (const [kata, rec] of Object.entries(diCakera)) {
|
|
154
|
+
const sedia = gabung[kata]
|
|
155
|
+
// Rekod penuh mengatasi rekod ringkas; selain itu cakera menang.
|
|
156
|
+
if (sedia && !sedia.ringkas && rec.ringkas) continue
|
|
157
|
+
gabung[kata] = rec
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Rekod benih tidak ditulis ke cache pengguna: ia sudah ada dalam pakej.
|
|
161
|
+
for (const [kata, rec] of Object.entries(gabung)) {
|
|
162
|
+
if (rec?.benih) delete gabung[kata]
|
|
163
|
+
}
|
|
164
|
+
|
|
132
165
|
const tmp = `${CACHE_PATH}.tmp`
|
|
133
|
-
await writeFile(tmp, JSON.stringify(
|
|
166
|
+
await writeFile(tmp, JSON.stringify(gabung, null, 1), 'utf8')
|
|
134
167
|
await rename(tmp, CACHE_PATH)
|
|
135
168
|
}
|
|
136
169
|
|
package/src/semak.js
CHANGED
|
@@ -102,3 +102,53 @@ export function ejaanLamaSahaja(a, b) {
|
|
|
102
102
|
if (!a || !b || a === b) return false
|
|
103
103
|
return samakanLama(a) === samakanLama(b)
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Rangka konsonan: buang huruf vokal pilihan dan samakan varian bentuk.
|
|
108
|
+
*
|
|
109
|
+
* Dokumen kurikulum Malaysia kerap menggunakan ejaan Jawi yang berbeza
|
|
110
|
+
* daripada piawai DBP. Corak yang disahkan pada pasangan sebenar DSKP:
|
|
111
|
+
*
|
|
112
|
+
* surah DBP سورة DSKP سوره ta marbutah jadi ha
|
|
113
|
+
* iktibar DBP اعتبار DSKP اعتبر alif digugurkan
|
|
114
|
+
* bersifat DBP برصيفت DSKP برصفة ya digugurkan, ta marbutah
|
|
115
|
+
* dalil DBP دليل DSKP داليل alif ditambah
|
|
116
|
+
* syirik DBP شيريک DSKP شرك ya digugurkan, kaf Arab
|
|
117
|
+
*
|
|
118
|
+
* Kesemuanya berbeza hanya pada huruf vokal dan bentuk huruf, bukan pada
|
|
119
|
+
* rangka konsonan. Membuang vokal menjadikan kedua-dua ejaan sepadan.
|
|
120
|
+
*
|
|
121
|
+
* INI LOSSY dan MESTI kekal di bawah lapisan 1 dan 2. Banyak perkataan
|
|
122
|
+
* berkongsi rangka yang sama, jadi padanan begini adalah inferens, bukan
|
|
123
|
+
* rekod DBP, dan hasilnya mesti berlabel.
|
|
124
|
+
*/
|
|
125
|
+
const VOKAL_PILIHAN = /[اويى]/g
|
|
126
|
+
|
|
127
|
+
const SAMA_BENTUK = new Map([
|
|
128
|
+
['ة', 'ه'],
|
|
129
|
+
['ك', 'ک'],
|
|
130
|
+
['ڬ', 'ݢ'],
|
|
131
|
+
['ف', 'ڤ'],
|
|
132
|
+
['ج', 'چ'],
|
|
133
|
+
['أ', 'ا'],
|
|
134
|
+
['إ', 'ا'],
|
|
135
|
+
['ء', ''],
|
|
136
|
+
])
|
|
137
|
+
|
|
138
|
+
// Pedoman §6.4: ta marbutah melambangkan [t] ATAU [h] sebagai kelainan
|
|
139
|
+
// sebutan pada kata serapan Arab (akibat/akibah, hidayat/hidayah). Ketiga-tiga
|
|
140
|
+
// huruf itu boleh bertukar di HUJUNG kata sahaja. Di tengah kata ia huruf
|
|
141
|
+
// berlainan dan tidak boleh dilipat.
|
|
142
|
+
const HUJUNG_SETARA = new Set(['ة', 'ه', 'ت'])
|
|
143
|
+
|
|
144
|
+
export function rangkaKonsonan(jawi) {
|
|
145
|
+
if (!jawi) return ''
|
|
146
|
+
let keluar = ''
|
|
147
|
+
for (const huruf of String(jawi).normalize('NFC')) {
|
|
148
|
+
const ganti = SAMA_BENTUK.has(huruf) ? SAMA_BENTUK.get(huruf) : huruf
|
|
149
|
+
keluar += ganti
|
|
150
|
+
}
|
|
151
|
+
keluar = keluar.replace(VOKAL_PILIHAN, '').replace(/\s+/g, '')
|
|
152
|
+
if (keluar && HUJUNG_SETARA.has(keluar.slice(-1))) keluar = keluar.slice(0, -1) + 'ه'
|
|
153
|
+
return keluar
|
|
154
|
+
}
|
package/src/tukar.js
CHANGED
|
@@ -12,7 +12,7 @@ import { lookup } from './prpm.js'
|
|
|
12
12
|
import { normalisasiJawi } from './kamus.js'
|
|
13
13
|
import { cariDataset, muatDataset } from './dataset.js'
|
|
14
14
|
import { cariNama, mungkinAkronim, mungkinNamaKhas } from './nama.js'
|
|
15
|
-
import { semakHurufAkhir } from './semak.js'
|
|
15
|
+
import { rangkaKonsonan, semakHurufAkhir } from './semak.js'
|
|
16
16
|
import {
|
|
17
17
|
imbuhAkhiranAn,
|
|
18
18
|
imbuhAkhiranKan,
|
|
@@ -162,6 +162,33 @@ export async function cariDasar(kata, cache, opts = {}) {
|
|
|
162
162
|
* bilangan perkataan terpanjang supaya pembacaan songsang boleh memadan
|
|
163
163
|
* jujukan, bukan token tunggal sahaja.
|
|
164
164
|
*/
|
|
165
|
+
/**
|
|
166
|
+
* Indeks rangka konsonan, untuk memadan ejaan bukan piawai.
|
|
167
|
+
*
|
|
168
|
+
* Dokumen kurikulum Malaysia kerap menggunakan ortografi yang berbeza
|
|
169
|
+
* daripada DBP: vokal digugur atau ditambah, ta marbutah bertukar ha, bentuk
|
|
170
|
+
* Arab menggantikan bentuk Jawi Melayu. Indeks ini memadan rangka konsonan
|
|
171
|
+
* supaya `سوره` masih dikenali sebagai `surah`.
|
|
172
|
+
*
|
|
173
|
+
* LOSSY: 29% rangka dikongsi lebih daripada satu perkataan, purata 4.1 calon.
|
|
174
|
+
* Kerana itu ia hanya digunakan selepas padanan tepat gagal, hasilnya sentiasa
|
|
175
|
+
* berlabel, dan ia TIDAK PERNAH memilih sendiri apabila calon berbilang.
|
|
176
|
+
*/
|
|
177
|
+
export function petaRangka(cache, dataset = null) {
|
|
178
|
+
const peta = new Map()
|
|
179
|
+
const tambah = (jawi, rumi) => {
|
|
180
|
+
const kunci = rangkaKonsonan(jawi)
|
|
181
|
+
if (!kunci) return
|
|
182
|
+
if (!peta.has(kunci)) peta.set(kunci, new Map())
|
|
183
|
+
if (!peta.get(kunci).has(rumi)) peta.get(kunci).set(rumi, jawi)
|
|
184
|
+
}
|
|
185
|
+
for (const rec of Object.values(cache)) if (rec?.jawi) tambah(rec.jawi, rec.kata)
|
|
186
|
+
for (const [rumi, ejaan] of Object.entries(dataset?.peta ?? {})) {
|
|
187
|
+
for (const j of ejaan) tambah(j, rumi)
|
|
188
|
+
}
|
|
189
|
+
return peta
|
|
190
|
+
}
|
|
191
|
+
|
|
165
192
|
export function petaSongsang(cache, dataset = null) {
|
|
166
193
|
const peta = new Map()
|
|
167
194
|
let maxKata = 1
|
|
@@ -425,12 +452,18 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
425
452
|
* menggugurkannya. Kerana itu perlanggaran jauh lebih jarang daripada yang
|
|
426
453
|
* disangka. Bila ia berlaku, SEMUA calon dilaporkan, bukan dipilih satu.
|
|
427
454
|
*/
|
|
428
|
-
export function keRumi(
|
|
455
|
+
export function keRumi(
|
|
456
|
+
teksJawi,
|
|
457
|
+
cache,
|
|
458
|
+
{ penanda = ['«', '»'], dataset = null, pilihLazim = false, padanRangka = false } = {},
|
|
459
|
+
) {
|
|
429
460
|
const peta = petaSongsang(cache, dataset)
|
|
430
461
|
const tidakDikenali = []
|
|
431
462
|
const kabur = []
|
|
432
463
|
const arab = []
|
|
433
464
|
const sendiDipisah = []
|
|
465
|
+
const bukanPiawai = []
|
|
466
|
+
const rangka = padanRangka ? petaRangka(cache, dataset) : null
|
|
434
467
|
const bahagian = teksJawi.split(PECAH_JAWI)
|
|
435
468
|
const keluar = []
|
|
436
469
|
|
|
@@ -487,6 +520,22 @@ export function keRumi(teksJawi, cache, { penanda = ['«', '»'], dataset = null
|
|
|
487
520
|
}
|
|
488
521
|
if (dipisah) { keluar.push(dipisah); sendiDipisah.push(bahagian[i]); continue }
|
|
489
522
|
|
|
523
|
+
// Lapisan terakhir: ejaan bukan piawai. Berlabel, di bawah lapisan
|
|
524
|
+
// 1 dan 2, dan tidak pernah memilih sendiri apabila calon berbilang.
|
|
525
|
+
if (rangka) {
|
|
526
|
+
const padananRangka = rangka.get(rangkaKonsonan(bahagian[i]))
|
|
527
|
+
if (padananRangka?.size) {
|
|
528
|
+
const senarai = [...padananRangka.entries()].map(([rumi, jawiDbp]) => ({ rumi, jawiDbp }))
|
|
529
|
+
bukanPiawai.push({ jawi: bahagian[i], calon: senarai })
|
|
530
|
+
if (senarai.length === 1) {
|
|
531
|
+
keluar.push(senarai[0].rumi)
|
|
532
|
+
continue
|
|
533
|
+
}
|
|
534
|
+
keluar.push(penanda[0] + senarai.map((x) => x.rumi).join('|') + penanda[1])
|
|
535
|
+
continue
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
490
539
|
tidakDikenali.push(bahagian[i])
|
|
491
540
|
keluar.push(penanda[0] + bahagian[i] + penanda[1])
|
|
492
541
|
continue
|
|
@@ -494,8 +543,19 @@ export function keRumi(teksJawi, cache, { penanda = ['«', '»'], dataset = null
|
|
|
494
543
|
|
|
495
544
|
if (padanan.length === 1) keluar.push(padanan[0])
|
|
496
545
|
else {
|
|
497
|
-
|
|
498
|
-
|
|
546
|
+
const rekod = { jawi: bahagian.slice(i, hujung + 1).join(''), calon: padanan }
|
|
547
|
+
kabur.push(rekod)
|
|
548
|
+
// pilihLazim menghasilkan teks yang boleh terus dibaca dengan memilih
|
|
549
|
+
// calon paling lazim dalam korpus DBP. Ia BUKAN pemahaman konteks:
|
|
550
|
+
// setiap pilihan direkod dalam medan `kabur` supaya agen atau manusia
|
|
551
|
+
// yang membaca ayat boleh menggantikannya. Kekerapan ialah tekaan
|
|
552
|
+
// terbaik tanpa konteks, bukan jawapan.
|
|
553
|
+
if (pilihLazim) {
|
|
554
|
+
rekod.dipilih = padanan[0]
|
|
555
|
+
keluar.push(padanan[0])
|
|
556
|
+
} else {
|
|
557
|
+
keluar.push(padanan.join('|'))
|
|
558
|
+
}
|
|
499
559
|
}
|
|
500
560
|
i = hujung
|
|
501
561
|
}
|
|
@@ -506,6 +566,7 @@ export function keRumi(teksJawi, cache, { penanda = ['«', '»'], dataset = null
|
|
|
506
566
|
kabur,
|
|
507
567
|
arab: [...new Set(arab)],
|
|
508
568
|
sendiDipisah,
|
|
569
|
+
ejaanBukanPiawai: bukanPiawai,
|
|
509
570
|
saizPeta: peta.size,
|
|
510
571
|
lengkap: tidakDikenali.length === 0,
|
|
511
572
|
}
|