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
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>