From 766f974cccad7e21bdf4414877f370b3e7fc9d59 Mon Sep 17 00:00:00 2001 From: Hani Mohammed Date: Wed, 24 Mar 2021 14:12:45 +0530 Subject: [PATCH 01/14] Add suggestions based on #252 --- assets/css/translation.css | 8 +- assets/js/localization.js | 37 ++++++++- assets/js/translator.js | 157 ++++++++++++++++++++++++++++++++++++- assets/strings/eng.json | 4 + assets/strings/rus.json | 4 + config.conf.example | 5 ++ html/index.html.in | 27 ++++++- tools/read-conf.py | 8 +- 8 files changed, 239 insertions(+), 11 deletions(-) diff --git a/assets/css/translation.css b/assets/css/translation.css index 5610dfa3b..81550ec86 100644 --- a/assets/css/translation.css +++ b/assets/css/translation.css @@ -77,13 +77,19 @@ html[dir='rtl'] .form-group.d-sm-none .translateBtn { /* sass-lint:disable-line } /* Action buttons use blue color instead of the primary orange, overwrite */ -#generate, #analyze, #request, .translateBtn { +#generate, #analyze, #request, #suggestBtn, .translateBtn { /* sass-lint:disable-block no-important */ background-color: #446e9b !important; border-color: #446e9b; color: #fff !important; } +#suggestBtn:focus, +#suggestBtn:hover, #suggestBtn.focus +#suggestBtn:active, #suggestBtn.active { + background-color: #385a7f !important; /* sass-lint:disable-line no-important */ +} + .translateBtn:focus, .translateBtn:hover, .translateBtn.focus .translateBtn:active, .translateBtn.active { diff --git a/assets/js/localization.js b/assets/js/localization.js index ff5b86552..40232d2ad 100644 --- a/assets/js/localization.js +++ b/assets/js/localization.js @@ -5,23 +5,29 @@ var locale/*: string */; var languages = {'af': 'Afrikaans', 'id': 'Bahasa Indonesia', 'cy': 'Cymraeg', 'de': 'Deutsch', 'en': 'English', 'ga': 'Gaeilge', 'gv': 'Gaelg', 'gd': 'Gàidhlig', 'rn': 'Ikirundi', 'sw': 'Kiswahili', 'lg': 'Luganda', 'mt': 'Malti', 'nl': 'Nederlands', 'sq': 'Shqip', 'vi': 'Tiếng Việt', 'tr': 'Türkçe', 'az': 'azərbaycan', 'br': 'brezhoneg', 'ca': 'català', 'da': 'dansk', 'se': 'davvisámegiella', 'et': 'eesti', 'es': 'español', 'eo': 'esperanto', 'eu': 'euskara', 'fr': 'français', 'fo': 'føroyskt', 'ia': 'interlingua', 'xh': 'isiXhosa', 'zu': 'isiZulu', 'it': 'italiano', 'mfe': 'kreol morisien', 'lv': 'latviešu', 'lt': 'lietuvių', 'hu': 'magyar', 'nb': 'norsk bokmål', 'nn': 'nynorsk', 'uz': 'oʻzbekcha', 'pl': 'polski', 'pt': 'português', 'ro': 'română', 'rm': 'rumantsch', 'sk': 'slovenčina', 'sl': 'slovenščina', 'fi': 'suomi', 'sv': 'svenska', 'is': 'íslenska', 'cs': 'čeština', 'el': 'Ελληνικά', 'tg': 'Тоҷикӣ', 'ba': 'башҡортса', 'be': 'беларуская', 'bg': 'български', 'os': 'ирон', 'kum': 'къумукъча', 'ky': 'кыргызча', 'mk': 'македонски', 'ru': 'русский', 'tt': 'татарча', 'uk': 'українська', 'kk': 'қазақша', 'hy': 'հայերեն', 'he': 'עברית', 'ur': 'اردو', 'ar': 'العربية', 'fa': 'فارسی', 'ne': 'नेपाली', 'mr': 'मराठी', 'hi': 'हिंदी', 'as': 'অসমীয়া', 'bn': 'বাংলা', 'pa': 'ਪੰਜਾਬੀ', 'te': 'తెలుగు', 'ml': 'മലയാളം', 'si': 'සිංහල', 'th': 'ไทย', 'lo': 'ລາວ', 'zh': '中文', 'ko': '한국어', 'mrj': 'Мары йӹлмӹ', 'gl': 'galego', 'myv': 'Эрзянь кель', 'oc': 'occitan', 'cv': 'чӑвашла', 'arg': 'aragonés', 'ast': 'asturianu', 'msa': 'bahasa malay', 'hbs': 'srpskohrvatski', 'srp': 'српски', 'hrv': 'hrvatski', 'bos': 'bosanski', 'nog': 'ногъайша', 'sah': 'сахалыы', 'uig': 'ئۇيغۇرچە', 'tyv': 'тыва дылда'}; // eslint-disable-next-line var iso639Codes = {'abk': 'ab', 'aar': 'aa', 'afr': 'af', 'aka': 'ak', 'sqi': 'sq', 'amh': 'am', 'ara': 'ar', 'arg': 'an', 'hye': 'hy', 'asm': 'as', 'ava': 'av', 'ave': 'ae', 'aym': 'ay', 'aze': 'az', 'bam': 'bm', 'bak': 'ba', 'eus': 'eu', 'bel': 'be', 'ben': 'bn', 'bih': 'bh', 'bis': 'bi', 'bos': 'bs', 'bre': 'br', 'bul': 'bg', 'mya': 'my', 'cat': 'ca', 'cha': 'ch', 'che': 'ce', 'nya': 'ny', 'zho': 'zh', 'chv': 'cv', 'cor': 'kw', 'cos': 'co', 'cre': 'cr', 'hrv': 'hr', 'ces': 'cs', 'dan': 'da', 'div': 'dv', 'nld': 'nl', 'dzo': 'dz', 'eng': 'en', 'epo': 'eo', 'est': 'et', 'ewe': 'ee', 'fao': 'fo', 'fij': 'fj', 'fin': 'fi', 'fra': 'fr', 'ful': 'ff', 'glg': 'gl', 'kat': 'ka', 'deu': 'de', 'ell': 'el', 'grn': 'gn', 'guj': 'gu', 'hat': 'ht', 'hau': 'ha', 'heb': 'he', 'her': 'hz', 'hin': 'hi', 'hmo': 'ho', 'hun': 'hu', 'ina': 'ia', 'ind': 'id', 'ile': 'ie', 'gle': 'ga', 'ibo': 'ig', 'ipk': 'ik', 'ido': 'io', 'isl': 'is', 'ita': 'it', 'iku': 'iu', 'jpn': 'ja', 'jav': 'jv', 'kal': 'kl', 'kan': 'kn', 'kau': 'kr', 'kas': 'ks', 'kaz': 'kk', 'khm': 'km', 'kik': 'ki', 'kin': 'rw', 'kir': 'ky', 'kom': 'kv', 'kon': 'kg', 'kor': 'ko', 'kur': 'ku', 'kua': 'kj', 'lat': 'la', 'ltz': 'lb', 'lug': 'lg', 'lim': 'li', 'lin': 'ln', 'lao': 'lo', 'lit': 'lt', 'lub': 'lu', 'lav': 'lv', 'glv': 'gv', 'mkd': 'mk', 'mlg': 'mg', 'msa': 'ms', 'mal': 'ml', 'mlt': 'mt', 'mri': 'mi', 'mar': 'mr', 'mah': 'mh', 'mon': 'mn', 'nau': 'na', 'nav': 'nv', 'nob': 'nb', 'nde': 'nd', 'nep': 'ne', 'ndo': 'ng', 'nno': 'nn', 'nor': 'no', 'iii': 'ii', 'nbl': 'nr', 'oci': 'oc', 'oji': 'oj', 'chu': 'cu', 'orm': 'om', 'ori': 'or', 'oss': 'os', 'pan': 'pa', 'pli': 'pi', 'fas': 'fa', 'pol': 'pl', 'pus': 'ps', 'por': 'pt', 'que': 'qu', 'roh': 'rm', 'run': 'rn', 'ron': 'ro', 'rus': 'ru', 'san': 'sa', 'srd': 'sc', 'snd': 'sd', 'sme': 'se', 'smo': 'sm', 'sag': 'sg', 'srp': 'sr', 'gla': 'gd', 'sna': 'sn', 'sin': 'si', 'slk': 'sk', 'slv': 'sl', 'som': 'so', 'sot': 'st', 'azb': 'az', 'spa': 'es', 'sun': 'su', 'swa': 'sw', 'ssw': 'ss', 'swe': 'sv', 'tam': 'ta', 'tel': 'te', 'tgk': 'tg', 'tha': 'th', 'tir': 'ti', 'bod': 'bo', 'tuk': 'tk', 'tgl': 'tl', 'tsn': 'tn', 'ton': 'to', 'tur': 'tr', 'tso': 'ts', 'tat': 'tt', 'twi': 'tw', 'tah': 'ty', 'uig': 'ug', 'ukr': 'uk', 'urd': 'ur', 'uzb': 'uz', 'ven': 've', 'vie': 'vi', 'vol': 'vo', 'wln': 'wa', 'cym': 'cy', 'wol': 'wo', 'fry': 'fy', 'xho': 'xh', 'yid': 'yi', 'yor': 'yo', 'zha': 'za', 'zul': 'zu', 'hbs': 'sh', 'pes': 'fa'}; +var localizeRecaptchaLanguages = ['ar', 'af', 'am', 'hy', 'az', 'eu', 'bn', 'bg', 'ca', 'zh-HK', 'zh-CN', 'zh-TW', 'hr', 'cs', 'da', 'nl', 'en-GB', 'en', 'et', 'fil', 'fi', 'fr', 'fr-CA', 'gl', 'ka', 'de', 'de-AT', 'de-CH', 'el', 'gu', 'iw', 'hi', 'hu', 'is', 'id', 'it', 'ja', 'kn', 'ko', 'lo', 'lv', 'lt', 'ms', 'ml', 'mr', 'mn', 'no', 'fa', 'pl', 'pt', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sr', 'si', 'sk', 'sl', 'es', 'es-419', 'sw', 'sv', 'ta', 'te', 'th', 'tr', 'uk', 'ur', 'vi', 'zu']; +var localizeReacaptchaAlternativeLanguages = {'zh': 'zh-TW', 'hrv': 'hr', 'srp': 'sr', 'msa': 'ms', 'cy': 'en', 'ga': 'en', 'gv': 'en', 'gd': 'en', 'rn': 'en', 'lg': 'en', 'mt': 'en', 'sq': 'fr', 'br': 'fr', 'se': 'no', 'eo': 'en', 'fo': 'da', 'ia': 'en', 'xh': 'af', 'mfe': 'fr', 'nb': 'no', 'nn': 'no', 'uz': 'ru', 'rm': 'en', 'tg': 'ru', 'ba': 'ru', 'be': 'ru', 'os': 'ru', 'kum': 'ru', 'ky': 'ru', 'mk': 'en', 'tt': 'ru', 'kk': 'ru', 'he': 'en', 'ne': 'hi', 'as': 'hi', 'pa': 'ur', 'mrj': 'ru', 'myv': 'ru', 'oc': 'es', 'cv': 'ru', 'arg': 'es', 'ast': 'es', 'hbs': 'en', 'bos': 'en', 'nog': 'ru', 'sah': 'ru', 'uig': 'zh-TW', 'tyv': 'ru'}; var rtlLanguages = ['heb', 'ara', 'pes', 'urd', 'uig']; var languagesInverse /*: {[string]: string} */ = {}, iso639CodesInverse /*: {[string]: string} */ = {}; var localizedLanguageCodes /*: {[string]: string} */ = {}, localizedLanguageNames /*: {[string]: string} */ = {}; -/* exported setLocale */ +/* exported setLocale, getRecaptchaSrc */ /* global config, getPairs, getGenerators, getAnalyzers, persistChoices, getURLParam, cache, ajaxSend, ajaxComplete, sendEvent, srcLangs, dstLangs, generators, analyzers, readCache, modeEnabled, populateTranslationList, populateGeneratorList, populateAnalyzerList, analyzerData, generatorData, curSrcLang, curDstLang, restoreChoices, refreshLangList, onlyUnique */ +var newSrc; var dynamicLocalizations /*: {[lang: string]: {[string]: string}} */ = { 'fallback': { 'Not_Available': 'Translation not yet available!', 'detected': 'detected', 'File_Too_Large': 'File is too large!', 'Format_Not_Supported': 'Format not supported!', - 'Download_File': 'Download {{fileName}}' + 'Download_File': 'Download {{fileName}}', + 'Suggest_Sentence': 'How would you suggest we translate {{targetWordCode}}?', + 'Suggest_Title': 'Improve Apertium\'s translation', + 'Suggest_Placeholder': 'New word' } }; @@ -38,12 +44,32 @@ function getDynamicLocalization(stringKey /*: string */) /*: string */ { var localizedHTML = false; +var recaptchaScriptSrc = 'https://www.google.com/recaptcha/api.js?onload=recaptchaRenderCallback&render=explicit&hl='; + /* exported getLangByCode */ if(!config.LANGNAMES) { config.LANGNAMES = {}; } +function getRecaptchaSrc(locale2 /*: string */) { + newSrc = recaptchaScriptSrc + 'en'; + var backoff = true; + + for(var i = 0; i < localizeRecaptchaLanguages.length; i++) { + if(locale2 === localizeRecaptchaLanguages[i]) { + newSrc = recaptchaScriptSrc + locale2; + backoff = false; + break; + } + } + + if(backoff) { + newSrc = recaptchaScriptSrc + localizeReacaptchaAlternativeLanguages[locale2]; + } + return newSrc; +} + $(document).ready(function () { $.each(languages, function (code /*: string */, language /*: string */) { languagesInverse[language] = code; @@ -90,6 +116,11 @@ $(document).ready(function () { localizeEverything(false); persistChoices('localization'); $('.localeSelect').val(locale); + + var locale2 = iso639Codes[locale]; + newSrc = getRecaptchaSrc(locale2); + $('#suggestRecaptcha').empty(); + $.getScript(newSrc); }); function localizeEverything(stringsFresh /*: boolean */) { @@ -422,7 +453,7 @@ function setLocale(newLocale /*: string */) { return newLocale; } -/*:: export {getLangByCode, getDynamicLocalization, iso639Codes, iso639CodesInverse, locale, localizeInterface, setLocale, +/*:: export {getLangByCode, getDynamicLocalization, getRecaptchaSrc, iso639Codes, iso639CodesInverse, locale, localizeInterface, setLocale, langDirection, languages} */ /*:: import {curDstLang, curSrcLang, dstLangs, getPairs, populateTranslationList, refreshLangList, srcLangs} from "./translator.js" */ diff --git a/assets/js/translator.js b/assets/js/translator.js index 262480321..83526b819 100644 --- a/assets/js/translator.js +++ b/assets/js/translator.js @@ -5,6 +5,8 @@ var srcLangs /*: string[] */ = [], dstLangs /*: string[] */ = []; var curSrcLang /*: string */, curDstLang/*: string */; var recentSrcLangs /*: string[] */ = [], recentDstLangs /*: string[] */ = []; var droppedFile/*: ?File */; +var grecaptcha; +var recaptchaRenderCallback; var translateRequest; var translationTimer/*: ?TimeoutID */; @@ -13,7 +15,8 @@ var UPLOAD_FILE_SIZE_LIMIT = 32E6, TRANSLATION_LIST_IDEAL_ROWS = 12, TRANSLATION_LIST_MAX_WIDTH = 800, TRANSLATION_LIST_MAX_COLUMNS = 5, - TRANSLATION_LISTS_BUFFER = 50; + TRANSLATION_LISTS_BUFFER = 50, + SUGGESTION_DESTROY_TIMEOUT = 3000; var INSTANT_TRANSLATION_URL_DELAY = 500, INSTANT_TRANSLATION_PUNCTUATION_DELAY = 1000, @@ -27,11 +30,21 @@ var PUNCTUATION_KEY_CODES = [46, 33, 58, 63, 47, 45, 190, 171, 49]; // eslint-di /* global config, modeEnabled, synchronizeTextareaHeights, persistChoices, getLangByCode, sendEvent, onlyUnique, restoreChoices getDynamicLocalization, locale, ajaxSend, ajaxComplete, localizeInterface, filterLangList, cache, readCache, iso639Codes, - callApy, apyRequestTimeout, isURL, removeSoftHyphens, parentLang, isVariant, langDirection, languages, iso639CodesInverse */ + callApy, apyRequestTimeout, isURL, getRecaptchaSrc, removeSoftHyphens, parentLang, isVariant, langDirection, languages, iso639CodesInverse */ /* global ENTER_KEY_CODE, HTTP_BAD_REQUEST_CODE, HTTP_OK_CODE, SPACE_KEY_CODE, XHR_DONE, XHR_LOADING */ if(modeEnabled('translation')) { $(document).ready(function () { + var locale2 = iso639Codes[$('.localeSelect').val()]; + var newSrc = getRecaptchaSrc(locale2); + $.getScript(newSrc); + + recaptchaRenderCallback = function () { // eslint-disable-line no-unused-vars + grecaptcha.render('suggestRecaptcha', { + 'sitekey': config.SUGGESTIONS.recaptcha_site_key + }); + }; + function updatePairList() { pairs = $('input#chainedTranslation').prop('checked') ? chainedPairs : originalPairs; } @@ -317,6 +330,69 @@ if(modeEnabled('translation')) { persistChoices('translator', true); }); + $('#translatedText').css('height', $('#originalText').css('height')); + $('#suggestCloseBtn').click(function () { + $('#suggestedWordInput').val(''); + grecaptcha.reset(); + }); + $('#suggestBtn').click(function () { + var fromWord = $('#suggestionTargetWord').html(); + var toWord = $('#suggestedWordInput').val(); + var recaptchaResponse = grecaptcha.getResponse(); + + if(toWord.length === 0) { + $('#suggestedWordInput').tooltip('destroy'); + $('#suggestedWordInput').tooltip({ + 'title': 'Suggestion cannot be empty.', + 'trigger': 'manual', + 'placement': 'bottom' + }); + $('#suggestedWordInput').tooltip('show'); + setTimeout(function () { + $('#suggestedWordInput').tooltip('destroy'); + }, SUGGESTION_DESTROY_TIMEOUT); + + return; + } + + $.ajax({ + url: config.APY_URL + '/suggest', + type: 'POST', + beforeSend: ajaxSend, + data: { + 'langpair': curSrcLang + '|' + curDstLang, + 'word': fromWord, + 'newWord': toWord, + 'context': getContext(fromWord), + 'g-recaptcha-response': recaptchaResponse + }, + success: function (_data, _textStatus, _jqXHR) { + $('#suggestedWordInput').tooltip('destroy'); + $('#suggestedWordInput').val(''); + $('#wordSuggestModal').modal('hide'); + }, + error: function (data /*: JQueryXHR */, _textStatus, _errorThrown) { + var responseText /*: string */ = (data.responseText /*: any */); + var data1 = $.parseJSON(responseText); + $('#suggestedWordInput').tooltip('destroy'); + $('#suggestedWordInput').tooltip({ + 'title': (data1.explanation ? data1.explanation : 'An error occurred'), + 'trigger': 'manual', + 'placement': 'bottom' + }); + $('#suggestedWordInput').tooltip('show'); + setTimeout(function () { + $('#suggestedWordInput').tooltip('hide'); + $('#suggestedWordInput').tooltip('destroy'); + }, SUGGESTION_DESTROY_TIMEOUT); + }, + complete: function (_jqXHR, _textStatus) { + ajaxComplete; + grecaptcha.reset(); + } + }); + }); + $('input#chainedTranslation').change(function () { updatePairList(); populateTranslationList(); @@ -371,6 +447,30 @@ if(modeEnabled('translation')) { }); } +function getContext(fromWord) { + var wrapLength = parseInt(config.SUGGESTIONS.context_size, 10); + var mark = 'MEGAWORD!'; + var markedWord = mark + fromWord; + var rawText = $('#translatedText').html(); + var cleanMarkedText = rawText.replace(/]*id="wordGettingSuggested"[^>]*>/g, mark); + cleanMarkedText = cleanMarkedText.replace(/<[/]?span[^>]*>/g, ''); + var splittedText = cleanMarkedText.replace(/\s+/g, ' ').split(' '); + var targetIndex = -1; + var wordCount = splittedText.length; + for(var i = 0; i < wordCount && targetIndex === -1; i++) { + if(splittedText[i].indexOf(markedWord) !== -1) { + targetIndex = i; + } + } + var beginning = (targetIndex > wrapLength) ? (targetIndex - wrapLength) : 0; + var ending = (splittedText.length - targetIndex - 1 > wrapLength) ? (targetIndex + wrapLength + 1) : splittedText.length; + var context = splittedText.slice(beginning, ending).join(' ').replace(mark, ''); + if(!context) { + context = $('#translatedText').attr('pristineText'); + } + return context; +} + function getPairs() /*: JQueryPromise */ { var deferred = $.Deferred(); @@ -776,8 +876,57 @@ function translate(ignoreIfEmpty) { function translateText(ignoreIfEmpty) { function handleTranslateSuccessResponse(data) { if(data.responseStatus === HTTP_OK_CODE) { - $('#translatedText').val(data.responseData.translatedText); + $('#translatedText').html(data.responseData.translatedText); $('#translatedText').removeClass('notAvailable text-danger'); + + $('#translatedText').attr('pristineText', data.responseData.translatedText); + + if(config.SUGGESTIONS.enabled) { + var localizedTitle = getDynamicLocalization('Suggest_Title'); + var placeholder = getDynamicLocalization('Suggest_Placeholder'); + $('#suggestedWordInput').attr('placeholder', placeholder); + $('#translatedText').html( + $('#translatedText').html().replace(/(\*|@|#)([^\s0-9.…,!?;:)_>"'«/»`“”„‘’‛+=–—‒―-]+)/g, + '$2') + ); + } + + $('.wordSuggestPopover').click(function () { + + $('#translatedTextClone').html($('#translatedText').attr('pristineText')); + var fromWord = $(this).html(); + $(this).attr('id', 'wordGettingSuggested'); + var context = getContext(fromWord); + $('#translatedTextClone').html(context); + + $('.wordSuggestPopover').removeAttr('id'); + $('.wordSuggestPopoverInline').removeAttr('id'); + + $('#translatedTextClone').html( + $('#translatedTextClone').html().replace(/(\*|@|#)([^\s0-9.…,!?;:)_>"'«/»`“”„‘’‛+=–—‒―-]+)/g, + '$2') + ); + + + $('.wordSuggestPopoverInline').click(function () { + $('.wordSuggestPopover').removeAttr('id'); + $('.wordSuggestPopoverInline').removeAttr('id'); + $(this).attr('id', 'wordGettingSuggested'); + + $('#suggestionTargetWord').html($(this).text().replace(/(\*|@|#)/g, '')); + $('#suggestedWordInput').val(''); + }); + + $('#suggestSentenceContainer').html(getDynamicLocalization('Suggest_Sentence').replace('{{targetWordCode}}', + '') + ); + $('#suggestionTargetWord').html($(this).text().replace(/(\*|@|#)/g, '')); + + $('#wordSuggestModal').modal(); + $(this).removeAttr('id'); + }); } else { translationNotAvailable(); @@ -1346,6 +1495,6 @@ function setDefaultSrcLang() { /*:: import {ENTER_KEY_CODE, HTTP_BAD_REQUEST_CODE, HTTP_OK_CODE, SPACE_KEY_CODE, XHR_DONE, XHR_LOADING} from "./util.js" */ /*:: import {persistChoices, restoreChoices} from "./persistence.js" */ /*:: import {localizeInterface, getLangByCode, getDynamicLocalization, locale, iso639Codes, langDirection, languages, - iso639CodesInverse} from "./localization.js" */ + iso639CodesInverse, getRecaptchaSrc} from "./localization.js" */ /*:: import {readCache, cache} from "./persistence.js" */ /*:: import {isURL} from "./util.js" */ diff --git a/assets/strings/eng.json b/assets/strings/eng.json index 6433a8a66..9e8e07558 100644 --- a/assets/strings/eng.json +++ b/assets/strings/eng.json @@ -54,6 +54,10 @@ "Download": "Download", "Contact": "Contact", "Documentation": "Documentation", + "Suggest_Title": "Improve Apertium's translation", + "Suggest_Button": "Suggest", + "Suggest_Sentence": "How would you suggest we translate {{targetWordCode}}?", + "Suggest_Placeholder": "New Word", "About_Apertium": "About Apertium", "What_Is_Apertium": "

