From 71de08ffed2fa223a6137758d25454bf77e3000d Mon Sep 17 00:00:00 2001 From: lkrsnik Date: Mon, 19 Feb 2024 15:33:19 +0100 Subject: [PATCH] Updated visuals and expanded website. --- Dockerfile | 6 +- README.md | 26 +++ app.py | 73 ++++++-- babel.cfg | 2 + messages.pot | 226 +++++++++++++++++++++++ requirements.txt | 1 + setup.py | 5 +- static/css/nouislider.css | 8 +- static/css/style.css | 98 ++++++++++ static/favicon/favicon.svg | 22 +++ static/js/init.js | 30 ++- templates/about.html | 17 +- templates/index.html | 81 +++++---- templates/result.html | 15 +- translations/en/LC_MESSAGES/messages.mo | Bin 0 -> 2802 bytes translations/en/LC_MESSAGES/messages.po | 232 ++++++++++++++++++++++++ translations/sl/LC_MESSAGES/messages.mo | Bin 0 -> 2829 bytes translations/sl/LC_MESSAGES/messages.po | 231 +++++++++++++++++++++++ 18 files changed, 995 insertions(+), 78 deletions(-) create mode 100644 babel.cfg create mode 100644 messages.pot create mode 100644 static/favicon/favicon.svg create mode 100644 translations/en/LC_MESSAGES/messages.mo create mode 100644 translations/en/LC_MESSAGES/messages.po create mode 100644 translations/sl/LC_MESSAGES/messages.mo create mode 100644 translations/sl/LC_MESSAGES/messages.po diff --git a/Dockerfile b/Dockerfile index d78ddb7..f85b0d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ FROM python:3.10.12 ADD . /stark-web WORKDIR /stark-web -RUN pip install --upgrade pip -RUN pip install waitress -RUN pip install . +RUN pip install --upgrade pip && \ + pip install waitress && \ + pip install . CMD ["waitress-serve", "--call", "app:create_app"] diff --git a/README.md b/README.md index 3c33ceb..cdf6c6d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ +# Translations +## Create .pot file +pybabel extract -F babel.cfg -o messages.pot . + +## Create language .po files +pybabel init -i messages.pot -d translations -l en +pybabel init -i messages.pot -d translations -l sl + +## Compile changes +pybabel compile -d translations + +## Update commands +Install gettext (sudo apt install gettext) + +```shell +pybabel extract -F babel.cfg -o messages.pot . +msgmerge translations/sl/LC_MESSAGES/messages.po messages.pot -o translations/sl/LC_MESSAGES/messages.po +msgmerge translations/en/LC_MESSAGES/messages.po messages.pot -o translations/en/LC_MESSAGES/messages.po + +# check and delete fuzzy in .po files +pybabel compile -d translations +``` + + + +# Deployment docker build -t my-flask-app . docker run -p 8080:8080 my-flask-app \ No newline at end of file diff --git a/app.py b/app.py index 41c9e0e..d3be384 100755 --- a/app.py +++ b/app.py @@ -8,7 +8,7 @@ import time import requests from flask import Flask, render_template, request, send_file, redirect, url_for -from flask_headers import headers +from flask_babel import Babel, gettext from werkzeug.utils import secure_filename from stark import run @@ -27,10 +27,45 @@ TABLE_COLUMNS2DISPLAYED_TABLE_COLUMNS = { 't-score': 't-score' } DISPLAYED_TABLE_COLUMNS2TABLE_COLUMNS = {v: k for k, v in TABLE_COLUMNS2DISPLAYED_TABLE_COLUMNS.items()} +DEFAULT_LANGUAGE = 'en' +LANGUAGES = ['en', 'sl'] + +_translations = { + 'en': { + 'hello': 'Hello', + 'welcome': 'Welcome', + 'greeting': 'How are you?', + 'name': 'Your name:', + 'code': 'en', + 'switch_code': 'SL', + 'switch_link': '?lang=sl', + }, + 'sl': { + 'hello': 'Hola', + 'welcome': 'Bienvenido', + 'greeting': '¿Cómo estás?', + 'name': 'Tu nombre:', + 'code': 'sl', + 'switch_code': 'EN', + 'switch_link': '?lang=en', + }, +} + + +def get_locale(): + lang = request.args.get('lang') + if lang in LANGUAGES: + return lang + return DEFAULT_LANGUAGE def create_app(): app = Flask(__name__) + + + + babel = Babel(app, locale_selector=get_locale, default_translation_directories='translations') + babel.list_translations = ['en', 'sl'] app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER def create_default_configs(): @@ -137,6 +172,7 @@ def create_app(): @app.route('/', methods=['GET', 'POST']) # @headers({'Cache-Control': 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'}) def index(): + translations = _translations[get_locale()] if request.method == 'POST': form = request.form configs = {} @@ -156,8 +192,8 @@ def create_app(): configs['input_path'] = input_path if 'input_url' in form and form['input_url']: - validation['file'] = 'Please insert either input url or file, not both of them.' - validation['input_url'] = 'Please insert either input url or file, not both of them.' + validation['file'] = gettext('Please insert either input url or file, not both of them.') + validation['input_url'] = gettext('Please insert either input url or file, not both of them.') # TODO OPTIONALLY ADD conllu FILE CHECK elif 'input_url' in form and form['input_url']: try: @@ -167,10 +203,10 @@ def create_app(): open(input_path, "wb").write(response.content) configs['input_path'] = input_path except: - validation['input_url'] = 'Incorrect URL!' + validation['input_url'] = gettext('Incorrect URL!') else: - validation['file'] = 'Please insert either input url or provide a file.' - validation['input_url'] = 'Please insert either input url or provide a file.' + validation['file'] = gettext('Please insert either input url or provide a file.') + validation['input_url'] = gettext('Please insert either input url or provide a file.') tree_size_min = None if 'tree_size_min' in form: @@ -182,11 +218,11 @@ def create_app(): def validate_tree_size(tree_size_min, tree_size_max): if tree_size_min is None or tree_size_max is None: - validation['tree_size'] = 'Please provide information about minimum and maximum tree size.' + validation['tree_size'] = gettext('Please provide information about minimum and maximum tree size.') return False if int(tree_size_min) > int(tree_size_max): - validation['tree_size'] = 'Tree size minimum should be smaller than tree size maximum.' + validation['tree_size'] = gettext('Tree size minimum should be smaller than tree size maximum.') return False return True @@ -197,12 +233,12 @@ def create_app(): # TODO EXPAND NODE TYPE node_type_options = {'upos', 'form', 'lemma', 'upos', 'xpos', 'feats', 'deprel'} if len(node_type) == 0: - validation['node_type'] = 'Please select at least one node type.' + validation['node_type'] = gettext('Please select at least one node type.') return False for el in node_type: if el not in node_type_options: - validation['node_type'] = f'Node option {el} is not supported. Please enter valid options.' + validation['node_type'] = gettext('Node option') + f' {el} ' + gettext('is not supported. Please enter valid options.') return False return True @@ -246,7 +282,7 @@ def create_app(): try: int(form['frequency_threshold']) except ValueError: - validation['frequency_threshold'] = f'Please insert an Integer.' + validation['frequency_threshold'] = gettext('Please insert an Integer.') else: configs['frequency_threshold'] = int(form['frequency_threshold']) @@ -266,24 +302,21 @@ def create_app(): name = ''.join(random.choices(string.ascii_uppercase + string.digits, k=60)) configs['output'] = os.path.join('media', name) if len(validation) > 0: - a = request.args.get('noreload') - b = request.args - c = request - return render_template('index.html', validation=validation) + return render_template('index.html', validation=validation, translations=translations) try: run(configs) except Exception as e: - validation['general'] = 'Processing failed! Please recheck your settings, e.g. input format or head node description.' + validation['general'] = gettext('Processing failed! Please recheck your settings, e.g. input format or head node description.') if len(validation) > 0: - return render_template('index.html', validation=validation) + return render_template('index.html', validation=validation, translations=translations) # check if there are no results with open(os.path.join('media', name), 'r') as rf: content = list(csv.reader(rf, delimiter='\t')) if len(content) == 1: validation['results'] = False - return render_template('index.html', validation=validation) - return redirect(url_for('result', result_id=name, order_by='Frequency ', order_type='desc')) - return render_template('index.html') + return render_template('index.html', validation=validation, translations=translations) + return redirect(url_for('result', result_id=name, order_by='Frequency ', order_type='desc', lang=gettext('code'))) + return render_template('index.html', translations=translations) return app diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..31d7eda --- /dev/null +++ b/babel.cfg @@ -0,0 +1,2 @@ +[python: app.py] +[jinja2: templates/**.html] \ No newline at end of file diff --git a/messages.pot b/messages.pot new file mode 100644 index 0000000..876e929 --- /dev/null +++ b/messages.pot @@ -0,0 +1,226 @@ +# Translations template for PROJECT. +# Copyright (C) 2024 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-02-19 09:57+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.14.0\n" + +#: app.py:202 app.py:203 +msgid "Please insert either input url or file, not both of them." +msgstr "" + +#: app.py:213 +msgid "Incorrect URL!" +msgstr "" + +#: app.py:215 app.py:216 +msgid "Please insert either input url or provide a file." +msgstr "" + +#: app.py:228 +msgid "Please provide information about minimum and maximum tree size." +msgstr "" + +#: app.py:232 +msgid "Tree size minimum should be smaller than tree size maximum." +msgstr "" + +#: app.py:243 +msgid "Please select at least one node type." +msgstr "" + +#: app.py:248 +msgid "Node option" +msgstr "" + +#: app.py:248 +msgid "is not supported. Please enter valid options." +msgstr "" + +#: app.py:292 +msgid "Please insert an Integer." +msgstr "" + +#: app.py:316 +msgid "" +"Processing failed! Please recheck your settings, e.g. input format or " +"head node description." +msgstr "" + +#: app.py:325 templates/about.html:16 templates/about.html:18 +#: templates/index.html:16 templates/index.html:18 templates/index.html:30 +#: templates/result.html:16 templates/result.html:18 +msgid "code" +msgstr "" + +#: templates/about.html:18 templates/about.html:29 templates/index.html:18 +#: templates/result.html:18 +msgid "About" +msgstr "" + +#: templates/about.html:19 templates/index.html:19 templates/result.html:19 +msgid "switch_link" +msgstr "" + +#: templates/about.html:20 templates/index.html:20 templates/result.html:20 +msgid "switch_code" +msgstr "" + +#: templates/about.html:30 +msgid "about_description" +msgstr "" + +#: templates/index.html:29 +msgid "intro_description" +msgstr "" + +#: templates/index.html:31 +msgid "Input data" +msgstr "" + +#: templates/index.html:34 +msgid "Upload a treebank" +msgstr "" + +#: templates/index.html:34 +msgid "in CONLL-U format" +msgstr "" + +#: templates/index.html:34 templates/index.html:62 templates/index.html:70 +#: templates/index.html:110 templates/index.html:125 templates/index.html:141 +#: templates/index.html:158 templates/index.html:168 +msgid "Help" +msgstr "" + +#: templates/index.html:37 +msgid "Browse" +msgstr "" + +#: templates/index.html:41 +msgid "Upload" +msgstr "" + +#: templates/index.html:50 +msgid "Or" +msgstr "" + +#: templates/index.html:50 +msgid "insert a URL link to a treebank in CONLL-U format" +msgstr "" + +#: templates/index.html:50 +msgid "Example" +msgstr "" + +#: templates/index.html:59 +msgid "Tree specification" +msgstr "" + +#: templates/index.html:62 +msgid "Tree size" +msgstr "" + +#: templates/index.html:62 +msgid "number of tokens in the tree" +msgstr "" + +#: templates/index.html:70 +msgid "Node type" +msgstr "" + +#: templates/index.html:70 +msgid "token characteristics to consider" +msgstr "" + +#: templates/index.html:76 +msgid "Part-of-speech" +msgstr "" + +#: templates/index.html:85 +msgid "Lemma" +msgstr "" + +#: templates/index.html:91 +msgid "Form" +msgstr "" + +#: templates/index.html:105 +msgid "Advanced settings" +msgstr "" + +#: templates/index.html:110 +msgid "Labeled trees" +msgstr "" + +#: templates/index.html:110 +msgid "include names of dependency relations" +msgstr "" + +#: templates/index.html:114 templates/index.html:129 templates/index.html:145 +msgid "No" +msgstr "" + +#: templates/index.html:117 templates/index.html:132 templates/index.html:148 +msgid "Yes" +msgstr "" + +#: templates/index.html:125 +msgid "Fixed order" +msgstr "" + +#: templates/index.html:125 +msgid "differentiate trees based on surface word order" +msgstr "" + +#: templates/index.html:141 +msgid "Association measures" +msgstr "" + +#: templates/index.html:141 +msgid "print MI, logDice and t-score" +msgstr "" + +#: templates/index.html:158 +msgid "Frequency threshold" +msgstr "" + +#: templates/index.html:158 +msgid "specify the minimum frequency of a tree in the treebank" +msgstr "" + +#: templates/index.html:168 +msgid "Head" +msgstr "" + +#: templates/index.html:168 +msgid "specify potential restrictions on the head node" +msgstr "" + +#: templates/index.html:179 +msgid "Submit" +msgstr "" + +#: templates/index.html:187 +msgid "No results" +msgstr "" + +#: templates/index.html:188 +msgid "Processing with your settings didnt produce any results!" +msgstr "" + +#: templates/result.html:30 +msgid "Download complete results" +msgstr "" + diff --git a/requirements.txt b/requirements.txt index 373c4c5..c89782f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Flask==3.0.0 requests==2.31.0 +flask-babel==4.0.0 stark @ git+https://github.com/clarinsi/STARK@eff0c8609c9acc2bc0b096339e91e71430cbf762 diff --git a/setup.py b/setup.py index 3756e92..6334fdf 100644 --- a/setup.py +++ b/setup.py @@ -16,8 +16,9 @@ setup(name='stark-api', license='Apache 2', packages=find_packages(), install_requires=[ - 'Flask>=3.0.0', - 'requests>=2.31.0', + 'Flask==3.0.0', + 'requests==2.31.0', + 'flask-babel==4.0.0', 'stark @ git+https://github.com/clarinsi/STARK@master' ], ) \ No newline at end of file diff --git a/static/css/nouislider.css b/static/css/nouislider.css index c3331aa..3474255 100644 --- a/static/css/nouislider.css +++ b/static/css/nouislider.css @@ -104,7 +104,7 @@ border: 1px solid transparent; } .noUi-connect { - background: #26A69A; + background: #a6a6a6; -webkit-transition: background 450ms; transition: background 450ms; } @@ -308,7 +308,7 @@ height: 15px; border-radius: 50%; box-shadow: none; - background-color: #26A69A; + background-color: #a6a6a6; border: none; left: -5px; top: -6px; @@ -337,7 +337,7 @@ width: 30px; top: -17px; left: -2px; - background-color: #26A69A; + background-color: #a6a6a6; border-radius: 50%; transition: border-radius .25s cubic-bezier(0.215, 0.610, 0.355, 1.000), transform .25s cubic-bezier(0.215, 0.610, 0.355, 1.000); @@ -376,7 +376,7 @@ width: 30px; top: -17px; left: -2px; - background-color: #26A69A; + background-color: #a6a6a6; border-radius: 50%; transition: border-radius .25s cubic-bezier(0.215, 0.610, 0.355, 1.000), transform .25s cubic-bezier(0.215, 0.610, 0.355, 1.000); diff --git a/static/css/style.css b/static/css/style.css index 963cc6c..211a674 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -22,6 +22,11 @@ transform: scale(1) rotate(-45deg) translate(0px, 4px); } +body { + font-family: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif; + font-size: 16px; +} + h4 { font-size: 1.8rem; } @@ -110,3 +115,96 @@ td { .wider-container { width: 80%; } + +.redcjvt { + background-color: #e12a26; +} + +.blackcjvt { + background-color: #161616; +} + +input:focus { + border-bottom: 1px solid #212121 !important; + box-shadow: 0 1px 0 0 #212121 !important; +} + +label.active { + color: #212121 !important; +} + +.backgroundcolorcjvt { + background-color: #f5f5f5; +} + +.btn:active { + background: #393939; +} +.btn:hover { + background: #212121; +} + +.btn.btn-round { + border-radius: 1.5rem; +} + +.btn, .btn-large, .btn-small { + display: inline-block; + line-height: 3rem; + padding: 0 1.5rem; + background-color: #161616; + color: white; + font-weight: 600; + margin-top: 1.5rem; + text-decoration: none; + cursor: pointer; + transition: background 0.3s ease-out; + font-family: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif; + text-transform: none; +} + +.insidebutton { + color: #161616; + background-color: #fff; + display: inline-block; + float: left; + line-height: 2.5rem; + padding: 0 1rem; + border-radius: 2px; + border: solid 1px #161616; + font-weight: 600; + font-size: 0.875rem; + margin-right: 1rem; + transition: opacity 0.3s ease-out; + cursor: pointer; + font-family: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif; + text-transform: none; +} + +.insidebutton:hover { + background-color: #fff; +} + +.switch label .lever { + background-color: #e0e0e0; +} +.switch label input[type=checkbox]:checked+.lever { + background-color: #a6a6a6; +} +.switch label input[type=checkbox]:checked+.lever:after { + background-color: #737373; +} + +input.valid[type=text]:not(.browser-default) { + border-bottom: 1px solid #9e9e9e; + box-shadow: none; +} + +input[type=text]:not(.browser-default).validate + label { + color: #9e9e9e !important; +} + +[type="checkbox"].filled-in:checked + span:not(.lever):after { + border: 2px solid #212121; + background-color: #212121; +} diff --git a/static/favicon/favicon.svg b/static/favicon/favicon.svg new file mode 100644 index 0000000..452de59 --- /dev/null +++ b/static/favicon/favicon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/static/js/init.js b/static/js/init.js index 2734280..4c36e8c 100644 --- a/static/js/init.js +++ b/static/js/init.js @@ -93,19 +93,26 @@ document.addEventListener("DOMContentLoaded", function(event) { } }*/ + + var urlParams = new URLSearchParams(window.location.search); + var lang='en' + if (urlParams.has('lang')) { + lang=urlParams.get('lang'); + } + var perfEntries = performance.getEntriesByType("navigation"); for (var i = 0; i < perfEntries.length; i++) { if (perfEntries[i].type === 'back_forward') { - window.location.href = '/' + window.location.href = '/?lang='+lang; return; } } - var urlParams = new URLSearchParams(window.location.search); + if (urlParams.has('reload')) { // if (Object.keys(localStorage).length > 1) { localStorage.clear(); - window.location.href = '/' + window.location.href = '/?lang='+lang; // } } @@ -162,5 +169,22 @@ document.addEventListener("DOMContentLoaded", function(event) { storeValuesToLocalstorage(); return true; }); + $("#switch-language").click(function(e) { + var spans = $(".noUi-tooltip").find('span'); + var tree_size_min = spans[0].innerText; + var tree_size_max = spans[1].innerText; + console.log('amm'); + $("").attr("type", "hidden") + .attr("name", "tree_size_min") + .attr("value", tree_size_min) + .appendTo("#submit-form"); + $("").attr("type", "hidden") + .attr("name", "tree_size_max") + .attr("value", tree_size_max) + .appendTo("#submit-form"); + + storeValuesToLocalstorage(); + return true; + }); })(jQuery); // end of jQuery name space diff --git a/templates/about.html b/templates/about.html index fa5b591..4dc298c 100644 --- a/templates/about.html +++ b/templates/about.html @@ -10,12 +10,17 @@ + + -