From 28c55ad8bc0e04e614054a1d48523baaf484a39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20F=C3=A9lix=20Onta=C3=B1=C3=B3n?= Date: Thu, 6 May 2021 09:58:43 +0200 Subject: [PATCH] Embedding andaluhgs as part of the gwao script. Adding a Add-On homepage using CardService. --- README.md | 19 +- src/andaluhgs/README.md | 67 +++++++ src/andaluhgs/epa.js | 432 ++++++++++++++++++++++++++++++++++++++++ src/andaluhgs/util.js | 60 ++++++ src/appsscript.json | 11 +- src/main.js | 87 +++++++- src/test.js | 2 +- 7 files changed, 658 insertions(+), 20 deletions(-) create mode 100644 src/andaluhgs/README.md create mode 100644 src/andaluhgs/epa.js create mode 100644 src/andaluhgs/util.js diff --git a/README.md b/README.md index dd9189b..f774f2e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # andaluh-googledocs-addon -A side panel add-on for Google Docs (Google Drive) to help you transliterating español (spanish) spelling to andaluz proposals. It imports [andaluh-gs](https://github.com/andalugeeks/andaluh-js/tree/google-apps-script) library, already hosted at Google Apps Script. +A side panel add-on for Google Docs (Google Drive) to help you transliterating español (spanish) spelling to andaluz proposals. It uses a fork from [andaluh-gs](https://github.com/andalugeeks/andaluh-js/tree/google-apps-script) library, already embedded under `src/andaluhgs`. andaluh-gs about @@ -61,11 +61,13 @@ Note the two links prompted. Push the source code to the google apps script proj $ clasp push (node:1428508) ExperimentalWarning: The fs.promises API is experimental ? Manifest file has been updated. Do you want to push and overwrite? Yes -└─ appsscript.json -└─ main.js -└─ sidebar.html -└─ test.js -Pushed 4 files. +└─ src/andaluhgs/epa.js +└─ src/andaluhgs/util.js +└─ src/appsscript.json +└─ src/main.js +└─ src/sidebar.html +└─ src/test.js +Pushed 6 files. ``` Go open the `google docs` file. Use the link prompted upon `clasp create`. @@ -78,9 +80,10 @@ Have a look at the featured video. Click for fullscreen: ## Development -To use a different [andaluh-gs](https://github.com/andalugeeks/andaluh-js/tree/google-apps-script) transcription library version, deploy your own copy on your Google App Script account. Then edit the `appscript.json` file to reference your own version and follow the installation steps from step 1. +To use a different [andaluh-gs](https://github.com/andalugeeks/andaluh-js/tree/google-apps-script) transcription library version you've two options -Further reference on how to edit the `appscript.json` file here: https://developers.google.com/apps-script/manifest/dependencies +* Edit the files under `src/andaluhgs` +* Deploy your own `andaluhgs` as a Google App Script. Then remove the files under `src/andaluhgs` and add your own `andaluhgs` library as a dependency on the `appscript.json` file (further reference on how to edit the `appscript.json` file here: https://developers.google.com/apps-script/manifest/dependencies). ## Roadmap diff --git a/src/andaluhgs/README.md b/src/andaluhgs/README.md new file mode 100644 index 0000000..241ccdd --- /dev/null +++ b/src/andaluhgs/README.md @@ -0,0 +1,67 @@ +# Andaluh-gs + +Google Apps Script implementation for [andaluh-js](https://github.com/andalugeeks/andaluh-js). Transliterate español (spanish) spelling to andaluz proposals. + +andaluh-gs about + +## Table of Contents + +- [Description](#description) +- [Installation](#installation) +- [Usage](#usage) +- [Roadmap](#roadmap) +- [Support](#support) +- [Contributing](#contributing) + +## Description + +The **Andalusian varieties of [Spanish]** (Spanish: *andaluz*; Andalusian) are spoken in Andalusia, Ceuta, Melilla, and Gibraltar. They include perhaps the most distinct of the southern variants of peninsular Spanish, differing in many respects from northern varieties, and also from Standard Spanish. Further info: https://en.wikipedia.org/wiki/Andalusian_Spanish. + +This package introduces transliteration functions to convert *español* (spanish) spelling to andaluz. As there's no official or standard andaluz spelling, andaluh-js is adopting the **EPA proposal (Er Prinzipito Andaluh)**. Further info: https://andaluhepa.wordpress.com. Other andaluz spelling proposals are planned to be added as well. + +## Installation + +Just upload the `andalu/*js` files into your Google Apps Script project, or automate with Google Apps Script `clasp` tool. The benefit of the second approach is you can keep developing locally your `andaluh-gs` based project instead of using the Google Apps Script web IDE. + +``` +$ npm install -g @google/clasp +``` + +Install the google apps script type definitions as well: + +``` +$ npm i -S @types/google-apps-script +``` + +Login and push! + +``` +$ clasp login +$ clasp push +``` + +## Usage + +Have a look at the following snippet + +```javascript +const andaluhEPA = new EPA(); +console.log(andaluhEPA.transcript('El veloz murciélago hindú comía feliz cardillo y kiwi. La cigüeña tocaba el saxofón detrás del palenque de paja')); +// Er belôh murçiélago indú comía felîh cardiyo y kiwi. La çigueña tocaba er çâççofón detrâh der palenque de paha. +``` + +## Roadmap + +* Adding more andaluh spelling proposals. +* Contractions and inter-word interaction rules pending to be implemented. +* Silent /h/ sounds spelling rules pending to be implemented. +* Some spelling intervowel /d/ rules are still pending to be implemented. +* Transliteration rules for some consonant ending words still pending to be implemented. + +## Support + +Please [open an issue](https://github.com/andalugeeks/andaluh-js/issues/new) for support. + +## Contributing + +Please contribute using [Github Flow](https://guides.github.com/introduction/flow/). Create a branch, add commits, and open a pull request. \ No newline at end of file diff --git a/src/andaluhgs/epa.js b/src/andaluhgs/epa.js new file mode 100644 index 0000000..6bbdd2a --- /dev/null +++ b/src/andaluhgs/epa.js @@ -0,0 +1,432 @@ +/** + * Copyleft (c) 2018-2021 Andalugeeks + * + * Authors: + * - Eduardo Amador (original javascript/typescript dev) + * - J. Félix Ontañón (port to google apps script) + * + */ + +// EPA character for Voiceless alveolar fricative /s/ https://en.wikipedia.org/wiki/Voiceless_alveolar_fricative +VAF = 'ç'; +// EPA character for Voiceless velar fricative /x/ https://en.wikipedia.org/wiki/Voiceless_velar_fricative +VVF = 'h'; + +// Digraphs producers. (vowel)(const)(const) that triggers the general digraph rule +DIGRAPHS = [ + 'bb', 'bc', 'bç', 'bÇ', 'bd', 'bf', 'bg', 'bh', 'bm', 'bn', 'bp', 'bq', 'bt', 'bx', 'by', 'cb', 'cc', + 'cç', 'cÇ', 'cd', 'cf', 'cg', 'ch', 'cm', 'cn', 'cp', 'cq', 'ct', 'cx', 'cy', + 'db', 'dc', 'dç', 'dÇ', 'dd', 'df', 'dg', 'dh', 'dl', 'dm', 'dn', 'dp', 'dq', 'dt', 'dx', 'dy', + 'fb', 'fc', 'fç', 'fÇ', 'fd', 'ff', 'fg', 'fh', 'fm', 'fn', 'fp', 'fq', 'ft', 'fx', 'fy', + 'gb', 'gc', 'gç', 'gÇ', 'gd', 'gf', 'gg', 'gh', 'gm', 'gn', 'gp', 'gq', 'gt', 'gx', 'gy', + 'jb', 'jc', 'jç', 'jÇ', 'jd', 'jf', 'jg', 'jh', 'jl', 'jm', 'jn', 'jp', 'jq', 'jr', 'jt', 'jx', 'jy', + 'lb', 'lc', 'lç', 'lÇ', 'ld', 'lf', 'lg', 'lh', 'll', 'lm', 'ln', 'lp', 'lq', 'lr', 'lt', 'lx', 'ly', + 'mm', 'mn', + 'nm', 'nn', + 'pb', 'pc', 'pç', 'pÇ', 'pd', 'pf', 'pg', 'ph', 'pm', 'pn', 'pp', 'pq', 'pt', 'px', 'py', + 'rn', + 'sb', 'sc', 'sç', 'sÇ', 'sd', 'sf', 'sg', 'sh', 'sk', 'sl', 'sm', 'sn', 'sñ', 'sp', 'sq', 'sr', 'st', 'sx', 'sy', + 'tb', 'tc', 'tç', 'tÇ', 'td', 'tf', 'tg', 'th', 'tl', 'tm', 'tn', 'tp', 'tq', 'tt', 'tx', 'ty', + 'xb', 'xc', 'xç', 'xÇ', 'xd', 'xf', 'xg', 'xh', 'xl', 'xm', 'xn', 'xp', 'xq', 'xr', 'xt', 'xx', 'xy', + 'zb', 'zc', 'zç', 'zÇ', 'zd', 'zf', 'zg', 'zh', 'zl', 'zm', 'zn', 'zp', 'zq', 'zr', 'zt', 'zx', 'zy' +]; + +H_RULES_EXCEPT = { + 'haz': 'âh', 'hez': 'êh', 'hoz': 'ôh', + 'oh': 'ôh', + 'yihad': 'yihá', + 'h': 'h' // Keep an isolated h as-is +}; + +GJ_RULES_EXCEPT = { + 'gin': 'yin', 'jazz': 'yâh', 'jet': 'yêh' +}; + +V_RULES_EXCEPT = { + 'vis': 'bî', 'ves': 'bêh' +}; + +LL_RULES_EXCEPT = { + 'grill': 'grîh' +}; + +WORDEND_D_RULES_EXCEPT = { + 'çed': 'çêh' +}; + +WORDEND_S_RULES_EXCEPT = { + 'bies': 'biêh', 'bis': 'bîh', 'blues': 'blû', 'bus': 'bûh', + 'dios': 'diôh', 'dos': 'dôh', + 'gas': 'gâh', 'gres': 'grêh', 'gris': 'grîh', + 'luis': 'luîh', + 'mies': 'miêh', 'mus': 'mûh', + 'os': 'ô', + 'pis': 'pîh', 'plus': 'plûh', 'pus': 'pûh', + 'ras': 'râh', 'res': 'rêh', + 'tos': 'tôh', 'tres': 'trêh', 'tris': 'trîh' +}; + +WORDEND_CONST_RULES_EXCEPT = { + 'al': 'al', 'cual': 'cuâ', 'del': 'del', 'dél': 'dél', 'el': 'el', 'él': 'él', 'tal': 'tal', 'bil': 'bîl', + // TODO: uir = huir. Maybe better to add the exceptions on h_rules? + 'por': 'por', 'uir': 'huîh', + // sic, tac + 'çic': 'çic', 'tac': 'tac', + 'yak': 'yak', + 'stop': 'êttôh', 'bip': 'bip' +}; + +WORDEND_D_INTERVOWEL_RULES_EXCEPT = { + // Ending with -ado + 'fado': 'fado', 'cado': 'cado', 'nado': 'nado', 'priado': 'priado', + // Ending with -ada + 'fabada': 'fabada', 'fabadas': 'fabadas', 'fada': 'fada', 'ada': 'ada', 'lada': 'lada', 'rada': 'rada', + // Ending with -adas + 'adas': 'adas', 'radas': 'radas', 'nadas': 'nadas', + // Ending with -ido + 'aikido': 'aikido', 'bûççido': 'bûççido', 'çido': 'çido', 'cuido': 'cuido', 'cupido': 'cupido', 'descuido': 'descuido', + 'despido': 'despido', 'eido': 'eido', 'embido': 'embido', 'fido': 'fido', 'hido': 'hido', 'ido': 'ido', 'infido': 'infido', + 'laido': 'laido', 'libido': 'libido', 'nido': 'nido', 'nucleido': 'nucleido', 'çonido': 'çonido', 'çuido': 'çuido' +}; + +ENDING_RULES_EXCEPTION = { + // Exceptions to digraph rules with nm + 'biêmmandao': 'bienmandao', 'biêmmeçabe': 'bienmeçabe', 'buêmmoço': 'buenmoço', 'çiêmmiléçima': 'çienmiléçima', 'çiêmmiléçimo': 'çienmiléçimo', 'çiêmmilímetro': 'çienmilímetro', 'çiêmmiyonéçima': 'çienmiyonéçima', 'çiêmmiyonéçimo': 'çienmiyonéçimo', 'çiêmmirmiyonéçima': 'çienmirmiyonéçima', 'çiêmmirmiyonéçimo': 'çienmirmiyonéçimo', + // Exceptions to l rules + 'marrotadôh': 'mârrotadôh', 'marrotâh': 'mârrotâh', 'mirrayâ': 'mîrrayâ', + // Exceptions to psico pseudo rules + 'herôççiquiatría': 'heroçiquiatría', 'herôççiquiátrico': 'heroçiquiátrico', 'farmacôççiquiatría': 'farmacoçiquiatría', 'metempçícoçî': 'metemçícoçî', 'necróçico': 'necróççico', 'pampçiquîmmo': 'pamçiquîmmo', + // Other exceptions + 'antîççerôttármico': 'antiçerôttármico', 'eclampçia': 'eclampçia', 'pôttoperatorio': 'pôççoperatorio', 'çáccrito': 'çánccrito', 'manbîh': 'mambîh', 'cômmelináçeo': 'commelináçeo', 'dîmmneçia': 'dînneçia', 'todo': 'tó', 'todô': 'tôh', 'toda': 'toa', 'todâ': 'toâ', + // Other exceptions monosyllables + 'as': 'âh', 'clown': 'claun', 'crack': 'crâh', 'down': 'daun', 'es': 'êh', 'ex': 'êh', 'ir': 'îh', 'miss': 'mîh', 'muy': 'mu', 'ôff': 'off', 'os': 'ô', 'para': 'pa', 'ring': 'rin', 'rock': 'rôh', 'spray': 'êppray', 'sprint': 'êpprín', 'wa': 'gua' +}; + +var EPA = /** @class */ (function () { + function EPA() { + var _this = this; + this.tags = []; + + // Regex compilation. + // Words to ignore in the translitaration in escapeLinks mode. + this.ignore_rules = function (text) { + var patterns = [ + //URLs, i.e. andaluh.es, www.andaluh.es, https://www.andaluh.es + /(https?:\/\/)?(?:www\.)?(?:[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})/gi, + /(?:@\S+)/gi, // Mentions, i.e. @andaluh + /(?:#\S+)/gi, // Hashtags, i.e. #andaluh + /(?=\b[MCDXLVI]{1,8}\b)M{0,4}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3})/g, // roman numerals + ]; + + return patterns.reduce(function (text, pattern) { + var matches = text.match(pattern); + if (matches) { + var randomInt = Math.floor(Math.random() * 999999999).toString(); + matches.forEach(function (match) { return _this.tags.push([randomInt, match]); }); + return text.replace(pattern, randomInt); + } + return text; + }, text); + }; + + this.h_rules = function (text) { + // chihuahua => chiguagua + return text + .replace(/([\\p{L}])?(? cacagûete + .replace(/([\\p{L}])?(? Aççila | Éxito => Éççito + .replace(/(a|e|i|o|u|á|é|í|ó|ú)(x)(a|e|i|o|u|y|á|é|í|ó|ú)/gi, function (_, prev_char, x_char, next_char) { return get_vowel_circumflex(prev_char) + keep_case(x_char, vaf).repeat(2) + next_char; }) + // Every word starting with /ks/ + // Xilófono roto => Çilófono roto + .replace(/\b(? gueno / aguelo / sagues + .replace(/(b)(uen)/gi, function (_, b_char, suffix) { return keep_case(b_char, 'g') + suffix; }) + .replace(/(s|a)?(? NB -> MB (i.e.: envidia -> embidia) + .replace(/nv/gi, function (chars) { return keep_case(chars[0], 'm') + keep_case(chars[1], 'b'); }) + // v -> b + .replace(/\b(\S*)(v)(\S*)\b/gi, function (word) { + if (V_RULES_EXCEPT[word.toLowerCase()]) { + return keep_case(word, V_RULES_EXCEPT[word.toLowerCase()]); + } + return word.replace(/v/gi, function (word) { return keep_case(word, 'b'); }); + }); + }; + + this.ll_rules = function (text) { + // Replacing /ʎ/ (digraph ll) with Greek Y for /ʤ/ sound (voiced postalveolar affricate) + return text.replace(/\b(\w*?)(l)(l)(\w*?)\b/gi, function (word) { + if (LL_RULES_EXCEPT[word.toLowerCase()]) { + return keep_case(word, LL_RULES_EXCEPT[word.toLowerCase()]); + } + return word.replace(/ll/gi, function (word) { return keep_case(word, 'y'); }); + }); + }; + + this.l_rules = function (text) { + // Rotating /l/ with /r/ + return text.replace(/(l)(b|c|ç|g|s|d|f|g|h|k|m|p|q|r|t|x|z)/gi, function (_, l_char, suffix) { return keep_case(l_char, 'r') + suffix; }); + }; + + //TODO: Keep case. + this.psico_pseudo_rules = function (text) { + return text.replace(/(psic|psiq|pseud)/gi, function (_, syllabe) { + return keep_case(syllabe[0], syllabe[1]) + syllabe.slice(2, syllabe.length) + }); + }; + + // Replacing Voiceless alveolar fricative (vaf) /s/ /θ/ with EPA's ç/Ç + this.vaf_rules = function (text, vaf) { + if (vaf === void 0) { vaf = VAF; } + return text + .replace(/(c(?=e|i|é|í|ê|î)|z|s)(a|e|i|o|u|á|é|í|ó|ú|Á|É|Í|Ó|Ú|â|ê|î|ô|û|Â|Ê|Î|Ô|Û)/gi, function (_, cons_char, suffix) { return keep_case(cons_char, vaf) + suffix; }); + }; + + this.word_ending_rules = function (text) { + var repl_rules = { + a: 'â', A: 'Â', á: 'â', Á: 'Â', + e: 'ê', E: 'Ê', é: 'ê', É: 'Ê', + i: 'î', I: 'Î', í: 'î', Í: 'Î', + o: 'ô', O: 'Ô', ó: 'ô', Ó: 'Ô', + u: 'û', U: 'Û', ú: 'û', Ú: 'Û' + }; + + var stressed_rules = { + a: 'á', A: 'Á', á: 'á', Á: 'Á', + e: 'é', E: 'É', é: 'é', É: 'É', + i: 'î', I: 'Î', í: 'î', Í: 'Î', + o: 'ô', O: 'Ô', ó: 'ô', Ó: 'Ô', + u: 'û', U: 'Û', ú: 'û', Ú: 'Û' + }; + + var contain_vocal_tilde = function (string) { return new RegExp(/á|é|í|ó|ú/gi).test(string); }; + + var replace_intervowel_d_end_with_case = function (word, prefix, suffix_vowel_a, suffix_d_char, suffix_vowel_b, ending_s) { + if (WORDEND_D_INTERVOWEL_RULES_EXCEPT[word.toLowerCase()]) { + return keep_case(word, WORDEND_D_INTERVOWEL_RULES_EXCEPT[word.toLowerCase()]); + } + if (contain_vocal_tilde(prefix)) + return word; + var suffix = suffix_vowel_a + suffix_d_char + suffix_vowel_b + ending_s; + switch (suffix.toLowerCase()) { + case 'ada': + return prefix + keep_case(suffix_vowel_b, 'á'); + case 'adas': + return prefix + keep_case(suffix.substring(0, 2), get_vowel_circumflex(suffix[0]) + 'h'); + case 'ado': + return prefix + suffix_vowel_a + suffix_vowel_b; + case 'ados': + case 'idos': + case 'ídos': + return prefix + get_vowel_tilde(suffix_vowel_a) + get_vowel_circumflex(suffix_vowel_b); + case 'ido': + case 'ído': + return prefix + keep_case(suffix_vowel_a, 'í') + suffix_vowel_b; + default: + return word; + } + }; + + var replace_eps_end_with_case = function (word, prefix, suffix_vowel, suffix_const) { + // Leave as it is. There shouldn't be any word with -eps ending withough accent. + if (!contain_vocal_tilde(prefix)) + return prefix + suffix_vowel + suffix_const; + return prefix + keep_case(suffix_vowel, 'ê'); + }; + + var replace_d_end_with_case = function (match, prefix, suffix_vowel, suffix_const) { + var word = prefix + suffix_vowel + suffix_const; + + if (WORDEND_D_RULES_EXCEPT[word.toLowerCase()]) { + var and_word = keep_case(word, WORDEND_D_RULES_EXCEPT[word.toLowerCase()]); + return match.replace(word, and_word); + } + + if (contain_vocal_tilde(prefix)) { + return match.replace(word, prefix + repl_rules[suffix_vowel]); + } + + if (['a', 'e', 'A', 'E', 'á', 'é', 'Á', 'É'].includes(suffix_vowel)) { + return match.replace(word, prefix + stressed_rules[suffix_vowel]); + } + + return match.replace(word, prefix + stressed_rules[suffix_vowel] + keep_case(suffix_const, 'h')); + }; + + var replace_s_end_with_case = function (match, prefix, suffix_vowel, suffix_const) { + var word = prefix + suffix_vowel + suffix_const; + + if (WORDEND_S_RULES_EXCEPT[word.toLowerCase()]) { + var and_word = keep_case(word, WORDEND_S_RULES_EXCEPT[word.toLowerCase()]); + return match.replace(word, and_word); + } + + if (!contain_vocal_tilde(suffix_vowel)) { + return match.replace(word, prefix + repl_rules[suffix_vowel]); + } + + return match.replace(word, prefix + repl_rules[suffix_vowel] + keep_case(suffix_const, 'h')); + }; + + var replace_const_end_with_case = function (match, prefix, suffix_vowel, suffix_const) { + var word = prefix + suffix_vowel + suffix_const; + + if (WORDEND_CONST_RULES_EXCEPT[word.toLowerCase()]) { + var and_word = keep_case(word, WORDEND_CONST_RULES_EXCEPT[word.toLowerCase()]); + return match.replace(word, and_word); + } + + if (contain_vocal_tilde(prefix)) { + return match.replace(word, prefix + repl_rules[suffix_vowel]); + } + return match.replace(word, prefix + repl_rules[suffix_vowel] + keep_case(suffix_vowel, 'h')); + }; + + return text + .replace(/\b(\S*?)(a|i|í|Í)(d)(o|a)(s?)\b/gi, replace_intervowel_d_end_with_case) + .replace(/\b(\S+?)(e)(ps)\b/gi, replace_eps_end_with_case) + .replace(/[\s.,;!?]?(\S+?)(a|e|i|o|u|á|é|í|ó|ú)(d)([\s.,;!?]|$)/gi, replace_d_end_with_case) + .replace(/[\s.,;!?]?(\S+?)(a|e|i|o|u|á|é|í|ó|ú)(s)([\s.,;!?]|$)/gi, replace_s_end_with_case) + .replace(/(\S+?)(a|e|i|o|u|á|é|í|ó|ú)(b|c|f|g|j|k|l|p|r|t|x|z)([\s.,;:!?\]\)\}]|$)/gi, replace_const_end_with_case); + }; + + this.digraph_rules = function (text) { + var digraphs = DIGRAPHS.join('|'); + var rDigraphs = new RegExp('(a|e|i|o|u|á|é|í|ó|ú)(' + digraphs + ')', 'gi'); + + return text + // intersticial / solsticio / superstición / cárstico => interttiçiâh / çorttiçio / çuperttiçión / cárttico + .replace(/(a|e|i|o|u|á|é|í|ó|ú)(l|r)s(t)/gi, function (_, vowel_char, lr_char, t_char) { + return vowel_char + (lr_char.toLowerCase() === 'l' ? keep_case(lr_char, 'r') : lr_char) + t_char + t_char; }) + // aerotransporte => aerotrâpporte | translado => trâl-lao | transcendente => trâççendente | postpalatal => pôppalatal + .replace(/(tr|p)(a|o)(?:ns|st)(b|c|ç|d|f|g|h|j|k|l|m|n|p|q|s|t|v|w|x|y|z)/gi, function (_, init_char, vowel_char, cons_char) { + return init_char + get_vowel_circumflex(vowel_char) + cons_char + (cons_char.toLowerCase() === 'l' ? '-' : '') + cons_char; }) + // abstracto => âttrâtto | adscrito => âccrito | perspectiva => pêrppêttiba + .replace(/(a|e|i|o|u|á|é|í|ó|ú)(b|d|n|r)(s)(b|c|ç|d|f|g|h|j|k|l|m|n|p|q|s|t|v|w|x|y|z)/gi, function (_, vowel_char, cons_char, s_char, digraph_char) { + var isrs = cons_char.toLowerCase() === 'r' && s_char.toLowerCase() === 's'; + return (isrs ? vowel_char + cons_char : get_vowel_circumflex(vowel_char)) + digraph_char.repeat(2); + }) + // atlántico => âl-lántico | orla => ôl-la | adlátere => âl-látere | tesla => têl-la ... + .replace(/(a|e|i|o|u|á|é|í|ó|ú)(?:d|j|r|s|t|x|z)(l)/gi, function (_, vowel_char, digraph_char) { + return get_vowel_circumflex(vowel_char) + digraph_char + '-' + digraph_char; + }) + // General digraph rules + .replace(rDigraphs, function (_, vowel_char, digraph_chars) { + return get_vowel_circumflex(vowel_char) + digraph_chars[1].repeat(2); + }) + ; + }; + + this.exception_rules = function (text) { + var exceptions = Object.keys(ENDING_RULES_EXCEPTION).join('|'); + var rExceptions = new RegExp('(?=|$|[^\\p{L}])(' + exceptions + ')(?=^|$|[^\\p{L}])', 'giu'); + + // Set of exceptions to the replacement algorithm + return text.replace(rExceptions, function (word) { + return keep_case(word, ENDING_RULES_EXCEPTION[word.toLowerCase()]); + }); + }; + + // Contractions and other word interaction rules + this.word_interaction_rules = function (text) { + // Rotating word ending /l/ with /r/ if first next word char is non-r consonant + return text.replace(/\b(\w*?)(l)(\s)(b|c|ç|d|f|g|h|j|k|l|m|n|ñ|p|q|s|t|v|w|x|y|z)/gi, function (_, prefix, l_char, whitespace_char, suffix) { + return prefix + keep_case(l_char, 'r') + whitespace_char + suffix; + }); + }; + + this.transcript = function (text, vaf, vvf, scapeLinks, debug=false) { + if (vaf === void 0) { vaf = VAF; } + if (vvf === void 0) { vvf = VVF; } + if (scapeLinks === void 0) { scapeLinks = false; } + + var substitutedText = text; + + if (scapeLinks) { + substitutedText = _this.ignore_rules(text); + } + + var rules = [ + _this.h_rules, + _this.x_rules, + _this.ch_rules, + _this.gj_rules, + _this.v_rules, + _this.ll_rules, + _this.l_rules, + _this.psico_pseudo_rules, + _this.vaf_rules, + _this.word_ending_rules, + _this.digraph_rules, + _this.exception_rules, + _this.word_interaction_rules + ]; + + var finalText = rules.reduce(function (substitutedText, rule) { + if (debug) { + //console.log(substitutedText); + } + + if (rule === _this.x_rules) + return _this.x_rules(substitutedText, vaf); + if (rule === _this.vaf_rules) + return _this.vaf_rules(substitutedText, vaf); + if (rule === _this.gj_rules) + return _this.gj_rules(substitutedText, vvf); + return rule(substitutedText); + }, substitutedText); + + return _this.tags.reduce(function (text, tags) { return text.replace(tags[0], tags[1]); }, finalText); + }; + } + return EPA; +}()); \ No newline at end of file diff --git a/src/andaluhgs/util.js b/src/andaluhgs/util.js new file mode 100644 index 0000000..12e4c06 --- /dev/null +++ b/src/andaluhgs/util.js @@ -0,0 +1,60 @@ +/** + * Copyleft (c) 2018-2021 Andalugeeks + * + * Authors: + * - Eduardo Amador (original javascript/typescript dev) + * - J. Félix Ontañón (port to google apps script) + * + */ + + function isUpperCase(str) { + return str.toUpperCase() === str; +} + +function isLowerCase(str) { + return str.toLowerCase() === str; +} + +function isCapitalized(word) { + return isUpperCase(word.charAt(0)) && isLowerCase(word.substr(1)); +} + +function capitalize(word) { + return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(); +} + +// TODO: This can be improved to perform replacement in a per character basis +// NOTE: It assumes replacement_word to be already lowercase +function keep_case(word, replacement_word) { + if (isLowerCase(word)) + return replacement_word; + if (isUpperCase(word)) + return replacement_word.toUpperCase(); + if (isCapitalized(word)) + return capitalize(replacement_word); + return replacement_word; +} + +// Useful to calculate the circumflex equivalents. +VOWELS_ALL_NOTILDE = 'aeiouâêîôûAEIOUÂÊÎÔÛ'; +VOWELS_ALL_TILDE = 'áéíóúâêîôûÁÉÍÓÚÂÊÎÔÛ'; + +function get_vowel_tilde(vowel) { + var i = VOWELS_ALL_NOTILDE.indexOf(vowel); + // If no tilde, replace with circumflex + if (vowel && i !== -1) + return VOWELS_ALL_TILDE[i]; + if (VOWELS_ALL_TILDE.includes(vowel)) + return vowel; + console.error('Not a vowel', vowel); +} + +function get_vowel_circumflex(vowel) { + var i = VOWELS_ALL_NOTILDE.indexOf(vowel); + // If no tilde, replace with circumflex + if (vowel && i !== -1) + return VOWELS_ALL_NOTILDE[i + 5]; + if (VOWELS_ALL_TILDE.includes(vowel)) + return vowel; + console.error('Not supported'); +} \ No newline at end of file diff --git a/src/appsscript.json b/src/appsscript.json index 7b640e2..d90914d 100644 --- a/src/appsscript.json +++ b/src/appsscript.json @@ -1,14 +1,5 @@ { "timeZone": "Europe/Madrid", - "dependencies": { - "libraries": [ - { - "userSymbol": "AndaluhGS", - "libraryId": "1qvvfm1jyjHNT6t_oYJWrhvug8Q1J8tpmNWqLSW0OBe802DxcwsOgMTV_", - "developmentMode": true - } - ] - }, "exceptionLogging": "STACKDRIVER", "oauthScopes": [ "https://www.googleapis.com/auth/documents.currentonly", @@ -28,7 +19,7 @@ }, "docs": { "homepageTrigger": { - "runFunction": "onOpen" + "runFunction": "onHomepage" } } } diff --git a/src/main.js b/src/main.js index 3f6267e..7235cd3 100644 --- a/src/main.js +++ b/src/main.js @@ -1,3 +1,11 @@ +/** + * Copyleft (c) 2021 Andalugeeks + * + * Authors: + * - J. Félix Ontañón + * + */ + /** * @OnlyCurrentDoc * @@ -8,6 +16,83 @@ * presented to users will reflect this limited scope. */ +/** + * Callback for rendering the homepage card. + * @return {CardService.Card} The card to show to the user. + */ + +// Inspired on the tutorial here: https://developers.google.com/workspace/add-ons/cats-quickstart +// and here: https://www.youtube.com/watch?v=Ma8nR7-erPM +function onHomepage(e) { + + // Generates the entry at add-ons menu + onOpen(e); + + // And now for the home page card creation + var findaddonText = CardService.newDecoratedText() + .setTopLabel("¡Complemento cargado!") + .setText("Encontrarás Andaluh para Google Docs en el menú Complementos (add-ons)") + .setWrapText(true); + + var findaddonImage = CardService.newImage() + .setImageUrl('https://drive.google.com/uc?export=download&id=11i0GuwBGaQRrKYLqXCykj7wze6YFLna4') + .setAltText('Ir al Menu Complementos') + + var videotutoText = CardService.newDecoratedText() + .setTopLabel("¿Necesitas más ayuda?") + .setText("Mira este tutorial en vídeo.") + .setWrapText(true); + + var videotutoImage = CardService.newImage() + .setImageUrl('https://drive.google.com/uc?export=download&id=1vK0tTkaVrKlLkATp-9JM2g7JgZcxDfTI') + .setAltText('Ver tutorial') + .setOpenLink(CardService.newOpenLink() + .setUrl('https://www.youtube.com/watch?v=L83zw6-Tzj4')); + + // Create a footer to be shown at the bottom. + var footer = CardService.newFixedFooter() + .setPrimaryButton(CardService.newTextButton() + .setText('Más información') + .setOpenLink(CardService.newOpenLink() + .setUrl('https://andaluh.es/google-drive-andaluz'))); + + // Assemble the widgets and return the card. + var findSection = CardService.newCardSection() + .addWidget(findaddonText) + .addWidget(findaddonImage); + + var videotutoSection = CardService.newCardSection() + .addWidget(videotutoText) + .addWidget(videotutoImage); + + var card = CardService.newCardBuilder() + .addCardAction(CardService.newCardAction() + .setText('Transcriptor Online') + .setOpenLink(CardService.newOpenLink() + .setUrl('https://andaluh.es/transcriptor')) + ) + .addCardAction(CardService.newCardAction() + .setText('Teclado Andaluz') + .setOpenLink(CardService.newOpenLink() + .setUrl('https://andaluh.es/teclado-andaluz')) + ) + .addCardAction(CardService.newCardAction() + .setText('Acerca de AndaluGeeks') + .setOpenLink(CardService.newOpenLink() + .setUrl('https://andaluh.es')) + ) + .addSection(findSection) + .addSection(videotutoSection) + .setFixedFooter(footer); + + return card.build(); +} + +// From here, the reference for this code was taken from the following tutorial +// https://github.com/googleworkspace/apps-script-samples/tree/master/docs/translate +// There's a pending task to convert this code into a full Google WorkSpace Add-On +// with a native CardService re-implementation + /** * Creates a menu entry in the Google Docs UI when the document is opened. * This method is only used by the regular add-on, and is never called by @@ -227,7 +312,7 @@ function insertText(newText) { * origin and dest languages are the same. */ -var epa = new AndaluhGS.EPA(); +var epa = new EPA(); function translateText(text, vaf, vvf) { return epa.transcript(text, vaf, vvf, true); diff --git a/src/test.js b/src/test.js index 28b903d..c5f0947 100644 --- a/src/test.js +++ b/src/test.js @@ -31,7 +31,7 @@ function words(s) { } function loop(tests, vaf, vvf, escapeLinks) { - var epa = new AndaluhGS.EPA(); + var epa = new EPA(); var errors = false;