├── romaja-lyrics ├── preview.gif ├── README.md └── romaja_lyrics.js ├── romaji-lyrics ├── preview.gif ├── README.md ├── romaji_lyrics.js └── marketplace │ └── romaji_lyrics.js ├── README.md └── manifest.json /romaja-lyrics/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/41pha1/spicetify-extensions/HEAD/romaja-lyrics/preview.gif -------------------------------------------------------------------------------- /romaji-lyrics/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/41pha1/spicetify-extensions/HEAD/romaji-lyrics/preview.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | ## Romaji Lyrics: 3 | ![preview](romaji-lyrics/preview.gif) 4 | 5 | *Converts Japanese lyrics to Romaji* 6 | 7 | ## Romaja Lyrics: 8 | ![preview](romaja-lyrics/preview.gif) 9 | 10 | *Converts Korean lyrics to Romaja* 11 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Korean Romaja Lyrics", 4 | "description": "Spicetify extension to convert Korean lyrics to Romaja.", 5 | "preview": "romaja-lyrics/preview.gif", 6 | "main": "romaja-lyrics/romaja_lyrics.js", 7 | "readme": "romaja-lyrics/README.md", 8 | "authors": [ 9 | { "name": "A1pha1", "url": "https://github.com/41pha1/" } 10 | ], 11 | "tags": ["lyrics", "korean", "romaja"] 12 | }, 13 | 14 | { 15 | "name": "Japanese Romaji Lyrics", 16 | "description": "Spicetify extension to convert Japanese lyrics to Romaji.", 17 | "preview": "romaji-lyrics/preview.gif", 18 | "main": "romaji-lyrics/marketplace/romaji_lyrics.js", 19 | "readme": "romaji-lyrics/README.md", 20 | "authors": [ 21 | { "name": "A1pha1", "url": "https://github.com/41pha1/" } 22 | ], 23 | "tags": ["lyrics", "japanese", "romaji"] 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /romaja-lyrics/README.md: -------------------------------------------------------------------------------- 1 | # Spicetify Romaja Lyrics 2 | [Spicetify](https://github.com/spicetify/spicetify-cli) extension to convert Korean lyrics to Romaja. 3 | * Auto detects Korean lyrics and converts them to Romaja 4 | * UI options are planned in future updates. 5 | 6 | ## ⚙️ Install Manually 7 | Copy `romaja_lyrics.js` into your [Spicetify](https://github.com/spicetify/spicetify-cli) extensions directory: 8 | | **Platform** | **Path** | 9 | |------------|------------------------------------------------------------------------------------------| 10 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` | 11 | | **MacOS** | `~/.config/spicetify/Extensions` or `$SPICETIFY_CONFIG/Extensions` | 12 | | **Windows** | `%appdata%/spicetify/Extensions/` | 13 | 14 | After putting the extension file into the correct folder, run the following command to install the extension: 15 | ``` 16 | spicetify config extensions romaja_lyrics.js 17 | spicetify apply 18 | ``` 19 | 20 | ## Usage 21 | Simply open the lyrics for a Korean Song and Romaja Lyrics will convert the songtext. 22 | 23 | Control options are planned in future updates. 24 | 25 | ## Credit 26 | 27 | [Romaja Lyrics](https://github.com/41pha1/spicetify-extensions/tree/main/romaja-lyrics) uses a modified version of [fujaru's](https://github.com/fujaru) [aromanize-js](https://github.com/fujaru/aromanize-js) Korean transliteration utility to convert the lyrics client side. 28 | 29 | Be sure to give them some love as well! 30 | 31 | ## More 32 | 🌟 Like it? Gimme some love! 33 | [![Github Stars badge](https://img.shields.io/github/stars/41pha1/spicetify-extensions?logo=github&style=social)](https://github.com/41pha1/spicetify-extensions/) 34 | 35 | If you find any bugs or have any suggestion for improvement, please [create a new issue](https://github.com/41pha1/spicetify-extensions/issues/new/choose) on the GitHub repo. 36 | ![https://github.com/41pha1/spicetify-extensions/issues](https://img.shields.io/github/issues/41pha1/spicetify-extensions?logo=github) 37 | -------------------------------------------------------------------------------- /romaji-lyrics/README.md: -------------------------------------------------------------------------------- 1 | # Spicetify Romaji Lyrics 2 | [Spicetify](https://github.com/spicetify/spicetify-cli) extension to convert Japanese lyrics to Romaji. 3 | * Auto detects Japanese lyrics and converts them to Romaji using the included dictionary 4 | * This extension works for the spotify lyrics, if you are interested in using this for Lyrics-plus, check out my [fork of the cli.](https://github.com/41pha1/spicetify-cli/tree/lyrics-converter/CustomApps/lyrics-plus) 5 | > simply replace spicetify\CustomApps\lyrics-plus with my version of lyrics-plus. 6 | 7 | # Note 8 | Due to the included dictionary this extensions can take some time to load after first installing it from the marketplace. 9 | 10 | ## ⚙️ Install Manually 11 | Copy `romaji_lyrics.js` into your [Spicetify](https://github.com/spicetify/spicetify-cli) extensions directory: 12 | | **Platform** | **Path** | 13 | |------------|------------------------------------------------------------------------------------------| 14 | | **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` | 15 | | **MacOS** | `~/.config/spicetify/Extensions` or `$SPICETIFY_CONFIG/Extensions` | 16 | | **Windows** | `%appdata%/spicetify/Extensions/` | 17 | 18 | After putting the extension file into the correct folder, run the following command to install the extension: 19 | ``` 20 | spicetify config extensions romaji_lyrics.js 21 | spicetify apply 22 | ``` 23 | 24 | ## Usage 25 | Simply open the lyrics for a Japanese Song and Romaji Lyrics will convert the songtext. 26 | 27 | You can toggle the conversion by pressing ctrl + tab. 28 | 29 | ## Credit 30 | 31 | [Romaji Lyrics](https://github.com/41pha1/spicetify-extensions/tree/main/romaji-lyrics) uses a modified version of [hexenq's](https://github.com/hexenq) [Kuroshiro](https://github.com/hexenq/kuroshiro) javascript language library to convert the lyrics client side. 32 | 33 | Be sure to give them some love as well! 34 | 35 | ## More 36 | 🌟 Like it? Gimme some love! 37 | [![Github Stars badge](https://img.shields.io/github/stars/41pha1/spicetify-extensions?logo=github&style=social)](https://github.com/41pha1/spicetify-extensions/) 38 | 39 | If you find any bugs or have any suggestion for improvement, please [create a new issue](https://github.com/41pha1/spicetify-extensions/issues/new/choose) on the GitHub repo. 40 | ![https://github.com/41pha1/spicetify-romaji-lyrics/issues](https://img.shields.io/github/issues/41pha1/spicetify-extensions?logo=github) 41 | -------------------------------------------------------------------------------- /romaji-lyrics/romaji_lyrics.js: -------------------------------------------------------------------------------- 1 | const kuroshiroPath = "https://cdn.jsdelivr.net/npm/kuroshiro@1.2.0/dist/kuroshiro.min.js"; 2 | const kuromojiPath = "https://cdn.jsdelivr.net/npm/kuroshiro-analyzer-kuromoji@1.1.0/dist/kuroshiro-analyzer-kuromoji.min.js"; 3 | 4 | const dictPath = "https:/cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict"; 5 | 6 | class JapaneseTranslator { 7 | constructor() { 8 | this.includeExternal(kuroshiroPath); 9 | this.includeExternal(kuromojiPath); 10 | 11 | this.createKuroshiro(); 12 | 13 | this.finished = false; 14 | } 15 | 16 | includeExternal(url) { 17 | var s = document.createElement("script"); 18 | s.setAttribute("type", "text/javascript"); 19 | s.setAttribute("src", url); 20 | var nodes = document.getElementsByTagName("*"); 21 | var node = nodes[nodes.length - 1].parentNode; 22 | node.appendChild(s); 23 | } 24 | 25 | /** 26 | * Fix an issue with kuromoji when loading dict from external urls 27 | * Adapted from: https://github.com/mobilusoss/textlint-browser-runner/pull/7 28 | */ 29 | applyKuromojiFix() { 30 | if(typeof XMLHttpRequest.prototype.realOpen !== "undefined") 31 | return; 32 | XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open; 33 | XMLHttpRequest.prototype.open = function (method, url, bool) { 34 | if (url.indexOf(dictPath.replace("https://", "https:/")) === 0) { 35 | this.realOpen(method, url.replace("https:/", "https://"), bool); 36 | } else { 37 | this.realOpen(method, url, bool); 38 | } 39 | }; 40 | } 41 | 42 | async createKuroshiro() { 43 | if (typeof Kuroshiro === "undefined" || typeof KuromojiAnalyzer === "undefined") { 44 | //Waiting for JSDeliver to load Kuroshiro and Kuromoji 45 | setTimeout(this.createKuroshiro.bind(this), 50); 46 | return; 47 | } 48 | 49 | this.kuroshiro = new Kuroshiro.default(); 50 | 51 | this.applyKuromojiFix(); 52 | 53 | this.kuroshiro.init(new KuromojiAnalyzer({ dictPath: dictPath })).then( 54 | function () { 55 | this.finished = true; 56 | }.bind(this) 57 | ); 58 | } 59 | 60 | async romanize(text, target = "romaji", mode = "spaced") { 61 | if (!this.finished) { 62 | setTimeout(this.romajifyText.bind(this), 100, text, target, mode); 63 | return; 64 | } 65 | 66 | return this.kuroshiro.convert(text, { 67 | to: target, 68 | mode: mode 69 | }); 70 | } 71 | } 72 | 73 | 74 | class Romaji_Lyrics 75 | { 76 | static LYRIC_DIV_SELECTOR = ".lyrics-lyricsContent-text"; 77 | static translator = new JapaneseTranslator(); 78 | static translated_lyrics = null; 79 | static original_lyrics = null; 80 | static enabled = true; 81 | static mode = "romaji" 82 | static target = "spaced"; 83 | 84 | static eventLoop() 85 | { 86 | const currentLyrics = this.getLyrics(); 87 | 88 | if(!this.original_lyrics || !(currentLyrics == this.translated_lyrics)){ 89 | this.original_lyrics = currentLyrics; 90 | this.translateLyrics(this.original_lyrics); 91 | } 92 | 93 | setTimeout(this.eventLoop.bind(this), 50) 94 | } 95 | 96 | static addControlUI() 97 | { 98 | this.mousetrap = new Mousetrap(); 99 | this.mousetrap.handleKey = (character, modifiers, e) => { 100 | if (e.type == "keydown") { 101 | if(character == "tab" && modifiers.includes("ctrl")) 102 | { 103 | this.enabled = !this.enabled; 104 | Romaji_Lyrics.translateLyrics(Romaji_Lyrics.original_lyrics); 105 | } 106 | } 107 | }; 108 | } 109 | 110 | static getLyrics() 111 | { 112 | if (!this.translator.finished) return null; 113 | 114 | const lyrics_div = document.querySelectorAll(Romaji_Lyrics.LYRIC_DIV_SELECTOR); 115 | 116 | if(!lyrics_div) return null; 117 | 118 | var lyrics = ""; 119 | for (let lyric_div of lyrics_div) lyrics += lyric_div.innerHTML + "\n"; 120 | lyrics = lyrics.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); 121 | return lyrics; 122 | } 123 | 124 | static translateLyrics(lyrics) { 125 | if(!this.translator.finished || !this.isJapanese(lyrics)) return; 126 | 127 | if(!this.enabled){ 128 | this.translated_lyrics = this.original_lyrics; 129 | Romaji_Lyrics.applyTranslation(); 130 | return; 131 | } 132 | 133 | this.translator.romanize(lyrics, this.mode, this.target).then((r) => {Romaji_Lyrics.translated_lyrics = r; Romaji_Lyrics.applyTranslation()}); 134 | } 135 | 136 | static applyTranslation() 137 | { 138 | const lyrics_div = document.querySelectorAll(Romaji_Lyrics.LYRIC_DIV_SELECTOR); 139 | if(!lyrics_div || !this.translated_lyrics) return; 140 | 141 | var lyrics_array = this.translated_lyrics.split("\n"); 142 | for (var j = 0; j < lyrics_div.length; j++) lyrics_div[j].innerHTML = lyrics_array[j]; 143 | } 144 | 145 | static isJapanese(f) { 146 | return /[\u3000-\u303F]|[\u3040-\u309F]|[\u30A0-\u30FF]|[\uFF00-\uFFEF]|[\u4E00-\u9FAF]|[\u2605-\u2606]|[\u2190-\u2195]|\u203B/g.test(f); 147 | } 148 | } 149 | 150 | 151 | (function romaji_lyrics() { 152 | new Romaji_Lyrics(); 153 | Romaji_Lyrics.addControlUI(); 154 | Romaji_Lyrics.eventLoop(); 155 | })(); -------------------------------------------------------------------------------- /romaji-lyrics/marketplace/romaji_lyrics.js: -------------------------------------------------------------------------------- 1 | const kuroshiroPath = "https://cdn.jsdelivr.net/npm/kuroshiro@1.2.0/dist/kuroshiro.min.js"; 2 | const kuromojiPath = "https://cdn.jsdelivr.net/npm/kuroshiro-analyzer-kuromoji@1.1.0/dist/kuroshiro-analyzer-kuromoji.min.js"; 3 | 4 | const dictPath = "https:/cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict"; 5 | 6 | class JapaneseTranslator { 7 | constructor() { 8 | this.includeExternal(kuroshiroPath); 9 | this.includeExternal(kuromojiPath); 10 | 11 | this.createKuroshiro(); 12 | 13 | this.finished = false; 14 | } 15 | 16 | includeExternal(url) { 17 | var s = document.createElement("script"); 18 | s.setAttribute("type", "text/javascript"); 19 | s.setAttribute("src", url); 20 | var nodes = document.getElementsByTagName("*"); 21 | var node = nodes[nodes.length - 1].parentNode; 22 | node.appendChild(s); 23 | } 24 | 25 | /** 26 | * Fix an issue with kuromoji when loading dict from external urls 27 | * Adapted from: https://github.com/mobilusoss/textlint-browser-runner/pull/7 28 | */ 29 | applyKuromojiFix() { 30 | if(typeof XMLHttpRequest.prototype.realOpen !== "undefined") 31 | return; 32 | XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open; 33 | XMLHttpRequest.prototype.open = function (method, url, bool) { 34 | if (url.indexOf(dictPath.replace("https://", "https:/")) === 0) { 35 | this.realOpen(method, url.replace("https:/", "https://"), bool); 36 | } else { 37 | this.realOpen(method, url, bool); 38 | } 39 | }; 40 | } 41 | 42 | async createKuroshiro() { 43 | if (typeof Kuroshiro === "undefined" || typeof KuromojiAnalyzer === "undefined") { 44 | //Waiting for JSDeliver to load Kuroshiro and Kuromoji 45 | setTimeout(this.createKuroshiro.bind(this), 50); 46 | return; 47 | } 48 | 49 | this.kuroshiro = new Kuroshiro.default(); 50 | 51 | this.applyKuromojiFix(); 52 | 53 | this.kuroshiro.init(new KuromojiAnalyzer({ dictPath: dictPath })).then( 54 | function () { 55 | this.finished = true; 56 | }.bind(this) 57 | ); 58 | } 59 | 60 | async romanize(text, target = "romaji", mode = "spaced") { 61 | if (!this.finished) { 62 | setTimeout(this.romajifyText.bind(this), 100, text, target, mode); 63 | return; 64 | } 65 | 66 | return this.kuroshiro.convert(text, { 67 | to: target, 68 | mode: mode 69 | }); 70 | } 71 | } 72 | 73 | 74 | class Romaji_Lyrics 75 | { 76 | static LYRIC_DIV_SELECTOR = ".lyrics-lyricsContent-text"; 77 | static translator = new JapaneseTranslator(); 78 | static translated_lyrics = null; 79 | static original_lyrics = null; 80 | static enabled = true; 81 | static mode = "romaji" 82 | static target = "spaced"; 83 | 84 | static eventLoop() 85 | { 86 | const currentLyrics = this.getLyrics(); 87 | 88 | if(!this.original_lyrics || !(currentLyrics == this.translated_lyrics)){ 89 | this.original_lyrics = currentLyrics; 90 | this.translateLyrics(this.original_lyrics); 91 | } 92 | 93 | setTimeout(this.eventLoop.bind(this), 50) 94 | } 95 | 96 | static addControlUI() 97 | { 98 | const trap = new Mousetrap(); 99 | trap.handleKey = (character, modifiers, e) => { 100 | if (e.type == "keydown") { 101 | if(character == "tab" && modifiers.includes("ctrl")) 102 | { 103 | this.enabled = !this.enabled; 104 | Romaji_Lyrics.translateLyrics(Romaji_Lyrics.original_lyrics); 105 | } 106 | } 107 | }; 108 | } 109 | 110 | static getLyrics() 111 | { 112 | if (!this.translator.finished) return null; 113 | 114 | const lyrics_div = document.querySelectorAll(Romaji_Lyrics.LYRIC_DIV_SELECTOR); 115 | 116 | if(!lyrics_div) return null; 117 | 118 | var lyrics = ""; 119 | for (let lyric_div of lyrics_div) lyrics += lyric_div.textContent + "\n"; 120 | lyrics = lyrics.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); 121 | return lyrics; 122 | } 123 | 124 | static translateLyrics(lyrics) { 125 | if(!this.translator.finished || !this.isJapanese(lyrics)) return; 126 | 127 | if(!this.enabled){ 128 | this.translated_lyrics = this.original_lyrics; 129 | Romaji_Lyrics.applyTranslation(); 130 | return; 131 | } 132 | 133 | this.translator.romanize(lyrics, this.mode, this.target).then((r) => { 134 | r = r.split('$ KStartK $'); 135 | var text = ''; 136 | for (var i = 0; i < r.length; i++) { 137 | var part = r[i]; 138 | var index = part.indexOf('$ KStopK $'); 139 | if (index == -1) { 140 | text += part; 141 | } 142 | else { 143 | text += part.substring(0, index).toUpperCase(); 144 | text += part.substring(index + '$ KStopK $'.length); 145 | } 146 | } 147 | Romaji_Lyrics.translated_lyrics = text; 148 | Romaji_Lyrics.applyTranslation() 149 | }); 150 | } 151 | 152 | static applyTranslation() 153 | { 154 | const lyrics_div = document.querySelectorAll(Romaji_Lyrics.LYRIC_DIV_SELECTOR); 155 | if(!lyrics_div || !this.translated_lyrics) return; 156 | 157 | var lyrics_array = this.translated_lyrics.split("\n"); 158 | for (var j = 0; j < lyrics_div.length; j++) lyrics_div[j].innerHTML = lyrics_array[j]; 159 | } 160 | 161 | static isJapanese(f) { 162 | return /[\u3000-\u303F]|[\u3040-\u309F]|[\u30A0-\u30FF]|[\uFF00-\uFFEF]|[\u4E00-\u9FAF]|[\u2605-\u2606]|[\u2190-\u2195]|\u203B/g.test(f); 163 | } 164 | } 165 | 166 | 167 | (function romaji_lyrics() { 168 | new Romaji_Lyrics(); 169 | Romaji_Lyrics.addControlUI(); 170 | Romaji_Lyrics.eventLoop(); 171 | })(); 172 | -------------------------------------------------------------------------------- /romaja-lyrics/romaja_lyrics.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // NAME: Romaja Lyrics 4 | // AUTHOR: A1pha1 5 | // DESCRIPTION: Convert Korean Lyrics to Romaja to sing along for non natives! 6 | 7 | class Korean_Translator { 8 | async romanize(text) { 9 | return Aromanize.romanize(text); 10 | } 11 | } 12 | 13 | class Romaja_Lyrics 14 | { 15 | static LYRIC_DIV_SELECTOR = ".lyrics-lyricsContent-text"; 16 | static translator = new Korean_Translator(); 17 | static translated_lyrics = null; 18 | static original_lyrics = null; 19 | static enabled = true; 20 | static mode = "romaji" 21 | static target = "spaced"; 22 | 23 | static eventLoop() 24 | { 25 | const currentLyrics = this.getLyrics(); 26 | 27 | if(currentLyrics && !this.original_lyrics || !(currentLyrics == this.translated_lyrics)){ 28 | this.original_lyrics = currentLyrics; 29 | this.translateLyrics(this.original_lyrics); 30 | } 31 | 32 | setTimeout(this.eventLoop.bind(this), 50) 33 | } 34 | 35 | static addControlUI() 36 | { 37 | const trap = new Mousetrap(); 38 | trap.handleKey = (character, modifiers, e) => { 39 | if (e.type == "keydown") { 40 | if(character == "tab" && modifiers.includes("ctrl")) 41 | { 42 | this.enabled = !this.enabled; 43 | Romaja_Lyrics.translateLyrics(Romaja_Lyrics.original_lyrics); 44 | } 45 | } 46 | }; 47 | } 48 | 49 | static getLyrics() 50 | { 51 | const lyrics_div = document.querySelectorAll(Romaja_Lyrics.LYRIC_DIV_SELECTOR); 52 | 53 | if(!lyrics_div) return null; 54 | 55 | var lyrics = ""; 56 | for (let lyric_div of lyrics_div) lyrics += lyric_div.innerHTML + "\n"; 57 | lyrics = lyrics.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); 58 | return lyrics; 59 | } 60 | 61 | static translateLyrics(lyrics) 62 | { 63 | if(!this.isKorean(lyrics)) return; 64 | 65 | if(!this.enabled){ 66 | this.translated_lyrics = this.original_lyrics; 67 | Romaja_Lyrics.applyTranslation(); 68 | return; 69 | } 70 | 71 | this.translator.romanize(lyrics).then((r) => {Romaja_Lyrics.translated_lyrics = r; Romaja_Lyrics.applyTranslation()}); 72 | } 73 | 74 | static applyTranslation() 75 | { 76 | const lyrics_div = document.querySelectorAll(Romaja_Lyrics.LYRIC_DIV_SELECTOR); 77 | if(!lyrics_div || !this.translated_lyrics) return; 78 | 79 | var lyrics_array = this.translated_lyrics.split("\n"); 80 | for (var j = 0; j < lyrics_div.length; j++) lyrics_div[j].innerHTML = lyrics_array[j]; 81 | } 82 | 83 | static isKorean(f) { 84 | return /[\uac00-\ud7af]|[\u1100-\u11ff]|[\u3130-\u318f]|[\ua960-\ua97f]|[\ud7b0-\ud7ff]/g.test(f); 85 | } 86 | } 87 | 88 | (function romaja_lyrics() { 89 | new Romaja_Lyrics(); 90 | Romaja_Lyrics.addControlUI(); 91 | Romaja_Lyrics.eventLoop(); 92 | })(); 93 | 94 | 95 | /** @license Aromanize-js 2017 - Fajar Chandra [ https://github.com/fujaru/aromanize-js ] The MIT License */ 96 | 97 | var Aromanize = { 98 | 99 | //////////////////////////////////////////////////////////////////// 100 | // Transliteration rules 101 | //////////////////////////////////////////////////////////////////// 102 | 103 | rules: { 104 | 105 | hangul: { 106 | 107 | /** 108 | * Revised Romanization Transcription 109 | */ 110 | 'rr': { 111 | // Note: giyeok (0x1100) for middle moeum is different than giyeok (0x3131) for standalone jamo 112 | cho: { 113 | 'ᄀ': 'g', 'ᄁ': 'kk', 114 | 'ᄂ': 'n', 115 | 'ᄃ': 'd', 'ᄄ': 'tt', 116 | 'ᄅ': 'r', 117 | 'ᄆ': 'm', 118 | 'ᄇ': 'b', 'ᄈ': 'pp', 119 | 'ᄉ': 's', 'ᄊ': 'ss', 120 | 'ᄋ': '', 121 | 'ᄌ': 'j', 'ᄍ': 'jj', 122 | 'ᄎ': 'ch', 123 | 'ᄏ': 'k', 124 | 'ᄐ': 't', 125 | 'ᄑ': 'p', 126 | 'ᄒ': 'h' 127 | }, 128 | 129 | // Note: ᅡ (0x1161) for middle moeum is different than ㅏ (0x314F) for standalone jamo 130 | jung: { 131 | 'ᅡ': 'a', 'ᅢ': 'ae', 'ᅣ': 'ya', 'ᅤ': 'yae', 132 | 'ᅥ': 'eo', 'ᅦ': 'e', 'ᅧ': 'yeo', 'ᅨ': 'ye', 133 | 'ᅩ': 'o', 'ᅪ': 'wa', 'ᅫ': 'wae', 'ᅬ': 'oe', 'ᅭ': 'yo', 134 | 'ᅮ': 'u', 'ᅯ': 'wo', 'ᅰ': 'we', 'ᅱ': 'wi', 'ᅲ': 'yu', 135 | 'ᅳ': 'eu', 'ᅴ': 'eui', 'ᅵ': 'i' 136 | }, 137 | 138 | // Note: ᆨ (0x11A8) for last jaeum (batchim) is different than ᄀ (0x1100) for first jaeum 139 | // also different than ㄱ (0x3131) for standalone jamo 140 | jong: { 141 | 'ᆨ': 'k', 'ᆨᄋ': 'g', 'ᆨᄂ': 'ngn', 'ᆨᄅ': 'ngn', 'ᆨᄆ': 'ngm', 'ᆨᄒ': 'kh', 142 | 'ᆩ': 'kk', 'ᆩᄋ': 'kg', 'ᆩᄂ': 'ngn', 'ᆩᄅ': 'ngn', 'ᆩᄆ': 'ngm', 'ᆩᄒ': 'kh', 143 | 'ᆪ': 'k', 'ᆪᄋ': 'ks', 'ᆪᄂ': 'ngn', 'ᆪᄅ': 'ngn', 'ᆪᄆ': 'ngm', 'ᆪᄒ': 'kch', 144 | 'ᆫ': 'n', 'ᆫᄅ': 'll', 145 | 'ᆬ': 'n', 'ᆬᄋ': 'nj', 'ᆬᄂ': 'nn', 'ᆬᄅ': 'nn', 'ᆬᄆ': 'nm', 'ᆬㅎ': 'nch', 146 | 'ᆭ': 'n', 'ᆭᄋ': 'nh', 'ᆭᄅ': 'nn', 147 | 'ᆮ': 't', 'ᆮᄋ': 'd', 'ᆮᄂ': 'nn', 'ᆮᄅ': 'nn', 'ᆮᄆ': 'nm', 'ᆮᄒ': 'th', 148 | 'ᆯ': 'l', 'ᆯᄋ': 'r', 'ᆯᄂ': 'll', 'ᆯᄅ': 'll', 149 | 'ᆰ': 'k', 'ᆰᄋ': 'lg', 'ᆰᄂ': 'ngn', 'ᆰᄅ': 'ngn', 'ᆰᄆ': 'ngm', 'ᆰᄒ': 'lkh', 150 | 'ᆱ': 'm', 'ᆱᄋ': 'lm', 'ᆱᄂ': 'mn', 'ᆱᄅ': 'mn', 'ᆱᄆ': 'mm', 'ᆱᄒ': 'lmh', 151 | 'ᆲ': 'p', 'ᆲᄋ': 'lb', 'ᆲᄂ': 'mn', 'ᆲᄅ': 'mn', 'ᆲᄆ': 'mm', 'ᆲᄒ': 'lph', 152 | 'ᆳ': 't', 'ᆳᄋ': 'ls', 'ᆳᄂ': 'nn', 'ᆳᄅ': 'nn', 'ᆳᄆ': 'nm', 'ᆳᄒ': 'lsh', 153 | 'ᆴ': 't', 'ᆴᄋ': 'lt', 'ᆴᄂ': 'nn', 'ᆴᄅ': 'nn', 'ᆴᄆ': 'nm', 'ᆴᄒ': 'lth', 154 | 'ᆵ': 'p', 'ᆵᄋ': 'lp', 'ᆵᄂ': 'mn', 'ᆵᄅ': 'mn', 'ᆵᄆ': 'mm', 'ᆵᄒ': 'lph', 155 | 'ᆶ': 'l', 'ᆶᄋ': 'lh', 'ᆶᄂ': 'll', 'ᆶᄅ': 'll', 'ᆶᄆ': 'lm', 'ᆶᄒ': 'lh', 156 | 'ᆷ': 'm', 'ᆷᄅ': 'mn', 157 | 'ᆸ': 'p', 'ᆸᄋ': 'b', 'ᆸᄂ': 'mn', 'ᆸᄅ': 'mn', 'ᆸᄆ': 'mm', 'ᆸᄒ': 'ph', 158 | 'ᆹ': 'p', 'ᆹᄋ': 'ps', 'ᆹᄂ': 'mn', 'ᆹᄅ': 'mn', 'ᆹᄆ': 'mm', 'ᆹᄒ': 'psh', 159 | 'ᆺ': 't', 'ᆺᄋ': 's', 'ᆺᄂ': 'nn', 'ᆺᄅ': 'nn', 'ᆺᄆ': 'nm', 'ᆺᄒ': 'sh', 160 | 'ᆻ': 't', 'ᆻᄋ': 'ss', 'ᆻᄂ': 'tn', 'ᆻᄅ': 'tn', 'ᆻᄆ': 'nm', 'ᆻᄒ': 'th', 161 | 'ᆼ': 'ng', 162 | 'ᆽ': 't', 'ᆽᄋ': 'j', 'ᆽᄂ': 'nn', 'ᆽᄅ': 'nn', 'ᆽᄆ': 'nm', 'ᆽᄒ': 'ch', 163 | 'ᆾ': 't', 'ᆾᄋ': 'ch', 'ᆾᄂ': 'nn', 'ᆾᄅ': 'nn', 'ᆾᄆ': 'nm', 'ᆾᄒ': 'ch', 164 | 'ᆿ': 'k', 'ᆿᄋ': 'k', 'ᆿᄂ': 'ngn', 'ᆿᄅ': 'ngn', 'ᆿᄆ': 'ngm', 'ᆿᄒ': 'kh', 165 | 'ᇀ': 't', 'ᇀᄋ': 't', 'ᇀᄂ': 'nn', 'ᇀᄅ': 'nn', 'ᇀᄆ': 'nm', 'ᇀᄒ': 'th', 166 | 'ᇁ': 'p', 'ᇁᄋ': 'p', 'ᇁᄂ': 'mn', 'ᇁᄅ': 'mn', 'ᇁᄆ': 'mm', 'ᇁᄒ': 'ph', 167 | 'ᇂ': 't', 'ᇂᄋ': 'h', 'ᇂᄂ': 'nn', 'ᇂᄅ': 'nn', 'ᇂᄆ': 'mm', 'ᇂᄒ': 't', 168 | 'ᇂᄀ': 'k', 169 | } 170 | }, 171 | 172 | /** 173 | * Revised Romanization Transliteration 174 | */ 175 | 'rr-translit': { 176 | // Note: giyeok (0x1100) for middle moeum is different than giyeok (0x3131) for standalone jamo 177 | cho: { 178 | 'ᄀ': 'g', 'ᄁ': 'kk', 179 | 'ᄂ': 'n', 180 | 'ᄃ': 'd', 'ᄄ': 'tt', 181 | 'ᄅ': 'l', 182 | 'ᄆ': 'm', 183 | 'ᄇ': 'b', 'ᄈ': 'pp', 184 | 'ᄉ': 's', 'ᄊ': 'ss', 185 | 'ᄋ': '', 186 | 'ᄌ': 'j', 'ᄍ': 'jj', 187 | 'ᄎ': 'ch', 188 | 'ᄏ': 'k', 189 | 'ᄐ': 't', 190 | 'ᄑ': 'p', 191 | 'ᄒ': 'h' 192 | }, 193 | 194 | // Note: ᅡ (0x1161) for middle moeum is different than ㅏ (0x314F) for standalone jamo 195 | jung: { 196 | 'ᅡ': 'a', 'ᅢ': 'ae', 'ᅣ': 'ya', 'ᅤ': 'yae', 197 | 'ᅥ': 'eo', 'ᅦ': 'e', 'ᅧ': 'yeo', 'ᅨ': 'ye', 198 | 'ᅩ': 'o', 'ᅪ': 'oa', 'ᅫ': 'oae', 'ᅬ': 'oi', 'ᅭ': 'yo', 199 | 'ᅮ': 'u', 'ᅯ': 'ueo', 'ᅰ': 'ue', 'ᅱ': 'ui', 'ᅲ': 'yu', 200 | 'ᅳ': 'eu', 'ᅴ': 'eui', 'ᅵ': 'i' 201 | }, 202 | 203 | // Note: ᆨ (0x11A8) for last jaeum (batchim) is different than ᄀ (0x1100) for first jaeum 204 | // also different than ㄱ (0x3131) for standalone jamo 205 | jong: { 206 | 'ᆨ': 'g', 'ᆨᄋ': 'g-', 207 | 'ᆩ': 'kk', 'ᆩᄋ': 'kk-', 208 | 'ᆪ': 'gs', 'ᆪᄋ': 'gs-', 'ᆪᄉ': 'gs-s', 209 | 'ᆫ': 'n', 'ᆫᄋ': 'n-', 210 | 'ᆬ': 'nj', 'ᆬᄋ': 'nj-', 'ᆬᄌ': 'nj-j', 211 | 'ᆭ': 'nh', 'ᆭᄋ': 'nh-', 212 | 'ᆮ': 'd', 'ᆮᄋ': 'd-', 213 | 'ᆯ': 'l', 'ᆯᄋ': 'l-', 214 | 'ᆰ': 'lg', 'ᆰᄋ': 'lg-', 215 | 'ᆱ': 'lm', 'ᆱᄋ': 'lm-', 216 | 'ᆲ': 'lb', 'ᆲᄋ': 'lb-', 217 | 'ᆳ': 'ls', 'ᆳᄋ': 'ls-', 'ᆳᄉ': 'ls-s', 218 | 'ᆴ': 'lt', 'ᆴᄋ': 'lt-', 219 | 'ᆵ': 'lp', 'ᆵᄋ': 'lp-', 220 | 'ᆶ': 'lh', 'ᆶᄋ': 'lh-', 221 | 'ᆷ': 'm', 'ᆷᄋ': 'm-', 222 | 'ᆸ': 'b', 'ᆸᄋ': 'b-', 223 | 'ᆹ': 'bs', 'ᆹᄋ': 'bs-', 'ᆹᄉ': 'bs-s', 224 | 'ᆺ': 's', 'ᆺᄋ': 's-', 'ᆺᄊ': 's-ss', 225 | 'ᆻ': 'ss', 'ᆻᄋ': 'ss-', 'ᆻᄉ': 'ss-s', 226 | 'ᆼ': 'ng', 'ᆼᄋ': 'ng-', 227 | 'ᆽ': 'j', 'ᆽᄋ': 'j-', 'ᆽᄌ': 'j-j', 228 | 'ᆾ': 'ch', 'ᆾᄋ': 'ch-', 229 | 'ᆿ': 'k', 'ᆿᄋ': 'k-', 230 | 'ᇀ': 't', 'ᇀᄋ': 't-', 231 | 'ᇁ': 'p', 'ᇁᄋ': 'p-', 232 | 'ᇂ': 'h', 'ᇂᄋ': 'h-' 233 | } 234 | }, 235 | 236 | 'skats': { 237 | hyphen: ' ', 238 | 239 | // Note: giyeok (0x1100) for middle moeum is different than giyeok (0x3131) for standalone jamo 240 | cho: { 241 | 'ᄀ': 'L', 'ᄁ': 'LL', 242 | 'ᄂ': 'F', 243 | 'ᄃ': 'B', 'ᄄ': 'BB', 244 | 'ᄅ': 'V', 245 | 'ᄆ': 'M', 246 | 'ᄇ': 'W', 'ᄈ': 'WW', 247 | 'ᄉ': 'G', 'ᄊ': 'GG', 248 | 'ᄋ': 'K', 249 | 'ᄌ': 'P', 'ᄍ': 'PP', 250 | 'ᄎ': 'C', 251 | 'ᄏ': 'X', 252 | 'ᄐ': 'Z', 253 | 'ᄑ': 'O', 254 | 'ᄒ': 'J', 255 | ' ': ' ' 256 | }, 257 | 258 | // Note: ᅡ (0x1161) for middle moeum is different than ㅏ (0x314F) for standalone jamo 259 | jung: { 260 | 'ᅡ': 'E', 'ᅢ': 'EU', 'ᅣ': 'I', 'ᅤ': 'IU', 261 | 'ᅥ': 'T', 'ᅦ': 'TU', 'ᅧ': 'S', 'ᅨ': 'SU', 262 | 'ᅩ': 'A', 'ᅪ': 'AE', 'ᅫ': 'AEU', 'ᅬ': 'AU', 'ᅭ': 'N', 263 | 'ᅮ': 'H', 'ᅯ': 'HT', 'ᅰ': 'HTU', 'ᅱ': 'HU', 'ᅲ': 'R', 264 | 'ᅳ': 'D', 'ᅴ': 'DU', 'ᅵ': 'U' 265 | }, 266 | 267 | // Note: ᆨ (0x11A8) for last jaeum (batchim) is different than ᄀ (0x1100) for first jaeum 268 | // also different than ㄱ (0x3131) for standalone jamo 269 | jong: { 270 | 'ᆨ': 'L', 'ᆩ': 'LL', 'ᆪ': 'LG', 271 | 'ᆫ': 'F', 'ᆬ': 'FP', 'ᆭ': 'FJ', 272 | 'ᆮ': 'B', 273 | 'ᆯ': 'V', 'ᆰ': 'VL', 'ᆱ': 'VM', 'ᆲ': 'VW', 'ᆳ': 'VG', 'ᆴ': 'VZ', 'ᆵ': 'VO', 'ᆶ': 'VJ', 274 | 'ᆷ': 'M', 275 | 'ᆸ': 'W', 'ᆹ': 'WG', 276 | 'ᆺ': 'G', 'ᆻ': 'GG', 277 | 'ᆼ': 'K', 278 | 'ᆽ': 'P', 279 | 'ᆾ': 'C', 280 | 'ᆿ': 'X', 281 | 'ᇀ': 'Z', 282 | 'ᇁ': 'O', 283 | 'ᇂ': 'J' 284 | } 285 | }, 286 | 287 | /** 288 | * Indonesian Transcription 289 | */ 290 | 'ebi': { 291 | // Note: giyeok (0x1100) for middle moeum is different than giyeok (0x3131) for standalone jamo 292 | cho: { 293 | 'ᄀ': 'gh', 'ᄁ': 'k', 294 | 'ᄂ': 'n', 295 | 'ᄃ': 'dh', 'ᄄ': 't', 296 | 'ᄅ': 'r', 297 | 'ᄆ': 'm', 298 | 'ᄇ': 'b', 'ᄈ': 'p', 299 | 'ᄉ': 's', 'ᄊ': 's', 300 | 'ᄋ': '', 301 | 'ᄌ': 'jh', 'ᄍ': 'c', 302 | 'ᄎ': 'ch', 303 | 'ᄏ': 'kh', 304 | 'ᄐ': 'th', 305 | 'ᄑ': 'ph', 306 | 'ᄒ': 'h' 307 | }, 308 | 309 | // Note: giyeok (0x1100) for middle moeum is different than giyeok (0x3131) for standalone jamo 310 | cho2: { 311 | 'ᄀ': 'g', 'ᄁ': 'k', 312 | 'ᄂ': 'n', 313 | 'ᄃ': 'd', 'ᄄ': 't', 314 | 'ᄅ': 'r', 315 | 'ᄆ': 'm', 316 | 'ᄇ': 'b', 'ᄈ': 'p', 317 | 'ᄉ': 's', 'ᄊ': 's', 318 | 'ᄋ': '', 319 | 'ᄌ': 'j', 'ᄍ': 'c', 320 | 'ᄎ': 'ch', 321 | 'ᄏ': 'kh', 322 | 'ᄐ': 'th', 323 | 'ᄑ': 'ph', 324 | 'ᄒ': 'h' 325 | }, 326 | 327 | // Note: ᅡ (0x1161) for middle moeum is different than ㅏ (0x314F) for standalone jamo 328 | jung: { 329 | 'ᅡ': 'a', 'ᅢ': 'è', 'ᅣ': 'ya', 'ᅤ': 'yè', 330 | 'ᅥ': 'ö', 'ᅦ': 'é', 'ᅧ': 'yö', 'ᅨ': 'yé', 331 | 'ᅩ': 'o', 'ᅪ': 'wa', 'ᅫ': 'wè', 'ᅬ': 'wé', 'ᅭ': 'yo', 332 | 'ᅮ': 'u', 'ᅯ': 'wo', 'ᅰ': 'wé', 'ᅱ': 'wi', 'ᅲ': 'yu', 333 | 'ᅳ': 'eu', 'ᅴ': 'eui', 'ᅵ': 'i' 334 | }, 335 | 336 | // Note: ᆨ (0x11A8) for last jaeum (batchim) is different than ᄀ (0x1100) for first jaeum 337 | // also different than ㄱ (0x3131) for standalone jamo 338 | jong: { 339 | 'ᆨ': 'k', 'ᆨᄋ': 'g', 'ᆨᄂ': 'ngn', 'ᆨᄅ': 'ngn', 'ᆨᄆ': 'ngm', 'ᆨᄒ': 'kh', 340 | 'ᆩ': 'k', 'ᆩᄋ': 'kg', 'ᆩᄂ': 'ngn', 'ᆩᄅ': 'ngn', 'ᆩᄆ': 'ngm', 'ᆩᄒ': 'kh', 341 | 'ᆪ': 'k', 'ᆪᄋ': 'ks', 'ᆪᄂ': 'ngn', 'ᆪᄅ': 'ngn', 'ᆪᄆ': 'ngm', 'ᆪᄒ': 'kch', 342 | 'ᆫ': 'n', 'ᆫᄅ': 'll', 343 | 'ᆬ': 'n', 'ᆬᄋ': 'nj', 'ᆬᄂ': 'nn', 'ᆬᄅ': 'nn', 'ᆬᄆ': 'nm', 'ᆬㅎ': 'nch', 344 | 'ᆭ': 'n', 'ᆭᄋ': 'nh', 'ᆭᄅ': 'nn', 345 | 'ᆮ': 't', 'ᆮᄋ': 'd', 'ᆮᄂ': 'nn', 'ᆮᄅ': 'nn', 'ᆮᄆ': 'nm', 'ᆮᄒ': 'th', 346 | 'ᆯ': 'l', 'ᆯᄋ': 'r', 'ᆯᄂ': 'll', 'ᆯᄅ': 'll', 347 | 'ᆰ': 'k', 'ᆰᄋ': 'lg', 'ᆰᄂ': 'ngn', 'ᆰᄅ': 'ngn', 'ᆰᄆ': 'ngm', 'ᆰᄒ': 'lkh', 348 | 'ᆱ': 'm', 'ᆱᄋ': 'lm', 'ᆱᄂ': 'mn', 'ᆱᄅ': 'mn', 'ᆱᄆ': 'mm', 'ᆱᄒ': 'lmh', 349 | 'ᆲ': 'p', 'ᆲᄋ': 'lb', 'ᆲᄂ': 'mn', 'ᆲᄅ': 'mn', 'ᆲᄆ': 'mm', 'ᆲᄒ': 'lph', 350 | 'ᆳ': 't', 'ᆳᄋ': 'ls', 'ᆳᄂ': 'nn', 'ᆳᄅ': 'nn', 'ᆳᄆ': 'nm', 'ᆳᄒ': 'lsh', 351 | 'ᆴ': 't', 'ᆴᄋ': 'lt', 'ᆴᄂ': 'nn', 'ᆴᄅ': 'nn', 'ᆴᄆ': 'nm', 'ᆴᄒ': 'lth', 352 | 'ᆵ': 'p', 'ᆵᄋ': 'lp', 'ᆵᄂ': 'mn', 'ᆵᄅ': 'mn', 'ᆵᄆ': 'mm', 'ᆵᄒ': 'lph', 353 | 'ᆶ': 'l', 'ᆶᄋ': 'lh', 'ᆶᄂ': 'll', 'ᆶᄅ': 'll', 'ᆶᄆ': 'lm', 'ᆶᄒ': 'lh', 354 | 'ᆷ': 'm', 'ᆷᄅ': 'mn', 355 | 'ᆸ': 'p', 'ᆸᄋ': 'b', 'ᆸᄂ': 'mn', 'ᆸᄅ': 'mn', 'ᆸᄆ': 'mm', 'ᆸᄒ': 'ph', 356 | 'ᆹ': 'p', 'ᆹᄋ': 'ps', 'ᆹᄂ': 'mn', 'ᆹᄅ': 'mn', 'ᆹᄆ': 'mm', 'ᆹᄒ': 'psh', 357 | 'ᆺ': 't', 'ᆺᄋ': 'sh', 'ᆺᄂ': 'nn', 'ᆺᄅ': 'nn', 'ᆺᄆ': 'nm', 'ᆺᄒ': 'sh', 358 | 'ᆻ': 't', 'ᆻᄋ': 's', 'ᆻᄂ': 'nn', 'ᆻᄅ': 'nn', 'ᆻᄆ': 'nm', 'ᆻᄒ': 'th', 359 | 'ᆼ': 'ng', 360 | 'ᆽ': 't', 'ᆽᄋ': 'j', 'ᆽᄂ': 'nn', 'ᆽᄅ': 'nn', 'ᆽᄆ': 'nm', 'ᆽᄒ': 'ch', 361 | 'ᆾ': 't', 'ᆾᄋ': 'ch', 'ᆾᄂ': 'nn', 'ᆾᄅ': 'nn', 'ᆾᄆ': 'nm', 'ᆾᄒ': 'ch', 362 | 'ᆿ': 'k', 'ᆿᄋ': 'k', 'ᆿᄂ': 'ngn', 'ᆿᄅ': 'ngn', 'ᆿᄆ': 'ngm', 'ᆿᄒ': 'kh', 363 | 'ᇀ': 't', 'ᇀᄋ': 't', 'ᇀᄂ': 'nn', 'ᇀᄅ': 'nn', 'ᇀᄆ': 'nm', 'ᇀᄒ': 'th', 'ᇀ이': 'ch', 364 | 'ᇁ': 'p', 'ᇁᄋ': 'p', 'ᇁᄂ': 'mn', 'ᇁᄅ': 'mn', 'ᇁᄆ': 'mm', 'ᇁᄒ': 'ph', 365 | 'ᇂ': 't', 'ᇂᄋ': 'h', 'ᇂᄂ': 'nn', 'ᇂᄅ': 'nn', 'ᇂᄆ': 'mm', 'ᇂᄒ': 't', 366 | 'ᇂᄀ': 'kh', 'ᇂᄃ': 'dh', 'ᇂᄇ': 'bh' 367 | } 368 | }, 369 | 370 | /** 371 | * Kontsevich 372 | */ 373 | 'konsevich': { 374 | // Note: giyeok (0x1100) for middle moeum is different than giyeok (0x3131) for standalone jamo 375 | cho: { 376 | 'ᄀ': 'к', 'ᄁ': 'кк', 377 | 'ᄂ': 'н', 378 | 'ᄃ': 'т', 'ᄄ': 'тт', 379 | 'ᄅ': 'р', 380 | 'ᄆ': 'м', 381 | 'ᄇ': 'п', 'ᄈ': 'пп', 382 | 'ᄉ': 'с', 'ᄊ': 'сс', 383 | 'ᄋ': '', 384 | 'ᄌ': 'ч', 'ᄍ': 'чч', 385 | 'ᄎ': 'чх', 386 | 'ᄏ': 'кх', 387 | 'ᄐ': 'тх', 388 | 'ᄑ': 'пх', 389 | 'ᄒ': 'х' 390 | }, 391 | 392 | // Note: ᅡ (0x1161) for middle moeum is different than ㅏ (0x314F) for standalone jamo 393 | jung: { 394 | 'ᅡ': 'а', 'ᅢ': 'э', 'ᅣ': 'я', 'ᅤ': 'йя', 395 | 'ᅥ': 'о', 'ᅦ': 'е́', 'ᅧ': 'ё', 'ᅨ': 'йе', 396 | 'ᅩ': 'о́', 'ᅪ': 'ва', 'ᅫ': 'вэ', 'ᅬ': 'ве', 'ᅭ': 'ё', 397 | 'ᅮ': 'у', 'ᅯ': 'во', 'ᅰ': 'ве', 'ᅱ': 'ви', 'ᅲ': 'ю', 398 | 'ᅳ': 'ы', 'ᅴ': 'ый', 'ᅵ': 'и' 399 | }, 400 | 401 | // Note: ᆨ (0x11A8) for last jaeum (batchim) is different than ᄀ (0x1100) for first jaeum 402 | // also different than ㄱ (0x3131) for standalone jamo 403 | jong: { 404 | 'ᆨ': 'к', 'ᆨᄋ': 'г', 'ᆨᄂ': 'нн', 'ᆨᄅ': 'нн', 'ᆨᄆ': 'нм', 405 | 'ᆩ': 'кк', 'ᆩᄋ': 'кк', 'ᆩᄂ': 'нн', 'ᆩᄅ': 'нн', 'ᆩᄆ': 'нм', 406 | 'ᆪ': 'к', 'ᆪᄋ': 'кс', 'ᆪᄂ': 'нн', 'ᆪᄅ': 'нн', 'ᆪᄆ': 'нм', 'ᆪᄒ': 'ксх', 407 | 'ᆫ': 'н', 'ᆫᄀ': 'нг', 'ᆫᄃ': 'нд', 'ᆫᄅ': 'лл', 'ᆫᄇ': 'нб', 'ᆫᄌ': 'ндж', 408 | 'ᆬ': 'н', 'ᆬᄋ': 'нч', 'ᆬᄂ': 'нн', 'ᆬᄅ': 'нн', 'ᆬᄆ': 'нм', 'ᆬㅎ': 'нчх', 409 | 'ᆭ': 'н', 'ᆭᄋ': 'нх', 'ᆭᄅ': 'нн', 410 | 'ᆮ': 'т', 'ᆮᄂ': 'тн', 'ᆮᄅ': 'нн', 'ᆮᄆ': 'нм', 411 | 'ᆯ': 'ль', 'ᆯᄋ': 'р', 'ᆯᄀ': 'льг', 'ᆯᄂ': 'лл', 'ᆯᄃ': 'льтт', 'ᆯᄅ': 'лл', 'ᆯᄇ': 'льб', 'ᆯᄉ': 'льсс', 'ᆯᄌ': 'льчч', 'ᆯᄒ': 'рх', 412 | 'ᆰ': 'к', 'ᆰᄋ': 'льг', 'ᆰᄀ': 'льг', 'ᆰᄂ': 'нн', 'ᆰᄅ': 'нн', 'ᆰᄆ': 'нм', 'ᆰᄒ': 'лькх', 413 | 'ᆱ': 'м', 'ᆱᄋ': 'льм', 'ᆱᄂ': 'мн', 'ᆱᄅ': 'мн', 'ᆱᄆ': 'мм', 'ᆱᄒ': 'льмх', 414 | 'ᆲ': 'п', 'ᆲᄋ': 'льп', 'ᆲᄂ': 'мн', 'ᆲᄅ': 'мн', 'ᆲᄆ': 'мм', 'ᆲᄒ': 'льпх', 415 | 'ᆳ': 'т', 'ᆳᄋ': 'льс', 'ᆳᄂ': 'нн', 'ᆳᄅ': 'нн', 'ᆳᄆ': 'мн', 'ᆳᄒ': 'льс', 416 | 'ᆴ': 'т', 'ᆴᄋ': 'льтх', 'ᆴᄂ': 'нн', 'ᆴᄅ': 'нн', 'ᆴᄆ': 'нм', 'ᆴᄒ': 'льтх', 417 | 'ᆵ': 'п', 'ᆵᄋ': 'льпх', 'ᆵᄂ': 'мн', 'ᆵᄅ': 'мн', 'ᆵᄆ': 'мм', 'ᆵᄒ': 'льпх', 418 | 'ᆶ': 'ль', 'ᆶᄋ': 'льх', 'ᆶᄂ': 'лл', 'ᆶᄅ': 'лл', 'ᆶᄆ': 'льм', 'ᆶᄒ': 'льх', 419 | 'ᆷ': 'м', 'ᆷᄀ': 'мг', 'ᆷᄃ': 'мд', 'ᆷᄅ': 'мн', 'ᆷᄇ': 'мб', 'ᆷᄌ': 'мдж', 420 | 'ᆸ': 'п', 'ᆸᄋ': 'б', 'ᆸᄂ': 'мн', 'ᆸᄅ': 'мн', 'ᆸᄆ': 'мм', 421 | 'ᆹ': 'п', 'ᆹᄋ': 'пс', 'ᆹᄂ': 'мн', 'ᆹᄅ': 'мн', 'ᆹᄆ': 'мм', 422 | 'ᆺ': 'т', 'ᆺᄋ': 'с', 'ᆺᄂ': 'нн', 'ᆺᄅ': 'нн', 'ᆺᄆ': 'нм', 'ᆺᄒ': 'с', 423 | 'ᆻ': 'т', 'ᆻᄋ': 'сс', 'ᆻᄅ': 'тн', 'ᆻᄆ': 'нм', 'ᆻᄒ': 'тх', 424 | 'ᆼ': 'нъ', 'ᆼᄀ': 'нг', 'ᆼᄃ': 'нд', 'ᆼᄅ': 'нн', 'ᆼᄇ': 'нб', 'ᆼᄌ': 'ндж', 425 | 'ᆽ': 'т', 'ᆽᄋ': 'ч', 'ᆽᄂ': 'нн', 'ᆽᄅ': 'нн', 'ᆽᄆ': 'нм', 'ᆽᄒ': 'чх', 426 | 'ᆾ': 'т', 'ᆾᄋ': 'чх', 'ᆾᄂ': 'нн', 'ᆾᄅ': 'нн', 'ᆾᄆ': 'нм', 'ᆾᄒ': 'чх', 427 | 'ᆿ': 'к', 'ᆿᄋ': 'кх', 'ᆿᄂ': 'нн', 'ᆿᄅ': 'нн', 'ᆿᄆ': 'нм', 'ᆿᄒ': 'кх', 428 | 'ᇀ': 'т', 'ᇀᄋ': 'тх', 'ᇀᄂ': 'нн', 'ᇀᄅ': 'нн', 'ᇀᄆ': 'нм', 'ᇀᄒ': 'тх', 429 | 'ᇁ': 'п', 'ᇁᄋ': 'пх', 'ᇁᄂ': 'мн', 'ᇁᄅ': 'мн', 'ᇁᄆ': 'мм', 'ᇁᄒ': 'пх', 430 | 'ᇂ': 'т', 'ᇂᄋ': 'х', 'ᇂᄂ': 'нн', 'ᇂᄅ': 'нн', 'ᇂᄆ': 'мм', 'ᇂᄒ': 'тх', 431 | 'ᇂᄀ': 'кх', 432 | } 433 | } 434 | 435 | } 436 | }, 437 | 438 | //////////////////////////////////////////////////////////////////// 439 | // Conversion methods 440 | //////////////////////////////////////////////////////////////////// 441 | 442 | /** 443 | * Converts Hangul to Romaja 444 | * 445 | * Options/Parameters: 446 | * text - (String) Source string. 447 | * rule - (String) Romanization rule. 448 | * Possible values: rr|rr-translit|skats 449 | * hyphen - (String) Hyphenate syllables with specified characters. 450 | * 451 | * Return: 452 | * (String) Romanized string. 453 | */ 454 | hangulToLatin: function() { // (text, rule, hyphen) 455 | 456 | // Helper functions 457 | // Check if it's letter or numbers 458 | var isChoseong = function(char) { 459 | if(char.charCodeAt(0) >= 0x1100 && char.charCodeAt(0) <= 0x1112) { 460 | return true; 461 | } 462 | else { 463 | return false; 464 | } 465 | }; 466 | 467 | // Options mapping 468 | var args = {}; 469 | if(typeof arguments[0] == 'object') { 470 | args = arguments[0]; 471 | } 472 | else { 473 | args.text = arguments[0]; 474 | args.rule = arguments[1]; 475 | args.hyphen = arguments[2]; 476 | } 477 | 478 | if(args.hyphen == null) { 479 | args.hyphen = ''; 480 | } 481 | 482 | var rulemap = this.rules.hangul.rr; 483 | if(args.rule != null && this.rules.hangul[args.rule] != null) { 484 | rulemap = this.rules.hangul[args.rule]; 485 | } 486 | else if(args.rule != null) { 487 | throw 'Invalid rule ' + args.rule; 488 | } 489 | 490 | var rom = ''; 491 | var curr = null, next; 492 | var skipJaeum = false; // Indicates jaeum of current iteration to be skipped 493 | for(var i = 0; i <= args.text.length; i++) { 494 | // If next is hangul syllable, separate it into jamo 495 | // 0xAC00 is the first hangul syllable in unicode table 496 | // 0x1100 is the first hangul jaeum in unicode table 497 | // 0x1161 is the first hangul moeum in unicode table 498 | // 0x11A8 is the first hangul batchim in unicode table 499 | nextIdx = args.text.charCodeAt(i) - 0xAC00; 500 | if(!isNaN(nextIdx) && nextIdx >= 0 && nextIdx <= 11171) { 501 | next = String.fromCharCode(Math.floor(nextIdx / 588) + 0x1100) 502 | + String.fromCharCode(Math.floor(nextIdx % 588 / 28) + 0x1161) 503 | + (nextIdx % 28 == 0 ? '' : String.fromCharCode(nextIdx % 28 + 0x11A7)); // Index 0 is reserved for nothing 504 | } 505 | else { 506 | next = args.text.charAt(i); 507 | } 508 | 509 | // Except for first iteration (curr is null), 510 | // Curr and next contains 2 or 3 jamo, or 1 non-hangul letter 511 | if(curr != null) { 512 | 513 | var res = ''; 514 | 515 | // Choseong Jaeum 516 | if(!skipJaeum) { 517 | // If not the first syllable, try cho2 if defined 518 | if(i > 0 && !/\s/.test(args.text.charAt(i-2)) && 519 | rulemap.cho2 != undefined && 520 | rulemap.cho2[curr.charAt(0)] != undefined 521 | ) { 522 | res += rulemap.cho2[curr.charAt(0)]; 523 | } 524 | else if(rulemap.cho[curr.charAt(0)] != undefined) { 525 | res += rulemap.cho[curr.charAt(0)]; 526 | } 527 | else { 528 | res += curr.charAt(0); 529 | } 530 | } 531 | skipJaeum = false; 532 | 533 | // Jungseong Moeum 534 | if(curr.length > 1) { 535 | if(rulemap.jung[curr.charAt(1)] != undefined) { 536 | res += rulemap.jung[curr.charAt(1)]; 537 | } 538 | else { 539 | res += curr.charAt(1); 540 | } 541 | 542 | // Add hyphen if no batchim 543 | if(curr.length == 2 && isChoseong(next.charAt(0))) { 544 | res += ' '; 545 | } 546 | } 547 | 548 | // Jongseong Jaeum (Batchim) 549 | if(curr.length > 2) { 550 | // Changing sound with next jaeum + moeum 551 | if(rulemap.jong[curr.charAt(2) + next.charAt(0) + next.charAt(1)] != undefined) { 552 | res += rulemap.jong[curr.charAt(2) + next.charAt(0) + next.charAt(1)]; 553 | skipJaeum = true; 554 | 555 | // No need to add hyphen here as it's already defined 556 | } 557 | // Changing sound with next jaeum 558 | else if(rulemap.jong[curr.charAt(2) + next.charAt(0)] != undefined) { 559 | res += rulemap.jong[curr.charAt(2) + next.charAt(0)]; 560 | skipJaeum = true; 561 | 562 | // No need to add hyphen here as it's already defined 563 | } 564 | // Unchanging sound 565 | else if(rulemap.jong[curr.charAt(2)] != undefined) { 566 | res += rulemap.jong[curr.charAt(2)]; 567 | 568 | // Add hyphen 569 | if(isChoseong(next.charAt(0))) { 570 | res += ' '; 571 | } 572 | } 573 | else { 574 | res += curr.charAt(2); 575 | 576 | // Add hyphen 577 | if(isChoseong(next.charAt(0))) { 578 | res += ' '; 579 | } 580 | } 581 | } 582 | 583 | // Replace hyphen (if this is hangeul word) 584 | if(curr.length > 1) { 585 | if(args.hyphen == '' && rulemap.hyphen != null) { 586 | res = res.replace(' ', rulemap.hyphen); 587 | } 588 | else { 589 | // Soft hyphen 590 | res = res.replace(' ', args.hyphen); 591 | // Hard hyphen 592 | if(args.hyphen != '') { 593 | res = res.replace('-', args.hyphen); 594 | } 595 | } 596 | } 597 | rom += res; 598 | } 599 | 600 | curr = next; 601 | } 602 | return rom; 603 | }, 604 | 605 | //////////////////////////////////////////////////////////////////// 606 | // All-in-one converters 607 | //////////////////////////////////////////////////////////////////// 608 | 609 | /** 610 | * Converts Hangul/Hiragana/Katakana to Romaja 611 | * 612 | * Conversion is done using default conversion rule for each script. 613 | * If you wish to specify which rule to use, use hangulToLatin(), 614 | * hiraganaToLatin(), or katakanaToLatin() function. 615 | * 616 | * Options/Parameters: 617 | * text - (String) Source text. 618 | * rule - (String) Romanization rule. 619 | * hyphen - (String) Hyphenate syllables with specified characters. 620 | * 621 | * Return: 622 | * (String) Romanized string. 623 | */ 624 | toLatin: function() { // (text, rule, hyphen) 625 | return this.hangulToLatin.apply(this, arguments); 626 | }, 627 | 628 | /** 629 | * Converts Hangul/Hiragana/Katakana to Romaja 630 | * 631 | * This is an alias of toRomaja(). 632 | */ 633 | romanize: function() { 634 | return this.toLatin.apply(this, arguments); 635 | }, 636 | 637 | /** 638 | * Converts Romaji/Hangul/Katakana to Hiragana 639 | */ 640 | toHiragana: function(text) { 641 | //TODO 642 | throw 'Not implemented'; 643 | return text; 644 | }, 645 | 646 | /** 647 | * Converts Romaji/Hangul/Hiragana to Katakana 648 | */ 649 | toKatakana: function(text) { 650 | //TODO 651 | throw 'Not implemented'; 652 | return text; 653 | }, 654 | 655 | /** 656 | * Converts Romaji/Hiragana/Katakana to Hangul 657 | */ 658 | toHangul: function(text) { 659 | //TODO 660 | throw 'Not implemented'; 661 | return text; 662 | }, 663 | 664 | }; --------------------------------------------------------------------------------