From fc99d36c551de6c8fc14c49d91c27077c57be7bb Mon Sep 17 00:00:00 2001 From: dousha Date: Sun, 31 Mar 2024 22:57:06 +0800 Subject: [PATCH] initial working draft --- .gitignore | 3 + LICENSE | 21 +++++ assets/css/style.css | 49 ++++++++++++ assets/js/work.js | 182 +++++++++++++++++++++++++++++++++++++++++++ shortcode.php | 147 ++++++++++++++++++++++++++++++++++ 5 files changed, 402 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 assets/css/style.css create mode 100644 assets/js/work.js create mode 100644 shortcode.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f13c0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea/ +/.vscode/ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..86490a8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Jiahao Lee + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..28bf621 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,49 @@ +.d-l10n-translate-template { + display: none; +} + +.d-l10n-translated { + text-decoration: underline dotted; + cursor: pointer; +} + +.d-l10n-translated-entity-name { + font-weight: bold; +} + +.d-l10n-translated-entity-name-cjk { + text-decoration: underline solid; +} + +.d-l10n-translated-manuscript-name-cjk:before { + content: '《'; +} + +.d-l10n-translated-manuscript-name-cjk:after { + content: '》'; +} + +.d-l10n-translated-manuscript-name { + font-style: italic; +} + +.d-l10n-translated-original:before { + content: ' ' +} + +.d-l10n-translated-original:after { + content: ' ' +} + +.d-l10n-color-box { + display: inline-block; + width: 0.6rem; + height: 0.6rem; + border: 1px solid #000; + margin-left: 0.2rem; + margin-right: -0.2rem; +} + +.d-l10n-color { + display: inline-block; +} diff --git a/assets/js/work.js b/assets/js/work.js new file mode 100644 index 0000000..1d40167 --- /dev/null +++ b/assets/js/work.js @@ -0,0 +1,182 @@ +function isCjkContext(str) { + // since js regex does not support 5-digit hex unicode + // we will have to check for the range ourselves + + // CJK Unified Ideographs + const cjkRange1 = [0x4E00, 0x9FFF] + // CJK Unified Ideographs Extension A + const cjkRange2 = [0x3400, 0x4DBF] + // CJK Unified Ideographs Extension B + const cjkRange3 = [0x20000, 0x2A6DF] + // CJK Unified Ideographs Extension C + const cjkRange4 = [0x2A700, 0x2B73F] + // CJK Unified Ideographs Extension D + const cjkRange5 = [0x2B740, 0x2B81F] + // CJK Unified Ideographs Extension E + const cjkRange6 = [0x2B820, 0x2CEAF] + // CJK Unified Ideographs Extension F + const cjkRange7 = [0x2CEB0, 0x2EBEF] + // CJK Unified Ideographs Extension G + const cjkRange8 = [0x30000, 0x3134F] + + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i) + if ((char >= cjkRange1[0] && char <= cjkRange1[1]) || + (char >= cjkRange2[0] && char <= cjkRange2[1]) || + (char >= cjkRange3[0] && char <= cjkRange3[1]) || + (char >= cjkRange4[0] && char <= cjkRange4[1]) || + (char >= cjkRange5[0] && char <= cjkRange5[1]) || + (char >= cjkRange6[0] && char <= cjkRange6[1]) || + (char >= cjkRange7[0] && char <= cjkRange7[1]) || + (char >= cjkRange8[0] && char <= cjkRange8[1])) { + return true + } + } + + return false +} + +function processColorBoxes() { + const allColorBoxes = document.querySelectorAll(".d-l10n-color"); + + const knownColors = {} + + // first pass: collect all colors + allColorBoxes.forEach((colorBox) => { + const key = colorBox.innerHTML + const value = colorBox.getAttribute('data-color') + if (value == null || value.length < 1) { + return; + } + + knownColors[key] = value + }); + + // second pass: add a little box with the color + allColorBoxes.forEach((colorBox) => { + const key = colorBox.innerHTML + const value = knownColors[key] + if (value == null || value.length < 1) { + return; + } + + colorBox.innerHTML = ` ${key}` + }); +} + +function getPreferredTranslation() { + if (localStorage) { + return localStorage.getItem("d-l10n-preferred-translation") || 'writer' + } + + return 'writer'; +} + +function changePreferredTranslation(mode) { + // can be 'writer', 'canonical', 'original' + if (mode !== 'writer' && mode !== 'canonical' && mode !== 'original') { + mode = 'writer' + } + + if (localStorage) { + localStorage.setItem("d-l10n-preferred-translation", mode) + } +} + +function processTranslationBoxes() { + const boxes = document.querySelectorAll(".d-l10n-translate-template"); + + const knownTranslations = {} + + // first pass: collect all translations + boxes.forEach((box) => { + const key = box.innerHTML + const originalValue = box.getAttribute('data-original') + const value = box.getAttribute('data-translated') + const canonicalValue = box.getAttribute('data-canonical-name') + const fullFormValue = box.getAttribute('data-full-form') + const rubyValue = box.getAttribute('data-ruby') + + const isEntity = box.getAttribute('data-entity') === 'true' + const isManuscript = box.getAttribute('data-manuscript') === 'true' + const isUntranslatable = box.getAttribute('data-untranslatable') === 'true' + + if (originalValue == null || originalValue.length < 1) { + return; + } + + knownTranslations[key] = { + original: originalValue, + translated: value, + canonical: canonicalValue, + fullForm: fullFormValue, + ruby: rubyValue, + isEntity: isEntity, + isManuscript: isManuscript, + isUntranslatable: isUntranslatable + } + }); + + const currentPreferredTranslation = getPreferredTranslation() + + // remove already generated boxes + const generatedBoxes = document.querySelectorAll(".d-l10n-generated") + generatedBoxes.forEach((box) => { + box.remove() + }) + + // second pass: generate the preferred translation + boxes.forEach((box) => { + const key = box.innerHTML + const translation = knownTranslations[key] + if (translation == null) { + return; + } + + if (translation.isUntranslatable) { + box.innerHTML = `${translation.original}` + return; + } + + let preferredTranslation + + if (currentPreferredTranslation === 'writer') { + preferredTranslation = translation.translated + } else if (currentPreferredTranslation === 'canonical') { + preferredTranslation = translation.canonical || translation.translated + } else if (currentPreferredTranslation === 'original') { + preferredTranslation = translation.original + } + + if (translation.isEntity) { + if (isCjkContext(preferredTranslation)) { + preferredTranslation = `${preferredTranslation}` + } else { + preferredTranslation = `${preferredTranslation}` + } + } + + if (translation.isManuscript) { + if (isCjkContext(preferredTranslation)) { + preferredTranslation = `${preferredTranslation}` + } else { + preferredTranslation = `${preferredTranslation}` + } + } + + if (translation.ruby) { + preferredTranslation = `${preferredTranslation}${translation.ruby}` + } + + if (currentPreferredTranslation === 'original') { + box.insertAdjacentHTML('afterend', `${translation.original}`) + } else { + box.insertAdjacentHTML('afterend', `${preferredTranslation}`) + } + }) +} + +window.addEventListener("load", () => { + processColorBoxes() + processTranslationBoxes() +}); diff --git a/shortcode.php b/shortcode.php new file mode 100644 index 0000000..fc344f2 --- /dev/null +++ b/shortcode.php @@ -0,0 +1,147 @@ + 0; + } + + return false; +} + +function d_l10n_compile_attributes($attr, $content): string +{ + if (empty($attr)) { + return 'data-ref="' . $content . '"'; + } + + $original = $attr['o']; + $translated = $attr['p']; + if (empty($translated)) { + $translated = $content; + } + + $isEntity = $attr['e']; + if (empty($isEntity)) { + $isEntity = $attr['ent']; + } + $isEntity = d_l10n_evaluate_truthiness($isEntity); + + $isManuscript = $attr['m']; + if (empty($isManuscript)) { + $isManuscript = $attr['man']; + } + $isManuscript = d_l10n_evaluate_truthiness($isManuscript); + + $canonicalName = $attr['cn']; + + $fullForm = $attr['a']; + $ruby = $attr['r']; + if (empty($ruby)) { + $ruby = $attr['ruby']; + } + if (empty($ruby)) { + $ruby = $attr['pro']; + } + + $isUntranslatable = $attr['u']; + if (empty($isUntranslatable)) { + $isUntranslatable = $attr['un']; + } + $isUntranslatable = d_l10n_evaluate_truthiness($isUntranslatable); + + // ok, now compile stuff + $compiled = ''; + if (!empty($original)) { + $compiled .= "data-original=\"" . $original . "\" "; + } + + if (!empty($translated)) { + $compiled .= "data-translated=\"" . $translated . "\" "; + } + + if ($isEntity) { + $compiled .= "data-entity=\"true\" "; + } + + if ($isManuscript) { + $compiled .= "data-manuscript=\"true\" "; + } + + if (!empty($canonicalName)) { + $compiled .= "data-canonical-name=\"" . $canonicalName . "\" "; + } + + if ($fullForm) { + $compiled .= "data-full-form=\"" . $fullForm . "\" "; + } + + if ($ruby) { + $compiled .= "data-ruby=\"" . $ruby . "\" "; + } + + if ($isUntranslatable) { + $compiled .= "data-untranslatable=\"true\" "; + } + + return $compiled; +} + +function d_l10n_translate_shortcode($attr = array(), $content = null, $tag = ''): ?string +{ + if (is_null($content)) { + return ""; + } + + $compiledAttributes = d_l10n_compile_attributes($attr, $content); + + return "" . $content . ""; +} + +function d_l10n_color_shortcode($attr = array(), $content = null, $tag = ''): ?string +{ + $color = $attr['v']; + if (empty($color)) { + return '' . $content . ''; + } + + return '' . $content . ''; +} + +add_action('init', 'd_l10n_add_shortcodes'); +add_action('init', 'd_l10n_enque_assets');