Pengguna:Hakimi97/penukar-rumi-jawi-kamus.js

Catatan: Selepas diterbitkan, anda mungkin perlu memintas cache pelayar web untuk melihat perubahan.

  • Firefox / Safari: Tahan Shift sambil klik Reload, atau tekan sama ada Ctrl-F5 atau Ctrl-R (⌘-R pada Mac)
  • Google Chrome: Tekan Ctrl-Shift-R (⌘-Shift-R pada Mac)
  • Edge: Tahan Ctrl sambil tekan Refresh, atau tekan Ctrl-F5.
/**
 ** LOG:
 ** Updated on 27th November 2024
 **
 **/

/* Change the text from rumi to jawi script */

/* Original author: [[Pengguna:Hakimi97]] */

/* Inspired by: [[Pengguna:Kurniasan/rumikpdjawi.js]] */

/* Licensed under the terms of the GNU General Public License version 3.0
* as published by the Free Software Foundation; See the
* GNU General Public License for more details.
* A copy of the GPL is available at https://www.gnu.org/licenses/gpl-3.0.txt 
*/

/*
 * PRINSIP KAMUS:
 *
 * 1. Semua nama khas berkaitan masa, geografi dan etnolinguistik akan ditambah 
 *    pada kata dasar. Hanya nama manusia sahaja diasingkan senarai dengan 
 *    terjemahan rumi paling lazim di kedudukan paling hadapan dengan 
 *    variasi-variasi nama di belakangnya. 
 *
 * 2. Semua hamzah bebas (ء) kecuali kata serapan Arab sepatutnya berbentuk  
 *    hamzah tiga suku. Namun disebabkan masalah teknikal maka hamzah tiga suku  
 *    buat masa ini belum diperkenalkan untuk semua perkataan dalam kamus ini.
 *
 * 3. Apabila ada penambahan alif (ا) atau hamzah (ء) di hadapan imbuhan “-an”
 *    sedangkan alif atau hamzah tambahan tidak terjumpa pada kata dasarnya, 
 *    maka kesemua akan ditambah dalam senarai berikut. Selain daripada itu 
 *    semua “-an” secara automatik akan ditambah nun (ن) sahaja. 
 * 
 * 4. Semua kata dalam Kamus Dewan Perdana yang memerlukan penanda gandaan
 *    “٢” akan ditambah dalam senarai berikut (contohnya:اݢق٢). Selain itu semua
 *    perkataan lain yang tidak tersenarai akan dipapar secara lalai dengan 
 *    tanda sengkang “-” (contohnya: برنتاي-رنتاي).
 *
 * 5. Perubahan alif berhamzah (أ) dan wau berhamzah (ؤ) pada kedudukan awal 
 *    suku kata kata dasar (contohnya "سأهلي") yang tercatat dalam Kamus Dewan
 *    Perdana akan ditambah selepas dan sebaris dengan entri kata dasarnya.
 * 
 * 6. Sebarang kata yang mengalami pengguguran alif (ا) dan ain (ع) pada 
 *    kedudukan awal kata dasarnya akan ditambah berserta imbuhan di 
 *    hadapannya, contohnya "مڠهلي" dan "مڠاءيب".
 *
 * 7. Ta marbutah (ة) yang fonem akhir kata ruminya /-t/ akan bertukar kepada 
 *    ta maftuhah (ت), contohnya seperti “عاقبة” berubah kepada“عاقبتن”. Manakala
 *    ta marbutah yang fonem akhir kata ruminya /-h/ pula akan digantikan dengan
 *    huruf ha tebal (ه) sekiranya disambung dengan imbuhan akhir “-an”, “-kan” 
 *    dan “-nya”, contohnya seperti "عقيدة" berubah kepada "عقيدهن". Perubahan ini
 *    yang dirakam dalam Kamus Dewan Perdana akan ditambah selepas dan sebaris 
 *    dengan entri kata dasarnya.
 *
 */

