From ffb3d4eddead204b0f67a98d8d0013cb4270d703 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 4 Apr 2020 01:14:18 +0200 Subject: [PATCH] String definition / usage tests + cleaning of stale strings --- src/js/yunohost/controllers/users.js | 4 +- src/js/yunohost/events.js | 63 -------------- src/locales/en.json | 43 +--------- src/views/backup/backup.ms | 4 - src/views/domain/domain_list.ms | 5 -- src/views/service/service_info.ms | 2 +- src/views/user/group_create.ms | 2 +- tests/test_i18n_keys.py | 120 +++++++++++++++++++++++++++ 8 files changed, 126 insertions(+), 117 deletions(-) create mode 100644 tests/test_i18n_keys.py diff --git a/src/js/yunohost/controllers/users.js b/src/js/yunohost/controllers/users.js index 7da1bcb6..7a26ce37 100644 --- a/src/js/yunohost/controllers/users.js +++ b/src/js/yunohost/controllers/users.js @@ -217,7 +217,7 @@ app.post('#/users/create', function (c) { if (c.params['password'] == c.params['confirmation']) { if (c.params['password'].length < PASSWORD_MIN_LENGTH) { - c.flash('fail', y18n.t('password_too_short')); + c.flash('fail', y18n.t('passwords_too_short')); } else { // Force unit or disable quota @@ -380,7 +380,7 @@ if (params['password']) { if (params['password'] == params['confirmation']) { if (params['password'].length < PASSWORD_MIN_LENGTH) { - c.flash('fail', y18n.t('password_too_short')); + c.flash('fail', y18n.t('passwords_too_short')); c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false}); } else { diff --git a/src/js/yunohost/events.js b/src/js/yunohost/events.js index 0f430e53..52d8b49f 100644 --- a/src/js/yunohost/events.js +++ b/src/js/yunohost/events.js @@ -14,69 +14,6 @@ c.flash('warning', y18n.t('warning_first_user')); } - /* - * Disabling this for now because there's a duplicate Access Allow - * Origin header thing preventing it from working and people keep - * complaining about it and we havent effectively used this in 2 - * years anyway despite various security issues. - * - * - // Get security feed and display new items - var securityFeed = 'https://yunohost.org/security.rss'; - var forumUrl = 'https://forum.yunohost.org'; - - $.ajax({ - url: securityFeed, - // dataType: (jQuery.browser.msie) ? "text" : "xml", - dataType: "xml" - }) - .done(function(xml){ - // Get viewed security alerts from cookie - var viewedItems = Cookies.get('ynhSecurityViewedItems') || []; - - // Get 6 month earlier date - var SixMonthEarlier = new Date(); - SixMonthEarlier.setMonth(SixMonthEarlier.getMonth() - 6); - - // Loop through items in a reverse order (older first) - $($('item', xml).get().reverse()).each(function(k, v) { - var link = $('link', v).text(); - if (typeof link == 'string' && link !== '' && link.charAt(0) == '/') { - link = forumUrl+link; - } - - // var description=$('description', v).text(); - // description=description.replace('href="/','href="'+forumUrl+'/'); - - var item = { - guid: $('guid', v).text(), - title: $('title', v).text(), - url: link, - // desc: description, - date: new Date($('pubDate', v).text()), - }; - - // If item is not already viewed and is not older than 6 month - if (viewedItems.indexOf(item.guid) === -1 && (item.date.getTime() > SixMonthEarlier.getTime())) { - // Show security message to administrator - var warning = item.title + ' - ' + - item.date.toISOString().substring(0, 10) + - ' ('+y18n.t('read_more')+')'; - c.flash('warning', warning); - // Store viewed item - viewedItems.push(item.guid); - } - }); - // Saved viewed items to cookie - Cookies.set('ynhSecurityViewedItems', viewedItems, { - expires: 7 - }); - }) - .fail(function(stuff) { - c.flash('fail', y18n.t('error_retrieve_feed', [securityFeed])); - }); - */ - c.api("GET", "/diagnosis/show?full", {}, function(data) { c.hideLoader(); basesystem = data.reports.filter(function(r) { return r.id == "basesystem"; })[0]; diff --git a/src/locales/en.json b/src/locales/en.json index fbcf9934..2ef9a749 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -3,7 +3,6 @@ "active": "Active", "add": "Add", "advanced": "Advanced", - "remove": "Remove", "administration_password": "Administration password", "all": "All", "all_apps": "All apps", @@ -12,18 +11,14 @@ "app_change_url": "Change URL", "app_info_access_desc": "Groups / users currently allowed to access this app:", "app_info_changelabel_desc": "Change app label in the portal.", - "app_info_debug_desc": "Display debugging information for this application.", "app_info_default_desc": "Redirect domain root to this application (%s).", "app_info_changeurl_desc": "Change the access URL of this application (domain and/or path).", "app_info_change_url_disabled_tooltip": "This feature hasn't been implemented in this app yet", "app_info_uninstall_desc": "Remove this application.", "app_install_cancel": "Installation cancelled.", "app_install_custom_no_manifest": "No manifest.json file", - "app_level": "App level", "app_make_default": "Make default", "app_no_actions": "This application doesn't have any actions", - "app_repository": "Application origin: ", - "app_state": "Application state: ", "app_state_inprogress": "not yet working", "app_state_inprogress_explanation": "This maintainer of this app declared that this application is not ready yet for production use. BE CAREFUL!", "app_state_notworking": "not working", @@ -34,22 +29,17 @@ "app_state_high-quality_explanation": "This app is well-integrated with YunoHost. It has been (and is!) peer-reviewed by the YunoHost app team. It can be expected to be safe and maintained on the long-term.", "app_state_working": "working", "app_state_working_explanation": "The maintainer of this app declared it as 'working'. It means that it should be functional (c.f. application level) but is not necessarily peer-reviewed, it may still contain issues or is not fully integrated with YunoHost.", - "application": "Application", "applications": "Applications", "archive_empty": "Empty archive", - "available_apps": "Available apps", "backup": "Backup", "backup_action": "Backup", - "backup_archive_copy": "Copy this archive on another storage", "backup_archive_delete": "Delete this archive", - "backup_archive_download": "Download this archive", "backup_content": "Backup content", "backup_create": "Create a backup", "backup_encryption_warning": "Don't forget this password, you'll need it if you want restore the archive", "backup_new": "New backup", "backup_optional_encryption": "Optional encryption", "backup_optional_password": "Optional password", - "backup_type": "Type", "backups_no": "No backup", "begin": "Begin", "both": "Both", @@ -84,9 +74,7 @@ "confirm_reboot_action_reboot": "Are you sure you want to reboot your server?", "confirm_reboot_action_shutdown": "Are you sure you want to shutdown your server?", "connection": "Connection", - "copy": "Copy", "created_at": "Created at", - "current_maintainer_title": "Current maintainer of this package", "custom_app_install": "Install custom app", "custom_app_url_only_github": "Currently only from GitHub", "default": "Default", @@ -107,26 +95,21 @@ "domain_add_dyndns_doc": "… and I want a dynamic DNS service.", "domain_add_panel_with_domain": "I already have a domain name…", "domain_add_panel_without_domain": "I don't have a domain name…", - "domain_default": "Default domain", "domain_default_desc": "The default domain is the connection domain where users log in.", "domain_default_longdesc": "This is your default domain.", "domain_delete_longdesc": "Delete this domain", "domain_dns_config": "DNS configuration", "domain_dns_longdesc": "View DNS configuration", - "domain_list": "Domain list", "domain_name": "Domain name", - "domain_select": "Select domain", "domain_visit": "Visit", "domain_visit_url": "Visit %s", "domains": "Domains", - "download": "Download", "enable": "Enable", "enabled": "Enabled", "errors": "%s errors", "error_modify_something": "You should modify something", "error_retrieve_feed": "Could not retrieve feed: %s. You might have a plugin prevent your browser from performing this request (or the website is down).", "error_select_domain": "You should indicate a domain", - "error_server": "Server error", "error_server_unexpected": "Unexpected server error (%s)", "error_connection_interrupted": "The server closed the connection instead of answering it. Has nginx or the yunohost-api been restarted or stoppted for some reason? (Error code/message: %s)", "everything_good": "Everything good!", @@ -134,7 +117,6 @@ "firewall": "Firewall", "footer_version": "Powered by YunoHost %s (%s).", "form_input_example": "Example: %s", - "free": "Free", "from_to": "from %s to %s", "good_practices_about_admin_password": "You are now about to define a new admin password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", @@ -149,6 +131,7 @@ "group_explain_all_users": "This is a special group containing all users accounts on the server", "group_explain_visitors": "This is a special group representing anonymous visitors", "group_specific_permissions": "User specific permissions", + "groups": "Groups", "groups_and_permissions": "Groups and permissions", "groups_and_permissions_manage": "Manage groups and permissions", "permissions": "Permissions", @@ -178,19 +161,15 @@ "install_time": "Install time", "installation_complete": "Installation complete", "installed": "Installed", - "installed_apps": "Installed apps", - "installing": "Installing", "internal_exception": "Yunohost encountered an internal error:/
Really sorry about that.
You should look for help on the forum or the chat to fix the situation, or report the bug on the bugtracker.

The following information might be useful for the person helping you:

Action

%s%s

Traceback

%s
", "ipv4": "IPv4", "ipv6": "IPv6", "label": "Label", "label_for_manifestname": "Label for %s", "last_ran": "Last time ran:", - "level": "level", "license": "License", "loading": "Loading …", "local_archives": "Local archives", - "log": "Log", "logged_in": "Logged in", "logged_out": "Logged out", "login": "Login", @@ -202,22 +181,18 @@ "manage_apps": "Manage apps", "manage_domains": "Manage domains", "manage_users": "Manage users", - "menu": "Menu", "migrations": "Migrations", "migrations_pending": "Pending migrations", "migrations_done": "Previous migrations", "migrations_no_pending": "No pending migrations", "migrations_no_done": "No previous migrations", - "mode": "Mode", "multi_instance": "Multi instance", "myserver": "myserver", "myserver_org": "myserver.org", "next": "Next", "no": "No", "no_installed_apps": "No installed apps.", - "no_log": "No log.", "nobody": "Nobody", - "non_compatible_api": "Non-compatible API", "ok": "OK", "only_highquality_apps": "Only high-quality apps", "only_working_apps": "Only working apps", @@ -229,7 +204,6 @@ "others": "Others", "password": "Password", "password_confirmation": "Password confirmation", - "password_description": "Password must be at least %s characters long.", "password_empty": "The password field is empty", "password_new": "New password", "passwords_dont_match": "Passwords don't match", @@ -275,12 +249,8 @@ "search_for_apps": "Search for apps...", "select_all": "Select all", "select_none": "Select none", - "service_description": "Description:", - "service_log": "%s log", "service_start_on_boot": "Start on boot", - "service_status": "Status: ", "services": "Services", - "services_list": "Service list", "set_default": "Set default", "size": "Size", "since": "since", @@ -288,16 +258,10 @@ "start": "Start", "status": "Status", "stop": "Stop", - "storage_create": "Add remote storage", - "storages_new": "New remote storage", - "storages_no": "No storages.", "system": "System", - "system_apps": "Apps", "system_apps_nothing": "All apps are up to date!", - "system_packages": "System packages", "system_packages_nothing": "All system packages are up to date!", "system_update": "System update", - "system_upgrade": "System upgrade", "system_upgrade_btn": "Upgrade", "system_upgrade_all_applications_btn": "Upgrade all applications", "system_upgrade_all_packages_btn": "Upgrade all packages", @@ -330,6 +294,7 @@ "unknown_action": "Unknown action %s", "unknown_argument": "Unknown argument: %s", "unmaintained": "Unmaintained", + "unmaintained_details": "This app has not been update for quite a while and the previous maintainer has gone away or does not have time to maintain this app. Feel free to check the app repository to provide your help", "upload": "Upload", "upload_archive": "Archive upload", "upnp": "UPnP", @@ -348,7 +313,6 @@ "user_username": "Username", "user_username_edit": "Edit %s’s account", "users": "Users", - "users_list": "User list", "users_new": "New user", "users_no": "No users.", "version": "Version", @@ -364,7 +328,6 @@ "certificate_alert_great": "Great! You're using a valid Let's Encrypt certificate!", "certificate_alert_unknown": "Unknown status", "certificate_manage": "Manage SSL certificate", - "certificate_old_letsencrypt_app_conflict": "The 'letsencrypt' app is currently installed and conflicts with this feature. Please uninstall it first to use the new certificate management interface.", "ssl_certificate": "SSL certificate", "confirm_cert_install_LE": "Are you sure you want to install a Let's Encrypt certificate for this domain?", "confirm_cert_regen_selfsigned": "Are you sure you want to regenerate a self-signed certificate for this domain?", @@ -379,12 +342,10 @@ "install_letsencrypt_cert": "Install a Let's Encrypt certificate", "manually_renew_letsencrypt_message": "Certificate will be automatically renewed during the last 15 days of validity. You can manually renew it if you want to. (Not recommended).", "manually_renew_letsencrypt": "Manually renew now", - "meltdown": "You are vulnerable to the meltdown critical security vulnerability. To fix that, you need to update your system then reboot it to load the new linux kernel.", "regenerate_selfsigned_cert_message": "If you want, you can regenerate the self-signed certificate.", "regenerate_selfsigned_cert": "Regenerate self-signed certificate", "revert_to_selfsigned_cert_message": "If you really want to, you can reinstall a self-signed certificate. (Not recommended)", "revert_to_selfsigned_cert": "Revert to a self-signed certificate", - "name": "Name", "purge_user_data_checkbox": "Purge %s's data? (This will remove the content of its home and mail directories.)", "purge_user_data_warning": "Purging user's data is not reversible. Be sure you know what you're doing!" } diff --git a/src/views/backup/backup.ms b/src/views/backup/backup.ms index e4bd4b0c..dd761da2 100644 --- a/src/views/backup/backup.ms +++ b/src/views/backup/backup.ms @@ -12,10 +12,6 @@

{{name}} {{id}}

{{uri}}

-{{else}} -
- - {{t 'storages_no'}}
{{/each}} diff --git a/src/views/domain/domain_list.ms b/src/views/domain/domain_list.ms index db1655d6..f9761000 100644 --- a/src/views/domain/domain_list.ms +++ b/src/views/domain/domain_list.ms @@ -21,10 +21,5 @@

https://{{url}}

- {{else}} -
- - {{t 'domains_no'}} -
{{/each}} diff --git a/src/views/service/service_info.ms b/src/views/service/service_info.ms index 4c0c0356..f4a412d6 100644 --- a/src/views/service/service_info.ms +++ b/src/views/service/service_info.ms @@ -9,7 +9,7 @@
-

{{t name}}

+

{{name}}

{{#if (eq status "running")}}
diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py new file mode 100644 index 00000000..722d78ee --- /dev/null +++ b/tests/test_i18n_keys.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +import os +import re +import glob +import json +import yaml +import subprocess + +############################################################################### +# Find used keys in python code # +############################################################################### + + +def find_expected_string_keys(): + + # Try to find : + # y18n.t("foo" +) # (the real key is a concatenation of 'foo' with something else) + # y18n.t("foo") or y18n.t('foo', ...) # actual full key + js_p1 = re.compile(r'y18n\.t\(\s*[\"\'](\w+)[\"\']\s*[\,\)]') + js_p2 = re.compile(r'y18n\.t\(\s*[\"\'](\w+)[\"\']\s*\+') + + js_files = glob.glob("../src/js/yunohost/controllers/*.js") + js_files.extend(glob.glob("../src/js/yunohost/*.js")) + + for js_file in js_files: + content = open(js_file).read() + for m in js_p1.findall(content): + yield m + for m in js_p2.findall(content): + yield m + + # In views we have stuff like {{t 'foo' arg}} + views_p1 = re.compile(r'{{t\s*[\"\'](\w+)[\"\']') + # Somes are inside {{ (t 'foo'))) + views_p2 = re.compile(r'\(t\s*[\"\'](\w+)[\"\']\)') + views_p3 = re.compile(r't \(concat\s*[\"\'](\w+)[\"\']') + views_p4 = re.compile(r'data-y18n=[\"\'](\w+)[\"\']') + + view_files = glob.glob("../src/*.html") + view_files.extend(glob.glob("../src/views/*.ms")) + view_files.extend(glob.glob("../src/views/*/*.ms")) + + for view_file in view_files: + content = open(view_file).read() + for m in views_p1.findall(content): + yield m + for m in views_p2.findall(content): + yield m + for m in views_p3.findall(content): + yield m + for m in views_p4.findall(content): + yield m + + # App maintenance state + for state in ['maintained', 'orphaned', 'request_adoption', 'request_help','unmaintained']: + yield state + yield state + "_details" + + # Service states + for state in ['active', 'disabled', 'enabled', 'inactive']: + yield state + + yield "confirm_cert_" + + +############################################################################### +# Load en locale json keys # +############################################################################### + + +def keys_defined_for_en(): + return json.loads(open("../src/locales/en.json").read()).keys() + +############################################################################### +# Compare keys used and keys defined # +############################################################################### + + +expected_string_keys = set(find_expected_string_keys()) +keys_defined = set(keys_defined_for_en()) + +def test_undefined_i18n_keys(): + undefined_keys = expected_string_keys.difference(keys_defined) + undefined_keys = sorted(undefined_keys) + + undefined_keys = [k for k in undefined_keys if not k.endswith("_")] + + return undefined_keys + + if undefined_keys: + raise Exception("Those i18n keys should be defined in en.json:\n" + " - " + "\n - ".join(undefined_keys)) + + +def test_unused_i18n_keys(): + + unused_keys = keys_defined.difference(expected_string_keys) + unused_keys = sorted(unused_keys) + + partial_match = [k for k in unused_keys if any(k.startswith(m) for m in expected_string_keys if m.endswith("_")) ] + + unused_keys_2 = [] + for k in unused_keys: + if not any(k.startswith(m) for m in expected_string_keys if m.endswith("_")): + unused_keys_2.append(k) + + return unused_keys_2 + + if unused_keys: + raise Exception("Those i18n keys appears unused:\n" + " - " + "\n - ".join(unused_keys)) + + +print("--------- undefined") +print() +print(test_undefined_i18n_keys()) +print("--------- unused") +print() +print(test_unused_i18n_keys())