UNPKG

id-auto-formalizer

Version:

Konversi teks Bahasa Indonesia dari kasual ke formal untuk surat, email, dan laporan resmi

509 lines (453 loc) 13.9 kB
// Core Formalizer Engine untuk Bahasa Indonesia class IndonesianFormalizer { constructor() { // Dictionary kata informal -> formal this.informalToFormal = { // Kata ganti 'gue': 'saya', 'gw': 'saya', 'aku': 'saya', 'lo': 'Anda', 'lu': 'Anda', 'elo': 'Anda', 'elu': 'Anda', 'kamu': 'Anda', 'kalian': 'Anda sekalian', 'gua': 'saya', 'ane': 'saya', 'ente': 'Anda', // Kata kerja informal 'ngomong': 'berbicara', 'ngobrol': 'berbincang', 'nanya': 'bertanya', 'nyari': 'mencari', 'ngerti': 'mengerti', 'ngeliat': 'melihat', 'ngerjain': 'mengerjakan', 'nunggu': 'menunggu', 'nyoba': 'mencoba', 'ngasih': 'memberikan', 'ngirim': 'mengirim', 'nyampe': 'sampai', 'nyampai': 'sampai', 'ngambil': 'mengambil', 'ngebuat': 'membuat', 'ngebantu': 'membantu', 'ngejar': 'mengejar', 'ngelamar': 'melamar', 'ngeluh': 'mengeluh', 'ngecheck': 'memeriksa', 'ngecek': 'memeriksa', // Kata sifat informal 'gede': 'besar', 'kecil banget': 'sangat kecil', 'gede banget': 'sangat besar', 'bagus banget': 'sangat baik', 'jelek banget': 'sangat buruk', 'banyak banget': 'sangat banyak', 'dikit': 'sedikit', 'cape': 'lelah', 'capek': 'lelah', 'males': 'malas', 'bosen': 'bosan', 'kesel': 'kesal', 'seneng': 'senang', 'sedih banget': 'sangat sedih', 'keren': 'bagus', 'mantap': 'baik', 'oke': 'baik', 'ok': 'baik', // Kata keterangan 'banget': 'sekali', 'bgt': 'sekali', 'bngt': 'sekali', 'udah': 'sudah', 'udh': 'sudah', 'belom': 'belum', 'belum': 'belum', 'blm': 'belum', 'blom': 'belum', 'gak': 'tidak', 'ga': 'tidak', 'g': 'tidak', 'nggak': 'tidak', 'ngga': 'tidak', 'enggak': 'tidak', 'engga': 'tidak', 'kagak': 'tidak', 'kaga': 'tidak', 'dah': 'sudah', 'trus': 'lalu', 'terus': 'lalu', 'trz': 'lalu', 'trs': 'lalu', 'abis': 'setelah', 'habis': 'setelah', 'abis itu': 'setelah itu', 'habis itu': 'setelah itu', 'emang': 'memang', 'emg': 'memang', 'doang': 'saja', 'doank': 'saja', 'aja': 'saja', 'cuma': 'hanya', 'cuman': 'hanya', 'cmn': 'hanya', 'cm': 'hanya', // Kata sambung & lainnya 'tapi': 'tetapi', 'tp': 'tetapi', 'kalo': 'jika', 'kalau': 'jika', 'klo': 'jika', 'karena': 'karena', 'krn': 'karena', 'karna': 'karena', 'soalnya': 'karena', 'sebab': 'karena', 'biar': 'agar', 'supaya': 'agar', 'spy': 'agar', 'buat': 'untuk', 'utk': 'untuk', 'u/': 'untuk', // Kata tanya 'gimana': 'bagaimana', 'gmn': 'bagaimana', 'gmana': 'bagaimana', 'kenapa': 'mengapa', 'knp': 'mengapa', 'knapa': 'mengapa', 'kapan': 'kapan', 'kpn': 'kapan', 'dimana': 'di mana', 'dmn': 'di mana', 'dmana': 'di mana', 'siapa': 'siapa', 'sapa': 'siapa', 'sp': 'siapa', // Singkatan umum 'yg': 'yang', 'dgn': 'dengan', 'dg': 'dengan', // eslint-disable-next-line no-dupe-keys 'utk': 'untuk', // eslint-disable-next-line no-dupe-keys 'u/': 'untuk', 'dr': 'dari', 'ke': 'ke', 'pd': 'pada', 'kpd': 'kepada', 'bg': 'bagi', // eslint-disable-next-line no-dupe-keys 'bg': 'bagi', 'dlm': 'dalam', 'di': 'di', // eslint-disable-next-line no-dupe-keys 'pd': 'pada', 'ttg': 'tentang', 'thd': 'terhadap', 'tsb': 'tersebut', 'dsb': 'dan sebagainya', 'dll': 'dan lain-lain', 'dkk': 'dan kawan-kawan', // Slang & gaul 'thanks': 'terima kasih', 'thx': 'terima kasih', 'tq': 'terima kasih', 'makasih': 'terima kasih', 'mksh': 'terima kasih', 'mksi': 'terima kasih', 'sorry': 'maaf', 'sori': 'maaf', 'sory': 'maaf', 'please': 'mohon', 'plis': 'mohon', 'pliss': 'mohon', 'tolong dong': 'mohon', 'dong': '', 'deh': '', 'sih': '', 'lah': '', 'nih': 'ini', 'tuh': 'itu', 'nah': '', 'yuk': 'mari', 'ayo': 'mari', 'ayok': 'mari', 'yuks': 'mari', 'yok': 'mari', // Kata informal lainnya 'bokap': 'ayah', 'nyokap': 'ibu', 'bapak': 'ayah', 'mamah': 'ibu', 'mama': 'ibu', 'papa': 'ayah', 'adek': 'adik', 'kakak': 'kakak', 'mbak': 'kakak', 'mas': 'kakak', 'bro': 'saudara', 'sis': 'saudari', 'guys': 'teman-teman', 'temen': 'teman', 'tmn': 'teman', 'gebetan': 'orang yang disukai', 'pacar': 'kekasih', 'doi': 'dia', 'dy': 'dia', 'dia': 'beliau', // untuk konteks formal // Frasa umum 'btw': 'omong-omong', 'by the way': 'omong-omong', 'fyi': 'untuk informasi', 'asap': 'segera', 'otw': 'sedang dalam perjalanan', 'on the way': 'sedang dalam perjalanan', 'gpp': 'tidak apa-apa', 'gapapa': 'tidak apa-apa', 'ga papa': 'tidak apa-apa', 'santai aja': 'tenang saja', 'santuy': 'tenang', 'gas': 'lanjutkan', 'gaskeun': 'lanjutkan', 'gaspol': 'lanjutkan dengan cepat', 'wkwk': '', 'wkwkwk': '', 'haha': '', 'hehe': '', 'hihi': '', 'xixi': '' }; // Pola regex untuk mendeteksi dan memperbaiki this.patterns = [ // Double spacing { pattern: /\s+/g, replacement: ' ' }, // Kapitalisasi awal kalimat { pattern: /^.|\.\s+./g, replacement: match => match.toUpperCase() }, // Tanda baca berulang { pattern: /\.{2,}/g, replacement: '.' }, { pattern: /\?{2,}/g, replacement: '?' }, { pattern: /!{2,}/g, replacement: '!' }, { pattern: /,{2,}/g, replacement: ',' }, // Spasi sebelum tanda baca { pattern: /\s+([.,!?;:])/g, replacement: '$1' }, // Spasi setelah tanda baca { pattern: /([.,!?;:])(?!\s|$)/g, replacement: '$1 ' }, // Angka dengan 'ribuan' informal { pattern: /(\d+)rb/gi, replacement: '$1 ribu' }, { pattern: /(\d+)jt/gi, replacement: '$1 juta' }, { pattern: /(\d+)m/gi, replacement: '$1 miliar' }, // Tanggal informal { pattern: /(\d{1,2})\/(\d{1,2})\/(\d{2,4})/g, replacement: '$1-$2-$3' } ]; // Kalimat pembuka formal this.formalGreetings = { 'hai': 'Dengan hormat', 'halo': 'Dengan hormat', 'hello': 'Dengan hormat', 'hi': 'Dengan hormat', 'pagi': 'Selamat pagi', 'siang': 'Selamat siang', 'sore': 'Selamat sore', 'malam': 'Selamat malam', 'met pagi': 'Selamat pagi', 'met siang': 'Selamat siang', 'met sore': 'Selamat sore', 'met malem': 'Selamat malam' }; // Kalimat penutup formal this.formalClosings = { 'bye': 'Hormat saya', 'dadah': 'Hormat saya', 'see you': 'Sampai jumpa', 'see u': 'Sampai jumpa', 'cu': 'Sampai jumpa', 'ttd': 'Tertanda', 'salam': 'Salam hormat', 'wassalam': 'Wassalamualaikum', 'wslm': 'Wassalamualaikum' }; } // Fungsi utama untuk formalisasi formalize(text, options = {}) { let result = text; // Lowercase untuk matching, tapi preserve kapitalisasi penting let words = result.split(/\s+/); // 1. Ganti kata-kata informal words = words.map(word => { const lowerWord = word.toLowerCase(); const punctuation = word.match(/[.,!?;:]+$/)?.[0] || ''; const cleanWord = lowerWord.replace(/[.,!?;:]+$/, ''); if (this.informalToFormal[cleanWord]) { return this.informalToFormal[cleanWord] + punctuation; } return word; }); result = words.join(' '); // 2. Deteksi dan ganti salam pembuka const firstWords = result.substring(0, 20).toLowerCase(); for (const [informal, formal] of Object.entries(this.formalGreetings)) { if (firstWords.includes(informal)) { result = result.replace(new RegExp(`^.*?${informal}\\s*,?\\s*`, 'i'), `${formal}, `); break; } } // 3. Apply patterns untuk perbaikan format for (const { pattern, replacement } of this.patterns) { result = result.replace(pattern, replacement); } // 4. Perbaikan kapitalisasi if (options.autoCapitalize !== false) { result = this.autoCapitalize(result); } // 5. Perbaikan tanda baca if (options.autoPunctuation !== false) { result = this.autoPunctuation(result); } // 6. Deteksi dan ganti salam penutup const lastWords = result.substring(result.length - 30).toLowerCase(); for (const [informal, formal] of Object.entries(this.formalClosings)) { if (lastWords.includes(informal)) { result = result.replace(new RegExp(`${informal}\\s*$`, 'i'), formal); break; } } return result.trim(); } // Auto kapitalisasi autoCapitalize(text) { // Kapitalisasi awal kalimat text = text.replace(/^./, match => match.toUpperCase()); // Kapitalisasi setelah titik, tanda tanya, tanda seru text = text.replace(/([.!?]\s+)(.)/g, (match, p1, p2) => p1 + p2.toUpperCase()); // Kapitalisasi kata "Anda", "Bapak", "Ibu" text = text.replace(/\b(anda|bapak|ibu|saudara|tuan|nyonya)\b/gi, match => { return match.charAt(0).toUpperCase() + match.slice(1).toLowerCase(); }); // Kapitalisasi nama hari, bulan const days = ['senin', 'selasa', 'rabu', 'kamis', 'jumat', 'sabtu', 'minggu']; const months = ['januari', 'februari', 'maret', 'april', 'mei', 'juni', 'juli', 'agustus', 'september', 'oktober', 'november', 'desember']; [...days, ...months].forEach(word => { const regex = new RegExp(`\\b${word}\\b`, 'gi'); text = text.replace(regex, word.charAt(0).toUpperCase() + word.slice(1)); }); return text; } // Auto punctuation autoPunctuation(text) { // Tambah titik di akhir jika tidak ada tanda baca if (!/[.!?]$/.test(text.trim())) { text = text.trim() + '.'; } // Fix multiple punctuation text = text.replace(/([.!?])\1+/g, '$1'); // Fix spacing around punctuation text = text.replace(/\s+([.,!?;:])/g, '$1'); text = text.replace(/([.,!?;:])(?!\s|$)/g, '$1 '); // Fix comma dalam list text = text.replace(/(\w)\s+(dan|atau|serta)\s+(\w)/g, '$1, $2 $3'); return text; } // Analisis tingkat formalitas analyzeFormalityLevel(text) { const words = text.toLowerCase().split(/\s+/); let informalCount = 0; let totalWords = words.length; words.forEach(word => { const cleanWord = word.replace(/[.,!?;:]+$/, ''); if (this.informalToFormal[cleanWord]) { informalCount++; } }); const informalityScore = (informalCount / totalWords) * 100; return { score: Math.round(100 - informalityScore), level: informalityScore > 30 ? 'Sangat Informal' : informalityScore > 20 ? 'Informal' : informalityScore > 10 ? 'Semi Formal' : informalityScore > 5 ? 'Formal' : 'Sangat Formal', informalWords: informalCount, totalWords: totalWords }; } // Suggest improvements suggestImprovements(text) { const suggestions = []; const words = text.split(/\s+/); words.forEach((word, index) => { const cleanWord = word.toLowerCase().replace(/[.,!?;:]+$/, ''); if (this.informalToFormal[cleanWord]) { suggestions.push({ type: 'word', position: index, original: word, suggestion: this.informalToFormal[cleanWord], reason: 'Kata informal' }); } }); // Check capitalization if (!/^[A-Z]/.test(text)) { suggestions.push({ type: 'capitalization', position: 0, original: text[0], suggestion: text[0].toUpperCase(), reason: 'Awal kalimat harus kapital' }); } // Check ending punctuation if (!/[.!?]$/.test(text.trim())) { suggestions.push({ type: 'punctuation', position: text.length, original: '', suggestion: '.', reason: 'Kalimat harus diakhiri tanda baca' }); } return suggestions; } // Batch processing formalizeBatch(texts, options = {}) { return texts.map(text => ({ original: text, formalized: this.formalize(text, options), analysis: this.analyzeFormalityLevel(text) })); } // Add custom mappings addMapping(informal, formal) { this.informalToFormal[informal.toLowerCase()] = formal; } // Add multiple mappings addMappings(mappings) { Object.entries(mappings).forEach(([informal, formal]) => { this.addMapping(informal, formal); }); } // Export current mappings exportMappings() { return { ...this.informalToFormal }; } // Get statistics getStatistics() { return { totalMappings: Object.keys(this.informalToFormal).length, categories: { pronouns: Object.keys(this.informalToFormal).filter(k => ['gue', 'lo', 'aku', 'kamu'].some(p => k.includes(p))).length, verbs: Object.keys(this.informalToFormal).filter(k => k.startsWith('ng') || k.startsWith('ny')).length, adjectives: Object.keys(this.informalToFormal).filter(k => ['banget', 'gede', 'dikit'].some(a => k.includes(a))).length, slang: Object.keys(this.informalToFormal).filter(k => ['btw', 'fyi', 'asap', 'otw'].includes(k)).length } }; } } export default IndonesianFormalizer;