if ([0, 1, 3, 4, 5, 12, 13, 14, 15].includes(mw.config.get('wgNamespaceNumber'))) {
  let cache = null;
  let titleCache = null;
  let RumiJawi = null;
  const processedTextCache = {};

  // Load the kamus data from GitHub
  const loadKamusData = () => {
    return fetch(
      'https://raw.githubusercontent.com/wikipediahakimi97/wikipedia-rumi-jawi-converter/refs/heads/main/fulldictionaryforconverter.js'
    )
      .then((response) => {
        if (!response.ok) {
          throw new Error('Failed to fetch kamus data: ' + response.statusText);
        }
        return response.text();
      })
      .then((scriptContent) => {
        eval(scriptContent);
      })
      .catch((error) => console.error(error.message));
  };

  // Initialize RumiJawi patterns
  const initRumiJawi = () => {
    if (RumiJawi) return;
    RumiJawi = {
      entries: Object.fromEntries(
        Object.keys(kamus).map((word) => [word, new RegExp(`\\b${word}\\b`, 'gi')])
      ),
      prefixes: Object.fromEntries(
        Object.keys(imbuhanAwalan).map((prefix) => [
          prefix,
          new RegExp(`(\\b${prefix})(\\w+)\\b`, 'gi'),
        ])
      ),
      suffixes: Object.fromEntries(
        Object.keys(imbuhanAkhiran).map((suffix) => [
          suffix,
          new RegExp(`\\b(\\w+)(${suffix})\\b`, 'i'),
        ])
      ),
    };
  };

  // Convert Rumi to Jawi
  const convertToJawi = (src, noRecursive = false) => {
    if (processedTextCache[src]) return processedTextCache[src];

    let result = src;
    if (kamus[src]) {
      result = kamus[src];
    } else if (!noRecursive) {
      result = processSuffixes(src);
    }

    processedTextCache[src] = result;
    return result;
  };

  // Process suffixes
  const processSuffixes = (src) => {
    for (const suffixKey of Object.keys(imbuhanAkhiran)) {
      src = src.replace(RumiJawi.suffixes[suffixKey], suffixProcessor);
    }
    return src;
  };

  // Prefix processor
  const prefixProcessor = (match, prefix, word) => {
    const convertedWord = convertToJawi(word);
    return convertedWord === word ? match : imbuhanAwalan[prefix.toLowerCase()] + convertedWord;
  };

  // Suffix processor
  const suffixProcessor = (match, baseWord, suffix) => {
    const convertedBase = convertToJawi(baseWord, true);
    return convertedBase === baseWord ? match : convertedBase + imbuhanAkhiran[suffix];
  };

  // Convert punctuation to Arabic equivalents
  const convertPunctuationToArabic = (src) => {
    const punctuationMap = {
      '.': '.',
      ',': '⹁',
      '!': '!',
      '?': '؟',
      ':': ':',
      ';': '⁏',
      '(': '(',
      ')': ')',
      '-': ' ـ ',
      "'": '’',
      '"': '“',
      ' ': ' ',
    };

    return src
      .replace(/(\d+)%/g, '%$1') // Adjust percentage position
      .replace(/[.,!?;()'"-]/g, (match) => punctuationMap[match] || match);
  };

  // Process content nodes
  const processContent = ($content) => {
    $content.contents().each(function () {
      if (this.nodeType === 3) {
        let text = this.textContent;
        if (text.trim()) {
          // Process the text
          for (const word of Object.keys(kamus)) {
            text = text.replace(RumiJawi.entries[word], () => kamus[word]);
          }

          for (const prefix of Object.keys(imbuhanAwalan)) {
            text = text.replace(RumiJawi.prefixes[prefix], prefixProcessor);
          }

          text = processSuffixes(text);
          text = convertPunctuationToArabic(text);

          this.textContent = text;
        }
      } else {
        processContent($(this));
      }
    });
  };

  const processTitle = () => {
    const $title = $('#firstHeading');
    let text = $title.text();

    if (text.trim()) {
      initRumiJawi();
      for (const word of Object.keys(kamus)) {
        text = text.replace(RumiJawi.entries[word], () => kamus[word]);
      }

      for (const prefix of Object.keys(imbuhanAwalan)) {
        text = text.replace(RumiJawi.prefixes[prefix], prefixProcessor);
      }

      text = processSuffixes(text);
      text = convertPunctuationToArabic(text);

      $title.text(text);
    }
  };

  // Add toggle switch
  $('#p-interaction ul').append(`
    <li id="ca-nstab-rkj">
      <span class="toggle-container">
        <span class="toggle-label">Rumi</span>
        <label class="switch">
          <input id="togol-rkj" type="checkbox">
          <span class="slider round"></span>
        </label>
        <span class="toggle-label">Jawi</span>
      </span>
    </li>
  `);

  // Inject CSS for the toggle switch
  const toggleSwitchStyle = `
    .toggle-container {
      display: flex;
      align-items: center;
      gap: 8px;
    }
    .toggle-label {
      font-size: 14px;
      user-select: none;
    }
    .switch {
      position: relative;
      display: inline-block;
      width: 34px;
      height: 20px;
    }
    .switch input {
      opacity: 0;
      width: 0;
      height: 0;
    }
    .slider {
      position: absolute;
      cursor: pointer;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: #c8ccd1;
      transition: 0.4s;
      border-radius: 20px;
    }
    .slider:before {
      position: absolute;
      content: "";
      height: 14px;
      width: 14px;
      left: 3px;
      bottom: 3px;
      background-color: white;
      transition: 0.4s;
      border-radius: 50%;
    }
    input:checked + .slider {
      background-color: #36c;
    }
    input:checked + .slider:before {
      transform: translateX(14px);
    }
  `;
  $('head').append(`<style>${toggleSwitchStyle}</style>`);

  // Toggle switch click handler
  $('#togol-rkj').click(function () {
    const $content = $('#mw-content-text');
    const $title = $('#firstHeading');
    if (this.checked) {
      cache = cache || $content.html();
      titleCache = titleCache || $title.html();
      initRumiJawi();
      processTitle();
      processContent($content);
      $content.attr({ dir: 'rtl', class: 'mw-content-rtl' });
    } else if (cache && titleCache) {
      $content.html(cache).attr({ dir: 'ltr', class: 'mw-content-ltr' });
      $title.html(titleCache);
      cache = null;
      titleCache = null;
    }
  });

  // Load resources
  const loadResources = () => {
    Promise.all([loadKamusData()])
      .then(() => {
        console.log('Resources loaded successfully.');
      })
      .catch((error) => console.error('Error loading resources:', error));
  };

  // Initialize
  loadResources();
}