kodingup.camp

Beranda/JavaScript Mastery: Fundamentals

Sistem Penerjemahan Karakter

Modul 6 dari 164 menit bacaCharacter translation systems

Isi modul

    Kenapa ini penting

    Di modul representasi teks kamu sudah tahu bahwa setiap karakter punya nomor. Sekarang kita bahas standarnya secara nyata: ASCII, Unicode, dan UTF-8. Ini penting karena aplikasi modern dipakai orang dari seluruh dunia. Nama pengguna bisa berisi huruf Jepang, komentar bisa penuh emoji, dan alamat bisa memakai huruf beraksen. JavaScript punya beberapa sifat unik soal teks, misalnya "๐Ÿ˜€".length bernilai 2, bukan 1. Kalau tidak paham alasannya, kamu bisa membuat fitur "batas 280 karakter" yang memotong emoji jadi setengah dan menampilkan karakter rusak ๏ฟฝ.

    Konsep inti

    ASCII: standar awal 7 bit

    ASCII (American Standard Code for Information Interchange, 1963) memetakan 128 karakter (0-127) memakai 7 bit:

    Rentang Isi
    0-31 Karakter kontrol, misalnya 9 = tab, 10 = newline, 13 = carriage return
    32 Spasi
    48-57 Angka 0-9
    65-90 Huruf besar A-Z
    97-122 Huruf kecil a-z

    Pola menarik: huruf kecil = huruf besar + 32. A (65) dan a (97) selisihnya 32. Angka "0" bernomor 48, jadi karakter "7" bernomor 55, bukan 7.

    Masalahnya, ASCII hanya untuk bahasa Inggris. Tidak ada รฉ, รฑ, huruf Arab, Mandarin, apalagi emoji. Dulu tiap negara membuat tabel sendiri yang saling bentrok, dan itulah sumber mojibake.

    Unicode: satu nomor untuk setiap karakter di dunia

    Unicode adalah katalog raksasa yang memberi setiap karakter sebuah nomor unik yang disebut code point, ditulis U+ diikuti heksadesimal:

    Karakter Code point Desimal
    A U+0041 65
    รฉ U+00E9 233
    โ‚ฌ U+20AC 8364
    ๊ฆฒ (aksara Jawa "ha") U+A9B2 43442
    ๐Ÿ˜€ U+1F600 128512

    128 code point pertama Unicode sama persis dengan ASCII, jadi teks ASCII lama tetap kompatibel. Unicode menyediakan ruang sampai U+10FFFF (lebih dari satu juta posisi).

    UTF-8: cara hemat menyimpan Unicode

    Unicode hanya memberi nomor. Untuk menyimpannya ke byte, dipakai encoding. Yang paling populer adalah UTF-8, dipakai oleh sekitar 98% website. UTF-8 memakai 1 sampai 4 byte tergantung besar nomornya:

    Rentang code point Jumlah byte Contoh
    U+0000 - U+007F 1 A
    U+0080 - U+07FF 2 รฉ
    U+0800 - U+FFFF 3 โ‚ฌ, ๊ฆฒ
    U+10000 - U+10FFFF 4 ๐Ÿ˜€

    Kelebihannya: teks bahasa Inggris tetap 1 byte per huruf (hemat), tapi semua karakter dunia tetap bisa ditulis.

    Bagaimana JavaScript menyimpan string: UTF-16

    Di dalam memori, string JavaScript disimpan sebagai deretan UTF-16 code unit (masing-masing 16 bit). Karakter dengan code point di atas U+FFFF, seperti kebanyakan emoji, tidak muat dalam satu unit. Karakter itu dipecah menjadi dua code unit yang disebut surrogate pair. Akibatnya:

    Masih ada satu lapis lagi: beberapa emoji adalah gabungan beberapa code point. Contohnya emoji keluarga ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง adalah 3 orang yang disambung karakter tak terlihat ZWJ (zero width joiner). [...teks] menghitungnya 5. Untuk menghitung "karakter yang dilihat manusia" (grapheme), JavaScript modern menyediakan Intl.Segmenter.

    Huruf yang terlihat sama tapi berbeda

    รฉ bisa ditulis sebagai satu code point (U+00E9) atau sebagai e + tanda aksen gabungan (U+0301). Tampilannya identik, tapi === bernilai false. Method normalize() menyamakan bentuknya sebelum dibandingkan.

    Contoh kode

    // ASCII dasar
    console.log("A".charCodeAt(0));            // โ†’ 65
    console.log("a".charCodeAt(0));            // โ†’ 97
    console.log("7".charCodeAt(0) - 48);        // โ†’ 7 (ubah karakter digit ke angka)
    console.log(String.fromCharCode(72, 105));  // โ†’ Hi
    
    // Mengubah huruf kecil ke besar secara manual (selisih 32)
    console.log(String.fromCharCode("k".charCodeAt(0) - 32)); // โ†’ K
    
    // Emoji: length, charCodeAt vs codePointAt
    const senyum = "๐Ÿ˜€";
    console.log(senyum.length);           // โ†’ 2  (dua code unit UTF-16)
    console.log(senyum.charCodeAt(0));    // โ†’ 55357 (separuh surrogate pair)
    console.log(senyum.codePointAt(0));   // โ†’ 128512
    console.log(senyum.codePointAt(0).toString(16)); // โ†’ 1f600 (U+1F600)
    console.log(String.fromCodePoint(128512));       // โ†’ ๐Ÿ˜€
    console.log([...senyum].length);      // โ†’ 1  (spread memecah per code point)
    
    // Jumlah byte di UTF-8
    const enc = new TextEncoder();
    for (const ch of ["A", "รฉ", "โ‚ฌ", "๐Ÿ˜€"]) {
      console.log(ch, enc.encode(ch).length, "byte");
    }
    // โ†’ A 1 byte
    // โ†’ รฉ 2 byte
    // โ†’ โ‚ฌ 3 byte
    // โ†’ ๐Ÿ˜€ 4 byte
    

    Memotong teks dengan aman dan menghitung karakter yang terlihat:

    const status = "Halo ๐Ÿ˜€๐Ÿ˜€";
    console.log(status.length);               // โ†’ 9
    console.log(status.slice(0, 6));          // โ†’ "Halo " + separuh emoji (tampil rusak ๏ฟฝ)
    console.log([...status].slice(0, 6).join("")); // โ†’ Halo ๐Ÿ˜€ (aman)
    
    // Emoji gabungan: hitung grapheme dengan Intl.Segmenter
    const keluarga = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง";
    console.log(keluarga.length);       // โ†’ 8
    console.log([...keluarga].length);  // โ†’ 5
    const seg = new Intl.Segmenter("id", { granularity: "grapheme" });
    console.log([...seg.segment(keluarga)].length); // โ†’ 1
    
    // Normalisasi huruf beraksen
    const a = "รฉ";         // satu code point U+00E9
    const b = "eฬ";   // e + aksen gabungan
    console.log(a === b);                                   // โ†’ false
    console.log(a.normalize("NFC") === b.normalize("NFC")); // โ†’ true
    

    Kesalahan umum

    1. Memakai .length untuk batas karakter yang dilihat pengguna. Emoji terhitung 2 atau lebih. Gunakan [...teks].length atau Intl.Segmenter untuk hitungan yang lebih akurat.
    2. Memakai charCodeAt atau fromCharCode untuk emoji. Gunakan codePointAt dan String.fromCodePoint.
    3. Memotong string dengan slice di tengah emoji. Hasilnya karakter rusak ๏ฟฝ. Pecah dulu dengan [...teks] sebelum dipotong.
    4. Mengira karakter "5" sama dengan angka 5. Kode karakter "5" adalah 53. Gunakan Number("5") untuk mendapatkan angkanya.
    5. Membandingkan teks beraksen tanpa normalize(). Dua teks yang terlihat sama bisa berbeda code point.

    Latihan

    1. Mudah, Tampilkan code point (dalam desimal dan format U+XXXX) dari karakter "Z", "รฑ", dan "โ‚ฌ".
    2. Sedang, Buat function hitungKarakter(teks) yang menghitung jumlah code point (bukan code unit). Uji dengan "Aku ๐Ÿ’™ JS".
    3. Menantang, Buat function potongAman(teks, maks) yang memotong teks menjadi maksimal maks grapheme dan menambahkan "โ€ฆ" kalau terpotong. Uji dengan "Mantap ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง sekali" dan maks = 8.
    Lihat solusi

    1.

    for (const ch of ["Z", "รฑ", "โ‚ฌ"]) {
      const cp = ch.codePointAt(0);
      const uplus = "U+" + cp.toString(16).toUpperCase().padStart(4, "0");
      console.log(ch, cp, uplus);
    }
    // โ†’ Z 90 U+005A
    // โ†’ รฑ 241 U+00F1
    // โ†’ โ‚ฌ 8364 U+20AC
    

    2.

    function hitungKarakter(teks) {
      return [...teks].length;
    }
    console.log("Aku ๐Ÿ’™ JS".length);          // โ†’ 9
    console.log(hitungKarakter("Aku ๐Ÿ’™ JS")); // โ†’ 8
    

    3.

    function potongAman(teks, maks) {
      const seg = new Intl.Segmenter("id", { granularity: "grapheme" });
      const potongan = [...seg.segment(teks)].map((s) => s.segment);
      if (potongan.length <= maks) return teks;
      return potongan.slice(0, maks).join("") + "โ€ฆ";
    }
    console.log(potongAman("Mantap ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง sekali", 8)); // โ†’ Mantap ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€ฆ
    

    Ringkasan

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.