UNPKG

id-auto-formalizer

Version:

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

744 lines (650 loc) 25.5 kB
<!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>🇮🇩 Bahasa Indonesia Auto Formalizer - Demo</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary: #DC143C; --secondary: #FFFFFF; --dark: #1a1a1a; --light: #f5f5f5; --success: #4CAF50; --warning: #ff9800; --danger: #f44336; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; color: var(--dark); } .container { background: white; border-radius: 20px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); max-width: 900px; width: 100%; overflow: hidden; animation: slideIn 0.5s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } .header { background: linear-gradient(135deg, var(--primary) 0%, #8B0000 100%); color: white; padding: 30px; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 10px; display: flex; align-items: center; justify-content: center; gap: 15px; } .header p { font-size: 1.1em; opacity: 0.9; } .content { padding: 40px; } .input-section, .output-section { margin-bottom: 30px; } .section-title { font-size: 1.3em; margin-bottom: 15px; color: var(--primary); display: flex; align-items: center; gap: 10px; } textarea { width: 100%; padding: 15px; border: 2px solid #e0e0e0; border-radius: 10px; font-size: 16px; font-family: inherit; resize: vertical; min-height: 120px; transition: border-color 0.3s; } textarea:focus { outline: none; border-color: var(--primary); } .controls { display: flex; gap: 15px; margin: 20px 0; flex-wrap: wrap; } button { background: var(--primary); color: white; border: none; padding: 12px 25px; border-radius: 25px; font-size: 16px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; gap: 8px; } button:hover { background: #8B0000; transform: translateY(-2px); box-shadow: 0 5px 15px rgba(220, 20, 60, 0.3); } button:active { transform: translateY(0); } button.secondary { background: #6c757d; } button.secondary:hover { background: #5a6268; } .checkbox-group { display: flex; gap: 20px; margin: 15px 0; } .checkbox-wrapper { display: flex; align-items: center; gap: 8px; } input[type="checkbox"] { width: 18px; height: 18px; cursor: pointer; } .result-box { background: var(--light); padding: 20px; border-radius: 10px; margin-top: 15px; position: relative; min-height: 60px; } .copy-btn { position: absolute; top: 10px; right: 10px; background: white; border: 1px solid #ddd; padding: 5px 10px; border-radius: 5px; cursor: pointer; font-size: 14px; transition: all 0.3s; } .copy-btn:hover { background: var(--light); } .copy-btn.copied { background: var(--success); color: white; } .analysis { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px; } .analysis-card { background: white; border: 2px solid #e0e0e0; border-radius: 10px; padding: 15px; text-align: center; transition: all 0.3s; } .analysis-card:hover { border-color: var(--primary); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); } .analysis-card .value { font-size: 2em; font-weight: bold; color: var(--primary); } .analysis-card .label { color: #666; margin-top: 5px; } .suggestions { margin-top: 20px; } .suggestion-item { background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; padding: 12px; margin-bottom: 10px; display: flex; align-items: center; gap: 10px; } .suggestion-item .icon { font-size: 1.5em; } .loading { display: none; text-align: center; padding: 20px; } .loading.active { display: block; } .spinner { border: 3px solid var(--light); border-top: 3px solid var(--primary); border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .examples { margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 10px; } .example-btn { background: white; color: var(--dark); border: 1px solid #dee2e6; padding: 8px 15px; margin: 5px; font-size: 14px; } .example-btn:hover { background: var(--light); border-color: var(--primary); color: var(--primary); } .stats { position: fixed; bottom: 20px; right: 20px; background: white; padding: 15px; border-radius: 10px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); font-size: 14px; } @media (max-width: 768px) { .header h1 { font-size: 2em; } .controls { flex-direction: column; } button { width: 100%; justify-content: center; } .stats { display: none; } } /* Dark mode support */ @media (prefers-color-scheme: dark) { body { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); } } </style> </head> <body> <div class="container"> <div class="header"> <h1> <span>🇮🇩</span> Bahasa Indonesia Auto Formalizer </h1> <p>Konversi teks kasual menjadi formal dengan mudah!</p> </div> <div class="content"> <div class="input-section"> <h2 class="section-title"> <span>✏️</span> Teks Informal </h2> <textarea id="inputText" placeholder="Ketik atau paste teks informal di sini... Contoh: gue mau nanya dong, lo udah selesai belom?"></textarea> <div class="checkbox-group"> <div class="checkbox-wrapper"> <input type="checkbox" id="autoCapitalize" checked> <label for="autoCapitalize">Auto Kapitalisasi</label> </div> <div class="checkbox-wrapper"> <input type="checkbox" id="autoPunctuation" checked> <label for="autoPunctuation">Auto Punctuation</label> </div> <div class="checkbox-wrapper"> <input type="checkbox" id="showAnalysis" checked> <label for="showAnalysis">Tampilkan Analisis</label> </div> <div class="checkbox-wrapper"> <input type="checkbox" id="showSuggestions"> <label for="showSuggestions">Tampilkan Saran</label> </div> </div> <div class="controls"> <button onclick="formalizeText()"> <span></span> Formalisasi </button> <button class="secondary" onclick="clearAll()"> <span>🗑️</span> Clear </button> <button class="secondary" onclick="swapText()"> <span>🔄</span> Swap </button> </div> </div> <div class="output-section"> <h2 class="section-title"> <span>📝</span> Teks Formal </h2> <div class="result-box" id="resultBox"> <div id="loading" class="loading"> <div class="spinner"></div> <p>Memproses...</p> </div> <div id="resultText">Hasil formalisasi akan muncul di sini...</div> <button class="copy-btn" onclick="copyResult()" style="display: none;"> <span id="copyIcon">📋</span> <span id="copyText">Copy</span> </button> </div> <div id="analysisSection" style="display: none;"> <h3 class="section-title"> <span>📊</span> Analisis Formalitas </h3> <div class="analysis" id="analysisResult"></div> </div> <div id="suggestionsSection" style="display: none;"> <h3 class="section-title"> <span>💡</span> Saran Perbaikan </h3> <div class="suggestions" id="suggestionsResult"></div> </div> </div> <div class="examples"> <h3 class="section-title"> <span>📌</span> Contoh Cepat </h3> <div> <button class="example-btn" onclick="loadExample('gue mau nanya dong, lo udah selesai ngerjain tugasnya belom?')"> Pertanyaan Kasual </button> <button class="example-btn" onclick="loadExample('makasih bgt ya udh bantuin gue kemaren, lo emang temen yg baik')"> Ucapan Terima Kasih </button> <button class="example-btn" onclick="loadExample('sorry ya gue telat, jalanan macet parah td pagi')"> Permintaan Maaf </button> <button class="example-btn" onclick="loadExample('btw meeting besok jadi ga? gue blm dapet konfirmasi dari bos')"> Email Kantor </button> <button class="example-btn" onclick="loadExample('hai bro, gmn kabarnya? lama ga ketemu ya, kapan kita ngobrol lagi?')"> Sapaan Teman </button> </div> </div> </div> </div> <div class="stats" id="stats"> <strong>Statistik Mapping</strong><br> <span id="totalMappings">Loading...</span> </div> <script> // Simulasi IndonesianFormalizer untuk demo // Dalam implementasi nyata, ini akan menggunakan package yang sebenarnya const informalToFormal = { 'gue': 'saya', 'gw': 'saya', 'aku': 'saya', 'lo': 'Anda', 'lu': 'Anda', 'elo': 'Anda', 'kamu': 'Anda', 'ngomong': 'berbicara', 'ngobrol': 'berbincang', 'nanya': 'bertanya', 'nyari': 'mencari', 'ngerti': 'mengerti', 'ngerjain': 'mengerjakan', 'makasih': 'terima kasih', 'thanks': 'terima kasih', 'thx': 'terima kasih', 'bgt': 'sekali', 'banget': 'sekali', 'udah': 'sudah', 'udh': 'sudah', 'belom': 'belum', 'belum': 'belum', 'blm': 'belum', 'gak': 'tidak', 'ga': 'tidak', 'nggak': 'tidak', 'emang': 'memang', 'temen': 'teman', 'yg': 'yang', 'kemaren': 'kemarin', 'sorry': 'maaf', 'td': 'tadi', 'btw': 'omong-omong', 'bos': 'atasan', 'hai': 'halo', 'bro': 'saudara', 'gmn': 'bagaimana', 'kapan': 'kapan' }; function simulateFormalizer(text, options) { let result = text; // Replace informal words const words = result.split(/\s+/); const formalizedWords = words.map(word => { const lowerWord = word.toLowerCase(); const punctuation = word.match(/[.,!?;:]+$/)?.[0] || ''; const cleanWord = lowerWord.replace(/[.,!?;:]+$/, ''); if (informalToFormal[cleanWord]) { return informalToFormal[cleanWord] + punctuation; } return word; }); result = formalizedWords.join(' '); // Auto capitalize if (options.autoCapitalize) { result = result.charAt(0).toUpperCase() + result.slice(1); result = result.replace(/([.!?]\s+)(.)/g, (match, p1, p2) => p1 + p2.toUpperCase()); } // Auto punctuation if (options.autoPunctuation) { if (!/[.!?]$/.test(result.trim())) { result = result.trim() + '.'; } } return result; } function analyzeFormality(text) { const words = text.toLowerCase().split(/\s+/); let informalCount = 0; words.forEach(word => { const cleanWord = word.replace(/[.,!?;:]+$/, ''); if (informalToFormal[cleanWord]) { informalCount++; } }); const score = Math.round((1 - informalCount / words.length) * 100); const level = score > 90 ? 'Sangat Formal' : score > 70 ? 'Formal' : score > 50 ? 'Semi Formal' : score > 30 ? 'Informal' : 'Sangat Informal'; return { score, level, informalWords: informalCount, totalWords: words.length }; } function getSuggestions(text) { const suggestions = []; const words = text.split(/\s+/); words.forEach((word, index) => { const cleanWord = word.toLowerCase().replace(/[.,!?;:]+$/, ''); if (informalToFormal[cleanWord]) { suggestions.push({ type: 'word', original: word, suggestion: informalToFormal[cleanWord], reason: 'Kata informal' }); } }); if (!/^[A-Z]/.test(text)) { suggestions.push({ type: 'capitalization', original: text[0], suggestion: text[0].toUpperCase(), reason: 'Awal kalimat harus kapital' }); } if (!/[.!?]$/.test(text.trim())) { suggestions.push({ type: 'punctuation', original: '', suggestion: '.', reason: 'Kalimat harus diakhiri tanda baca' }); } return suggestions; } function formalizeText() { const inputText = document.getElementById('inputText').value; if (!inputText.trim()) { alert('Silakan masukkan teks terlebih dahulu!'); return; } const options = { autoCapitalize: document.getElementById('autoCapitalize').checked, autoPunctuation: document.getElementById('autoPunctuation').checked }; // Show loading document.getElementById('loading').classList.add('active'); document.getElementById('resultText').style.display = 'none'; // Simulate processing delay setTimeout(() => { const formalized = simulateFormalizer(inputText, options); // Show result document.getElementById('loading').classList.remove('active'); document.getElementById('resultText').style.display = 'block'; document.getElementById('resultText').textContent = formalized; document.querySelector('.copy-btn').style.display = 'block'; // Show analysis if checked if (document.getElementById('showAnalysis').checked) { const analysis = analyzeFormality(inputText); showAnalysis(analysis); } // Show suggestions if checked if (document.getElementById('showSuggestions').checked) { const suggestions = getSuggestions(inputText); showSuggestions(suggestions); } }, 500); } function showAnalysis(analysis) { const section = document.getElementById('analysisSection'); const result = document.getElementById('analysisResult'); result.innerHTML = ` <div class="analysis-card"> <div class="value">${analysis.score}%</div> <div class="label">Skor Formalitas</div> </div> <div class="analysis-card"> <div class="value">${analysis.level}</div> <div class="label">Level</div> </div> <div class="analysis-card"> <div class="value">${analysis.informalWords}</div> <div class="label">Kata Informal</div> </div> <div class="analysis-card"> <div class="value">${analysis.totalWords}</div> <div class="label">Total Kata</div> </div> `; section.style.display = 'block'; } function showSuggestions(suggestions) { const section = document.getElementById('suggestionsSection'); const result = document.getElementById('suggestionsResult'); if (suggestions.length === 0) { result.innerHTML = '<p>Tidak ada saran perbaikan. Teks sudah cukup formal!</p>'; } else { result.innerHTML = suggestions.map(s => ` <div class="suggestion-item"> <span class="icon">${s.type === 'word' ? '📝' : s.type === 'capitalization' ? '🔤' : '❗'}</span> <div> <strong>${s.reason}:</strong> ${s.original ? `"${s.original}"` : 'Tidak ada'} → "${s.suggestion}" </div> </div> `).join(''); } section.style.display = 'block'; } function copyResult() { const resultText = document.getElementById('resultText').textContent; navigator.clipboard.writeText(resultText).then(() => { const copyBtn = document.querySelector('.copy-btn'); const copyText = document.getElementById('copyText'); const copyIcon = document.getElementById('copyIcon'); copyBtn.classList.add('copied'); copyText.textContent = 'Copied!'; copyIcon.textContent = '✅'; setTimeout(() => { copyBtn.classList.remove('copied'); copyText.textContent = 'Copy'; copyIcon.textContent = '📋'; }, 2000); }); } function clearAll() { document.getElementById('inputText').value = ''; document.getElementById('resultText').textContent = 'Hasil formalisasi akan muncul di sini...'; document.querySelector('.copy-btn').style.display = 'none'; document.getElementById('analysisSection').style.display = 'none'; document.getElementById('suggestionsSection').style.display = 'none'; } function swapText() { const inputText = document.getElementById('inputText').value; const resultText = document.getElementById('resultText').textContent; if (resultText !== 'Hasil formalisasi akan muncul di sini...') { document.getElementById('inputText').value = resultText; document.getElementById('resultText').textContent = inputText || 'Hasil formalisasi akan muncul di sini...'; } } function loadExample(text) { document.getElementById('inputText').value = text; formalizeText(); } // Load stats document.addEventListener('DOMContentLoaded', () => { const totalMappings = Object.keys(informalToFormal).length; document.getElementById('totalMappings').textContent = `${totalMappings} kata mapping`; }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.ctrlKey || e.metaKey) { if (e.key === 'Enter') { e.preventDefault(); formalizeText(); } else if (e.key === 'l') { e.preventDefault(); clearAll(); } } }); </script> </body> </html>