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
JavaScript
// 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;