diff --git a/app.py b/app.py index d33f63c..07ba5fe 100644 --- a/app.py +++ b/app.py @@ -246,7 +246,7 @@ def login(): session.permanent = request.form.get('remember_me', False) return redirect(url_for('index')) else: - flash("Wrong password", 'danger') + flash(_("Wrong password"), 'danger') return redirect(url_for('admin.login')) else: if logged_in: @@ -321,7 +321,7 @@ def blacklist(): blacklist = request.form.get('blacklist') with open(const.blacklistFile, 'w') as file: file.write(blacklist) - return {'message': 'Blacklist updated!'}, 200 + return {'message': _("Blacklist updated!")}, 200 else: blacklist = func.readPlainFile(const.blacklistFile) @@ -355,7 +355,7 @@ def postInstall(): # -- server routes -- @api_bp.errorhandler(404) -def notFound(): +def notFound(e): return jsonify({'error': 'Not found'}), 404 @api_bp.errorhandler(400) @@ -523,9 +523,9 @@ def addAnswer(): cw = request.form.get('cw', '') if not question_id: - abort(400, "Missing 'question_id' attribute or 'question_id' is empty") + abort(400, _("Missing 'question_id' attribute or 'question_id' is empty")) if not answer: - abort(400, "Missing 'answer' attribute or 'answer' is empty") + abort(400, _("Missing 'answer' attribute or 'answer' is empty")) return func.addAnswer(question_id, answer, cw) @@ -543,24 +543,24 @@ def uploadFavicon(): func.generateFavicon(filename) - return {'message': 'Successfully updated favicon!'}, 201 + return {'message': _("Successfully updated favicon!")}, 201 elif favicon and not func.allowedFile(favicon.filename): - return {'error': 'File type is not supported'}, 400 + return {'error': _("File type is not supported")}, 400 elif not favicon: - return {'error': "favicon is not specified"}, 400 + return {'error': _("favicon is not specified")}, 400 @api_bp.route('/upload_emoji/', methods=['POST']) @loginRequired def uploadEmoji(): if 'emoji' not in request.files: - return jsonify({'error': 'No file part in the request'}), 400 + return jsonify({'error': _("No file part in the request")}), 400 emoji = request.files['emoji'] if emoji.filename == '': - return jsonify({'error': 'No file selected for uploading'}), 400 + return jsonify({'error': _("No file selected for uploading")}), 400 if not func.allowedFile(emoji.filename): - return jsonify({'error': 'Invalid file type. Only png, jpg, jpeg, webp, bmp, jxl supported'}), 400 + return jsonify({'error': _("Invalid file type. Only png, jpg, jpeg, webp, bmp, jxl, gif supported")}), 400 # Secure the filename and determine the archive name filename = secure_filename(emoji.filename) @@ -569,20 +569,20 @@ def uploadEmoji(): emoji.save(emoji_path) - return jsonify({'message': f'Emoji {filename} successfully uploaded'}), 201 + return jsonify({'message': _("Emoji {} successfully uploaded").format(filename)}), 201 @api_bp.route('/upload_emoji_pack/', methods=['POST']) @loginRequired def uploadEmojiPack(): if 'emoji_archive' not in request.files: - return jsonify({'error': 'No file part in the request'}), 400 + return jsonify({'error': _("No file part in the request")}), 400 emoji_archive = request.files['emoji_archive'] if emoji_archive.filename == '': - return jsonify({'error': 'No file selected for uploading'}), 400 + return jsonify({'error': _("No file selected for uploading")}), 400 if not func.allowedArchive(emoji_archive.filename): - return jsonify({'error': 'Invalid file type. Only .zip, .tar, .tar.gz, .tar.bz2 allowed'}), 400 + return jsonify({'error': _("Invalid file type. Only .zip, .tar, .tar.gz, .tar.bz2 allowed")}), 400 filename = secure_filename(emoji_archive.filename) archive_name = func.stripArchExtension(filename) @@ -601,15 +601,15 @@ def uploadEmojiPack(): with tarfile.open(archive_path, 'r:*') as tar_ref: tar_ref.extractall(extract_path) else: - return jsonify({'error': 'Unsupported archive format'}), 400 + return jsonify({'error': _("Unsupported archive format")}), 400 # parse meta.json if it exists meta_json_path = extract_path / 'meta.json' if meta_json_path.exists(): processed_emojis = func.processEmojis(meta_json_path) - return jsonify({'message': f'Successfully uploaded and processed {len(processed_emojis)} emojis from archive "{filename}".'}), 201 + return jsonify({'message': _('Successfully uploaded and processed {} emojis from archive "{}".').format(len(processed_emojis), filename)}), 201 else: - return jsonify({'message': f'Archive {filename} successfully uploaded and extracted.'}), 201 + return jsonify({'message': _("Archive {} successfully uploaded and extracted.").format(filename)}), 201 except Exception as e: return jsonify({'error': str(e)}), 500 @@ -654,11 +654,11 @@ def deleteEmoji(): emoji_ext = os.path.splitext(f"{emoji_base_path}/{emoji_file_path}") if not emoji_file_path.exists() or not emoji_file_path.is_file(): - return jsonify({'error': 'Emoji not found'}), 404 + return jsonify({'error': _("Emoji not found")}), 404 try: emoji_file_path.unlink() - return jsonify({'message': f'Emoji "{emoji_name}" deleted successfully'}), 200 + return jsonify({'message': _('Emoji "{}" deleted successfully').format(emoji_name)}), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @@ -670,14 +670,14 @@ def deleteEmojiPack(): emoji_base_path = Path.cwd() / 'static' / 'emojis' / pack_name.lower() if not emoji_base_path.exists() or not emoji_base_path.is_dir(): - return jsonify({'error': f'Emoji pack "{pack_name.capitalize()}" not found'}), 404 + return jsonify({'error': _('Emoji pack "{}" not found').format(pack_name)}), 404 try: for json_file in emojis_path.glob(f'{pack_name.lower()}.json'): json_file.unlink() shutil.rmtree(emoji_base_path) - return jsonify({'message': f'Emoji pack "{pack_name.capitalize()}" deleted successfully.'}), 200 + return jsonify({'message': _('Emoji pack "{}" deleted successfully.').format(pack_name)}), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @@ -760,10 +760,10 @@ def deleteExport(): def viewQuestion(): question_id = request.args.get('question_id', '') if not question_id: - abort(400, "Missing 'question_id' attribute or 'question_id' is empty") + abort(400, _("Missing 'question_id' attribute or 'question_id' is empty")) conn = func.connectToDb() - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor() cursor.execute("SELECT id, from_who, creation_date, content, answered, answer_id FROM questions WHERE id=%s", (question_id,)) question = cursor.fetchone() cursor.close() @@ -775,10 +775,10 @@ def viewQuestion(): def viewAnswer(): answer_id = request.args.get('answer_id', '') if not answer_id: - abort(400, "Missing 'answer_id' attribute or 'answer_id' is empty") + abort(400, _("Missing 'answer_id' attribute or 'answer_id' is empty")) conn = func.connectToDb() - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor() cursor.execute("SELECT id, question_id, creation_date, content FROM answers WHERE id=%s", (answer_id,)) answer = cursor.fetchone() cursor.close() @@ -809,7 +809,9 @@ def updateConfig(): func.saveJSON(cfg, const.configFile) app.config.update(cfg) - return {'message': 'Settings saved!'} + # loading the config file again to read new values + func.loadJSON(const.configFile) + return {'message': _("Settings saved!")} app.register_blueprint(api_bp, url_prefix='/api/v1') app.register_blueprint(admin_bp, url_prefix='/admin') diff --git a/functions.py b/functions.py index 4af067c..d3bc37c 100644 --- a/functions.py +++ b/functions.py @@ -24,6 +24,25 @@ import constants as const app = Flask(const.appName) +app.config['BABEL_DEFAULT_LOCALE'] = 'en' +app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'locales' +# refreshing locale +refresh() + +# update this once more languages are supported +app.config['available_languages'] = { + "en_US": _("English (US)"), + "ru_RU": _("Russian") +} + +def getLocale(): + if not session.get('language'): + app.config.update(cfg) + session['language'] = cfg['languages']['default'] + return session.get('language') + +babel = Babel(app, locale_selector=getLocale) + # load json file def loadJSON(file_path): # open the file @@ -197,7 +216,7 @@ def addQuestion(from_who, question, cw, noAntispam=False): antispam_valid = antispam in antispam_wordlist if not antispam_valid: # return a generic error message so bad actors wouldn't figure out the antispam list - return {'error': 'An error has occurred'}, 500 + return {'error': _('An error has occurred')}, 500 # it's probably bad to hardcode the siteverify urls, but meh, that will do for now elif cfg['antispam']['type'] == 'recaptcha': r = requests.post( diff --git a/templates/admin/base.html b/templates/admin/base.html index 397531d..1f4f1c8 100644 --- a/templates/admin/base.html +++ b/templates/admin/base.html @@ -1,9 +1,10 @@ {% extends 'base.html' %} -{% block title %}{% block _title %}{% endblock %} - Admin{% endblock %} +{% block title %}{% block _title %}{% endblock %} - {{ _('Admin') }}{% endblock %} {% set adminLink = 'active' %} {% block additionalHeadItems %} +{% block _additionalHeadItems %}{% endblock %} +{% endblock %} {% block _content %} -