Apertium is a free/open-source machine translation platform, initially aimed at related-language pairs but expanded to deal with more divergent language pairs (such as English-Catalan). The platform provides

  1. a language-independent machine translation engine
  2. tools to manage the linguistic data necessary to build a machine translation system for a given language pair and
  3. linguistic data for a growing number of language pairs.

Apertium welcomes new developers: if you think you can improve the engine or the tools, or develop linguistic data for us, do not hesitate to contact us.

", "Documentation_Para": "Documentation can be found on our Wiki under the Documentation sub-page. We have published various conference papers and journal articles, a list of which may be found on the Wiki under Publications.", diff --git a/assets/strings/rus.json b/assets/strings/rus.json index 3014a77d0..d465fecab 100644 --- a/assets/strings/rus.json +++ b/assets/strings/rus.json @@ -60,6 +60,10 @@ "Download": "Загрузки", "Contact": "Связаться с разработчиками", "Documentation": "Документация", + "Suggest_Title": "Предложить улучшенный перевод Apertium", + "Suggest_Button": "Предложить перевод", + "Suggest_Sentence": "Как лучше перевести {{targetWordCode}}?", + "Suggest_Placeholder": "Новое слово", "About_Apertium": "Об Apertium", "What_Is_Apertium": "

Apertium — это открытая платформа машинного перевода. Изначально она была создана для связанных языковых пар, но теперь поддерживает даже такие необычные пары, как английский—каталанский. Платформа предоставляет

  1. независимый от языка машинный переводчик;
  2. инструменты для сбора лингвистических данных для машинного перевода;
  3. лингвистические данные для большого количества языковых пар.

Apertium приветствует новых разработчиков: если Вы можете улучшить движок машинного перевода или инструменты или собрать лингвистические данные для нас, то свяжитесь с нами.

", "Documentation_Para": "Документация расположена в нашей вики на подстранице Документация. Мы публикуем разнообразные бумаги с конференций и статьи из журналов, список которых доступен на странице Публикации.", diff --git a/config.conf.example b/config.conf.example index 558bdc4ff..742d94736 100644 --- a/config.conf.example +++ b/config.conf.example @@ -30,3 +30,8 @@ AVAILABLE_LOCALES_CACHE_EXPIRY = 24 # Localisation replacement strings, see assets/strings/*.json for usage: [REPLACEMENTS] maintainer = Apertium + +[SUGGESTIONS] +ENABLED = True +RECAPTCHA_SITE_KEY = None +CONTEXT_SIZE = 5 diff --git a/html/index.html.in b/html/index.html.in index 7c376eda4..f0613d905 100644 --- a/html/index.html.in +++ b/html/index.html.in @@ -197,8 +197,7 @@
- +
@@ -599,6 +598,30 @@
+ +