@indodev/toolkit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/text/constants.ts","../../src/text/capitalization.ts","../../src/text/slug.ts","../../src/text/sanitize.ts","../../src/text/abbreviation.ts","../../src/text/extract.ts","../../src/text/compare.ts"],"names":["escapeRegex"],"mappings":";AA8QO,IAAM,eAAA,GAAkB;AAAA;AAAA,EAE7B,IAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA;AAAA,EAGA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA;AAAA,EAGA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA;AAAA,EAGA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA;AAAA,EAGA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA;AAAA,EAGA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYF;AAMO,IAAM,QAAA,GAAW;AAAA;AAAA,EAEtB,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,IAAA;AAAA;AAAA,EACA,IAAA;AAAA;AAAA,EACA,IAAA;AAAA;AAAA,EACA,IAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA;AAAA,EAGA,IAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;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","file":"index.js","sourcesContent":["/**\n * ============================================================================\n * INDONESIAN TEXT UTILITIES - CONSTANTS\n * ============================================================================\n *\n * This file contains constants for Indonesian and English text processing:\n * - LOWERCASE_WORDS: Particles that stay lowercase in title case\n * - ACRONYMS: Abbreviations that stay UPPERCASE in title case\n * - ABBREVIATIONS: Full expansions of common Indonesian abbreviations\n *\n * ============================================================================\n * MAINTENANCE GUIDE\n * ============================================================================\n *\n * ## How to Add New Entries\n *\n * ### 1. LOWERCASE_WORDS (Particles)\n *\n * Add words that should remain lowercase in title case (except when first word).\n *\n * **Indonesian Grammar Rules (PUEBI):**\n * - Prepositions: di, ke, dari, untuk, dengan, pada, dalam, etc.\n * - Conjunctions: dan, atau, tetapi, serta, maupun, etc.\n * - Articles/particles: yang, sebagai, adalah, akan, telah, etc.\n *\n * **English Grammar Rules (Chicago Manual of Style):**\n * - Articles: a, an, the\n * - Conjunctions: and, or, but, nor, for, yet, so\n * - Short prepositions (<5 letters): at, by, in, of, on, to, up, etc.\n *\n * **Example Addition:**\n * ```typescript\n * export const LOWERCASE_WORDS = [\n * // ... existing entries\n * 'bagi', // Indonesian: for/to (preposition)\n * 'antara', // Indonesian: between (preposition)\n * 'into', // English: preposition\n * ] as const;\n * ```\n *\n * **Testing:** Add test case in `toTitleCase.test.ts`:\n * ```typescript\n * it('keeps \"bagi\" lowercase in middle', () => {\n * expect(toTitleCase('buku bagi pemula')).toBe('Buku bagi Pemula');\n * });\n * ```\n *\n * ### 2. ACRONYMS (Always Uppercase)\n *\n * Add abbreviations that should always appear in UPPERCASE.\n *\n * **Categories:**\n * - Government & Military: TNI, POLRI, KPK, DPR, etc.\n * - Business Entities: PT, CV, BUMN, etc.\n * - Banks: BCA, BRI, BNI, etc.\n * - Services: BPJS, PLN, KTP, SIM, etc.\n * - Technology: IT, AI, API, SEO, etc.\n * - Education: UI, ITB, UGM, etc.\n * - International: UN, WHO, NATO, ASEAN, etc.\n *\n * **Validation Checklist:**\n * ✅ Is it commonly written in ALL CAPS?\n * ✅ Is it an official acronym (not just shortened word)?\n * ✅ Will it look wrong if title-cased (e.g., \"Pt\" instead of \"PT\")?\n *\n * **Example Addition:**\n * ```typescript\n * export const ACRONYMS = [\n * // ... existing entries\n * 'OJK', // Otoritas Jasa Keuangan\n * 'BI', // Bank Indonesia\n * 'NASA', // National Aeronautics and Space Administration\n * ] as const;\n * ```\n *\n * **Testing:** Add test case in `toTitleCase.test.ts`:\n * ```typescript\n * it('preserves OJK uppercase', () => {\n * expect(toTitleCase('ojk indonesia')).toBe('OJK Indonesia');\n * });\n * ```\n *\n * ### 3. ABBREVIATIONS (Expansion Mapping)\n *\n * Add abbreviation → full form mappings for `expandAbbreviation()` function.\n *\n * **Categories (use comment headers):**\n * - Address: Jl., Gg., Kec., Kab., etc.\n * - Academic Titles: Dr., Ir., Prof., S.H., M.M., etc.\n * - Honorifics: Bpk., Yth., H., Hj., etc.\n * - Organizations: PT., CV., UD., etc.\n * - Common: dst., dll., a.n., etc.\n * - Contact Info: Tlp., HP., Fax, etc.\n * - Days/Months: Sen., Jan., Feb., etc.\n * - Units: kg., km., lt., etc.\n *\n * **Key Format Rules:**\n * - Include period if commonly written: `'Jl.'` not `'Jl'`\n * - Use proper capitalization: `'Jalan'` not `'jalan'`\n * - Keep it concise: Full form only, no explanations\n *\n * **Example Addition:**\n * ```typescript\n * export const ABBREVIATIONS: Record<string, string> = {\n * // ... existing entries\n *\n * // ========== New Category Example ==========\n * 'Apt.': 'Apartemen',\n * 'Ruko': 'Rumah Toko',\n * 'Rukan': 'Rumah Kantor',\n * };\n * ```\n *\n * **Testing:** Add test case in `abbreviation.test.ts`:\n * ```typescript\n * it('expands Apt. to Apartemen', () => {\n * expect(expandAbbreviation('Apt. Sudirman'))\n * .toBe('Apartemen Sudirman');\n * });\n * ```\n *\n * ============================================================================\n * DATA SOURCES & REFERENCES\n * ============================================================================\n *\n * When adding new entries, refer to these authoritative sources:\n *\n * **Indonesian Language:**\n * - PUEBI (Pedoman Umum Ejaan Bahasa Indonesia)\n * https://puebi.js.org/\n *\n * - KBBI (Kamus Besar Bahasa Indonesia)\n * https://kbbi.kemdikbud.go.id/\n *\n * - Wikipedia Indonesia - Daftar Singkatan\n * https://id.wikipedia.org/wiki/Daftar_singkatan_di_Indonesia\n *\n * **English Language:**\n * - Chicago Manual of Style (Title Case Rules)\n * https://www.chicagomanualofstyle.org/\n *\n * - AP Stylebook\n * https://www.apstylebook.com/\n *\n * **Government & Official:**\n * - Kemendagri (addresses, administrative divisions)\n * https://www.kemendagri.go.id/\n *\n * - Kemenkumham (business entities)\n * https://www.kemenkumham.go.id/\n *\n * - Kemendikbud (education, degrees)\n * https://www.kemdikbud.go.id/\n *\n * ============================================================================\n * CONTRIBUTION GUIDELINES\n * ============================================================================\n *\n * **Before Adding:**\n * 1. ✅ Check if entry already exists (Ctrl+F)\n * 2. ✅ Verify spelling from official sources\n * 3. ✅ Ensure it's commonly used (not obscure)\n * 4. ✅ Choose correct category/section\n *\n * **After Adding:**\n * 1. ✅ Add corresponding test case\n * 2. ✅ Run tests: `npm test constants`\n * 3. ✅ Update this file's documentation if needed\n * 4. ✅ Add source reference in PR description\n *\n * **PR Template:**\n * ```\n * ### Added Constants\n *\n * **Type:** [LOWERCASE_WORDS | ACRONYMS | ABBREVIATIONS]\n *\n * **Entries:**\n * - `OJK` - Otoritas Jasa Keuangan\n * - `BI` - Bank Indonesia\n *\n * **Source:** https://www.ojk.go.id/\n *\n * **Test Coverage:** ✅ Added in toTitleCase.test.ts line 245\n *\n * **Rationale:**\n * Commonly used financial regulatory bodies in Indonesian context.\n * ```\n *\n * ============================================================================\n * COMMON PITFALLS TO AVOID\n * ============================================================================\n *\n * ❌ **Don't add brand-specific styling** (e.g., \"iPhone\" → keep user control)\n * ❌ **Don't add regional dialects** (stick to standard Indonesian/English)\n * ❌ **Don't add context-dependent acronyms** (e.g., \"UI\" = both User Interface & Universitas Indonesia)\n * ❌ **Don't add very rare/obscure terms** (focus on common usage)\n * ❌ **Don't forget the period** in ABBREVIATIONS (e.g., use `'Dr.'` not `'Dr'`)\n * ❌ **Don't mix singular/plural** in ABBREVIATIONS (choose one consistently)\n *\n * ✅ **Do keep entries alphabetically sorted** within categories\n * ✅ **Do use proper capitalization** in expanded forms\n * ✅ **Do add comments** for non-obvious entries\n * ✅ **Do verify against official sources** before adding\n * ✅ **Do write test cases** for new additions\n *\n * ============================================================================\n * FUTURE EXTENSIBILITY\n * ============================================================================\n *\n * **Planned Enhancements:**\n *\n * 1. **External Data Source Support:**\n * ```typescript\n * import customAcronyms from './data/custom-acronyms.json';\n * export const ACRONYMS = [...DEFAULT_ACRONYMS, ...customAcronyms];\n * ```\n *\n * 2. **Context-Aware Acronyms:**\n * ```typescript\n * export const CONTEXT_ACRONYMS = {\n * 'UI': {\n * tech: 'UI', // User Interface\n * education: 'UI', // Universitas Indonesia\n * }\n * };\n * ```\n *\n * 3. **Locale-Specific Sets:**\n * ```typescript\n * export const LOWERCASE_WORDS = {\n * id: [...], // Indonesian\n * en: [...], // English\n * mixed: [...], // Combined (default)\n * };\n * ```\n *\n * 4. **Dynamic Loading:**\n * ```typescript\n * // Load additional acronyms from user config\n * export async function loadCustomConstants(url: string) {\n * const data = await fetch(url).then(r => r.json());\n * return [...ACRONYMS, ...data.acronyms];\n * }\n * ```\n *\n * ============================================================================\n * VERSIONING & CHANGELOG\n * ============================================================================\n *\n * Track major additions here:\n *\n * - v0.2.0 (2024-12-18): Initial comprehensive dataset\n * - 50+ Indonesian particles\n * - 150+ acronyms (Indonesian + International)\n * - 80+ abbreviation mappings\n *\n * - v0.2.1 (TBD): Add financial sector acronyms (OJK, BI, etc.)\n * - v0.2.2 (TBD): Add technology company acronyms\n *\n * ============================================================================\n */\n\n/**\n * Indonesian and English lowercase particles\n * These words remain lowercase in title case (except when first word)\n *\n * Based on:\n * - Indonesian grammar (PUEBI)\n * - English title case rules (Chicago Manual of Style)\n */\nexport const LOWERCASE_WORDS = [\n // Indonesian prepositions (kata depan)\n 'di',\n 'ke',\n 'dari',\n 'pada',\n 'dalam',\n 'untuk',\n 'dengan',\n 'oleh',\n 'kepada',\n 'terhadap',\n 'tentang',\n 'tanpa',\n 'hingga',\n 'sampai',\n 'sejak',\n 'menuju',\n 'melalui',\n\n // Indonesian conjunctions (kata hubung)\n 'dan',\n 'atau',\n 'tetapi',\n 'namun',\n 'serta',\n 'maupun',\n 'melainkan',\n 'sedangkan',\n\n // Indonesian articles/particles\n 'yang',\n 'sebagai',\n 'adalah',\n 'ialah',\n 'yaitu',\n 'bahwa',\n 'akan',\n 'telah',\n 'sudah',\n 'belum',\n\n // English articles\n 'a',\n 'an',\n 'the',\n\n // English conjunctions\n 'and',\n 'or',\n 'but',\n 'nor',\n 'for',\n 'yet',\n 'so',\n 'as',\n\n // English prepositions (short ones, < 5 letters)\n 'at',\n 'by',\n 'in',\n 'of',\n 'on',\n 'to',\n 'up',\n 'via',\n 'per',\n 'off',\n 'out',\n\n // English prepositions (5+ letters - optional, some style guides capitalize these)\n // 'about',\n // 'above',\n // 'across',\n // 'after',\n // 'among',\n // 'below',\n // 'under',\n // 'until',\n // 'with',\n] as const;\n\n/**\n * Indonesian and international acronyms\n * These always remain UPPERCASE in title case\n */\nexport const ACRONYMS = [\n // Indonesian government & military\n 'DKI', // Daerah Khusus Ibukota\n 'DIY', // Daerah Istimewa Yogyakarta\n 'TNI', // Tentara Nasional Indonesia\n 'POLRI', // Kepolisian Republik Indonesia\n 'ABRI', // Angkatan Bersenjata Republik Indonesia\n 'MPR', // Majelis Permusyawaratan Rakyat\n 'DPR', // Dewan Perwakilan Rakyat\n 'KPK', // Komisi Pemberantasan Korupsi\n 'BIN', // Badan Intelijen Negara\n\n // Indonesian business entities\n 'PT', // Perseroan Terbatas\n 'CV', // Commanditaire Vennootschap\n 'UD', // Usaha Dagang\n 'PD', // Perusahaan Daerah\n 'Tbk', // Terbuka (publicly traded)\n 'BUMN', // Badan Usaha Milik Negara\n 'BUMD', // Badan Usaha Milik Daerah\n\n // Indonesian banks\n 'BCA', // Bank Central Asia\n 'BRI', // Bank Rakyat Indonesia\n 'BNI', // Bank Negara Indonesia\n 'BTN', // Bank Tabungan Negara\n 'BSI', // Bank Syariah Indonesia\n 'BPD', // Bank Pembangunan Daerah\n\n // Indonesian government services\n 'KTP', // Kartu Tanda Penduduk\n 'NIK', // Nomor Induk Kependudukan\n 'NPWP', // Nomor Pokok Wajib Pajak\n 'SIM', // Surat Izin Mengemudi\n 'STNK', // Surat Tanda Nomor Kendaraan\n 'BPJS', // Badan Penyelenggara Jaminan Sosial\n 'KIS', // Kartu Indonesia Sehat\n 'KIP', // Kartu Indonesia Pintar\n 'PKH', // Program Keluarga Harapan\n\n // Indonesian utilities & infrastructure\n 'PLN', // Perusahaan Listrik Negara\n 'PDAM', // Perusahaan Daerah Air Minum\n 'PGN', // Perusahaan Gas Negara\n 'KAI', // Kereta Api Indonesia\n 'MRT', // Mass Rapid Transit\n 'LRT', // Light Rail Transit\n\n // Indonesian taxes & fees\n 'PBB', // Pajak Bumi dan Bangunan\n 'PPh', // Pajak Penghasilan\n 'PPN', // Pajak Pertambahan Nilai\n 'BPHTB', // Bea Perolehan Hak atas Tanah dan Bangunan\n\n // Indonesian education\n 'UI', // Universitas Indonesia\n 'ITB', // Institut Teknologi Bandung\n 'UGM', // Universitas Gadjah Mada\n 'IPB', // Institut Pertanian Bogor\n 'ITS', // Institut Teknologi Sepuluh Nopember\n 'UNPAD', // Universitas Padjadjaran\n 'UNDIP', // Universitas Diponegoro\n 'UNAIR', // Universitas Airlangga\n 'UNS', // Universitas Sebelas Maret\n\n // Indonesian degrees (gelar)\n 'S.Pd', // Sarjana Pendidikan\n 'S.H', // Sarjana Hukum\n 'S.E', // Sarjana Ekonomi\n 'S.T', // Sarjana Teknik\n 'S.Kom', // Sarjana Komputer\n 'S.Si', // Sarjana Sains\n 'S.Sos', // Sarjana Sosial\n 'M.Pd', // Magister Pendidikan\n 'M.M', // Magister Manajemen\n 'M.T', // Magister Teknik\n 'M.Kom', // Magister Komputer\n\n // Common services\n 'ATM', // Automated Teller Machine\n 'POS', // Point of Sale\n 'SMS', // Short Message Service\n 'GPS', // Global Positioning System\n 'WiFi', // Wireless Fidelity (technically Wi-Fi)\n 'USB', // Universal Serial Bus\n 'PIN', // Personal Identification Number\n 'OTP', // One Time Password\n 'QR', // Quick Response\n\n // Technology & IT\n 'IT', // Information Technology\n 'AI', // Artificial Intelligence\n 'ML', // Machine Learning\n 'API', // Application Programming Interface\n 'UI', // User Interface (duplicate with Universitas Indonesia, context matters)\n 'UX', // User Experience\n 'SEO', // Search Engine Optimization\n 'SaaS', // Software as a Service\n 'CRM', // Customer Relationship Management\n 'ERP', // Enterprise Resource Planning\n\n // Business titles\n 'CEO', // Chief Executive Officer\n 'CFO', // Chief Financial Officer\n 'CTO', // Chief Technology Officer\n 'COO', // Chief Operating Officer\n 'CMO', // Chief Marketing Officer\n 'HR', // Human Resources\n 'PR', // Public Relations\n 'VP', // Vice President\n 'GM', // General Manager\n\n // International organizations\n 'UN', // United Nations\n 'WHO', // World Health Organization\n 'UNESCO', // United Nations Educational, Scientific and Cultural Organization\n 'NATO', // North Atlantic Treaty Organization\n 'ASEAN', // Association of Southeast Asian Nations\n 'APEC', // Asia-Pacific Economic Cooperation\n 'WTO', // World Trade Organization\n 'IMF', // International Monetary Fund\n\n // Medical\n 'ICU', // Intensive Care Unit\n 'ER', // Emergency Room\n 'MRI', // Magnetic Resonance Imaging\n 'CT', // Computed Tomography\n 'DNA', // Deoxyribonucleic Acid\n 'RNA', // Ribonucleic Acid\n 'HIV', // Human Immunodeficiency Virus\n 'AIDS', // Acquired Immunodeficiency Syndrome\n 'COVID', // Coronavirus Disease\n\n // Measurements & units\n 'KM', // Kilometer\n 'CM', // Centimeter\n 'MM', // Millimeter\n 'KG', // Kilogram\n 'RPM', // Revolutions Per Minute\n 'MPH', // Miles Per Hour\n 'KPH', // Kilometers Per Hour\n\n // Finance\n 'IPO', // Initial Public Offering\n 'ATM', // Automated Teller Machine (duplicate)\n 'ROI', // Return on Investment\n 'GDP', // Gross Domestic Product\n 'VAT', // Value Added Tax\n] as const;\n\n/**\n * Indonesian abbreviations mapping\n * Organized by category for maintainability\n */\nexport const ABBREVIATIONS: Record<string, string> = {\n // ========== Address Abbreviations ==========\n 'Jl.': 'Jalan',\n 'Gg.': 'Gang',\n 'No.': 'Nomor',\n 'Kp.': 'Kampung',\n 'Ds.': 'Desa',\n 'Kel.': 'Kelurahan',\n 'Kec.': 'Kecamatan',\n 'Kab.': 'Kabupaten',\n Kota: 'Kota',\n 'Prov.': 'Provinsi',\n 'Prop.': 'Provinsi',\n 'Rt.': 'Rukun Tetangga',\n 'Rw.': 'Rukun Warga',\n Blok: 'Blok',\n 'Komp.': 'Kompleks',\n Perumahan: 'Perumahan',\n 'Perum.': 'Perumahan',\n\n // ========== Academic Titles ==========\n 'Dr.': 'Doktor',\n 'Ir.': 'Insinyur',\n 'Prof.': 'Profesor',\n 'Drs.': 'Doktorandus',\n 'Dra.': 'Doktoranda',\n\n // Bachelor degrees\n 'S.Pd.': 'Sarjana Pendidikan',\n 'S.H.': 'Sarjana Hukum',\n 'S.E.': 'Sarjana Ekonomi',\n 'S.T.': 'Sarjana Teknik',\n 'S.Kom.': 'Sarjana Komputer',\n 'S.Si.': 'Sarjana Sains',\n 'S.Sos.': 'Sarjana Sosial',\n 'S.I.Kom.': 'Sarjana Ilmu Komunikasi',\n 'S.S.': 'Sarjana Sastra',\n 'S.Psi.': 'Sarjana Psikologi',\n 'S.Farm.': 'Sarjana Farmasi',\n 'S.Ked.': 'Sarjana Kedokteran',\n\n // Master degrees\n 'M.Sc.': 'Master of Science',\n 'M.M.': 'Magister Manajemen',\n 'M.Pd.': 'Magister Pendidikan',\n 'M.T.': 'Magister Teknik',\n 'M.Kom.': 'Magister Komputer',\n 'M.Si.': 'Magister Sains',\n 'M.H.': 'Magister Hukum',\n 'M.A.': 'Master of Arts',\n MBA: 'Master of Business Administration',\n\n // ========== Honorifics ==========\n 'Bpk.': 'Bapak',\n Ibu: 'Ibu',\n 'Sdr.': 'Saudara',\n 'Sdri.': 'Saudari',\n 'Yth.': 'Yang Terhormat',\n 'H.': 'Haji',\n 'Hj.': 'Hajjah',\n 'Tn.': 'Tuan',\n 'Ny.': 'Nyonya',\n 'Nn.': 'Nona',\n\n // ========== Organizations ==========\n 'PT.': 'Perseroan Terbatas',\n 'CV.': 'Commanditaire Vennootschap',\n 'UD.': 'Usaha Dagang',\n 'PD.': 'Perusahaan Daerah',\n 'Tbk.': 'Terbuka',\n Koperasi: 'Koperasi',\n Yayasan: 'Yayasan',\n\n // ========== Common Abbreviations ==========\n 'dst.': 'dan seterusnya',\n 'dsb.': 'dan sebagainya',\n 'dll.': 'dan lain-lain',\n 'dkk.': 'dan kawan-kawan',\n 'a.n.': 'atas nama',\n 'u.p.': 'untuk perhatian',\n 'u.b.': 'untuk beliau',\n 'c.q.': 'casu quo',\n 'hlm.': 'halaman',\n 'tgl.': 'tanggal',\n 'bln.': 'bulan',\n 'thn.': 'tahun',\n 'ttd.': 'tertanda',\n\n // ========== Contact Information ==========\n 'Tlp.': 'Telepon',\n 'Telp.': 'Telepon',\n 'HP.': 'Handphone',\n Fax: 'Faksimile',\n Email: 'Email',\n Website: 'Website',\n\n // ========== Days (Indonesian) ==========\n 'Sen.': 'Senin',\n 'Sel.': 'Selasa',\n 'Rab.': 'Rabu',\n 'Kam.': 'Kamis',\n 'Jum.': 'Jumat',\n 'Sab.': 'Sabtu',\n 'Min.': 'Minggu',\n\n // ========== Months (Indonesian) ==========\n 'Jan.': 'Januari',\n 'Feb.': 'Februari',\n 'Mar.': 'Maret',\n 'Apr.': 'April',\n Mei: 'Mei',\n 'Jun.': 'Juni',\n 'Jul.': 'Juli',\n 'Agt.': 'Agustus',\n 'Sep.': 'September',\n 'Okt.': 'Oktober',\n 'Nov.': 'November',\n 'Des.': 'Desember',\n\n // ========== Units & Measurements ==========\n 'kg.': 'kilogram',\n 'gr.': 'gram',\n 'lt.': 'liter',\n 'ml.': 'mililiter',\n 'km.': 'kilometer',\n 'cm.': 'sentimeter',\n 'mm.': 'milimeter',\n 'm2.': 'meter persegi',\n 'm3.': 'meter kubik',\n 'ha.': 'hektar',\n};\n","import { ACRONYMS, LOWERCASE_WORDS } from './constants';\nimport { TitleCaseOptions } from './types';\n\n/**\n * Capitalize the first letter of a string and lowercase the rest\n *\n * This function converts the first character to uppercase and all remaining\n * characters to lowercase. It handles empty strings, Unicode characters,\n * and multi-word strings (only first word is affected).\n *\n * @param text - The text to capitalize\n * @returns The capitalized text\n *\n * @example\n * Basic usage:\n * ```typescript\n * capitalize('joko') // → 'Joko'\n * capitalize('JOKO') // → 'Joko'\n * capitalize('jOKO') // → 'Joko'\n * ```\n *\n * @example\n * Multi-word strings (only first word capitalized):\n * ```typescript\n * capitalize('joko widodo') // → 'Joko widodo'\n * capitalize('JOKO WIDODO') // → 'Joko widodo'\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * capitalize('') // → ''\n * capitalize('a') // → 'A'\n * capitalize('123abc') // → '123abc'\n * ```\n *\n * @public\n */\nexport function capitalize(text: string): string {\n if (!text) return text;\n return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();\n}\n\n/**\n * Convert text to title case following Indonesian grammar rules\n *\n * This function capitalizes the first letter of each word while respecting\n * Indonesian language conventions:\n * - Keeps particles lowercase (di, ke, dari, untuk, dan, etc.)\n * - Preserves known acronyms in uppercase (PT, CV, TNI, DKI, etc.)\n * - Handles hyphenated words correctly (anak-anak → Anak-Anak)\n * - Normalizes whitespace automatically\n *\n * @param text - The text to convert to title case\n * @param options - Optional configuration\n * @returns The title-cased text with proper Indonesian grammar\n *\n * @example\n * Basic usage:\n * ```typescript\n * toTitleCase('joko widodo')\n * // → 'Joko Widodo'\n *\n * toTitleCase('JOKO WIDODO')\n * // → 'Joko Widodo'\n * ```\n *\n * @example\n * Indonesian particles (kept lowercase):\n * ```typescript\n * toTitleCase('buku untuk anak dan orang tua')\n * // → 'Buku untuk Anak dan Orang Tua'\n *\n * toTitleCase('dari jakarta ke bandung')\n * // → 'Dari Jakarta ke Bandung'\n * // (first word always capitalized)\n * ```\n *\n * @example\n * Acronyms (preserved in uppercase):\n * ```typescript\n * toTitleCase('pt bank bca tbk')\n * // → 'PT Bank BCA Tbk'\n *\n * toTitleCase('dki jakarta')\n * // → 'DKI Jakarta'\n *\n * toTitleCase('tni angkatan darat')\n * // → 'TNI Angkatan Darat'\n * ```\n *\n * @example\n * Hyphenated words:\n * ```typescript\n * toTitleCase('anak-anak bermain')\n * // → 'Anak-Anak Bermain'\n *\n * toTitleCase('makan-makan di rumah')\n * // → 'Makan-Makan di Rumah'\n * ```\n *\n * @example\n * With options:\n * ```typescript\n * toTitleCase('PT BCA', { preserveAcronyms: false })\n * // → 'Pt Bca'\n *\n * toTitleCase('mobil dari jepang', { exceptions: ['jepang'] })\n * // → 'Mobil dari jepang'\n *\n * toTitleCase('HELLO WORLD', { strict: true })\n * // → 'Hello World'\n * ```\n *\n * @public\n */\nexport function toTitleCase(text: string, options?: TitleCaseOptions): string {\n if (!text) return text;\n\n const {\n preserveAcronyms = true,\n strict = false,\n exceptions = [],\n } = options || {};\n\n const lowercaseSet = new Set([...LOWERCASE_WORDS, ...exceptions]);\n const acronymSet = new Set(ACRONYMS);\n\n const normalized = normalizeSpaces(text);\n const words = normalized.split(' ');\n\n return words\n .map((word, index) => {\n if (!word) return word;\n\n if (word.includes('-')) {\n return processHyphenatedWord(word, index === 0, {\n lowercaseSet,\n acronymSet,\n preserveAcronyms,\n strict,\n });\n }\n\n return processWord(word, index === 0, {\n lowercaseSet,\n acronymSet,\n preserveAcronyms,\n strict,\n });\n })\n .join(' ');\n}\n\n/**\n * Normalize whitespace in text (trim and collapse multiple spaces)\n */\nfunction normalizeSpaces(text: string): string {\n return text.trim().replace(/\\s+/g, ' ');\n}\n\n/**\n * Process a single word according to title case rules\n */\nfunction processWord(\n word: string,\n isFirstWord: boolean,\n context: {\n lowercaseSet: Set<string>;\n acronymSet: Set<string>;\n preserveAcronyms: boolean;\n strict: boolean;\n }\n): string {\n const { lowercaseSet, acronymSet, preserveAcronyms, strict } = context;\n const lowerWord = word.toLowerCase();\n const upperWord = word.toUpperCase();\n\n // Check if it's a known acronym\n if (preserveAcronyms && acronymSet.has(upperWord)) {\n return upperWord;\n }\n\n // Check if it should be lowercase (but not at the start)\n if (!isFirstWord && lowercaseSet.has(lowerWord)) {\n return lowerWord;\n }\n\n // Capitalize first letter\n if (strict) {\n return capitalizeFirstLetter(lowerWord);\n }\n\n return capitalizeFirstLetter(word.toLowerCase());\n}\n\n/**\n * Process hyphenated word (e.g., \"anak-anak\")\n */\nfunction processHyphenatedWord(\n word: string,\n isFirstWord: boolean,\n context: {\n lowercaseSet: Set<string>;\n acronymSet: Set<string>;\n preserveAcronyms: boolean;\n strict: boolean;\n }\n): string {\n return word\n .split('-')\n .map((part, index) =>\n processWord(part, isFirstWord && index === 0, context)\n )\n .join('-');\n}\n\n/**\n * Capitalize first letter of a word\n */\nfunction capitalizeFirstLetter(word: string): string {\n if (!word) return word;\n return word.charAt(0).toUpperCase() + word.slice(1);\n}\n\n/**\n * Convert text to sentence case (capitalize first letter of sentences only)\n *\n * This function capitalizes the first character of the text and the first\n * character after sentence-ending punctuation (. ! ?), while keeping\n * everything else in lowercase.\n *\n * **Sentence Detection Rules:**\n * - Period (.), exclamation (!), question mark (?) mark sentence endings\n * - Next letter after punctuation + space is capitalized\n * - Handles multiple spaces and newlines\n * - Does NOT treat abbreviations as sentence endings (e.g., \"Dr. Smith\")\n *\n * @param text - The text to convert to sentence case\n * @returns The sentence-cased text\n *\n * @example\n * Basic usage:\n * ```typescript\n * toSentenceCase('JOKO WIDODO ADALAH PRESIDEN')\n * // → 'Joko widodo adalah presiden'\n *\n * toSentenceCase('joko widodo adalah presiden')\n * // → 'Joko widodo adalah presiden'\n * ```\n *\n * @example\n * Multiple sentences:\n * ```typescript\n * toSentenceCase('halo, apa kabar? baik-baik saja.')\n * // → 'Halo, apa kabar? Baik-baik saja.'\n *\n * toSentenceCase('jakarta. surabaya. bandung.')\n * // → 'Jakarta. Surabaya. Bandung.'\n * ```\n *\n * @example\n * Different punctuation:\n * ```typescript\n * toSentenceCase('wow! amazing! fantastic!')\n * // → 'Wow! Amazing! Fantastic!'\n *\n * toSentenceCase('siapa nama anda? saya joko.')\n * // → 'Siapa nama anda? Saya joko.'\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * toSentenceCase('')\n * // → ''\n *\n * toSentenceCase('hello')\n * // → 'Hello'\n *\n * toSentenceCase(' hello. world. ')\n * // → 'Hello. World.'\n * ```\n *\n * @public\n */\nexport function toSentenceCase(text: string): string {\n if (!text) return text;\n\n const normalized = text.trim().replace(/\\s+/g, ' ');\n\n let result = '';\n let shouldCapitalize = true;\n\n for (let i = 0; i < normalized.length; i++) {\n const char = normalized[i];\n\n // Capitalize if we should and this is a letter\n if (shouldCapitalize && /[a-zA-ZÀ-ÿ]/.test(char)) {\n result += char.toUpperCase();\n shouldCapitalize = false;\n } else {\n result += char.toLowerCase();\n }\n\n // Mark next letter for capitalization after sentence-ending punctuation\n if (isSentenceEnd(char)) {\n shouldCapitalize = true;\n }\n\n // Handle abbreviations: don't capitalize after period in abbreviations\n if (char === '.' && i + 1 < normalized.length) {\n const nextChar = normalized[i + 1];\n\n // If next char is not a space, likely an abbreviation\n if (nextChar !== ' ' && !/[.!?]/.test(nextChar)) {\n shouldCapitalize = false;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Check if a character marks the end of a sentence\n */\nfunction isSentenceEnd(char: string): boolean {\n return char === '.' || char === '!' || char === '?';\n}\n","import type { SlugifyOptions } from './types';\n\n/**\n * Generate URL-safe slugs with Indonesian language support\n *\n * This function converts text into URL-friendly slugs by:\n * - Converting to lowercase (configurable)\n * - Replacing spaces with separators (default: hyphen)\n * - Replacing Indonesian conjunctions (& → dan, / → atau)\n * - Removing special characters\n * - Collapsing multiple separators\n * - Trimming leading/trailing separators\n *\n * **Character Handling:**\n * - Alphanumeric (a-z, A-Z, 0-9): Preserved\n * - Spaces: Replaced with separator\n * - Ampersand (&): Replaced with \"dan\"\n * - Slash (/): Replaced with \"atau\"\n * - Hyphens (-): Preserved as separators\n * - Other special chars: Removed\n *\n * @param text - The text to convert to slug\n * @param options - Optional configuration\n * @returns The URL-safe slug\n *\n * @example\n * Basic usage:\n * ```typescript\n * slugify('Cara Mudah Belajar TypeScript')\n * // → 'cara-mudah-belajar-typescript'\n *\n * slugify('HELLO WORLD')\n * // → 'hello-world'\n * ```\n *\n * @example\n * Indonesian conjunctions:\n * ```typescript\n * slugify('Ibu & Anak: Tips Kesehatan')\n * // → 'ibu-dan-anak-tips-kesehatan'\n *\n * slugify('Baju Pria/Wanita')\n * // → 'baju-pria-atau-wanita'\n *\n * slugify('A & B / C')\n * // → 'a-dan-b-atau-c'\n * ```\n *\n * @example\n * Special characters removed:\n * ```typescript\n * slugify('Harga Rp 100.000 (Diskon 20%)')\n * // → 'harga-rp-100000-diskon-20'\n *\n * slugify('Email: test@example.com')\n * // → 'email-testexamplecom'\n * ```\n *\n * @example\n * Multiple spaces/separators collapsed:\n * ```typescript\n * slugify('Produk Terbaru - - - 2024')\n * // → 'produk-terbaru-2024'\n *\n * slugify(' Hello World ')\n * // → 'hello-world'\n * ```\n *\n * @example\n * With options:\n * ```typescript\n * slugify('Hello World', { separator: '_' })\n * // → 'hello_world'\n *\n * slugify('Hello World', { lowercase: false })\n * // → 'Hello-World'\n *\n * slugify('C++ Programming', {\n * replacements: { 'C++': 'cpp' }\n * })\n * // → 'cpp-programming'\n *\n * slugify('Hello-World', { trim: false })\n * // → 'hello-world' (same, but won't trim if leading/trailing)\n * ```\n *\n * @public\n */\n\nexport function slugify(text: string, options?: SlugifyOptions): string {\n if (!text) return '';\n\n const {\n separator = '-',\n lowercase = true,\n replacements = {},\n trim = true,\n } = options || {};\n\n let result = text;\n\n // Apply custom replacements first\n for (const [search, replace] of Object.entries(replacements)) {\n result = result.replace(new RegExp(escapeRegex(search), 'g'), replace);\n }\n\n // Replace Indonesian conjunctions\n result = result.replace(/&/g, ' dan ');\n result = result.replace(/\\//g, ' atau ');\n\n // Convert to lowercase if needed\n if (lowercase) {\n result = result.toLowerCase();\n }\n\n // Remove chars that should NOT become separators (dots, apostrophes, @, accents, etc)\n result = result.replace(/[.'@éèêëàâäôöûüùïîçñ™®©]/g, '');\n\n // Replace remaining special chars with separator (everything except alphanumeric, spaces, hyphens)\n result = result.replace(/[^\\w\\s-]+/g, separator);\n\n // Replace spaces with separator\n result = result.replace(/\\s+/g, separator);\n\n // Replace existing hyphens with separator (if separator is not hyphen)\n if (separator !== '-') {\n result = result.replace(/-/g, separator);\n }\n\n // Only when trim: true, collapse multiple separators AND trim edges\n if (trim) {\n const separatorRegex = new RegExp(`\\\\${separator}+`, 'g');\n result = result.replace(separatorRegex, separator);\n\n const trimRegex = new RegExp(`^\\\\${separator}+|\\\\${separator}+$`, 'g');\n result = result.replace(trimRegex, '');\n }\n\n return result;\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import type { SanitizeOptions } from './types';\n\n/**\n * Normalize all whitespace characters to single spaces\n *\n * This function:\n * - Collapses multiple spaces into one\n * - Converts tabs, newlines, and other whitespace to single space\n * - Trims leading and trailing whitespace\n * - Handles Unicode whitespace characters\n *\n * **Whitespace Characters Normalized:**\n * - Space (` `)\n * - Tab (`\\t`)\n * - Newline (`\\n`)\n * - Carriage return (`\\r`)\n * - Form feed (`\\f`)\n * - Vertical tab (`\\v`)\n * - Non-breaking space (`\\u00A0`)\n * - Other Unicode spaces\n *\n * @param text - The text to normalize\n * @returns Text with normalized whitespace\n *\n * @example\n * Basic usage:\n * ```typescript\n * normalizeWhitespace('hello world')\n * // → 'hello world'\n *\n * normalizeWhitespace('hello\\tworld')\n * // → 'hello world'\n * ```\n *\n * @example\n * Multiple types of whitespace:\n * ```typescript\n * normalizeWhitespace('hello\\n\\nworld')\n * // → 'hello world'\n *\n * normalizeWhitespace('hello\\r\\nworld')\n * // → 'hello world'\n *\n * normalizeWhitespace('line1\\n\\nline2\\tword')\n * // → 'line1 line2 word'\n * ```\n *\n * @example\n * Leading and trailing whitespace:\n * ```typescript\n * normalizeWhitespace(' hello world ')\n * // → 'hello world'\n *\n * normalizeWhitespace('\\n\\thello\\t\\n')\n * // → 'hello'\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * normalizeWhitespace('')\n * // → ''\n *\n * normalizeWhitespace(' ')\n * // → ''\n *\n * normalizeWhitespace('hello')\n * // → 'hello'\n * ```\n *\n * @public\n */\nexport function normalizeWhitespace(text: string): string {\n if (!text) return text;\n\n // Replace all whitespace characters with single space\n // \\s matches: space, tab, newline, carriage return, form feed, vertical tab\n return text.trim().replace(/\\s+/g, ' ');\n}\n\n/**\n * Remove or replace unwanted characters from text\n *\n * This function provides flexible text sanitization with options to:\n * - Remove newlines\n * - Remove extra spaces\n * - Remove punctuation\n * - Keep only allowed characters\n * - Trim leading/trailing whitespace\n *\n * @param text - The text to sanitize\n * @param options - Sanitization options\n * @returns The sanitized text\n *\n * @example\n * Remove extra spaces (default):\n * ```typescript\n * sanitize('hello world')\n * // → 'hello world'\n * ```\n *\n * @example\n * Remove newlines:\n * ```typescript\n * sanitize('line1\\nline2\\nline3', { removeNewlines: true })\n * // → 'line1 line2 line3'\n * ```\n *\n * @example\n * Remove punctuation:\n * ```typescript\n * sanitize('Hello, World!', { removePunctuation: true })\n * // → 'Hello World'\n * ```\n *\n * @example\n * Allow only specific characters:\n * ```typescript\n * sanitize('ABC123!@#', { allowedChars: 'A-Za-z0-9' })\n * // → 'ABC123'\n *\n * sanitize('Hello123!@#', { allowedChars: 'a-z' })\n * // → 'ello'\n * ```\n *\n * @example\n * Combined options:\n * ```typescript\n * sanitize(' Hello,\\n World! ', {\n * removeNewlines: true,\n * removePunctuation: true,\n * removeExtraSpaces: true,\n * trim: true\n * })\n * // → 'Hello World'\n * ```\n *\n * @public\n */\nexport function sanitize(text: string, options?: SanitizeOptions): string {\n if (!text) return text;\n\n const {\n removeNewlines = false,\n removeExtraSpaces = true,\n removePunctuation = false,\n allowedChars,\n trim = true,\n } = options || {};\n\n let result = text;\n\n // Remove newlines first (replace with space)\n if (removeNewlines) {\n result = result.replace(/[\\n\\r]/g, ' ');\n }\n\n // Remove punctuation\n if (removePunctuation) {\n result = result.replace(/[!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~]/g, '');\n }\n\n // Keep only allowed characters\n if (allowedChars) {\n const allowedRegex = new RegExp(`[^${allowedChars}]`, 'g');\n result = result.replace(allowedRegex, '');\n }\n\n // Remove extra spaces\n if (removeExtraSpaces) {\n if (trim) {\n // When trimming, we can safely collapse all spaces\n if (removeNewlines) {\n result = result.replace(/\\s+/g, ' ');\n } else {\n result = result.replace(/[ \\t]+/g, ' ');\n }\n } else {\n // When not trimming, preserve leading/trailing spaces\n // Only collapse spaces in the middle\n const leadingMatch = result.match(/^([ \\t]*)/);\n const trailingMatch = result.match(/([ \\t]*)$/);\n const leading = leadingMatch ? leadingMatch[1] : '';\n const trailing = trailingMatch ? trailingMatch[1] : '';\n\n const middle = result.slice(\n leading.length,\n result.length - trailing.length\n );\n const normalizedMiddle = removeNewlines\n ? middle.replace(/\\s+/g, ' ')\n : middle.replace(/[ \\t]+/g, ' ');\n\n result = leading + normalizedMiddle + trailing;\n }\n }\n\n // Trim\n if (trim) {\n result = result.trim();\n }\n\n return result;\n}\n\n/**\n * Remove diacritical marks (accents) from characters\n *\n * Converts accented characters to their base form:\n * - é → e\n * - ñ → n\n * - ü → u\n * - etc.\n *\n * Useful for:\n * - Search normalization\n * - Sorting/comparison\n * - URL generation\n * - Database queries\n *\n * @param text - The text to remove accents from\n * @returns Text with accents removed\n *\n * @example\n * Basic usage:\n * ```typescript\n * removeAccents('café')\n * // → 'cafe'\n *\n * removeAccents('résumé')\n * // → 'resume'\n * ```\n *\n * @example\n * Various accents:\n * ```typescript\n * removeAccents('naïve')\n * // → 'naive'\n *\n * removeAccents('Zürich')\n * // → 'Zurich'\n *\n * removeAccents('São Paulo')\n * // → 'Sao Paulo'\n * ```\n *\n * @example\n * Mixed text:\n * ```typescript\n * removeAccents('École française à Montréal')\n * // → 'Ecole francaise a Montreal'\n * ```\n *\n * @public\n */\nexport function removeAccents(text: string): string {\n if (!text) return text;\n\n // Manual mapping for Nordic and other special characters that don't decompose\n const specialChars: Record<string, string> = {\n Ø: 'O',\n ø: 'o',\n Æ: 'AE',\n æ: 'ae',\n Å: 'A',\n å: 'a',\n Đ: 'D',\n đ: 'd',\n Ł: 'L',\n ł: 'l',\n Þ: 'TH',\n þ: 'th',\n ß: 'ss',\n };\n\n // First apply manual mappings\n let result = text;\n for (const [accented, plain] of Object.entries(specialChars)) {\n result = result.replace(new RegExp(accented, 'g'), plain);\n }\n\n // Then normalize to NFD and remove combining diacritical marks\n return result.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\n}\n","import { ABBREVIATIONS } from './constants';\nimport type { ExpandOptions } from './types';\n\n/**\n * Expand Indonesian abbreviations to their full form\n *\n * This function expands common Indonesian abbreviations like:\n * - Address: Jl. → Jalan, Kec. → Kecamatan\n * - Titles: Dr. → Doktor, S.H. → Sarjana Hukum\n * - Honorifics: Bpk. → Bapak, Yth. → Yang Terhormat\n * - Organizations: PT. → Perseroan Terbatas\n * - Common: dll. → dan lain-lain\n *\n * **Features:**\n * - Case-insensitive matching (Jl. = jl. = JL.)\n * - Mode filtering (all, address, title, org)\n * - Custom mapping support\n * - Preserves surrounding text\n * - Multiple abbreviations in one string\n *\n * @param text - The text containing abbreviations to expand\n * @param options - Optional configuration\n * @returns Text with abbreviations expanded\n *\n * @example\n * Basic usage:\n * ```typescript\n * expandAbbreviation('Jl. Sudirman No. 123')\n * // → 'Jalan Sudirman Nomor 123'\n *\n * expandAbbreviation('Dr. Joko Widodo, S.H.')\n * // → 'Doktor Joko Widodo, Sarjana Hukum'\n * ```\n *\n * @example\n * Address abbreviations:\n * ```typescript\n * expandAbbreviation('Kab. Bogor, Kec. Ciawi')\n * // → 'Kabupaten Bogor, Kecamatan Ciawi'\n *\n * expandAbbreviation('Jl. Merdeka Gg. 5 No. 10')\n * // → 'Jalan Merdeka Gang 5 Nomor 10'\n * ```\n *\n * @example\n * Academic titles:\n * ```typescript\n * expandAbbreviation('Prof. Dr. Ir. Ahmad')\n * // → 'Profesor Doktor Insinyur Ahmad'\n *\n * expandAbbreviation('Saya lulusan S.T. dari ITB')\n * // → 'Saya lulusan Sarjana Teknik dari ITB'\n * ```\n *\n * @example\n * Honorifics:\n * ```typescript\n * expandAbbreviation('Yth. Bpk. H. Ahmad')\n * // → 'Yang Terhormat Bapak Haji Ahmad'\n * ```\n *\n * @example\n * Organizations:\n * ```typescript\n * expandAbbreviation('PT. Maju Jaya Tbk.')\n * // → 'Perseroan Terbatas Maju Jaya Terbuka'\n * ```\n *\n * @example\n * Mode filtering:\n * ```typescript\n * expandAbbreviation('Dr. Joko di Jl. Sudirman', { mode: 'address' })\n * // → 'Dr. Joko di Jalan Sudirman'\n * // Only expands address abbreviations\n *\n * expandAbbreviation('Prof. Dr. di Jl. Sudirman', { mode: 'title' })\n * // → 'Profesor Doktor di Jl. Sudirman'\n * // Only expands title abbreviations\n * ```\n *\n * @example\n * Custom mappings:\n * ```typescript\n * expandAbbreviation('BUMN adalah perusahaan negara', {\n * customMap: { 'BUMN': 'Badan Usaha Milik Negara' }\n * })\n * // → 'Badan Usaha Milik Negara adalah perusahaan negara'\n * ```\n *\n * @example\n * Case sensitivity:\n * ```typescript\n * expandAbbreviation('jl. sudirman')\n * // → 'Jalan sudirman' (default: preserves surrounding case)\n *\n * expandAbbreviation('JL. SUDIRMAN')\n * // → 'Jalan SUDIRMAN'\n * ```\n *\n * @public\n */\nexport function expandAbbreviation(\n text: string,\n options?: ExpandOptions\n): string {\n if (!text) return text;\n\n const { mode = 'all', customMap = {}, preserveCase = false } = options || {};\n\n // Combine built-in and custom abbreviations\n const abbreviationsMap = {\n ...getAbbreviationsByMode(mode),\n ...customMap,\n };\n\n let result = text;\n\n // Sort by length (longest first) to avoid partial replacements\n // Example: Replace \"S.H.\" before \"S.\" to avoid \"Sarjana.H.\"\n const sortedAbbrevs = Object.keys(abbreviationsMap).sort(\n (a, b) => b.length - a.length\n );\n\n for (const abbrev of sortedAbbrevs) {\n const expansion = abbreviationsMap[abbrev];\n\n // Create case-insensitive regex with word boundaries\n // Handle word boundaries intelligently:\n // - If starts with word char, add \\b prefix\n // - If ends with word char, add \\b suffix\n // - If ends with dot, don't add \\b suffix (dots are non-word chars)\n const startBoundary = /^\\w/.test(abbrev) ? '\\\\b' : '';\n const endBoundary = /\\w$/.test(abbrev) ? '\\\\b' : '';\n\n const regex = new RegExp(\n `${startBoundary}${escapeRegex(abbrev)}${endBoundary}`,\n 'gi'\n );\n\n result = result.replace(regex, (match) => {\n // If preserveCase is false, use the expansion as-is\n if (!preserveCase) {\n return expansion;\n }\n\n // If preserveCase is true, try to match case of original\n return matchCase(match, expansion);\n });\n }\n\n return result;\n}\n\n/**\n * Get abbreviations filtered by mode\n */\nfunction getAbbreviationsByMode(\n mode: 'all' | 'address' | 'title' | 'org'\n): Record<string, string> {\n if (mode === 'all') {\n return ABBREVIATIONS;\n }\n\n const filtered: Record<string, string> = {};\n\n // Define which abbreviations belong to which category\n const addressAbbrevs = [\n 'Jl.',\n 'Gg.',\n 'No.',\n 'Kp.',\n 'Ds.',\n 'Kel.',\n 'Kec.',\n 'Kab.',\n 'Kota',\n 'Prov.',\n 'Prop.',\n 'Rt.',\n 'Rw.',\n 'Blok',\n 'Komp.',\n 'Perumahan',\n 'Perum.',\n ];\n\n const titleAbbrevs = [\n 'Dr.',\n 'Ir.',\n 'Prof.',\n 'Drs.',\n 'Dra.',\n 'S.Pd.',\n 'S.H.',\n 'S.E.',\n 'S.T.',\n 'S.Kom.',\n 'S.Si.',\n 'S.Sos.',\n 'S.I.Kom.',\n 'S.S.',\n 'S.Psi.',\n 'S.Farm.',\n 'S.Ked.',\n 'M.Sc.',\n 'M.M.',\n 'M.Pd.',\n 'M.T.',\n 'M.Kom.',\n 'M.Si.',\n 'M.H.',\n 'M.A.',\n 'MBA',\n ];\n\n const orgAbbrevs = [\n 'PT.',\n 'CV.',\n 'UD.',\n 'PD.',\n 'Tbk.',\n 'Koperasi',\n 'Yayasan',\n ];\n\n // Filter based on mode\n for (const [abbrev, expansion] of Object.entries(ABBREVIATIONS)) {\n if (mode === 'address' && addressAbbrevs.includes(abbrev)) {\n filtered[abbrev] = expansion;\n } else if (mode === 'title' && titleAbbrevs.includes(abbrev)) {\n filtered[abbrev] = expansion;\n } else if (mode === 'org' && orgAbbrevs.includes(abbrev)) {\n filtered[abbrev] = expansion;\n }\n }\n\n return filtered;\n}\n\n/**\n * Escape special regex characters in a string\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Match the case pattern of original string to replacement\n * - ALL CAPS → ALL CAPS\n * - Title Case → Title Case\n * - lowercase → lowercase\n */\nfunction matchCase(original: string, replacement: string): string {\n // Check if original is all uppercase\n if (original === original.toUpperCase()) {\n return replacement.toUpperCase();\n }\n\n // Check if original is all lowercase\n if (original === original.toLowerCase()) {\n return replacement.toLowerCase();\n }\n\n // Check if original is title case (first letter uppercase)\n if (original.charAt(0) === original.charAt(0).toUpperCase()) {\n return (\n replacement.charAt(0).toUpperCase() + replacement.slice(1).toLowerCase()\n );\n }\n\n // Default: return as-is\n return replacement;\n}\n\n/**\n * Contract full forms to abbreviations (reverse of expand)\n *\n * @param text - The text containing full forms to contract\n * @param options - Optional configuration\n * @returns Text with full forms contracted\n *\n * @example\n * ```typescript\n * contractAbbreviation('Jalan Sudirman Nomor 123')\n * // → 'Jl. Sudirman No. 123'\n *\n * contractAbbreviation('Doktor Ahmad, Sarjana Hukum')\n * // → 'Dr. Ahmad, S.H.'\n * ```\n *\n * @public\n */\nexport function contractAbbreviation(\n text: string,\n options?: { mode?: 'all' | 'address' | 'title' | 'org' }\n): string {\n if (!text) return text;\n\n const { mode = 'all' } = options || {};\n\n // Get abbreviations and create reverse mapping\n const abbreviationsMap = getAbbreviationsByMode(mode);\n const reverseMap: Record<string, string> = {};\n\n for (const [abbrev, expansion] of Object.entries(abbreviationsMap)) {\n reverseMap[expansion] = abbrev;\n }\n\n let result = text;\n\n // Sort by length (longest first) to avoid partial replacements\n const sortedExpansions = Object.keys(reverseMap).sort(\n (a, b) => b.length - a.length\n );\n\n for (const expansion of sortedExpansions) {\n const abbrev = reverseMap[expansion];\n\n // Case-insensitive replace\n const regex = new RegExp(`\\\\b${escapeRegex(expansion)}\\\\b`, 'gi');\n\n result = result.replace(regex, abbrev);\n }\n\n return result;\n}\n","import type { TruncateOptions, ExtractOptions } from './types';\n\n/**\n * Truncate text to specified length, word-aware\n *\n * This function shortens text to a maximum length while:\n * - Respecting word boundaries (don't cut words in half)\n * - Adding ellipsis to indicate truncation\n * - Preserving original text if already short enough\n * - Accounting for ellipsis length in total character count\n *\n * **Features:**\n * - Smart word boundary detection\n * - Customizable ellipsis\n * - No truncation for short text\n * - Handles edge cases gracefully\n *\n * @param text - The text to truncate\n * @param maxLength - Maximum length of output (including ellipsis)\n * @param options - Optional configuration\n * @returns The truncated text with ellipsis if needed\n *\n * @example\n * Basic usage:\n * ```typescript\n * truncate('Ini adalah contoh text yang panjang', 20)\n * // → 'Ini adalah contoh...'\n *\n * truncate('Short text', 20)\n * // → 'Short text' (no truncation needed)\n * ```\n *\n * @example\n * Word boundary handling:\n * ```typescript\n * truncate('Ini adalah contoh text yang panjang', 20, { wordBoundary: true })\n * // → 'Ini adalah contoh...' (stops at word)\n *\n * truncate('Ini adalah contoh text yang panjang', 20, { wordBoundary: false })\n * // → 'Ini adalah contoh t...' (cuts mid-word)\n * ```\n *\n * @example\n * Custom ellipsis:\n * ```typescript\n * truncate('Ini adalah contoh text yang panjang', 20, { ellipsis: '…' })\n * // → 'Ini adalah contoh…'\n *\n * truncate('Ini adalah contoh text yang panjang', 20, { ellipsis: ' [...]' })\n * // → 'Ini adalah [...]'\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * truncate('', 10)\n * // → ''\n *\n * truncate('Hello', 10)\n * // → 'Hello'\n *\n * truncate('Hello World', 11)\n * // → 'Hello World' (exact length, no ellipsis)\n * ```\n *\n * @public\n */\nexport function truncate(\n text: string,\n maxLength: number,\n options?: TruncateOptions\n): string {\n // Early return for empty or invalid input\n if (!text || maxLength <= 0) {\n return '';\n }\n\n const { ellipsis = '...', wordBoundary = true } = options || {};\n\n // Early return if text is already short enough\n if (text.length <= maxLength) {\n return text;\n }\n\n // Calculate available space for actual text (excluding ellipsis)\n const availableLength = maxLength - ellipsis.length;\n\n // If ellipsis is longer than maxLength, just return ellipsis truncated\n if (availableLength <= 0) {\n return ellipsis.slice(0, maxLength);\n }\n\n // Get preliminary truncated text\n let truncated = text.slice(0, availableLength);\n\n // If word boundary is enabled, find last complete word\n if (wordBoundary) {\n // Find the last space within the truncated text\n const lastSpaceIndex = truncated.lastIndexOf(' ');\n\n // Only cut at word boundary if we found a space\n // Why: Prevents returning empty string for single long word\n if (lastSpaceIndex > 0) {\n truncated = truncated.slice(0, lastSpaceIndex);\n }\n // If no space found, keep the full availableLength\n }\n\n // Trim any trailing whitespace before adding ellipsis\n truncated = truncated.trimEnd();\n\n return truncated + ellipsis;\n}\n\n/**\n * Extract words from text, respecting Indonesian language rules\n *\n * This function splits text into individual words while:\n * - Respecting hyphenated words (anak-anak as single word)\n * - Filtering by minimum length\n * - Optional lowercase conversion\n * - Removing punctuation and special characters\n *\n * **Features:**\n * - Indonesian hyphenation support (anak-anak, buku-buku)\n * - Minimum word length filtering\n * - Case normalization\n * - Handles punctuation gracefully\n *\n * @param text - The text to extract words from\n * @param options - Optional configuration\n * @returns Array of extracted words\n *\n * @example\n * Basic usage:\n * ```typescript\n * extractWords('Anak-anak bermain di taman')\n * // → ['Anak-anak', 'bermain', 'di', 'taman']\n *\n * extractWords('Hello, World! How are you?')\n * // → ['Hello', 'World', 'How', 'are', 'you']\n * ```\n *\n * @example\n * Hyphenated word handling:\n * ```typescript\n * extractWords('Anak-anak bermain di taman', { includeHyphenated: true })\n * // → ['Anak-anak', 'bermain', 'di', 'taman']\n *\n * extractWords('Anak-anak bermain di taman', { includeHyphenated: false })\n * // → ['Anak', 'anak', 'bermain', 'di', 'taman']\n * ```\n *\n * @example\n * Minimum length filtering:\n * ```typescript\n * extractWords('Di rumah ada 3 kucing', { minLength: 3 })\n * // → ['rumah', 'ada', 'kucing']\n * // 'Di' (2 chars) and '3' (1 char) filtered out\n *\n * extractWords('a b cd def ghij', { minLength: 3 })\n * // → ['def', 'ghij']\n * ```\n *\n * @example\n * Lowercase conversion:\n * ```typescript\n * extractWords('Hello WORLD', { lowercase: true })\n * // → ['hello', 'world']\n *\n * extractWords('Hello WORLD', { lowercase: false })\n * // → ['Hello', 'WORLD']\n * ```\n *\n * @example\n * Combined options:\n * ```typescript\n * extractWords('Anak-Anak BERMAIN di Taman', {\n * includeHyphenated: true,\n * minLength: 3,\n * lowercase: true\n * })\n * // → ['anak-anak', 'bermain', 'taman']\n * // 'di' filtered out (< 3 chars)\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * extractWords('')\n * // → []\n *\n * extractWords(' ')\n * // → []\n *\n * extractWords('!!!@@##')\n * // → []\n * ```\n *\n * @public\n */\nexport function extractWords(text: string, options?: ExtractOptions): string[] {\n // Early return for empty input\n if (!text || !text.trim()) {\n return [];\n }\n\n const {\n minLength = 0,\n includeHyphenated = true,\n lowercase = false,\n } = options || {};\n\n // Remove punctuation but preserve hyphens and spaces\n // Why: We want to keep hyphenated words like 'anak-anak' intact\n let cleaned = text;\n\n if (includeHyphenated) {\n // Keep hyphens, remove other punctuation\n // Replace punctuation except hyphens with spaces\n cleaned = text.replace(/[^\\w\\s-]/g, ' ');\n } else {\n // Replace all punctuation including hyphens with spaces\n cleaned = text.replace(/[^\\w\\s]/g, ' ');\n }\n\n // Split by whitespace to get individual words\n const words = cleaned\n .split(/\\s+/)\n .map((word) => word.trim())\n .filter((word) => word.length > 0)\n // Filter out words that are just hyphens (common artifact of punctuation removal)\n .filter((word) => !/^-+$/.test(word));\n\n // Apply filters\n let result = words;\n\n // Filter by minimum length\n if (minLength > 0) {\n result = result.filter((word) => word.length >= minLength);\n }\n\n // Convert to lowercase if requested\n if (lowercase) {\n result = result.map((word) => word.toLowerCase());\n }\n\n return result;\n}\n","import type { CompareOptions } from './types';\nimport { normalizeWhitespace, removeAccents } from './sanitize';\n\n/**\n * Compare strings with Indonesian-aware normalization\n *\n * This function allows flexible string comparison with options to ignore\n * case, whitespace, and accents. Useful for search, filtering, and\n * validation.\n *\n * **Features:**\n * - Case-insensitive comparison (default: false)\n * - Whitespace normalization (ignore extra spaces)\n * - Accent removal (café == cafe)\n * - Null-safe (handles empty strings)\n *\n * @param str1 - First string to compare\n * @param str2 - Second string to compare\n * @param options - Comparison options\n * @returns True if strings match according to options\n *\n * @example\n * Basic matching:\n * ```typescript\n * compareStrings('Hello', 'Hello') // → true\n * compareStrings('Hello', 'hello') // → false\n * ```\n *\n * @example\n * Case insensitive:\n * ```typescript\n * compareStrings('Hello', 'hello', { caseSensitive: false }) // → true\n * // Note: default is caseSensitive: false for convenience in many utils,\n * // but strict comparison usually defaults to true.\n * // Let's check the implementation default.\n * ```\n *\n * @example\n * Ignore whitespace:\n * ```typescript\n * compareStrings(' Hello World ', 'Hello World', { ignoreWhitespace: true })\n * // → true\n * ```\n *\n * @example\n * Ignore accents:\n * ```typescript\n * compareStrings('café', 'cafe', { ignoreAccents: true })\n * // → true\n * ```\n *\n * @public\n */\nexport function compareStrings(\n str1: string,\n str2: string,\n options?: CompareOptions\n): boolean {\n // Early return for exact match (optimization)\n if (str1 === str2) {\n return true;\n }\n\n // Handle null/undefined as empty strings for robust comparison\n // usage of (str || '') pattern\n const s1 = str1 || '';\n const s2 = str2 || '';\n\n const {\n caseSensitive = false,\n ignoreWhitespace = false,\n ignoreAccents = false,\n } = options || {};\n\n let normalized1 = s1;\n let normalized2 = s2;\n\n // Apply whitespace normalization\n if (ignoreWhitespace) {\n normalized1 = normalizeWhitespace(normalized1);\n normalized2 = normalizeWhitespace(normalized2);\n }\n\n // Apply accent removal\n if (ignoreAccents) {\n normalized1 = removeAccents(normalized1);\n normalized2 = removeAccents(normalized2);\n }\n\n // Apply case sensitivity\n if (!caseSensitive) {\n normalized1 = normalized1.toLowerCase();\n normalized2 = normalized2.toLowerCase();\n }\n\n return normalized1 === normalized2;\n}\n\n/**\n * Calculate similarity score between two strings (0-1) using Levenshtein distance\n *\n * This function measures the difference between two strings and returns a score\n * where 1.0 means identical and 0.0 means completely different.\n *\n * **Algorithm:**\n * Uses Levenshtein distance to calculate the minimum number of single-character\n * edits (insertions, deletions, substitutions) required to change one string\n * into the other.\n *\n * @param str1 - First string\n * @param str2 - Second string\n * @returns Similarity score between 0.0 and 1.0\n *\n * @example\n * Basic Usage:\n * ```typescript\n * similarity('hello', 'hello') // → 1.0 (identical)\n * similarity('hello', 'hallo') // → 0.8 (1 edit / 5 length)\n * similarity('hello', 'world') // → 0.2 (4 edits / 5 length)\n * ```\n *\n * @example\n * Case sensitivity:\n * Note: This function is case-sensitive. Use compareStrings options or\n * manual lowercasing if you need case-insensitive similarity.\n *\n * @public\n */\nexport function similarity(str1: string, str2: string): number {\n if (str1 === str2) return 1.0;\n if (str1.length === 0) return str2.length === 0 ? 1.0 : 0.0;\n if (str2.length === 0) return 0.0;\n\n const len1 = str1.length;\n const len2 = str2.length;\n\n // Track previous and current rows of the matrix\n // Optimization: We only need two rows, not the full matrix O(min(m,n)) space\n let prevRow = Array(len2 + 1).fill(0);\n let currentRow = Array(len2 + 1).fill(0);\n\n // Initialize first row\n for (let j = 0; j <= len2; j++) {\n prevRow[j] = j;\n }\n\n // Calculate distance\n for (let i = 1; i <= len1; i++) {\n currentRow[0] = i;\n\n for (let j = 1; j <= len2; j++) {\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\n\n currentRow[j] = Math.min(\n currentRow[j - 1] + 1, // Insertion\n prevRow[j] + 1, // Deletion\n prevRow[j - 1] + cost // Substitution\n );\n }\n\n // Move current row to previous for next iteration\n [prevRow, currentRow] = [currentRow, prevRow];\n }\n\n // Calculate similarity: 1 - (distance / max_length)\n const distance = prevRow[len2];\n const maxLength = Math.max(len1, len2);\n\n return 1.0 - distance / maxLength;\n}\n"]}
1
+ {"version":3,"sources":["../../src/text/constants.ts","../../src/text/capitalization.ts","../../src/text/slug.ts","../../src/text/sanitize.ts","../../src/text/abbreviation.ts","../../src/text/filter.ts","../../src/text/normalization.ts","../../src/text/extract.ts","../../src/text/compare.ts"],"names":["escapeRegex"],"mappings":";AA8QO,IAAM,eAAA,GAAkB;AAAA;AAAA,EAE7B,IAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA;AAAA,EAGA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA;AAAA,EAGA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA;AAAA,EAGA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA;AAAA,EAGA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA;AAAA,EAGA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYF;AAMO,IAAM,QAAA,GAAW;AAAA;AAAA,EAEtB,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,IAAA;AAAA;AAAA,EACA,IAAA;AAAA;AAAA,EACA,IAAA;AAAA;AAAA,EACA,IAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA;AAAA,EAGA,IAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA;AAAA,EAGA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,OAAA;AAAA;AAAA;AAAA,EAGA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;AAAA;AAAA,EACA,KAAA;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","file":"index.js","sourcesContent":["/**\n * ============================================================================\n * INDONESIAN TEXT UTILITIES - CONSTANTS\n * ============================================================================\n *\n * This file contains constants for Indonesian and English text processing:\n * - LOWERCASE_WORDS: Particles that stay lowercase in title case\n * - ACRONYMS: Abbreviations that stay UPPERCASE in title case\n * - ABBREVIATIONS: Full expansions of common Indonesian abbreviations\n *\n * ============================================================================\n * MAINTENANCE GUIDE\n * ============================================================================\n *\n * ## How to Add New Entries\n *\n * ### 1. LOWERCASE_WORDS (Particles)\n *\n * Add words that should remain lowercase in title case (except when first word).\n *\n * **Indonesian Grammar Rules (PUEBI):**\n * - Prepositions: di, ke, dari, untuk, dengan, pada, dalam, etc.\n * - Conjunctions: dan, atau, tetapi, serta, maupun, etc.\n * - Articles/particles: yang, sebagai, adalah, akan, telah, etc.\n *\n * **English Grammar Rules (Chicago Manual of Style):**\n * - Articles: a, an, the\n * - Conjunctions: and, or, but, nor, for, yet, so\n * - Short prepositions (<5 letters): at, by, in, of, on, to, up, etc.\n *\n * **Example Addition:**\n * ```typescript\n * export const LOWERCASE_WORDS = [\n * // ... existing entries\n * 'bagi', // Indonesian: for/to (preposition)\n * 'antara', // Indonesian: between (preposition)\n * 'into', // English: preposition\n * ] as const;\n * ```\n *\n * **Testing:** Add test case in `toTitleCase.test.ts`:\n * ```typescript\n * it('keeps \"bagi\" lowercase in middle', () => {\n * expect(toTitleCase('buku bagi pemula')).toBe('Buku bagi Pemula');\n * });\n * ```\n *\n * ### 2. ACRONYMS (Always Uppercase)\n *\n * Add abbreviations that should always appear in UPPERCASE.\n *\n * **Categories:**\n * - Government & Military: TNI, POLRI, KPK, DPR, etc.\n * - Business Entities: PT, CV, BUMN, etc.\n * - Banks: BCA, BRI, BNI, etc.\n * - Services: BPJS, PLN, KTP, SIM, etc.\n * - Technology: IT, AI, API, SEO, etc.\n * - Education: UI, ITB, UGM, etc.\n * - International: UN, WHO, NATO, ASEAN, etc.\n *\n * **Validation Checklist:**\n * ✅ Is it commonly written in ALL CAPS?\n * ✅ Is it an official acronym (not just shortened word)?\n * ✅ Will it look wrong if title-cased (e.g., \"Pt\" instead of \"PT\")?\n *\n * **Example Addition:**\n * ```typescript\n * export const ACRONYMS = [\n * // ... existing entries\n * 'OJK', // Otoritas Jasa Keuangan\n * 'BI', // Bank Indonesia\n * 'NASA', // National Aeronautics and Space Administration\n * ] as const;\n * ```\n *\n * **Testing:** Add test case in `toTitleCase.test.ts`:\n * ```typescript\n * it('preserves OJK uppercase', () => {\n * expect(toTitleCase('ojk indonesia')).toBe('OJK Indonesia');\n * });\n * ```\n *\n * ### 3. ABBREVIATIONS (Expansion Mapping)\n *\n * Add abbreviation → full form mappings for `expandAbbreviation()` function.\n *\n * **Categories (use comment headers):**\n * - Address: Jl., Gg., Kec., Kab., etc.\n * - Academic Titles: Dr., Ir., Prof., S.H., M.M., etc.\n * - Honorifics: Bpk., Yth., H., Hj., etc.\n * - Organizations: PT., CV., UD., etc.\n * - Common: dst., dll., a.n., etc.\n * - Contact Info: Tlp., HP., Fax, etc.\n * - Days/Months: Sen., Jan., Feb., etc.\n * - Units: kg., km., lt., etc.\n *\n * **Key Format Rules:**\n * - Include period if commonly written: `'Jl.'` not `'Jl'`\n * - Use proper capitalization: `'Jalan'` not `'jalan'`\n * - Keep it concise: Full form only, no explanations\n *\n * **Example Addition:**\n * ```typescript\n * export const ABBREVIATIONS: Record<string, string> = {\n * // ... existing entries\n *\n * // ========== New Category Example ==========\n * 'Apt.': 'Apartemen',\n * 'Ruko': 'Rumah Toko',\n * 'Rukan': 'Rumah Kantor',\n * };\n * ```\n *\n * **Testing:** Add test case in `abbreviation.test.ts`:\n * ```typescript\n * it('expands Apt. to Apartemen', () => {\n * expect(expandAbbreviation('Apt. Sudirman'))\n * .toBe('Apartemen Sudirman');\n * });\n * ```\n *\n * ============================================================================\n * DATA SOURCES & REFERENCES\n * ============================================================================\n *\n * When adding new entries, refer to these authoritative sources:\n *\n * **Indonesian Language:**\n * - PUEBI (Pedoman Umum Ejaan Bahasa Indonesia)\n * https://puebi.js.org/\n *\n * - KBBI (Kamus Besar Bahasa Indonesia)\n * https://kbbi.kemdikbud.go.id/\n *\n * - Wikipedia Indonesia - Daftar Singkatan\n * https://id.wikipedia.org/wiki/Daftar_singkatan_di_Indonesia\n *\n * **English Language:**\n * - Chicago Manual of Style (Title Case Rules)\n * https://www.chicagomanualofstyle.org/\n *\n * - AP Stylebook\n * https://www.apstylebook.com/\n *\n * **Government & Official:**\n * - Kemendagri (addresses, administrative divisions)\n * https://www.kemendagri.go.id/\n *\n * - Kemenkumham (business entities)\n * https://www.kemenkumham.go.id/\n *\n * - Kemendikbud (education, degrees)\n * https://www.kemdikbud.go.id/\n *\n * ============================================================================\n * CONTRIBUTION GUIDELINES\n * ============================================================================\n *\n * **Before Adding:**\n * 1. ✅ Check if entry already exists (Ctrl+F)\n * 2. ✅ Verify spelling from official sources\n * 3. ✅ Ensure it's commonly used (not obscure)\n * 4. ✅ Choose correct category/section\n *\n * **After Adding:**\n * 1. ✅ Add corresponding test case\n * 2. ✅ Run tests: `npm test constants`\n * 3. ✅ Update this file's documentation if needed\n * 4. ✅ Add source reference in PR description\n *\n * **PR Template:**\n * ```\n * ### Added Constants\n *\n * **Type:** [LOWERCASE_WORDS | ACRONYMS | ABBREVIATIONS]\n *\n * **Entries:**\n * - `OJK` - Otoritas Jasa Keuangan\n * - `BI` - Bank Indonesia\n *\n * **Source:** https://www.ojk.go.id/\n *\n * **Test Coverage:** ✅ Added in toTitleCase.test.ts line 245\n *\n * **Rationale:**\n * Commonly used financial regulatory bodies in Indonesian context.\n * ```\n *\n * ============================================================================\n * COMMON PITFALLS TO AVOID\n * ============================================================================\n *\n * ❌ **Don't add brand-specific styling** (e.g., \"iPhone\" → keep user control)\n * ❌ **Don't add regional dialects** (stick to standard Indonesian/English)\n * ❌ **Don't add context-dependent acronyms** (e.g., \"UI\" = both User Interface & Universitas Indonesia)\n * ❌ **Don't add very rare/obscure terms** (focus on common usage)\n * ❌ **Don't forget the period** in ABBREVIATIONS (e.g., use `'Dr.'` not `'Dr'`)\n * ❌ **Don't mix singular/plural** in ABBREVIATIONS (choose one consistently)\n *\n * ✅ **Do keep entries alphabetically sorted** within categories\n * ✅ **Do use proper capitalization** in expanded forms\n * ✅ **Do add comments** for non-obvious entries\n * ✅ **Do verify against official sources** before adding\n * ✅ **Do write test cases** for new additions\n *\n * ============================================================================\n * FUTURE EXTENSIBILITY\n * ============================================================================\n *\n * **Planned Enhancements:**\n *\n * 1. **External Data Source Support:**\n * ```typescript\n * import customAcronyms from './data/custom-acronyms.json';\n * export const ACRONYMS = [...DEFAULT_ACRONYMS, ...customAcronyms];\n * ```\n *\n * 2. **Context-Aware Acronyms:**\n * ```typescript\n * export const CONTEXT_ACRONYMS = {\n * 'UI': {\n * tech: 'UI', // User Interface\n * education: 'UI', // Universitas Indonesia\n * }\n * };\n * ```\n *\n * 3. **Locale-Specific Sets:**\n * ```typescript\n * export const LOWERCASE_WORDS = {\n * id: [...], // Indonesian\n * en: [...], // English\n * mixed: [...], // Combined (default)\n * };\n * ```\n *\n * 4. **Dynamic Loading:**\n * ```typescript\n * // Load additional acronyms from user config\n * export async function loadCustomConstants(url: string) {\n * const data = await fetch(url).then(r => r.json());\n * return [...ACRONYMS, ...data.acronyms];\n * }\n * ```\n *\n * ============================================================================\n * VERSIONING & CHANGELOG\n * ============================================================================\n *\n * Track major additions here:\n *\n * - v0.2.0 (2024-12-18): Initial comprehensive dataset\n * - 50+ Indonesian particles\n * - 150+ acronyms (Indonesian + International)\n * - 80+ abbreviation mappings\n *\n * - v0.2.1 (TBD): Add financial sector acronyms (OJK, BI, etc.)\n * - v0.2.2 (TBD): Add technology company acronyms\n *\n * ============================================================================\n */\n\n/**\n * Indonesian and English lowercase particles\n * These words remain lowercase in title case (except when first word)\n *\n * Based on:\n * - Indonesian grammar (PUEBI)\n * - English title case rules (Chicago Manual of Style)\n */\nexport const LOWERCASE_WORDS = [\n // Indonesian prepositions (kata depan)\n 'di',\n 'ke',\n 'dari',\n 'pada',\n 'dalam',\n 'untuk',\n 'dengan',\n 'oleh',\n 'kepada',\n 'terhadap',\n 'tentang',\n 'tanpa',\n 'hingga',\n 'sampai',\n 'sejak',\n 'menuju',\n 'melalui',\n\n // Indonesian conjunctions (kata hubung)\n 'dan',\n 'atau',\n 'tetapi',\n 'namun',\n 'serta',\n 'maupun',\n 'melainkan',\n 'sedangkan',\n\n // Indonesian articles/particles\n 'yang',\n 'sebagai',\n 'adalah',\n 'ialah',\n 'yaitu',\n 'bahwa',\n 'akan',\n 'telah',\n 'sudah',\n 'belum',\n\n // English articles\n 'a',\n 'an',\n 'the',\n\n // English conjunctions\n 'and',\n 'or',\n 'but',\n 'nor',\n 'for',\n 'yet',\n 'so',\n 'as',\n\n // English prepositions (short ones, < 5 letters)\n 'at',\n 'by',\n 'in',\n 'of',\n 'on',\n 'to',\n 'up',\n 'via',\n 'per',\n 'off',\n 'out',\n\n // English prepositions (5+ letters - optional, some style guides capitalize these)\n // 'about',\n // 'above',\n // 'across',\n // 'after',\n // 'among',\n // 'below',\n // 'under',\n // 'until',\n // 'with',\n] as const;\n\n/**\n * Indonesian and international acronyms\n * These always remain UPPERCASE in title case\n */\nexport const ACRONYMS = [\n // Indonesian government & military\n 'DKI', // Daerah Khusus Ibukota\n 'DIY', // Daerah Istimewa Yogyakarta\n 'TNI', // Tentara Nasional Indonesia\n 'POLRI', // Kepolisian Republik Indonesia\n 'ABRI', // Angkatan Bersenjata Republik Indonesia\n 'MPR', // Majelis Permusyawaratan Rakyat\n 'DPR', // Dewan Perwakilan Rakyat\n 'KPK', // Komisi Pemberantasan Korupsi\n 'BIN', // Badan Intelijen Negara\n\n // Indonesian business entities\n 'PT', // Perseroan Terbatas\n 'CV', // Commanditaire Vennootschap\n 'UD', // Usaha Dagang\n 'PD', // Perusahaan Daerah\n 'Tbk', // Terbuka (publicly traded)\n 'BUMN', // Badan Usaha Milik Negara\n 'BUMD', // Badan Usaha Milik Daerah\n\n // Indonesian banks\n 'BCA', // Bank Central Asia\n 'BRI', // Bank Rakyat Indonesia\n 'BNI', // Bank Negara Indonesia\n 'BTN', // Bank Tabungan Negara\n 'BSI', // Bank Syariah Indonesia\n 'BPD', // Bank Pembangunan Daerah\n\n // Indonesian government services\n 'KTP', // Kartu Tanda Penduduk\n 'NIK', // Nomor Induk Kependudukan\n 'NPWP', // Nomor Pokok Wajib Pajak\n 'SIM', // Surat Izin Mengemudi\n 'STNK', // Surat Tanda Nomor Kendaraan\n 'BPJS', // Badan Penyelenggara Jaminan Sosial\n 'KIS', // Kartu Indonesia Sehat\n 'KIP', // Kartu Indonesia Pintar\n 'PKH', // Program Keluarga Harapan\n\n // Indonesian utilities & infrastructure\n 'PLN', // Perusahaan Listrik Negara\n 'PDAM', // Perusahaan Daerah Air Minum\n 'PGN', // Perusahaan Gas Negara\n 'KAI', // Kereta Api Indonesia\n 'MRT', // Mass Rapid Transit\n 'LRT', // Light Rail Transit\n\n // Indonesian taxes & fees\n 'PBB', // Pajak Bumi dan Bangunan\n 'PPh', // Pajak Penghasilan\n 'PPN', // Pajak Pertambahan Nilai\n 'BPHTB', // Bea Perolehan Hak atas Tanah dan Bangunan\n\n // Indonesian education\n 'UI', // Universitas Indonesia\n 'ITB', // Institut Teknologi Bandung\n 'UGM', // Universitas Gadjah Mada\n 'IPB', // Institut Pertanian Bogor\n 'ITS', // Institut Teknologi Sepuluh Nopember\n 'UNPAD', // Universitas Padjadjaran\n 'UNDIP', // Universitas Diponegoro\n 'UNAIR', // Universitas Airlangga\n 'UNS', // Universitas Sebelas Maret\n\n // Indonesian degrees (gelar)\n 'S.Pd', // Sarjana Pendidikan\n 'S.H', // Sarjana Hukum\n 'S.E', // Sarjana Ekonomi\n 'S.T', // Sarjana Teknik\n 'S.Kom', // Sarjana Komputer\n 'S.Si', // Sarjana Sains\n 'S.Sos', // Sarjana Sosial\n 'M.Pd', // Magister Pendidikan\n 'M.M', // Magister Manajemen\n 'M.T', // Magister Teknik\n 'M.Kom', // Magister Komputer\n\n // Common services\n 'ATM', // Automated Teller Machine\n 'POS', // Point of Sale\n 'SMS', // Short Message Service\n 'GPS', // Global Positioning System\n 'WiFi', // Wireless Fidelity (technically Wi-Fi)\n 'USB', // Universal Serial Bus\n 'PIN', // Personal Identification Number\n 'OTP', // One Time Password\n 'QR', // Quick Response\n\n // Technology & IT\n 'IT', // Information Technology\n 'AI', // Artificial Intelligence\n 'ML', // Machine Learning\n 'API', // Application Programming Interface\n 'UI', // User Interface (duplicate with Universitas Indonesia, context matters)\n 'UX', // User Experience\n 'SEO', // Search Engine Optimization\n 'SaaS', // Software as a Service\n 'CRM', // Customer Relationship Management\n 'ERP', // Enterprise Resource Planning\n\n // Business titles\n 'CEO', // Chief Executive Officer\n 'CFO', // Chief Financial Officer\n 'CTO', // Chief Technology Officer\n 'COO', // Chief Operating Officer\n 'CMO', // Chief Marketing Officer\n 'HR', // Human Resources\n 'PR', // Public Relations\n 'VP', // Vice President\n 'GM', // General Manager\n\n // International organizations\n 'UN', // United Nations\n 'WHO', // World Health Organization\n 'UNESCO', // United Nations Educational, Scientific and Cultural Organization\n 'NATO', // North Atlantic Treaty Organization\n 'ASEAN', // Association of Southeast Asian Nations\n 'APEC', // Asia-Pacific Economic Cooperation\n 'WTO', // World Trade Organization\n 'IMF', // International Monetary Fund\n\n // Medical\n 'ICU', // Intensive Care Unit\n 'ER', // Emergency Room\n 'MRI', // Magnetic Resonance Imaging\n 'CT', // Computed Tomography\n 'DNA', // Deoxyribonucleic Acid\n 'RNA', // Ribonucleic Acid\n 'HIV', // Human Immunodeficiency Virus\n 'AIDS', // Acquired Immunodeficiency Syndrome\n 'COVID', // Coronavirus Disease\n\n // Measurements & units\n 'KM', // Kilometer\n 'CM', // Centimeter\n 'MM', // Millimeter\n 'KG', // Kilogram\n 'RPM', // Revolutions Per Minute\n 'MPH', // Miles Per Hour\n 'KPH', // Kilometers Per Hour\n\n // Finance\n 'IPO', // Initial Public Offering\n 'ATM', // Automated Teller Machine (duplicate)\n 'ROI', // Return on Investment\n 'GDP', // Gross Domestic Product\n 'VAT', // Value Added Tax\n] as const;\n\n/**\n * Indonesian abbreviations mapping\n * Organized by category for maintainability\n */\nexport const ABBREVIATIONS: Record<string, string> = {\n // ========== Address Abbreviations ==========\n 'Jl.': 'Jalan',\n 'Gg.': 'Gang',\n 'No.': 'Nomor',\n 'Kp.': 'Kampung',\n 'Ds.': 'Desa',\n 'Kel.': 'Kelurahan',\n 'Kec.': 'Kecamatan',\n 'Kab.': 'Kabupaten',\n Kota: 'Kota',\n 'Prov.': 'Provinsi',\n 'Prop.': 'Provinsi',\n 'Rt.': 'Rukun Tetangga',\n 'Rw.': 'Rukun Warga',\n Blok: 'Blok',\n 'Komp.': 'Kompleks',\n Perumahan: 'Perumahan',\n 'Perum.': 'Perumahan',\n\n // ========== Academic Titles ==========\n 'Dr.': 'Doktor',\n 'Ir.': 'Insinyur',\n 'Prof.': 'Profesor',\n 'Drs.': 'Doktorandus',\n 'Dra.': 'Doktoranda',\n\n // Bachelor degrees\n 'S.Pd.': 'Sarjana Pendidikan',\n 'S.H.': 'Sarjana Hukum',\n 'S.E.': 'Sarjana Ekonomi',\n 'S.T.': 'Sarjana Teknik',\n 'S.Kom.': 'Sarjana Komputer',\n 'S.Si.': 'Sarjana Sains',\n 'S.Sos.': 'Sarjana Sosial',\n 'S.I.Kom.': 'Sarjana Ilmu Komunikasi',\n 'S.S.': 'Sarjana Sastra',\n 'S.Psi.': 'Sarjana Psikologi',\n 'S.Farm.': 'Sarjana Farmasi',\n 'S.Ked.': 'Sarjana Kedokteran',\n\n // Master degrees\n 'M.Sc.': 'Master of Science',\n 'M.M.': 'Magister Manajemen',\n 'M.Pd.': 'Magister Pendidikan',\n 'M.T.': 'Magister Teknik',\n 'M.Kom.': 'Magister Komputer',\n 'M.Si.': 'Magister Sains',\n 'M.H.': 'Magister Hukum',\n 'M.A.': 'Master of Arts',\n MBA: 'Master of Business Administration',\n\n // ========== Honorifics ==========\n 'Bpk.': 'Bapak',\n Ibu: 'Ibu',\n 'Sdr.': 'Saudara',\n 'Sdri.': 'Saudari',\n 'Yth.': 'Yang Terhormat',\n 'H.': 'Haji',\n 'Hj.': 'Hajjah',\n 'Tn.': 'Tuan',\n 'Ny.': 'Nyonya',\n 'Nn.': 'Nona',\n\n // ========== Organizations ==========\n 'PT.': 'Perseroan Terbatas',\n 'CV.': 'Commanditaire Vennootschap',\n 'UD.': 'Usaha Dagang',\n 'PD.': 'Perusahaan Daerah',\n 'Tbk.': 'Terbuka',\n Koperasi: 'Koperasi',\n Yayasan: 'Yayasan',\n\n // ========== Common Abbreviations ==========\n 'dst.': 'dan seterusnya',\n 'dsb.': 'dan sebagainya',\n 'dll.': 'dan lain-lain',\n 'dkk.': 'dan kawan-kawan',\n 'a.n.': 'atas nama',\n 'u.p.': 'untuk perhatian',\n 'u.b.': 'untuk beliau',\n 'c.q.': 'casu quo',\n 'hlm.': 'halaman',\n 'tgl.': 'tanggal',\n 'bln.': 'bulan',\n 'thn.': 'tahun',\n 'ttd.': 'tertanda',\n\n // ========== Contact Information ==========\n 'Tlp.': 'Telepon',\n 'Telp.': 'Telepon',\n 'HP.': 'Handphone',\n Fax: 'Faksimile',\n Email: 'Email',\n Website: 'Website',\n\n // ========== Days (Indonesian) ==========\n 'Sen.': 'Senin',\n 'Sel.': 'Selasa',\n 'Rab.': 'Rabu',\n 'Kam.': 'Kamis',\n 'Jum.': 'Jumat',\n 'Sab.': 'Sabtu',\n 'Min.': 'Minggu',\n\n // ========== Months (Indonesian) ==========\n 'Jan.': 'Januari',\n 'Feb.': 'Februari',\n 'Mar.': 'Maret',\n 'Apr.': 'April',\n Mei: 'Mei',\n 'Jun.': 'Juni',\n 'Jul.': 'Juli',\n 'Agt.': 'Agustus',\n 'Sep.': 'September',\n 'Okt.': 'Oktober',\n 'Nov.': 'November',\n 'Des.': 'Desember',\n\n // ========== Units & Measurements ==========\n 'kg.': 'kilogram',\n 'gr.': 'gram',\n 'lt.': 'liter',\n 'ml.': 'mililiter',\n 'km.': 'kilometer',\n 'cm.': 'sentimeter',\n 'mm.': 'milimeter',\n 'm2.': 'meter persegi',\n 'm3.': 'meter kubik',\n 'ha.': 'hektar',\n};\n\n/**\n * Common Indonesian profanity/swear words for filtering.\n * This is a basic set for simple masking.\n */\nexport const PROFANITY = [\n 'anjing',\n 'babi',\n 'bangsat',\n 'bajingan',\n 'brengsek',\n 'goblok',\n 'tolol',\n 'idiot',\n 'perek',\n 'jablay',\n 'kontol',\n 'memek',\n 'ngewe',\n 'puki',\n 'jembut',\n 'asu',\n 'itil',\n 'lanjiao',\n 'pantek',\n 'anying',\n 'anjrit',\n] as const;\n\n/**\n * Common Indonesian stopwords for removal.\n */\nexport const STOPWORDS = [\n 'ada',\n 'adalah',\n 'adanya',\n 'adapun',\n 'agak',\n 'agaknya',\n 'agar',\n 'akan',\n 'akankah',\n 'akhir',\n 'akhiri',\n 'akhirnya',\n 'aku',\n 'akulah',\n 'amat',\n 'amatlah',\n 'anda',\n 'andalah',\n 'antar',\n 'antara',\n 'antaranya',\n 'apa',\n 'apaan',\n 'apabila',\n 'apakah',\n 'apalagi',\n 'apatah',\n 'artinya',\n 'asal',\n 'asalkan',\n 'atas',\n 'atau',\n 'ataukah',\n 'ataupun',\n 'awal',\n 'awalnya',\n 'bagai',\n 'bagaikan',\n 'bagaimana',\n 'bagaimanakah',\n 'bagaimanapun',\n 'bagi',\n 'bagian',\n 'bahkan',\n 'bahwa',\n 'bahwasanya',\n 'baik',\n 'bakal',\n 'bakalan',\n 'balik',\n 'banyak',\n 'bapak',\n 'baru',\n 'bawah',\n 'beberapa',\n 'begini',\n 'beginian',\n 'beginikah',\n 'beginilah',\n 'begitu',\n 'begitukah',\n 'begitulah',\n 'begitupun',\n 'bekerja',\n 'belakang',\n 'belakangan',\n 'belum',\n 'belumlah',\n 'benar',\n 'benarkah',\n 'benarlah',\n 'berada',\n 'berakhir',\n 'berakhirlah',\n 'berakhirnya',\n 'berapa',\n 'berapakah',\n 'berapalah',\n 'berapapun',\n 'berarti',\n 'berawal',\n 'berbagai',\n 'berikut',\n 'berikutnya',\n 'berjumlah',\n 'berkali-kali',\n 'berkata',\n 'berkeinginan',\n 'berkenaan',\n 'berlainan',\n 'berlalu',\n 'berlangsung',\n 'berlebihan',\n 'bermacam',\n 'bermacam-macam',\n 'bermaksud',\n 'bermula',\n 'bersama',\n 'bersama-sama',\n 'bersiap',\n 'bersiap-siap',\n 'bertanya',\n 'bertanya-tanya',\n 'berturut',\n 'berturut-turut',\n 'bertutur',\n 'berujar',\n 'berupa',\n 'besar',\n 'betul',\n 'betulkah',\n 'biasa',\n 'biasanya',\n 'bila',\n 'bilakah',\n 'bisa',\n 'bisakah',\n 'boleh',\n 'bolehkah',\n 'bolehlah',\n 'buat',\n 'bukan',\n 'bukankah',\n 'bukanlah',\n 'bukannya',\n 'bulan',\n 'bung',\n 'cara',\n 'caranya',\n 'cukup',\n 'cukupkah',\n 'cukuplah',\n 'cuma',\n 'dahulu',\n 'dalam',\n 'dan',\n 'dapat',\n 'dari',\n 'daripada',\n 'datang',\n 'dekat',\n 'demi',\n 'demikian',\n 'demikianlah',\n 'dengan',\n 'depan',\n 'di',\n 'dia',\n 'diakhiri',\n 'diakhirinya',\n 'dialah',\n 'diantara',\n 'diantaranya',\n 'diberi',\n 'diberikan',\n 'diberikannya',\n 'dibuat',\n 'dibuatnya',\n 'didapat',\n 'didatangkan',\n 'digunakan',\n 'diibaratkan',\n 'diingat',\n 'diingatkan',\n 'diinginkan',\n 'dijawab',\n 'dijelaskan',\n 'dijelaskannya',\n 'dikarenakan',\n 'dikatakan',\n 'dikatakannya',\n 'dikerjakan',\n 'diketahui',\n 'diketahuinya',\n 'dikira',\n 'dilakukan',\n 'dilalui',\n 'dilihat',\n 'dimaksud',\n 'dimaksudkan',\n 'dimaksudkannya',\n 'dimana',\n 'dimanalah',\n 'dimulai',\n 'dimulailah',\n 'dimulainya',\n 'diminta',\n 'dimintai',\n 'dimisalkan',\n 'dimungkinkan',\n 'dini',\n 'dipastikan',\n 'diperbuat',\n 'diperbuatnya',\n 'dipergunakan',\n 'diperkirakan',\n 'diperlihatkan',\n 'diperlukan',\n 'diperlukannya',\n 'dipersoalkan',\n 'dipertanyakan',\n 'dipunyai',\n 'diri',\n 'dirinya',\n 'disampaikan',\n 'disebut',\n 'disebutkan',\n 'disebutkannya',\n 'disini',\n 'disinilah',\n 'disitulah',\n 'diterangkan',\n 'diterangkannya',\n 'diteruskan',\n 'ditujukan',\n 'ditunjuk',\n 'ditunjuki',\n 'ditunjukkan',\n 'ditunjukkannya',\n 'ditunjuknya',\n 'dituturkan',\n 'dituturkannya',\n 'diucapkan',\n 'diucapkannya',\n 'diungkapkan',\n 'dua',\n 'dulu',\n 'empat',\n 'enggak',\n 'enggaknya',\n 'entah',\n 'entahlah',\n 'guna',\n 'gunakan',\n 'hal',\n 'hampir',\n 'hanya',\n 'hanyalah',\n 'hari',\n 'harus',\n 'haruslah',\n 'harusnya',\n 'hendak',\n 'hendaklah',\n 'hendaknya',\n 'hingga',\n 'ia',\n 'ialah',\n 'ibarat',\n 'ibaratkan',\n 'ibaratnya',\n 'ibu',\n 'ikut',\n 'ingat',\n 'ingat-ingat',\n 'ingin',\n 'inginkah',\n 'inginkan',\n 'ini',\n 'inikah',\n 'inilah',\n 'itu',\n 'itukah',\n 'itulah',\n 'jadi',\n 'jadilah',\n 'jadinya',\n 'jangan',\n 'jangankan',\n 'janganlah',\n 'jauh',\n 'jawab',\n 'jawaban',\n 'jawabnya',\n 'jelas',\n 'jelaskan',\n 'jelaslah',\n 'jelasnya',\n 'jika',\n 'jikalau',\n 'juga',\n 'jumlah',\n 'jumlahnya',\n 'justru',\n 'kala',\n 'kalau',\n 'kalaulah',\n 'kalaupun',\n 'kali',\n 'kalian',\n 'kami',\n 'kamilah',\n 'kamu',\n 'kamulah',\n 'kan',\n 'kapan',\n 'kapankah',\n 'kapanpun',\n 'karena',\n 'karenanya',\n 'ke',\n 'keadaan',\n 'kebetulan',\n 'kecil',\n 'kedua',\n 'keduanya',\n 'keinginan',\n 'kelak',\n 'kelihatan',\n 'kelihatannya',\n 'kelima',\n 'keluar',\n 'kembali',\n 'kemudian',\n 'kemungkinan',\n 'kemungkinannya',\n 'kenapa',\n 'kepada',\n 'kepadanya',\n 'kesampaian',\n 'keseluruhan',\n 'keseluruhannya',\n 'keterlaluan',\n 'ketika',\n 'khususnya',\n 'kini',\n 'kinilah',\n 'kira',\n 'kira-kira',\n 'kiranya',\n 'kita',\n 'kitalah',\n 'kok',\n 'kurang',\n 'lagi',\n 'lagian',\n 'lah',\n 'lain',\n 'lainnya',\n 'lalu',\n 'lama',\n 'lamanya',\n 'lanjut',\n 'lanjutnya',\n 'lebih',\n 'lewat',\n 'luar',\n 'macam',\n 'maka',\n 'makanya',\n 'makin',\n 'malah',\n 'malahan',\n 'mampu',\n 'mampukah',\n 'mana',\n 'manakala',\n 'manalagi',\n 'masih',\n 'masihkah',\n 'masing',\n 'masing-masing',\n 'mau',\n 'maupun',\n 'melainkan',\n 'melakukan',\n 'melalui',\n 'melihat',\n 'melihatnya',\n 'memang',\n 'memastikan',\n 'memberi',\n 'memberikan',\n 'membuat',\n 'memerlukan',\n 'memihak',\n 'meminta',\n 'memisalkan',\n 'memperbuat',\n 'mempergunakan',\n 'memperkirakan',\n 'memperlihatkan',\n 'mempersiapkan',\n 'mempersoalkan',\n 'mempertanyakan',\n 'mempunyai',\n 'memulai',\n 'memungkinkan',\n 'memutuskan',\n 'menanti',\n 'menanti-nanti',\n 'menantikan',\n 'menunjuk',\n 'menunjuknya',\n 'menuju',\n 'menurut',\n 'menurutnya',\n 'menurutmu',\n 'menurutku',\n 'menurutnya',\n 'menurut mereka',\n 'menyampaikan',\n 'menyebut',\n 'menyebutkan',\n 'menjelaskan',\n 'menjadi',\n 'menjadikan',\n 'menjalani',\n 'menjelang',\n 'menjawab',\n 'menunjukkan',\n 'menuangkan',\n 'menulis',\n 'menyatakan',\n 'merupakan',\n 'mereka',\n 'merekalah',\n 'meski',\n 'meskipun',\n 'mula',\n 'mulai',\n 'mulailah',\n 'mulanya',\n 'mungkin',\n 'mungkinkah',\n 'nah',\n 'naik',\n 'namun',\n 'nanti',\n 'nantinya',\n 'nyaris',\n 'oleh',\n 'olehnya',\n 'orang',\n 'pada',\n 'padahal',\n 'padanya',\n 'pakai',\n 'paling',\n 'panjang',\n 'pantas',\n 'para',\n 'pasti',\n 'pastilah',\n 'pagi',\n 'per',\n 'pernah',\n 'persoalan',\n 'pertama',\n 'pertama-tama',\n 'perlu',\n 'perlukah',\n 'perlulah',\n 'pernah',\n 'pihak',\n 'pihaknya',\n 'pukul',\n 'pula',\n 'pun',\n 'punya',\n 'rasa',\n 'rasanya',\n 'rata',\n 'rupanya',\n 'saat',\n 'saatnya',\n 'saja',\n 'sajalah',\n 'salam',\n 'saling',\n 'sama',\n 'sama-sama',\n 'sambil',\n 'sampai',\n 'sampai-sampai',\n 'sampaikan',\n 'sana',\n 'sangat',\n 'sangatlah',\n 'satu',\n 'saya',\n 'sayalah',\n 'sayang',\n 'seperti',\n 'seperti-itu',\n 'sepura',\n 'sebab',\n 'sebabnya',\n 'sebagai',\n 'sebagaimana',\n 'sebagainya',\n 'sebagian',\n 'sebaik',\n 'sebaik-baiknya',\n 'sebaiknya',\n 'sebaliknya',\n 'sebanyak',\n 'sebegini',\n 'sebegitu',\n 'sebelum',\n 'sebelumnya',\n 'sebenarnya',\n 'seberapa',\n 'sebesar',\n 'sebetulnya',\n 'sebisanya',\n 'sebuah',\n 'sebut',\n 'sebutkan',\n 'sebutnya',\n 'secara',\n 'secukupnya',\n 'sedang',\n 'sedangkan',\n 'sedikit',\n 'sedikitnya',\n 'sedemikian',\n 'sediakala',\n 'sedikit',\n 'sedikitnya',\n 'segala',\n 'segalanya',\n 'segera',\n 'seharusnya',\n 'sehingga',\n 'seingat',\n 'sejak',\n 'sejauh',\n 'sejenak',\n 'sejumlah',\n 'sekali',\n 'sekali-kali',\n 'sekalian',\n 'sekaligus',\n 'sekalipun',\n 'sekarang',\n 'sekaranglah',\n 'sekecil',\n 'seketika',\n 'sekiranya',\n 'sekitar',\n 'sekitarnya',\n 'sekurang',\n 'sekurangnya',\n 'sela',\n 'selalu',\n 'selama',\n 'selama-lamanya',\n 'selamanya',\n 'selanjutnya',\n 'seluruh',\n 'seluruhnya',\n 'semacam',\n 'semakin',\n 'semampu',\n 'semampunya',\n 'semasa',\n 'semata',\n 'semata-mata',\n 'semaunya',\n 'sementara',\n 'semisal',\n 'semisalnya',\n 'sempat',\n 'semua',\n 'semuanya',\n 'semula',\n 'sendiri',\n 'sendirinya',\n 'seolah',\n 'seolah-olah',\n 'seorang',\n 'sepanjang',\n 'sepantasnya',\n 'sepantasnyalah',\n 'seperempat',\n 'seperti',\n 'sepertinya',\n 'sepihak',\n 'sepuluh',\n 'seratus',\n 'seribu',\n 'sering',\n 'seringnya',\n 'serta',\n 'serupa',\n 'sesaat',\n 'sesama',\n 'sesampai',\n 'sesampainya',\n 'sesegera',\n 'sesekali',\n 'seseorang',\n 'sesuatu',\n 'sesuatunya',\n 'sesudah',\n 'sesudahnya',\n 'setelah',\n 'setempat',\n 'setengah',\n 'seterusnya',\n 'setiap',\n 'setidaknya',\n 'setinggi',\n 'seusai',\n 'sewaktu',\n 'siap',\n 'siapa',\n 'siapakah',\n 'siapapun',\n 'sini',\n 'sinilah',\n 'situ',\n 'situlah',\n 'suatu',\n 'sudah',\n 'sudahkah',\n 'sudahlah',\n 'supaya',\n 'tadi',\n 'tadinya',\n 'tahu',\n 'tak',\n 'tambah',\n 'tambahnya',\n 'tampak',\n 'tampaknya',\n 'tandas',\n 'tandasnya',\n 'tanpa',\n 'tanya',\n 'tanyakan',\n 'tanyanya',\n 'tapi',\n 'tegas',\n 'tegasnya',\n 'telah',\n 'tempat',\n 'tengah',\n 'tentang',\n 'tentu',\n 'tentulah',\n 'tentunya',\n 'tepat',\n 'terakhir',\n 'terasa',\n 'terbanyak',\n 'terdahulu',\n 'terdapat',\n 'terdiri',\n 'terdiri-dari',\n 'terhadap',\n 'terhadapnya',\n 'teringat',\n 'teringat-ingat',\n 'terjadi',\n 'terjadilah',\n 'terjadinya',\n 'terkira',\n 'terlalu',\n 'terlebih',\n 'terlihat',\n 'termasuk',\n 'ternyata',\n 'tersampaikan',\n 'tersebut',\n 'tersebutlah',\n 'tertentu',\n 'tertuju',\n 'terus',\n 'terutama',\n 'tetap',\n 'tetapi',\n 'tiap',\n 'tiba',\n 'tiba-tiba',\n 'tidak',\n 'tidakkah',\n 'tidaklah',\n 'tiga',\n 'tadi',\n 'tadinya',\n 'tinggi',\n 'toh',\n 'tuju',\n 'tunjuk',\n 'turut',\n 'tutur',\n 'tuturnya',\n 'ucap',\n 'ucapnya',\n 'ujar',\n 'ujarnya',\n 'umumnya',\n 'ungkap',\n 'ungkapnya',\n 'untuk',\n 'untaian',\n 'usai',\n 'usah',\n 'waduh',\n 'wah',\n 'wahai',\n 'walau',\n 'walaupun',\n 'wong',\n 'yaitu',\n 'yakin',\n 'yakni',\n 'yang',\n] as const;\n","import { ACRONYMS, LOWERCASE_WORDS } from './constants';\nimport { TitleCaseOptions } from './types';\n\n/**\n * Capitalize the first letter of a string and lowercase the rest\n *\n * This function converts the first character to uppercase and all remaining\n * characters to lowercase. It handles empty strings, Unicode characters,\n * and multi-word strings (only first word is affected).\n *\n * @param text - The text to capitalize\n * @returns The capitalized text\n *\n * @example\n * Basic usage:\n * ```typescript\n * capitalize('joko') // → 'Joko'\n * capitalize('JOKO') // → 'Joko'\n * capitalize('jOKO') // → 'Joko'\n * ```\n *\n * @example\n * Multi-word strings (only first word capitalized):\n * ```typescript\n * capitalize('joko widodo') // → 'Joko widodo'\n * capitalize('JOKO WIDODO') // → 'Joko widodo'\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * capitalize('') // → ''\n * capitalize('a') // → 'A'\n * capitalize('123abc') // → '123abc'\n * ```\n *\n * @public\n */\nexport function capitalize(text: string): string {\n if (!text) return text;\n return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();\n}\n\n/**\n * Convert text to title case following Indonesian grammar rules\n *\n * This function capitalizes the first letter of each word while respecting\n * Indonesian language conventions:\n * - Keeps particles lowercase (di, ke, dari, untuk, dan, etc.)\n * - Preserves known acronyms in uppercase (PT, CV, TNI, DKI, etc.)\n * - Handles hyphenated words correctly (anak-anak → Anak-Anak)\n * - Normalizes whitespace automatically\n *\n * @param text - The text to convert to title case\n * @param options - Optional configuration\n * @returns The title-cased text with proper Indonesian grammar\n *\n * @example\n * Basic usage:\n * ```typescript\n * toTitleCase('joko widodo')\n * // → 'Joko Widodo'\n *\n * toTitleCase('JOKO WIDODO')\n * // → 'Joko Widodo'\n * ```\n *\n * @example\n * Indonesian particles (kept lowercase):\n * ```typescript\n * toTitleCase('buku untuk anak dan orang tua')\n * // → 'Buku untuk Anak dan Orang Tua'\n *\n * toTitleCase('dari jakarta ke bandung')\n * // → 'Dari Jakarta ke Bandung'\n * // (first word always capitalized)\n * ```\n *\n * @example\n * Acronyms (preserved in uppercase):\n * ```typescript\n * toTitleCase('pt bank bca tbk')\n * // → 'PT Bank BCA Tbk'\n *\n * toTitleCase('dki jakarta')\n * // → 'DKI Jakarta'\n *\n * toTitleCase('tni angkatan darat')\n * // → 'TNI Angkatan Darat'\n * ```\n *\n * @example\n * Hyphenated words:\n * ```typescript\n * toTitleCase('anak-anak bermain')\n * // → 'Anak-Anak Bermain'\n *\n * toTitleCase('makan-makan di rumah')\n * // → 'Makan-Makan di Rumah'\n * ```\n *\n * @example\n * With options:\n * ```typescript\n * toTitleCase('PT BCA', { preserveAcronyms: false })\n * // → 'Pt Bca'\n *\n * toTitleCase('mobil dari jepang', { exceptions: ['jepang'] })\n * // → 'Mobil dari jepang'\n *\n * toTitleCase('HELLO WORLD', { strict: true })\n * // → 'Hello World'\n * ```\n *\n * @public\n */\nexport function toTitleCase(text: string, options?: TitleCaseOptions): string {\n if (!text) return text;\n\n const {\n preserveAcronyms = true,\n strict = false,\n exceptions = [],\n } = options || {};\n\n const lowercaseSet = new Set([...LOWERCASE_WORDS, ...exceptions]);\n const acronymSet = new Set(ACRONYMS);\n\n const normalized = normalizeSpaces(text);\n const words = normalized.split(' ');\n\n return words\n .map((word, index) => {\n if (!word) return word;\n\n if (word.includes('-')) {\n return processHyphenatedWord(word, index === 0, {\n lowercaseSet,\n acronymSet,\n preserveAcronyms,\n strict,\n });\n }\n\n return processWord(word, index === 0, {\n lowercaseSet,\n acronymSet,\n preserveAcronyms,\n strict,\n });\n })\n .join(' ');\n}\n\n/**\n * Normalize whitespace in text (trim and collapse multiple spaces)\n */\nfunction normalizeSpaces(text: string): string {\n return text.trim().replace(/\\s+/g, ' ');\n}\n\n/**\n * Process a single word according to title case rules\n */\nfunction processWord(\n word: string,\n isFirstWord: boolean,\n context: {\n lowercaseSet: Set<string>;\n acronymSet: Set<string>;\n preserveAcronyms: boolean;\n strict: boolean;\n }\n): string {\n const { lowercaseSet, acronymSet, preserveAcronyms, strict } = context;\n const lowerWord = word.toLowerCase();\n const upperWord = word.toUpperCase();\n\n // Check if it's a known acronym\n if (preserveAcronyms && acronymSet.has(upperWord)) {\n return upperWord;\n }\n\n // Check if it should be lowercase (but not at the start)\n if (!isFirstWord && lowercaseSet.has(lowerWord)) {\n return lowerWord;\n }\n\n // Capitalize first letter\n if (strict) {\n return capitalizeFirstLetter(lowerWord);\n }\n\n return capitalizeFirstLetter(word.toLowerCase());\n}\n\n/**\n * Process hyphenated word (e.g., \"anak-anak\")\n */\nfunction processHyphenatedWord(\n word: string,\n isFirstWord: boolean,\n context: {\n lowercaseSet: Set<string>;\n acronymSet: Set<string>;\n preserveAcronyms: boolean;\n strict: boolean;\n }\n): string {\n return word\n .split('-')\n .map((part, index) =>\n processWord(part, isFirstWord && index === 0, context)\n )\n .join('-');\n}\n\n/**\n * Capitalize first letter of a word\n */\nfunction capitalizeFirstLetter(word: string): string {\n if (!word) return word;\n return word.charAt(0).toUpperCase() + word.slice(1);\n}\n\n/**\n * Convert text to sentence case (capitalize first letter of sentences only)\n *\n * This function capitalizes the first character of the text and the first\n * character after sentence-ending punctuation (. ! ?), while keeping\n * everything else in lowercase.\n *\n * **Sentence Detection Rules:**\n * - Period (.), exclamation (!), question mark (?) mark sentence endings\n * - Next letter after punctuation + space is capitalized\n * - Handles multiple spaces and newlines\n * - Does NOT treat abbreviations as sentence endings (e.g., \"Dr. Smith\")\n *\n * @param text - The text to convert to sentence case\n * @returns The sentence-cased text\n *\n * @example\n * Basic usage:\n * ```typescript\n * toSentenceCase('JOKO WIDODO ADALAH PRESIDEN')\n * // → 'Joko widodo adalah presiden'\n *\n * toSentenceCase('joko widodo adalah presiden')\n * // → 'Joko widodo adalah presiden'\n * ```\n *\n * @example\n * Multiple sentences:\n * ```typescript\n * toSentenceCase('halo, apa kabar? baik-baik saja.')\n * // → 'Halo, apa kabar? Baik-baik saja.'\n *\n * toSentenceCase('jakarta. surabaya. bandung.')\n * // → 'Jakarta. Surabaya. Bandung.'\n * ```\n *\n * @example\n * Different punctuation:\n * ```typescript\n * toSentenceCase('wow! amazing! fantastic!')\n * // → 'Wow! Amazing! Fantastic!'\n *\n * toSentenceCase('siapa nama anda? saya joko.')\n * // → 'Siapa nama anda? Saya joko.'\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * toSentenceCase('')\n * // → ''\n *\n * toSentenceCase('hello')\n * // → 'Hello'\n *\n * toSentenceCase(' hello. world. ')\n * // → 'Hello. World.'\n * ```\n *\n * @public\n */\nexport function toSentenceCase(text: string): string {\n if (!text) return text;\n\n const normalized = text.trim().replace(/\\s+/g, ' ');\n\n let result = '';\n let shouldCapitalize = true;\n\n for (let i = 0; i < normalized.length; i++) {\n const char = normalized[i];\n\n // Capitalize if we should and this is a letter\n if (shouldCapitalize && /[a-zA-ZÀ-ÿ]/.test(char)) {\n result += char.toUpperCase();\n shouldCapitalize = false;\n } else {\n result += char.toLowerCase();\n }\n\n // Mark next letter for capitalization after sentence-ending punctuation\n if (isSentenceEnd(char)) {\n shouldCapitalize = true;\n }\n\n // Handle abbreviations: don't capitalize after period in abbreviations\n if (char === '.' && i + 1 < normalized.length) {\n const nextChar = normalized[i + 1];\n\n // If next char is not a space, likely an abbreviation\n if (nextChar !== ' ' && !/[.!?]/.test(nextChar)) {\n shouldCapitalize = false;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Check if a character marks the end of a sentence\n */\nfunction isSentenceEnd(char: string): boolean {\n return char === '.' || char === '!' || char === '?';\n}\n","import type { SlugifyOptions } from './types';\n\n/**\n * Generate URL-safe slugs with Indonesian language support\n *\n * This function converts text into URL-friendly slugs by:\n * - Converting to lowercase (configurable)\n * - Replacing spaces with separators (default: hyphen)\n * - Replacing Indonesian conjunctions (& → dan, / → atau)\n * - Removing special characters\n * - Collapsing multiple separators\n * - Trimming leading/trailing separators\n *\n * **Character Handling:**\n * - Alphanumeric (a-z, A-Z, 0-9): Preserved\n * - Spaces: Replaced with separator\n * - Ampersand (&): Replaced with \"dan\"\n * - Slash (/): Replaced with \"atau\"\n * - Hyphens (-): Preserved as separators\n * - Other special chars: Removed\n *\n * @param text - The text to convert to slug\n * @param options - Optional configuration\n * @returns The URL-safe slug\n *\n * @example\n * Basic usage:\n * ```typescript\n * slugify('Cara Mudah Belajar TypeScript')\n * // → 'cara-mudah-belajar-typescript'\n *\n * slugify('HELLO WORLD')\n * // → 'hello-world'\n * ```\n *\n * @example\n * Indonesian conjunctions:\n * ```typescript\n * slugify('Ibu & Anak: Tips Kesehatan')\n * // → 'ibu-dan-anak-tips-kesehatan'\n *\n * slugify('Baju Pria/Wanita')\n * // → 'baju-pria-atau-wanita'\n *\n * slugify('A & B / C')\n * // → 'a-dan-b-atau-c'\n * ```\n *\n * @example\n * Special characters removed:\n * ```typescript\n * slugify('Harga Rp 100.000 (Diskon 20%)')\n * // → 'harga-rp-100000-diskon-20'\n *\n * slugify('Email: test@example.com')\n * // → 'email-testexamplecom'\n * ```\n *\n * @example\n * Multiple spaces/separators collapsed:\n * ```typescript\n * slugify('Produk Terbaru - - - 2024')\n * // → 'produk-terbaru-2024'\n *\n * slugify(' Hello World ')\n * // → 'hello-world'\n * ```\n *\n * @example\n * With options:\n * ```typescript\n * slugify('Hello World', { separator: '_' })\n * // → 'hello_world'\n *\n * slugify('Hello World', { lowercase: false })\n * // → 'Hello-World'\n *\n * slugify('C++ Programming', {\n * replacements: { 'C++': 'cpp' }\n * })\n * // → 'cpp-programming'\n *\n * slugify('Hello-World', { trim: false })\n * // → 'hello-world' (same, but won't trim if leading/trailing)\n * ```\n *\n * @public\n */\n\nexport function slugify(text: string, options?: SlugifyOptions): string {\n if (!text) return '';\n\n const {\n separator = '-',\n lowercase = true,\n replacements = {},\n trim = true,\n } = options || {};\n\n let result = text;\n\n // Apply custom replacements first\n for (const [search, replace] of Object.entries(replacements)) {\n result = result.replace(new RegExp(escapeRegex(search), 'g'), replace);\n }\n\n // Replace Indonesian conjunctions\n result = result.replace(/&/g, ' dan ');\n result = result.replace(/\\//g, ' atau ');\n\n // Convert to lowercase if needed\n if (lowercase) {\n result = result.toLowerCase();\n }\n\n // Remove chars that should NOT become separators (dots, apostrophes, @, accents, etc)\n result = result.replace(/[.'@éèêëàâäôöûüùïîçñ™®©]/g, '');\n\n // Replace remaining special chars with separator (everything except alphanumeric, spaces, hyphens)\n result = result.replace(/[^\\w\\s-]+/g, separator);\n\n // Replace spaces with separator\n result = result.replace(/\\s+/g, separator);\n\n // Replace existing hyphens with separator (if separator is not hyphen)\n if (separator !== '-') {\n result = result.replace(/-/g, separator);\n }\n\n // Only when trim: true, collapse multiple separators AND trim edges\n if (trim) {\n const separatorRegex = new RegExp(`\\\\${separator}+`, 'g');\n result = result.replace(separatorRegex, separator);\n\n const trimRegex = new RegExp(`^\\\\${separator}+|\\\\${separator}+$`, 'g');\n result = result.replace(trimRegex, '');\n }\n\n return result;\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import type { SanitizeOptions } from './types';\n\n/**\n * Normalize all whitespace characters to single spaces\n *\n * This function:\n * - Collapses multiple spaces into one\n * - Converts tabs, newlines, and other whitespace to single space\n * - Trims leading and trailing whitespace\n * - Handles Unicode whitespace characters\n *\n * **Whitespace Characters Normalized:**\n * - Space (` `)\n * - Tab (`\\t`)\n * - Newline (`\\n`)\n * - Carriage return (`\\r`)\n * - Form feed (`\\f`)\n * - Vertical tab (`\\v`)\n * - Non-breaking space (`\\u00A0`)\n * - Other Unicode spaces\n *\n * @param text - The text to normalize\n * @returns Text with normalized whitespace\n *\n * @example\n * Basic usage:\n * ```typescript\n * normalizeWhitespace('hello world')\n * // → 'hello world'\n *\n * normalizeWhitespace('hello\\tworld')\n * // → 'hello world'\n * ```\n *\n * @example\n * Multiple types of whitespace:\n * ```typescript\n * normalizeWhitespace('hello\\n\\nworld')\n * // → 'hello world'\n *\n * normalizeWhitespace('hello\\r\\nworld')\n * // → 'hello world'\n *\n * normalizeWhitespace('line1\\n\\nline2\\tword')\n * // → 'line1 line2 word'\n * ```\n *\n * @example\n * Leading and trailing whitespace:\n * ```typescript\n * normalizeWhitespace(' hello world ')\n * // → 'hello world'\n *\n * normalizeWhitespace('\\n\\thello\\t\\n')\n * // → 'hello'\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * normalizeWhitespace('')\n * // → ''\n *\n * normalizeWhitespace(' ')\n * // → ''\n *\n * normalizeWhitespace('hello')\n * // → 'hello'\n * ```\n *\n * @public\n */\nexport function normalizeWhitespace(text: string): string {\n if (!text) return text;\n\n // Replace all whitespace characters with single space\n // \\s matches: space, tab, newline, carriage return, form feed, vertical tab\n return text.trim().replace(/\\s+/g, ' ');\n}\n\n/**\n * Remove or replace unwanted characters from text\n *\n * This function provides flexible text sanitization with options to:\n * - Remove newlines\n * - Remove extra spaces\n * - Remove punctuation\n * - Keep only allowed characters\n * - Trim leading/trailing whitespace\n *\n * @param text - The text to sanitize\n * @param options - Sanitization options\n * @returns The sanitized text\n *\n * @example\n * Remove extra spaces (default):\n * ```typescript\n * sanitize('hello world')\n * // → 'hello world'\n * ```\n *\n * @example\n * Remove newlines:\n * ```typescript\n * sanitize('line1\\nline2\\nline3', { removeNewlines: true })\n * // → 'line1 line2 line3'\n * ```\n *\n * @example\n * Remove punctuation:\n * ```typescript\n * sanitize('Hello, World!', { removePunctuation: true })\n * // → 'Hello World'\n * ```\n *\n * @example\n * Allow only specific characters:\n * ```typescript\n * sanitize('ABC123!@#', { allowedChars: 'A-Za-z0-9' })\n * // → 'ABC123'\n *\n * sanitize('Hello123!@#', { allowedChars: 'a-z' })\n * // → 'ello'\n * ```\n *\n * @example\n * Combined options:\n * ```typescript\n * sanitize(' Hello,\\n World! ', {\n * removeNewlines: true,\n * removePunctuation: true,\n * removeExtraSpaces: true,\n * trim: true\n * })\n * // → 'Hello World'\n * ```\n *\n * @public\n */\nexport function sanitize(text: string, options?: SanitizeOptions): string {\n if (!text) return text;\n\n const {\n removeNewlines = false,\n removeExtraSpaces = true,\n removePunctuation = false,\n allowedChars,\n trim = true,\n } = options || {};\n\n let result = text;\n\n // Remove newlines first (replace with space)\n if (removeNewlines) {\n result = result.replace(/[\\n\\r]/g, ' ');\n }\n\n // Remove punctuation\n if (removePunctuation) {\n result = result.replace(/[!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~]/g, '');\n }\n\n // Keep only allowed characters\n if (allowedChars) {\n const allowedRegex = new RegExp(`[^${allowedChars}]`, 'g');\n result = result.replace(allowedRegex, '');\n }\n\n // Remove extra spaces\n if (removeExtraSpaces) {\n if (trim) {\n // When trimming, we can safely collapse all spaces\n if (removeNewlines) {\n result = result.replace(/\\s+/g, ' ');\n } else {\n result = result.replace(/[ \\t]+/g, ' ');\n }\n } else {\n // When not trimming, preserve leading/trailing spaces\n // Only collapse spaces in the middle\n const leadingMatch = result.match(/^([ \\t]*)/);\n const trailingMatch = result.match(/([ \\t]*)$/);\n const leading = leadingMatch ? leadingMatch[1] : '';\n const trailing = trailingMatch ? trailingMatch[1] : '';\n\n const middle = result.slice(\n leading.length,\n result.length - trailing.length\n );\n const normalizedMiddle = removeNewlines\n ? middle.replace(/\\s+/g, ' ')\n : middle.replace(/[ \\t]+/g, ' ');\n\n result = leading + normalizedMiddle + trailing;\n }\n }\n\n // Trim\n if (trim) {\n result = result.trim();\n }\n\n return result;\n}\n\n/**\n * Remove diacritical marks (accents) from characters\n *\n * Converts accented characters to their base form:\n * - é → e\n * - ñ → n\n * - ü → u\n * - etc.\n *\n * Useful for:\n * - Search normalization\n * - Sorting/comparison\n * - URL generation\n * - Database queries\n *\n * @param text - The text to remove accents from\n * @returns Text with accents removed\n *\n * @example\n * Basic usage:\n * ```typescript\n * removeAccents('café')\n * // → 'cafe'\n *\n * removeAccents('résumé')\n * // → 'resume'\n * ```\n *\n * @example\n * Various accents:\n * ```typescript\n * removeAccents('naïve')\n * // → 'naive'\n *\n * removeAccents('Zürich')\n * // → 'Zurich'\n *\n * removeAccents('São Paulo')\n * // → 'Sao Paulo'\n * ```\n *\n * @example\n * Mixed text:\n * ```typescript\n * removeAccents('École française à Montréal')\n * // → 'Ecole francaise a Montreal'\n * ```\n *\n * @public\n */\nexport function removeAccents(text: string): string {\n if (!text) return text;\n\n // Manual mapping for Nordic and other special characters that don't decompose\n const specialChars: Record<string, string> = {\n Ø: 'O',\n ø: 'o',\n Æ: 'AE',\n æ: 'ae',\n Å: 'A',\n å: 'a',\n Đ: 'D',\n đ: 'd',\n Ł: 'L',\n ł: 'l',\n Þ: 'TH',\n þ: 'th',\n ß: 'ss',\n };\n\n // First apply manual mappings\n let result = text;\n for (const [accented, plain] of Object.entries(specialChars)) {\n result = result.replace(new RegExp(accented, 'g'), plain);\n }\n\n // Then normalize to NFD and remove combining diacritical marks\n return result.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\n}\n","import { ABBREVIATIONS } from './constants';\nimport type { ExpandOptions } from './types';\n\n/**\n * Expand Indonesian abbreviations to their full form\n *\n * This function expands common Indonesian abbreviations like:\n * - Address: Jl. → Jalan, Kec. → Kecamatan\n * - Titles: Dr. → Doktor, S.H. → Sarjana Hukum\n * - Honorifics: Bpk. → Bapak, Yth. → Yang Terhormat\n * - Organizations: PT. → Perseroan Terbatas\n * - Common: dll. → dan lain-lain\n *\n * **Features:**\n * - Case-insensitive matching (Jl. = jl. = JL.)\n * - Mode filtering (all, address, title, org)\n * - Custom mapping support\n * - Preserves surrounding text\n * - Multiple abbreviations in one string\n *\n * @param text - The text containing abbreviations to expand\n * @param options - Optional configuration\n * @returns Text with abbreviations expanded\n *\n * @example\n * Basic usage:\n * ```typescript\n * expandAbbreviation('Jl. Sudirman No. 123')\n * // → 'Jalan Sudirman Nomor 123'\n *\n * expandAbbreviation('Dr. Joko Widodo, S.H.')\n * // → 'Doktor Joko Widodo, Sarjana Hukum'\n * ```\n *\n * @example\n * Address abbreviations:\n * ```typescript\n * expandAbbreviation('Kab. Bogor, Kec. Ciawi')\n * // → 'Kabupaten Bogor, Kecamatan Ciawi'\n *\n * expandAbbreviation('Jl. Merdeka Gg. 5 No. 10')\n * // → 'Jalan Merdeka Gang 5 Nomor 10'\n * ```\n *\n * @example\n * Academic titles:\n * ```typescript\n * expandAbbreviation('Prof. Dr. Ir. Ahmad')\n * // → 'Profesor Doktor Insinyur Ahmad'\n *\n * expandAbbreviation('Saya lulusan S.T. dari ITB')\n * // → 'Saya lulusan Sarjana Teknik dari ITB'\n * ```\n *\n * @example\n * Honorifics:\n * ```typescript\n * expandAbbreviation('Yth. Bpk. H. Ahmad')\n * // → 'Yang Terhormat Bapak Haji Ahmad'\n * ```\n *\n * @example\n * Organizations:\n * ```typescript\n * expandAbbreviation('PT. Maju Jaya Tbk.')\n * // → 'Perseroan Terbatas Maju Jaya Terbuka'\n * ```\n *\n * @example\n * Mode filtering:\n * ```typescript\n * expandAbbreviation('Dr. Joko di Jl. Sudirman', { mode: 'address' })\n * // → 'Dr. Joko di Jalan Sudirman'\n * // Only expands address abbreviations\n *\n * expandAbbreviation('Prof. Dr. di Jl. Sudirman', { mode: 'title' })\n * // → 'Profesor Doktor di Jl. Sudirman'\n * // Only expands title abbreviations\n * ```\n *\n * @example\n * Custom mappings:\n * ```typescript\n * expandAbbreviation('BUMN adalah perusahaan negara', {\n * customMap: { 'BUMN': 'Badan Usaha Milik Negara' }\n * })\n * // → 'Badan Usaha Milik Negara adalah perusahaan negara'\n * ```\n *\n * @example\n * Case sensitivity:\n * ```typescript\n * expandAbbreviation('jl. sudirman')\n * // → 'Jalan sudirman' (default: preserves surrounding case)\n *\n * expandAbbreviation('JL. SUDIRMAN')\n * // → 'Jalan SUDIRMAN'\n * ```\n *\n * @public\n */\nexport function expandAbbreviation(\n text: string,\n options?: ExpandOptions\n): string {\n if (!text) return text;\n\n const { mode = 'all', customMap = {}, preserveCase = false } = options || {};\n\n // Combine built-in and custom abbreviations\n const abbreviationsMap = {\n ...getAbbreviationsByMode(mode),\n ...customMap,\n };\n\n let result = text;\n\n // Sort by length (longest first) to avoid partial replacements\n // Example: Replace \"S.H.\" before \"S.\" to avoid \"Sarjana.H.\"\n const sortedAbbrevs = Object.keys(abbreviationsMap).sort(\n (a, b) => b.length - a.length\n );\n\n for (const abbrev of sortedAbbrevs) {\n const expansion = abbreviationsMap[abbrev];\n\n // Create case-insensitive regex with word boundaries\n // Handle word boundaries intelligently:\n // - If starts with word char, add \\b prefix\n // - If ends with word char, add \\b suffix\n // - If ends with dot, don't add \\b suffix (dots are non-word chars)\n const startBoundary = /^\\w/.test(abbrev) ? '\\\\b' : '';\n const endBoundary = /\\w$/.test(abbrev) ? '\\\\b' : '';\n\n const regex = new RegExp(\n `${startBoundary}${escapeRegex(abbrev)}${endBoundary}`,\n 'gi'\n );\n\n result = result.replace(regex, (match) => {\n // If preserveCase is false, use the expansion as-is\n if (!preserveCase) {\n return expansion;\n }\n\n // If preserveCase is true, try to match case of original\n return matchCase(match, expansion);\n });\n }\n\n return result;\n}\n\n/**\n * Get abbreviations filtered by mode\n */\nfunction getAbbreviationsByMode(\n mode: 'all' | 'address' | 'title' | 'org'\n): Record<string, string> {\n if (mode === 'all') {\n return ABBREVIATIONS;\n }\n\n const filtered: Record<string, string> = {};\n\n // Define which abbreviations belong to which category\n const addressAbbrevs = [\n 'Jl.',\n 'Gg.',\n 'No.',\n 'Kp.',\n 'Ds.',\n 'Kel.',\n 'Kec.',\n 'Kab.',\n 'Kota',\n 'Prov.',\n 'Prop.',\n 'Rt.',\n 'Rw.',\n 'Blok',\n 'Komp.',\n 'Perumahan',\n 'Perum.',\n ];\n\n const titleAbbrevs = [\n 'Dr.',\n 'Ir.',\n 'Prof.',\n 'Drs.',\n 'Dra.',\n 'S.Pd.',\n 'S.H.',\n 'S.E.',\n 'S.T.',\n 'S.Kom.',\n 'S.Si.',\n 'S.Sos.',\n 'S.I.Kom.',\n 'S.S.',\n 'S.Psi.',\n 'S.Farm.',\n 'S.Ked.',\n 'M.Sc.',\n 'M.M.',\n 'M.Pd.',\n 'M.T.',\n 'M.Kom.',\n 'M.Si.',\n 'M.H.',\n 'M.A.',\n 'MBA',\n ];\n\n const orgAbbrevs = [\n 'PT.',\n 'CV.',\n 'UD.',\n 'PD.',\n 'Tbk.',\n 'Koperasi',\n 'Yayasan',\n ];\n\n // Filter based on mode\n for (const [abbrev, expansion] of Object.entries(ABBREVIATIONS)) {\n if (mode === 'address' && addressAbbrevs.includes(abbrev)) {\n filtered[abbrev] = expansion;\n } else if (mode === 'title' && titleAbbrevs.includes(abbrev)) {\n filtered[abbrev] = expansion;\n } else if (mode === 'org' && orgAbbrevs.includes(abbrev)) {\n filtered[abbrev] = expansion;\n }\n }\n\n return filtered;\n}\n\n/**\n * Escape special regex characters in a string\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Match the case pattern of original string to replacement\n * - ALL CAPS → ALL CAPS\n * - Title Case → Title Case\n * - lowercase → lowercase\n */\nfunction matchCase(original: string, replacement: string): string {\n // Check if original is all uppercase\n if (original === original.toUpperCase()) {\n return replacement.toUpperCase();\n }\n\n // Check if original is all lowercase\n if (original === original.toLowerCase()) {\n return replacement.toLowerCase();\n }\n\n // Check if original is title case (first letter uppercase)\n if (original.charAt(0) === original.charAt(0).toUpperCase()) {\n return (\n replacement.charAt(0).toUpperCase() + replacement.slice(1).toLowerCase()\n );\n }\n\n // Default: return as-is\n return replacement;\n}\n\n/**\n * Contract full forms to abbreviations (reverse of expand)\n *\n * @param text - The text containing full forms to contract\n * @param options - Optional configuration\n * @returns Text with full forms contracted\n *\n * @example\n * ```typescript\n * contractAbbreviation('Jalan Sudirman Nomor 123')\n * // → 'Jl. Sudirman No. 123'\n *\n * contractAbbreviation('Doktor Ahmad, Sarjana Hukum')\n * // → 'Dr. Ahmad, S.H.'\n * ```\n *\n * @public\n */\nexport function contractAbbreviation(\n text: string,\n options?: { mode?: 'all' | 'address' | 'title' | 'org' }\n): string {\n if (!text) return text;\n\n const { mode = 'all' } = options || {};\n\n // Get abbreviations and create reverse mapping\n const abbreviationsMap = getAbbreviationsByMode(mode);\n const reverseMap: Record<string, string> = {};\n\n for (const [abbrev, expansion] of Object.entries(abbreviationsMap)) {\n reverseMap[expansion] = abbrev;\n }\n\n let result = text;\n\n // Sort by length (longest first) to avoid partial replacements\n const sortedExpansions = Object.keys(reverseMap).sort(\n (a, b) => b.length - a.length\n );\n\n for (const expansion of sortedExpansions) {\n const abbrev = reverseMap[expansion];\n\n // Case-insensitive replace\n const regex = new RegExp(`\\\\b${escapeRegex(expansion)}\\\\b`, 'gi');\n\n result = result.replace(regex, abbrev);\n }\n\n return result;\n}\n","import { PROFANITY, STOPWORDS } from './constants';\n\n/**\n * Filters common Indonesian profanity words by masking them.\n *\n * @param text - The text to filter\n * @param mask - The masking character (default: '*')\n * @returns Filtered text\n *\n * @example\n * ```typescript\n * profanityFilter('kamu anjing banget'); // 'kamu ****** banget'\n * ```\n */\nexport function profanityFilter(text: string, mask: string = '*'): string {\n let filtered = text;\n\n PROFANITY.forEach((word) => {\n const regex = new RegExp(`\\\\b${word}\\\\b`, 'gi');\n filtered = filtered.replace(regex, mask.repeat(word.length));\n });\n\n return filtered;\n}\n\n/**\n * Removes common Indonesian stopwords from text.\n *\n * @param text - The text to process\n * @returns Text with stopwords removed\n *\n * @example\n * ```typescript\n * removeStopwords('saya sedang makan nasi'); // 'makan nasi'\n * ```\n */\nexport function removeStopwords(text: string): string {\n const words = text.split(/\\s+/);\n const filtered = words.filter(\n (word) => !(STOPWORDS as readonly string[]).includes(word.toLowerCase())\n );\n\n return filtered.join(' ');\n}\n","/**\n * Basic mapping of common informal Indonesian words to formal ones.\n */\nconst INFORMAL_MAP: Record<string, string> = {\n gw: 'saya',\n gua: 'saya',\n lu: 'kamu',\n lo: 'kamu',\n elo: 'kamu',\n lagi: 'sedang',\n gue: 'saya',\n gwe: 'saya',\n gak: 'tidak',\n ga: 'tidak',\n nggak: 'tidak',\n kalo: 'kalau',\n karna: 'karena',\n tapi: 'tetapi',\n udah: 'sudah',\n dah: 'sudah',\n aja: 'saja',\n banget: 'sekali',\n emang: 'memang',\n pake: 'pakai',\n bikin: 'membuat',\n kasih: 'memberi',\n dapet: 'dapat',\n liat: 'lihat',\n ngasih: 'memberi',\n nyari: 'mencari',\n nanya: 'bertanya',\n bilang: 'berkata',\n};\n\n/**\n * Normalizes informal Indonesian text to a more formal version.\n * This is a basic rule-based implementation.\n *\n * @param text - The text to normalize\n * @returns Formalized text\n *\n * @example\n * ```typescript\n * toFormal('gw lagi makan'); // 'saya sedang makan'\n * ```\n */\nexport function toFormal(text: string): string {\n const words = text.split(/\\s+/);\n const formalized = words.map((word) => {\n const lower = word.toLowerCase().replace(/[^\\w]/g, '');\n const formal = INFORMAL_MAP[lower];\n if (formal) {\n // Keep capitalization if possible\n if (word[0] === word[0].toUpperCase()) {\n return formal.charAt(0).toUpperCase() + formal.slice(1);\n }\n return formal;\n }\n return word;\n });\n\n return formalized.join(' ');\n}\n\n/**\n * Detects if a text follows \"alay\" style (unconventional capitalization or number substitution).\n *\n * @param text - The text to check\n * @returns `true` if alay style detected, `false` otherwise\n *\n * @example\n * ```typescript\n * isAlay('AqU sAyAnG qMu'); // true\n * isAlay('Makan 4y4m'); // true\n * ```\n */\nexport function isAlay(text: string): boolean {\n if (!text) return false;\n\n // Rule 1: Alternating caps (mOxEd cAsE)\n // More specific: lower-UPPER-lower or UPPER-lower-UPPER, multiple times\n const alternatingCaps = /([a-z][A-Z][a-z]|[A-Z][a-z][A-Z])/.test(text);\n\n // Rule 2: Number substitution in words\n const numberSub = /\\b\\w*[0431572]\\w*\\b/.test(text);\n\n // Rule 3: Excessive 'q' instead of 'k'\n const qSub = /q/i.test(text) && !/u/i.test(text);\n\n // Rule 4: Excessive characters (e.g., 'siiaappp')\n const excessiveChars = /(.)\\1{2,}/.test(text);\n\n return alternatingCaps || numberSub || qSub || excessiveChars;\n}\n","import type { TruncateOptions, ExtractOptions } from './types';\n\n/**\n * Truncate text to specified length, word-aware\n *\n * This function shortens text to a maximum length while:\n * - Respecting word boundaries (don't cut words in half)\n * - Adding ellipsis to indicate truncation\n * - Preserving original text if already short enough\n * - Accounting for ellipsis length in total character count\n *\n * **Features:**\n * - Smart word boundary detection\n * - Customizable ellipsis\n * - No truncation for short text\n * - Handles edge cases gracefully\n *\n * @param text - The text to truncate\n * @param maxLength - Maximum length of output (including ellipsis)\n * @param options - Optional configuration\n * @returns The truncated text with ellipsis if needed\n *\n * @example\n * Basic usage:\n * ```typescript\n * truncate('Ini adalah contoh text yang panjang', 20)\n * // → 'Ini adalah contoh...'\n *\n * truncate('Short text', 20)\n * // → 'Short text' (no truncation needed)\n * ```\n *\n * @example\n * Word boundary handling:\n * ```typescript\n * truncate('Ini adalah contoh text yang panjang', 20, { wordBoundary: true })\n * // → 'Ini adalah contoh...' (stops at word)\n *\n * truncate('Ini adalah contoh text yang panjang', 20, { wordBoundary: false })\n * // → 'Ini adalah contoh t...' (cuts mid-word)\n * ```\n *\n * @example\n * Custom ellipsis:\n * ```typescript\n * truncate('Ini adalah contoh text yang panjang', 20, { ellipsis: '…' })\n * // → 'Ini adalah contoh…'\n *\n * truncate('Ini adalah contoh text yang panjang', 20, { ellipsis: ' [...]' })\n * // → 'Ini adalah [...]'\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * truncate('', 10)\n * // → ''\n *\n * truncate('Hello', 10)\n * // → 'Hello'\n *\n * truncate('Hello World', 11)\n * // → 'Hello World' (exact length, no ellipsis)\n * ```\n *\n * @public\n */\nexport function truncate(\n text: string,\n maxLength: number,\n options?: TruncateOptions\n): string {\n // Early return for empty or invalid input\n if (!text || maxLength <= 0) {\n return '';\n }\n\n const { ellipsis = '...', wordBoundary = true } = options || {};\n\n // Early return if text is already short enough\n if (text.length <= maxLength) {\n return text;\n }\n\n // Calculate available space for actual text (excluding ellipsis)\n const availableLength = maxLength - ellipsis.length;\n\n // If ellipsis is longer than maxLength, just return ellipsis truncated\n if (availableLength <= 0) {\n return ellipsis.slice(0, maxLength);\n }\n\n // Get preliminary truncated text\n let truncated = text.slice(0, availableLength);\n\n // If word boundary is enabled, find last complete word\n if (wordBoundary) {\n // Find the last space within the truncated text\n const lastSpaceIndex = truncated.lastIndexOf(' ');\n\n // Only cut at word boundary if we found a space\n // Why: Prevents returning empty string for single long word\n if (lastSpaceIndex > 0) {\n truncated = truncated.slice(0, lastSpaceIndex);\n }\n // If no space found, keep the full availableLength\n }\n\n // Trim any trailing whitespace before adding ellipsis\n truncated = truncated.trimEnd();\n\n return truncated + ellipsis;\n}\n\n/**\n * Extract words from text, respecting Indonesian language rules\n *\n * This function splits text into individual words while:\n * - Respecting hyphenated words (anak-anak as single word)\n * - Filtering by minimum length\n * - Optional lowercase conversion\n * - Removing punctuation and special characters\n *\n * **Features:**\n * - Indonesian hyphenation support (anak-anak, buku-buku)\n * - Minimum word length filtering\n * - Case normalization\n * - Handles punctuation gracefully\n *\n * @param text - The text to extract words from\n * @param options - Optional configuration\n * @returns Array of extracted words\n *\n * @example\n * Basic usage:\n * ```typescript\n * extractWords('Anak-anak bermain di taman')\n * // → ['Anak-anak', 'bermain', 'di', 'taman']\n *\n * extractWords('Hello, World! How are you?')\n * // → ['Hello', 'World', 'How', 'are', 'you']\n * ```\n *\n * @example\n * Hyphenated word handling:\n * ```typescript\n * extractWords('Anak-anak bermain di taman', { includeHyphenated: true })\n * // → ['Anak-anak', 'bermain', 'di', 'taman']\n *\n * extractWords('Anak-anak bermain di taman', { includeHyphenated: false })\n * // → ['Anak', 'anak', 'bermain', 'di', 'taman']\n * ```\n *\n * @example\n * Minimum length filtering:\n * ```typescript\n * extractWords('Di rumah ada 3 kucing', { minLength: 3 })\n * // → ['rumah', 'ada', 'kucing']\n * // 'Di' (2 chars) and '3' (1 char) filtered out\n *\n * extractWords('a b cd def ghij', { minLength: 3 })\n * // → ['def', 'ghij']\n * ```\n *\n * @example\n * Lowercase conversion:\n * ```typescript\n * extractWords('Hello WORLD', { lowercase: true })\n * // → ['hello', 'world']\n *\n * extractWords('Hello WORLD', { lowercase: false })\n * // → ['Hello', 'WORLD']\n * ```\n *\n * @example\n * Combined options:\n * ```typescript\n * extractWords('Anak-Anak BERMAIN di Taman', {\n * includeHyphenated: true,\n * minLength: 3,\n * lowercase: true\n * })\n * // → ['anak-anak', 'bermain', 'taman']\n * // 'di' filtered out (< 3 chars)\n * ```\n *\n * @example\n * Edge cases:\n * ```typescript\n * extractWords('')\n * // → []\n *\n * extractWords(' ')\n * // → []\n *\n * extractWords('!!!@@##')\n * // → []\n * ```\n *\n * @public\n */\nexport function extractWords(text: string, options?: ExtractOptions): string[] {\n // Early return for empty input\n if (!text || !text.trim()) {\n return [];\n }\n\n const {\n minLength = 0,\n includeHyphenated = true,\n lowercase = false,\n } = options || {};\n\n // Remove punctuation but preserve hyphens and spaces\n // Why: We want to keep hyphenated words like 'anak-anak' intact\n let cleaned = text;\n\n if (includeHyphenated) {\n // Keep hyphens, remove other punctuation\n // Replace punctuation except hyphens with spaces\n cleaned = text.replace(/[^\\w\\s-]/g, ' ');\n } else {\n // Replace all punctuation including hyphens with spaces\n cleaned = text.replace(/[^\\w\\s]/g, ' ');\n }\n\n // Split by whitespace to get individual words\n const words = cleaned\n .split(/\\s+/)\n .map((word) => word.trim())\n .filter((word) => word.length > 0)\n // Filter out words that are just hyphens (common artifact of punctuation removal)\n .filter((word) => !/^-+$/.test(word));\n\n // Apply filters\n let result = words;\n\n // Filter by minimum length\n if (minLength > 0) {\n result = result.filter((word) => word.length >= minLength);\n }\n\n // Convert to lowercase if requested\n if (lowercase) {\n result = result.map((word) => word.toLowerCase());\n }\n\n return result;\n}\n","import type { CompareOptions } from './types';\nimport { normalizeWhitespace, removeAccents } from './sanitize';\n\n/**\n * Compare strings with Indonesian-aware normalization\n *\n * This function allows flexible string comparison with options to ignore\n * case, whitespace, and accents. Useful for search, filtering, and\n * validation.\n *\n * **Features:**\n * - Case-insensitive comparison (default: false)\n * - Whitespace normalization (ignore extra spaces)\n * - Accent removal (café == cafe)\n * - Null-safe (handles empty strings)\n *\n * @param str1 - First string to compare\n * @param str2 - Second string to compare\n * @param options - Comparison options\n * @returns True if strings match according to options\n *\n * @example\n * Basic matching:\n * ```typescript\n * compareStrings('Hello', 'Hello') // → true\n * compareStrings('Hello', 'hello') // → false\n * ```\n *\n * @example\n * Case insensitive:\n * ```typescript\n * compareStrings('Hello', 'hello', { caseSensitive: false }) // → true\n * // Note: default is caseSensitive: false for convenience in many utils,\n * // but strict comparison usually defaults to true.\n * // Let's check the implementation default.\n * ```\n *\n * @example\n * Ignore whitespace:\n * ```typescript\n * compareStrings(' Hello World ', 'Hello World', { ignoreWhitespace: true })\n * // → true\n * ```\n *\n * @example\n * Ignore accents:\n * ```typescript\n * compareStrings('café', 'cafe', { ignoreAccents: true })\n * // → true\n * ```\n *\n * @public\n */\nexport function compareStrings(\n str1: string,\n str2: string,\n options?: CompareOptions\n): boolean {\n // Early return for exact match (optimization)\n if (str1 === str2) {\n return true;\n }\n\n // Handle null/undefined as empty strings for robust comparison\n // usage of (str || '') pattern\n const s1 = str1 || '';\n const s2 = str2 || '';\n\n const {\n caseSensitive = false,\n ignoreWhitespace = false,\n ignoreAccents = false,\n } = options || {};\n\n let normalized1 = s1;\n let normalized2 = s2;\n\n // Apply whitespace normalization\n if (ignoreWhitespace) {\n normalized1 = normalizeWhitespace(normalized1);\n normalized2 = normalizeWhitespace(normalized2);\n }\n\n // Apply accent removal\n if (ignoreAccents) {\n normalized1 = removeAccents(normalized1);\n normalized2 = removeAccents(normalized2);\n }\n\n // Apply case sensitivity\n if (!caseSensitive) {\n normalized1 = normalized1.toLowerCase();\n normalized2 = normalized2.toLowerCase();\n }\n\n return normalized1 === normalized2;\n}\n\n/**\n * Calculate similarity score between two strings (0-1) using Levenshtein distance\n *\n * This function measures the difference between two strings and returns a score\n * where 1.0 means identical and 0.0 means completely different.\n *\n * **Algorithm:**\n * Uses Levenshtein distance to calculate the minimum number of single-character\n * edits (insertions, deletions, substitutions) required to change one string\n * into the other.\n *\n * @param str1 - First string\n * @param str2 - Second string\n * @returns Similarity score between 0.0 and 1.0\n *\n * @example\n * Basic Usage:\n * ```typescript\n * similarity('hello', 'hello') // → 1.0 (identical)\n * similarity('hello', 'hallo') // → 0.8 (1 edit / 5 length)\n * similarity('hello', 'world') // → 0.2 (4 edits / 5 length)\n * ```\n *\n * @example\n * Case sensitivity:\n * Note: This function is case-sensitive. Use compareStrings options or\n * manual lowercasing if you need case-insensitive similarity.\n *\n * @public\n */\nexport function similarity(str1: string, str2: string): number {\n if (str1 === str2) return 1.0;\n if (str1.length === 0) return str2.length === 0 ? 1.0 : 0.0;\n if (str2.length === 0) return 0.0;\n\n const len1 = str1.length;\n const len2 = str2.length;\n\n // Track previous and current rows of the matrix\n // Optimization: We only need two rows, not the full matrix O(min(m,n)) space\n let prevRow = Array(len2 + 1).fill(0);\n let currentRow = Array(len2 + 1).fill(0);\n\n // Initialize first row\n for (let j = 0; j <= len2; j++) {\n prevRow[j] = j;\n }\n\n // Calculate distance\n for (let i = 1; i <= len1; i++) {\n currentRow[0] = i;\n\n for (let j = 1; j <= len2; j++) {\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\n\n currentRow[j] = Math.min(\n currentRow[j - 1] + 1, // Insertion\n prevRow[j] + 1, // Deletion\n prevRow[j - 1] + cost // Substitution\n );\n }\n\n // Move current row to previous for next iteration\n [prevRow, currentRow] = [currentRow, prevRow];\n }\n\n // Calculate similarity: 1 - (distance / max_length)\n const distance = prevRow[len2];\n const maxLength = Math.max(len1, len2);\n\n return 1.0 - distance / maxLength;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indodev/toolkit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Indonesian developer utilities for validation, formatting, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -57,6 +57,26 @@
57
57
  "types": "./dist/text/index.d.cts",
58
58
  "default": "./dist/text/index.cjs"
59
59
  }
60
+ },
61
+ "./npwp": {
62
+ "import": {
63
+ "types": "./dist/npwp/index.d.ts",
64
+ "default": "./dist/npwp/index.js"
65
+ },
66
+ "require": {
67
+ "types": "./dist/npwp/index.d.cts",
68
+ "default": "./dist/npwp/index.cjs"
69
+ }
70
+ },
71
+ "./plate": {
72
+ "import": {
73
+ "types": "./dist/plate/index.d.ts",
74
+ "default": "./dist/plate/index.js"
75
+ },
76
+ "require": {
77
+ "types": "./dist/plate/index.d.cts",
78
+ "default": "./dist/plate/index.cjs"
79
+ }
60
80
  }
61
81
  },