Customize

-

Customize {{ const.appName }} to your liking

-

Favicon

+

{{ _('Customize') }}

+

{{ _('Customize {} to your liking').format(const.appName) }}

+

{{ _('Favicon') }}

-

Current favicon: {{ cfg.instance.title }}'s icon

+

{{ _('Current favicon:') }} {{ cfg.instance.title }}'s icon

- +
-
-

Accent color

+ +

{{ _('Accent color') }}

{#

Color

#}
- - -

Default: #6345d9

+ + +

{{ _('Default') }}: #6345d9

- - -

Default: #7a63e3

+ + +

{{ _('Default') }}: #7a63e3

{# brain doesn't feel like implementing this rn (9/27/24)

Background

@@ -55,7 +80,7 @@ role="switch" {% if cfg.style.tintColors == true %}checked{% endif %}> - +
- +
-

Note: on mobile screens text will always be hidden if this option is enabled

+

{{ _('Note') }}: {{ _('on mobile screens text will always be hidden if this option is enabled') }}

- +
{#
diff --git a/templates/admin/categories/emojis.html b/templates/admin/categories/emojis.html index 8f8614b..0da914e 100644 --- a/templates/admin/categories/emojis.html +++ b/templates/admin/categories/emojis.html @@ -1,49 +1,49 @@ {% extends 'admin/base.html' %} -{% block _title %}Emojis{% endblock %} +{% block _title %}{{ _('Emojis') }}{% endblock %} {% set emojis_link = 'active' %} {% block _content %} -

Custom emojis

-

Add custom emojis to your {{ const.appName }} instance

-

Upload

+

{{ _('Custom emojis') }}

+

{{ _('Add custom emojis to your {} instance').format(const.appName) }}

+

{{ _('Upload') }}

- +

- + -

Supported archive formats: .zip, .tar, .tar.gz, .tar.bz2, .tar.xz

+

{{ _('Supported archive formats:') }} .zip, .tar, .tar.gz, .tar.bz2, .tar.xz

-

Manage

-

Emojis

+

{{ _('Manage') }}

+

{{ _('Emojis') }}

{% if emojis %}
- - - - + + + + @@ -54,23 +54,23 @@
ImageNameFilenameActions{{ _('Image') }}{{ _('Name') }}{{ _('Filename') }}{{ _('Actions') }}
{% else %} -

No emojis uploaded

+

{{ _('No emojis uploaded') }}

{% endif %} -

Emoji packs

-

Please note that if meta.json is not found in the archive, preview images are selected by first emoji name to appear alphabetically, so they may not be accurate sometimes

+

{{ _('Emoji packs') }}

+

{{ _('Please note that if meta.json is not found in the archive, preview images are selected by first emoji name to appear alphabetically, so they may not be accurate sometimes') }}

{% if packs %}
- - + + {% if json_pack %} - - + + {% endif %} - - + + @@ -83,6 +83,6 @@
ImageName{{ _('Image') }}{{ _('Name') }}AuthorReleased{{ _('Author') }}{{ _('Released') }}FolderActions{{ _('Folder') }}{{ _('Actions') }}
{% else %} -

No emoji packs uploaded

+

{{ _('No emoji packs uploaded') }}

{% endif %} {% endblock %} diff --git a/templates/admin/categories/general.html b/templates/admin/categories/general.html index 9f49653..e8df816 100644 --- a/templates/admin/categories/general.html +++ b/templates/admin/categories/general.html @@ -25,7 +25,7 @@ role="switch" {% if cfg.lockInbox %}checked{% endif %}> - +
- +
- +
- -
-
- +
+ {% include 'snippets/admin/saveBtn.html' %} {% endblock %} {% block _scripts %} diff --git a/templates/admin/categories/import.html b/templates/admin/categories/import.html index 18d4fe2..1bc0711 100644 --- a/templates/admin/categories/import.html +++ b/templates/admin/categories/import.html @@ -1,20 +1,26 @@ {% extends 'admin/base.html' %} -{% block _title %}Import/Export{% endblock %} +{% block _title %}{{ _('Import/Export') }}{% endblock %} {% set import_link = 'active' %} {% block _content %} -

Import/Export

-

Import or export your {{ const.appName }} instance data

-

Import

+

{{ _('Import/Export') }}

+

{{ _('Import or export your {} instance data').format(const.appName) }}

+

{{ _('Import') }}

+
- + -

Note: Retrospring exports are not supported yet

+

{{ _('Note: Retrospring exports are not supported yet') }}

{# @@ -32,13 +38,13 @@ #} -

Export

+

{{ _('Export') }}

{% if exports %} @@ -46,8 +52,8 @@ - - + + @@ -55,8 +61,8 @@ {% endfor %} @@ -64,7 +70,7 @@
DateActions{{ _('Date') }}{{ _('Actions') }}
{{ export.timestamp }} - Download - + {{ _('Download') }} +
{% else %} -

No exports created yet

+

{{ _('No exports created yet') }}

{% endif %} {% endblock %} diff --git a/templates/admin/categories/instance.html b/templates/admin/categories/instance.html index 46bad80..5ddb4f2 100644 --- a/templates/admin/categories/instance.html +++ b/templates/admin/categories/instance.html @@ -1,47 +1,41 @@ {% extends 'admin/base.html' %} -{% block _title %}Information{% endblock %} +{% block _title %}{{ _('Information') }}{% endblock %} {% set info_link = 'active' %} {% block _content %}
-

Information

-

Essential information about your {{ const.appName }} instance

+

{{ _('Information') }}

+

{{ _('Essential information about your {} instance').format(const.appName) }}

- + -

Title of this CatAsk instance

+

{{ _('Title of this {} instance').format(const.appName) }}

-

Description of this CatAsk instance

+

{{ _('Description of this {} instance').format(const.appName) }}

-

Rules of this CatAsk instance

+

{{ _('Rules of this {} instance').format(const.appName) }}

- - -

Image that's going to be used in a link preview

+ + +

{{ _("Image that's going to be used in a link preview") }}

- - -

Full URL to homepage of this CatAsk instance without a trailing slash

-
-
- + + +

{{ _('Full URL to homepage of this {} instance without a trailing slash').format(const.appName) }}

+ {% include 'snippets/admin/saveBtn.html' %}
{% endblock %} diff --git a/templates/admin/categories/notifications.html b/templates/admin/categories/notifications.html index b12c428..b6e4fc9 100644 --- a/templates/admin/categories/notifications.html +++ b/templates/admin/categories/notifications.html @@ -1,10 +1,10 @@ {% extends 'admin/base.html' %} -{% block _title %}Notifications{% endblock %} +{% block _title %}{{ _('Notifications') }}{% endblock %} {% set notif_link = 'active' %} {% block _content %}
-

Notifications

-

Configure notifications for new questions using ntfy

+

{{ _('Notifications') }}

+

{{ _('Configure notifications for new questions using') }} ntfy

- +
-

Server & Topic

+

{{ _('Server & Topic') }}

- + / - +
-

Credentials (optional)

-

Set credentials if the topic is protected

+

{{ _('Credentials (optional)') }}

+

{{ _('Set credentials if the topic is protected') }}

- +

- Topic user + {{ _('Topic user') }}

- +

- Topic password + {{ _('Topic password') }}

-
- -
+ {% include 'snippets/admin/saveBtn.html' %}
{% endblock %} {% block _scripts %} diff --git a/templates/admin/login.html b/templates/admin/login.html index 70d8a12..e353e9e 100644 --- a/templates/admin/login.html +++ b/templates/admin/login.html @@ -1,22 +1,22 @@ {% extends 'base.html' %} -{% block title %}Admin Login{% endblock %} +{% block title %}{{ _('Admin Login') }}{% endblock %} {% set loginLink = 'active' %} {% block content %}
-
+
-

Login to {{ cfg.instance.title }}

+

{{ _('Login to {}').format(cfg.instance.title) }}

- - + +
- +
- +
diff --git a/templates/inbox.html b/templates/inbox.html index 75e4604..4daccb1 100644 --- a/templates/inbox.html +++ b/templates/inbox.html @@ -1,26 +1,26 @@ {% extends 'base.html' %} -{% block title %}Inbox {% if len(questions) > 0 %}({{ len(questions) }}){% endif %}{% endblock %} +{% block title %}{{ _('Inbox') }} {% if len(questions) > 0 %}({{ len(questions) }}){% endif %}{% endblock %} {% set inboxLink = 'active' %} {% block additionalHeadItems %} {% endblock %} {% block content %} {% if questions != [] %} -

{{ len(questions) }} question(s)

+

{{ len(questions) }} {{ _('question(s)') }}

{% for question in questions %}
-
+
{% if question.from_who %} {{ render_markdown(question.from_who, fromWho=True) }} {% else %} - {{ cfg.anonName }} + {{ cfg.anonName }} {% endif %}
-
+
{# reserved for version 1.6.0 or later @@ -38,8 +38,8 @@ {{ question.content | render_markdown }}
{% else %} {{ question.content | render_markdown }} @@ -85,15 +85,15 @@ {% else %} -

Inbox is currently empty.

+

{{ _('Inbox is currently empty.') }}

{% endif %} {% endblock %} {% block scripts %}