62
82
  "files": [
@@ -68,6 +88,22 @@
68
88
  "access": "public",
69
89
  "registry": "https://registry.npmjs.org/"
70
90
  },
91
+ "scripts": {
92
+ "dev": "tsup --watch",
93
+ "build": "tsup",
94
+ "test": "vitest",
95
+ "test:ui": "vitest --ui",
96
+ "test:coverage": "vitest --coverage",
97
+ "test:run": "vitest run",
98
+ "typecheck": "tsc --noEmit",
99
+ "lint": "eslint src --ext .ts",
100
+ "lint:fix": "eslint src --ext .ts --fix",
101
+ "format": "prettier --write \"src/**/*.ts\"",
102
+ "format:check": "prettier --check \"src/**/*.ts\"",
103
+ "prepublish:only": "pnpm run build && pnpm run test:run && publint",
104
+ "changeset": "changeset",
105
+ "release": "changeset publish"
106
+ },
71
107
  "keywords": [
72
108
  "indonesia",
73
109
  "indonesian",
@@ -122,21 +158,5 @@
122
158
  "typescript-eslint": "^8.49.0",
123
159
  "vitest": "^2.1.8",
124
160
  "zod": "^3.24.1"
125
- },
126
- "scripts": {
127
- "dev": "tsup --watch",
128
- "build": "tsup",
129
- "test": "vitest",
130
- "test:ui": "vitest --ui",
131
- "test:coverage": "vitest --coverage",
132
- "test:run": "vitest run",
133
- "typecheck": "tsc --noEmit",
134
- "lint": "eslint src --ext .ts",
135
- "lint:fix": "eslint src --ext .ts --fix",
136
- "format": "prettier --write \"src/**/*.ts\"",
137
- "format:check": "prettier --check \"src/**/*.ts\"",
138
- "prepublish:only": "pnpm run build && pnpm run test:run && publint",
139
- "changeset": "changeset",
140
- "release": "changeset publish"
141
161
  }
142
- }
162
+ }
package/LICENCE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Adamm
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.