From 073ef5654ce547360448d00a0ba73a991a402d80 Mon Sep 17 00:00:00 2001 From: John Hensley Date: Wed, 17 Mar 2021 16:18:15 -0400 Subject: [PATCH 1/3] Fix some translation problems Correct some misplaced parentheses in gettext/format calls. Mark some strings that were being presented untranslated. Fix locale/session handling on CSRF error. We were translating the CSRF error message, then clearing the session and showing the rest of the page in the default locale, almost certainly English. We need to reevaluate how we want to handle the locale when clearing the session, but for now, this just fixes the inconsistency. --- securedrop/journalist_app/__init__.py | 3 +-- securedrop/journalist_app/admin.py | 30 ++++++++++++--------- securedrop/journalist_app/utils.py | 25 ++++++++++++------ securedrop/models.py | 38 ++++++++++++++------------- securedrop/source_app/__init__.py | 25 +++++++++++------- 5 files changed, 72 insertions(+), 49 deletions(-) diff --git a/securedrop/journalist_app/__init__.py b/securedrop/journalist_app/__init__.py index 47cd724e0d..920e56a356 100644 --- a/securedrop/journalist_app/__init__.py +++ b/securedrop/journalist_app/__init__.py @@ -94,9 +94,8 @@ def create_app(config: 'SDConfig') -> Flask: @app.errorhandler(CSRFError) def handle_csrf_error(e: CSRFError) -> 'Response': - # render the message first to ensure it's localized. - msg = gettext('You have been logged out due to inactivity.') session.clear() + msg = gettext('You have been logged out due to inactivity.') flash(msg, 'error') return redirect(url_for('main.login')) diff --git a/securedrop/journalist_app/admin.py b/securedrop/journalist_app/admin.py index 7676ae3dba..c6b0024b38 100644 --- a/securedrop/journalist_app/admin.py +++ b/securedrop/journalist_app/admin.py @@ -52,8 +52,8 @@ def manage_config() -> Union[str, werkzeug.Response]: f.save(custom_logo_filepath) flash(gettext("Image updated."), "logo-success") except Exception: - flash("Unable to process the image file." - " Try another one.", "logo-error") + # Translators: This error is shown when an uploaded image cannot be used. + flash(gettext("Unable to process the image file. Try another one."), "logo-error") finally: return redirect(url_for("admin.manage_config") + "#config-logoimage") else: @@ -131,13 +131,16 @@ def add_user() -> Union[str, werkzeug.Response]: form_valid = False except InvalidUsernameException as e: form_valid = False - flash('Invalid username: ' + str(e), "error") + # Translators: Here, "{message}" explains the problem with the username. + flash(gettext('Invalid username: {message}').format(message=e), "error") except IntegrityError as e: db.session.rollback() form_valid = False if "UNIQUE constraint failed: journalists.username" in str(e): - flash(gettext('Username "{user}" already taken.'.format( - user=username)), "error") + flash( + gettext('Username "{username}" already taken.').format(username=username), + "error" + ) else: flash(gettext("An error occurred saving this user" " to the database." @@ -214,7 +217,7 @@ def edit_user(user_id: int) -> Union[str, werkzeug.Response]: try: Journalist.check_username_acceptable(new_username) except InvalidUsernameException as e: - flash('Invalid username: ' + str(e), 'error') + flash(gettext('Invalid username: {message}').format(message=e), "error") return redirect(url_for("admin.edit_user", user_id=user_id)) @@ -222,10 +225,12 @@ def edit_user(user_id: int) -> Union[str, werkzeug.Response]: pass elif Journalist.query.filter_by( username=new_username).one_or_none(): - flash(gettext( - 'Username "{user}" already taken.').format( - user=new_username), - "error") + flash( + gettext('Username "{username}" already taken.').format( + username=new_username + ), + "error" + ) return redirect(url_for("admin.edit_user", user_id=user_id)) else: @@ -236,7 +241,8 @@ def edit_user(user_id: int) -> Union[str, werkzeug.Response]: Journalist.check_name_acceptable(first_name) user.first_name = first_name except FirstOrLastNameError as e: - flash(gettext('Name not updated: {}'.format(e)), "error") + # Translators: Here, "{message}" explains the problem with the name. + flash(gettext('Name not updated: {message}').format(message=e), "error") return redirect(url_for("admin.edit_user", user_id=user_id)) try: @@ -244,7 +250,7 @@ def edit_user(user_id: int) -> Union[str, werkzeug.Response]: Journalist.check_name_acceptable(last_name) user.last_name = last_name except FirstOrLastNameError as e: - flash(gettext('Name not updated: {}'.format(e)), "error") + flash(gettext('Name not updated: {message}').format(message=e), "error") return redirect(url_for("admin.edit_user", user_id=user_id)) user.is_admin = bool(request.form.get('is_admin')) diff --git a/securedrop/journalist_app/utils.py b/securedrop/journalist_app/utils.py index 198f87bd0e..586d946ff5 100644 --- a/securedrop/journalist_app/utils.py +++ b/securedrop/journalist_app/utils.py @@ -99,7 +99,7 @@ def validate_user( InvalidPasswordLength) as e: current_app.logger.error("Login for '{}' failed: {}".format( username, e)) - login_flashed_msg = error_message if error_message else gettext('Login failed.') + login_flashed_msg = error_message if error_message else gettext('Login failed.') if isinstance(e, LoginThrottledException): login_flashed_msg += " " @@ -123,7 +123,7 @@ def validate_user( except Exception: pass - flash(Markup(login_flashed_msg), "error") + flash(login_flashed_msg, "error") return None @@ -414,7 +414,7 @@ def set_name(user: Journalist, first_name: Optional[str], last_name: Optional[st db.session.commit() flash(gettext('Name updated.'), "success") except FirstOrLastNameError as e: - flash(gettext('Name not updated: {}'.format(e)), "error") + flash(gettext('Name not updated: {message}').format(message=e), "error") def set_diceware_password(user: Journalist, password: Optional[str]) -> bool: @@ -437,11 +437,20 @@ def set_diceware_password(user: Journalist, password: Optional[str]) -> bool: return False # using Markup so the HTML isn't escaped - flash(Markup("

" + gettext( - "Password updated. Don't forget to " - "save it in your KeePassX database. New password:") + - ' {}

'.format(password)), - 'success') + flash( + Markup( + "

{message} {password}

".format( + message=Markup.escape( + gettext( + "Password updated. Don't forget to save it in your KeePassX database. " + "New password:" + ) + ), + password=Markup.escape("" if password is None else password) + ) + ), + 'success' + ) return True diff --git a/securedrop/models.py b/securedrop/models.py index 95c02e12d5..5820349e86 100644 --- a/securedrop/models.py +++ b/securedrop/models.py @@ -12,6 +12,7 @@ from io import BytesIO from flask import current_app, url_for +from flask_babel import gettext, ngettext from itsdangerous import TimedJSONWebSignatureSerializer, BadData from jinja2 import Markup from passlib.hash import argon2 @@ -342,10 +343,8 @@ def __init__(self, msg: str) -> None: class InvalidNameLength(FirstOrLastNameError): """Raised when attempting to create a Journalist with an invalid name length.""" - def __init__(self, name: str) -> None: - name_len = len(name) - msg = "Name too long (len={})".format(name_len) - super(InvalidNameLength, self).__init__(msg) + def __init__(self) -> None: + super(InvalidNameLength, self).__init__(gettext("Name too long")) class LoginThrottledException(Exception): @@ -380,11 +379,9 @@ def __init__(self, passphrase: str) -> None: def __str__(self) -> str: if self.passphrase_len > Journalist.MAX_PASSWORD_LEN: - return "Password too long (len={})".format(self.passphrase_len) + return "Password is too long." if self.passphrase_len < Journalist.MIN_PASSWORD_LEN: - return "Password needs to be at least {} characters".format( - Journalist.MIN_PASSWORD_LEN - ) + return "Password is too short." return "" # return empty string that can be appended harmlessly @@ -496,18 +493,25 @@ def set_name(self, first_name: Optional[str], last_name: Optional[str]) -> None: def check_username_acceptable(cls, username: str) -> None: if len(username) < cls.MIN_USERNAME_LEN: raise InvalidUsernameException( - 'Username "{}" must be at least {} characters long.' - .format(username, cls.MIN_USERNAME_LEN)) + ngettext( + 'Must be at least one character long.', + 'Must be at least {num} characters long.', + cls.MIN_USERNAME_LEN + ).format(num=cls.MIN_USERNAME_LEN) + ) if username in cls.INVALID_USERNAMES: raise InvalidUsernameException( + gettext( "This username is invalid because it is reserved " - "for internal use by the software.") + "for internal use by the software." + ) + ) @classmethod def check_name_acceptable(cls, name: str) -> None: # Enforce a reasonable maximum length for names if len(name) > cls.MAX_NAME_LEN: - raise InvalidNameLength(name) + raise InvalidNameLength() @classmethod def check_password_acceptable(cls, password: str) -> None: @@ -675,13 +679,11 @@ def login(cls, try: user = Journalist.query.filter_by(username=username).one() except NoResultFound: - raise InvalidUsernameException( - "invalid username '{}'".format(username)) + raise InvalidUsernameException(gettext("Invalid username")) if user.username in Journalist.INVALID_USERNAMES and \ user.uuid in Journalist.INVALID_USERNAMES: - raise InvalidUsernameException( - "Invalid username") + raise InvalidUsernameException(gettext("Invalid username")) if LOGIN_HARDENING: cls.throttle_login(user) @@ -876,9 +878,9 @@ def get_current(cls) -> "InstanceConfig": def check_name_acceptable(cls, name: str) -> None: # Enforce a reasonable maximum length for names if name is None or len(name) == 0: - raise InvalidNameLength(name) + raise InvalidNameLength() if len(name) > cls.MAX_ORG_NAME_LEN: - raise InvalidNameLength(name) + raise InvalidNameLength() @classmethod def set_organization_name(cls, name: str) -> None: diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py index c0efeb4806..37297e2392 100644 --- a/securedrop/source_app/__init__.py +++ b/securedrop/source_app/__init__.py @@ -3,7 +3,7 @@ from typing import Optional import werkzeug -from flask import (Flask, render_template, flash, Markup, request, g, session, +from flask import (Flask, render_template, escape, flash, Markup, request, g, session, url_for, redirect) from flask_babel import gettext from flask_assets import Environment @@ -127,14 +127,21 @@ def check_tor2web() -> None: # ignore_static here so we only flash a single message warning # about Tor2Web, corresponding to the initial page load. if 'X-tor2web' in request.headers: - flash(Markup(gettext( - 'WARNING:  ' - 'You appear to be using Tor2Web. ' - 'This  does not  ' - 'provide anonymity. ' - 'Why is this dangerous?') - .format(url=url_for('info.tor2web_warning'))), - "banner-warning") + flash( + Markup( + '{} {} {}'.format( + escape(gettext("WARNING:")), + escape( + gettext( + 'You appear to be using Tor2Web, which does not provide anonymity.' + ) + ), + url_for('info.tor2web_warning'), + escape(gettext('Why is this dangerous?')), + ) + ), + "banner-warning" + ) @app.before_request @ignore_static From 5bf8fb1bb0dff213f01ea79422ae1ae0dea5c90f Mon Sep 17 00:00:00 2001 From: John Hensley Date: Thu, 18 Feb 2021 18:05:51 -0500 Subject: [PATCH 2/3] Add localization checks to basic source/journalist interface tests We haven't really been testing translations. In the page layout tests, the content of strings is often only checked after making sure the page is *not* translated. This has led to us not catching text which didn't get marked for translation, thus being shown in English to users who are using a different locale. This commit includes the following changes to make it easier to test that source strings are properly translated: - a context manager which will xfail a test for a locale if any of the message IDs passed to it are not present in the locale's message catalog - a pytest flaky filter which prevents retries of expected failures - turning off flaky's reporting of successful tests - functions to obtain test locales and counts to use in testing their pluralization rules, which can be used with pytest.parametrize to easily run a test in multiple locales, including fuzzing plural strings to check that translations are complete, while only trying as many variations as the rules need. Adding locale parameterization to tests increases the overall time they take, of course: test_journalist.py went from around 2.5 minutes to 3.5 minutes. --- securedrop/bin/dev-shell | 2 +- securedrop/bin/run-test | 6 +- securedrop/bin/translation-test | 2 +- securedrop/tests/conftest.py | 3 + .../tests/pageslayout/functional_test.py | 4 +- securedrop/tests/test_journalist.py | 1415 +++++++++++------ securedrop/tests/test_source.py | 34 +- securedrop/tests/utils/__init__.py | 11 + securedrop/tests/utils/i18n.py | 106 ++ 9 files changed, 1100 insertions(+), 483 deletions(-) create mode 100644 securedrop/tests/utils/i18n.py diff --git a/securedrop/bin/dev-shell b/securedrop/bin/dev-shell index 544653ea86..7d86d991b5 100755 --- a/securedrop/bin/dev-shell +++ b/securedrop/bin/dev-shell @@ -79,7 +79,7 @@ function docker_run() { -e LOADDATA_ARGS \ -e LC_ALL=C.UTF-8 \ -e LANG=C.UTF-8 \ - -e PAGE_LAYOUT_LOCALES \ + -e TEST_LOCALES \ -e PATH \ -e BASE_OS=$BASE_OS \ --user "${USER:-root}" \ diff --git a/securedrop/bin/run-test b/securedrop/bin/run-test index 2cb353376b..eec4134111 100755 --- a/securedrop/bin/run-test +++ b/securedrop/bin/run-test @@ -33,13 +33,15 @@ fi mkdir -p "../test-results" -: "${PAGE_LAYOUT_LOCALES:=en_US,ar}" -export PAGE_LAYOUT_LOCALES +: "${TEST_LOCALES:="ar en_US"}" +export TEST_LOCALES export TOR_FORCE_NET_CONFIG=0 pytest \ --force-flaky --max-runs=3 \ + -rx \ + --no-success-flaky-report \ --page-layout \ --durations 10 \ --junitxml=../test-results/junit.xml \ diff --git a/securedrop/bin/translation-test b/securedrop/bin/translation-test index e7ac86cd2a..df21404b7f 100755 --- a/securedrop/bin/translation-test +++ b/securedrop/bin/translation-test @@ -36,7 +36,7 @@ printf "Running tests in these locales: %s\n" "$LOCALES" # separately avoids this. for locale in $LOCALES do - PAGE_LAYOUT_LOCALES=$locale pytest \ + TEST_LOCALES=$locale pytest \ -v \ --force-flaky --max-runs=3 \ --page-layout \ diff --git a/securedrop/tests/conftest.py b/securedrop/tests/conftest.py index 78122c2a71..e3c2bf86a3 100644 --- a/securedrop/tests/conftest.py +++ b/securedrop/tests/conftest.py @@ -32,6 +32,7 @@ import models from source_app import create_app as create_source_app from . import utils +from .utils import i18n # The PID file for the redis worker is hard-coded below. # Ideally this constant would be provided by a test harness. @@ -130,6 +131,8 @@ def config(gpg_key_dir: Path) -> Generator[SDConfig, None, None]: config.DATABASE_FILE = str(sqlite_db_path) subprocess.check_call(["sqlite3", config.DATABASE_FILE, ".databases"]) + config.SUPPORTED_LOCALES = i18n.get_test_locales() + yield config diff --git a/securedrop/tests/pageslayout/functional_test.py b/securedrop/tests/pageslayout/functional_test.py index 86fbb912ce..3d57dcfe40 100644 --- a/securedrop/tests/pageslayout/functional_test.py +++ b/securedrop/tests/pageslayout/functional_test.py @@ -30,8 +30,8 @@ def list_locales(): - if "PAGE_LAYOUT_LOCALES" in os.environ: - locales = os.environ["PAGE_LAYOUT_LOCALES"].split(",") + if "TEST_LOCALES" in os.environ: + locales = os.environ["TEST_LOCALES"].split() else: locales = ["en_US"] return locales diff --git a/securedrop/tests/test_journalist.py b/securedrop/tests/test_journalist.py index 3192b2e70d..246f0dba48 100644 --- a/securedrop/tests/test_journalist.py +++ b/securedrop/tests/test_journalist.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# flake8: noqa: E741 import base64 import binascii import io @@ -11,7 +12,9 @@ from base64 import b64decode from io import BytesIO +from flaky import flaky from flask import current_app, escape, g, session, url_for +from flask_babel import gettext, ngettext from mock import patch from pyotp import TOTP from sqlalchemy.exc import IntegrityError @@ -38,6 +41,7 @@ from sdconfig import config from .utils.instrument import InstrumentedApp +from .utils.i18n import get_plural_tests, get_test_locales, language_tag, page_language, xfail_untranslated_messages from . import utils @@ -47,12 +51,6 @@ VALID_PASSWORD = 'correct horse battery staple generic passphrase hooray' VALID_PASSWORD_2 = 'another correct horse battery staple generic passphrase' -# These are factored out of the tests because some test have a -# postive/negative case under varying conditions, and we don't want -# false postives after modifying a string in the application. -EMPTY_REPLY_TEXT = "You cannot send an empty reply." -ADMIN_LINK = '' - def _login_user(app, username, password, otp_secret): resp = app.post(url_for('main.login'), @@ -166,7 +164,9 @@ def test_reply_error_logging(journalist_app, test_journo, test_source): exception_class)) -def test_reply_error_flashed_message(journalist_app, test_journo, test_source): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_reply_error_flashed_message(config, journalist_app, test_journo, test_source, locale): exception_class = StaleDataError with journalist_app.test_client() as app: @@ -176,43 +176,58 @@ def test_reply_error_flashed_message(journalist_app, test_journo, test_source): with InstrumentedApp(app) as ins: with patch.object(db.session, 'commit', side_effect=exception_class()): - app.post(url_for('main.reply'), - data={'filesystem_id': test_source['filesystem_id'], - 'message': '_'}) + resp = app.post( + url_for('main.reply', l=locale), + data={'filesystem_id': test_source['filesystem_id'], 'message': '_'}, + follow_redirects=True + ) - ins.assert_message_flashed( - 'An unexpected error occurred! Please ' - 'inform your admin.', 'error') + assert page_language(resp.data) == language_tag(locale) + msgids = ['An unexpected error occurred! Please inform your admin.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'error') -def test_empty_replies_are_rejected(journalist_app, test_journo, test_source): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_empty_replies_are_rejected(config, journalist_app, test_journo, test_source, locale): with journalist_app.test_client() as app: _login_user(app, test_journo['username'], test_journo['password'], test_journo['otp_secret']) - resp = app.post(url_for('main.reply'), + resp = app.post(url_for('main.reply', l=locale), data={'filesystem_id': test_source['filesystem_id'], 'message': ''}, follow_redirects=True) - text = resp.data.decode('utf-8') - assert EMPTY_REPLY_TEXT in text + assert page_language(resp.data) == language_tag(locale) + msgids = ['You cannot send an empty reply.'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') -def test_nonempty_replies_are_accepted(journalist_app, test_journo, - test_source): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_nonempty_replies_are_accepted(config, journalist_app, test_journo, test_source, locale): with journalist_app.test_client() as app: _login_user(app, test_journo['username'], test_journo['password'], test_journo['otp_secret']) - resp = app.post(url_for('main.reply'), - data={'filesystem_id': test_source['filesystem_id'], - 'message': '_'}, - follow_redirects=True) + resp = app.post( + url_for('main.reply', l=locale), + data={'filesystem_id': test_source['filesystem_id'], 'message': '_'}, + follow_redirects=True + ) - text = resp.data.decode('utf-8') - assert EMPTY_REPLY_TEXT not in text + assert page_language(resp.data) == language_tag(locale) + msgids = ['You cannot send an empty reply.'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) not in resp.data.decode('utf-8') -def test_successful_reply_marked_as_seen_by_sender(journalist_app, test_journo, test_source): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_successful_reply_marked_as_seen_by_sender( + config, journalist_app, test_journo, test_source, locale +): with journalist_app.test_client() as app: journo = test_journo['journalist'] _login_user(app, journo.username, test_journo['password'], test_journo['otp_secret']) @@ -221,13 +236,15 @@ def test_successful_reply_marked_as_seen_by_sender(journalist_app, test_journo, assert not seen_reply resp = app.post( - url_for('main.reply'), + url_for('main.reply', l=locale), data={'filesystem_id': test_source['filesystem_id'], 'message': '_'}, follow_redirects=True ) - text = resp.data.decode('utf-8') - assert EMPTY_REPLY_TEXT not in text + assert page_language(resp.data) == language_tag(locale) + msgids = ['You cannot send an empty reply.'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) not in resp.data.decode('utf-8') seen_reply = SeenReply.query.filter_by(journalist_id=journo.id).one_or_none() assert seen_reply @@ -239,36 +256,60 @@ def test_unauthorized_access_redirects_to_login(journalist_app): ins.assert_redirects(resp, url_for('main.login')) -def test_login_throttle(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_login_throttle(config, journalist_app, test_journo, locale): # Overwrite the default value used during testing original_hardening = models.LOGIN_HARDENING models.LOGIN_HARDENING = True try: with journalist_app.test_client() as app: - for _ in range(Journalist._MAX_LOGIN_ATTEMPTS_PER_PERIOD): - resp = app.post( - url_for('main.login'), - data=dict(username=test_journo['username'], - password='invalid', - token='invalid')) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "Login failed" in text + with InstrumentedApp(app) as ins: + for _ in range(Journalist._MAX_LOGIN_ATTEMPTS_PER_PERIOD): + resp = app.post( + url_for('main.login'), + data=dict( + username=test_journo['username'], + password='invalid', + token='invalid' + ) + ) + assert resp.status_code == 200 + text = resp.data.decode('utf-8') + assert "Login failed" in text - resp = app.post( - url_for('main.login'), - data=dict(username=test_journo['username'], - password='invalid', - token='invalid')) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert ("Please wait at least {} seconds".format( - Journalist._LOGIN_ATTEMPT_PERIOD) in text) + resp = app.post( + url_for('main.login', l=locale), + data=dict( + username=test_journo['username'], + password='invalid', + token='invalid' + ) + ) + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Login failed.", + "Please wait at least {num} second before logging in again." + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + "{} {}".format( + gettext(msgids[0]), + ngettext( + msgids[1], + "Please wait at least {num} seconds before logging in again.", + Journalist._LOGIN_ATTEMPT_PERIOD + ).format(num=Journalist._LOGIN_ATTEMPT_PERIOD) + ), + 'error' + ) finally: models.LOGIN_HARDENING = original_hardening -def test_login_throttle_is_not_global(journalist_app, test_journo, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_login_throttle_is_not_global(config, journalist_app, test_journo, test_admin, locale): """The login throttling should be per-user, not global. Global login throttling can prevent all users logging into the application.""" @@ -278,71 +319,106 @@ def test_login_throttle_is_not_global(journalist_app, test_journo, test_admin): models.LOGIN_HARDENING = True try: with journalist_app.test_client() as app: - for _ in range(Journalist._MAX_LOGIN_ATTEMPTS_PER_PERIOD): - resp = app.post( - url_for('main.login'), - data=dict(username=test_journo['username'], - password='invalid', - token='invalid')) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "Login failed" in text + with InstrumentedApp(app) as ins: + for _ in range(Journalist._MAX_LOGIN_ATTEMPTS_PER_PERIOD): + resp = app.post( + url_for('main.login', l=locale), + data=dict( + username=test_journo['username'], + password='invalid', + token='invalid' + ) + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Login failed.'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') - resp = app.post( - url_for('main.login'), - data=dict(username=test_journo['username'], - password='invalid', - token='invalid')) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert ("Please wait at least {} seconds".format( - Journalist._LOGIN_ATTEMPT_PERIOD) in text) + resp = app.post( + url_for('main.login', l=locale), + data=dict( + username=test_journo['username'], + password='invalid', + token='invalid' + ) + ) + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Login failed.", + "Please wait at least {num} second before logging in again." + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + "{} {}".format( + gettext(msgids[0]), + ngettext( + msgids[1], + "Please wait at least {num} seconds before logging in again.", + Journalist._LOGIN_ATTEMPT_PERIOD + ).format(num=Journalist._LOGIN_ATTEMPT_PERIOD) + ), + 'error' + ) # A different user should be able to login resp = app.post( - url_for('main.login'), + url_for('main.login', l=locale), data=dict(username=test_admin['username'], password=test_admin['password'], token=TOTP(test_admin['otp_secret']).now()), follow_redirects=True) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "Sources" in text + assert page_language(resp.data) == language_tag(locale) + msgids = ['All Sources'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') finally: models.LOGIN_HARDENING = original_hardening -def test_login_invalid_credentials(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_login_invalid_credentials(config, journalist_app, test_journo, locale): with journalist_app.test_client() as app: - resp = app.post(url_for('main.login'), + resp = app.post(url_for('main.login', l=locale), data=dict(username=test_journo['username'], password='invalid', token='mocked')) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "Login failed" in text + assert page_language(resp.data) == language_tag(locale) + msgids = ['Login failed.'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') -def test_validate_redirect(journalist_app): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_validate_redirect(config, journalist_app, locale): with journalist_app.test_client() as app: - resp = app.post(url_for('main.index'), follow_redirects=True) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "Login to access" in text + resp = app.post(url_for('main.index', l=locale), follow_redirects=True) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Login to access the journalist interface'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') -def test_login_valid_credentials(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_login_valid_credentials(config, journalist_app, test_journo, locale): with journalist_app.test_client() as app: resp = app.post( - url_for('main.login'), + url_for('main.login', l=locale), data=dict(username=test_journo['username'], password=test_journo['password'], token=TOTP(test_journo['otp_secret']).now()), follow_redirects=True) - assert resp.status_code == 200 # successful login redirects to index - text = resp.data.decode('utf-8') - assert "Sources" in text - assert "No documents have been submitted!" in text + assert page_language(resp.data) == language_tag(locale) + msgids = [ + 'All Sources', + 'No documents have been submitted!' + ] + with xfail_untranslated_messages(config, locale, msgids): + resp_text = resp.data.decode('utf-8') + for msgid in msgids: + assert gettext(msgid) in resp_text def test_admin_login_redirects_to_index(journalist_app, test_admin): @@ -409,7 +485,7 @@ def test_admin_has_link_to_admin_index_page_in_index_page(journalist_app, token=TOTP(test_admin['otp_secret']).now()), follow_redirects=True) text = resp.data.decode('utf-8') - assert ADMIN_LINK in text + assert '' in text def test_user_lacks_link_to_admin_index_page_in_index_page(journalist_app, @@ -422,7 +498,7 @@ def test_user_lacks_link_to_admin_index_page_in_index_page(journalist_app, token=TOTP(test_journo['otp_secret']).now()), follow_redirects=True) text = resp.data.decode('utf-8') - assert ADMIN_LINK not in text + assert '' not in text def test_admin_logout_redirects_to_index(journalist_app, test_admin): @@ -445,17 +521,22 @@ def test_user_logout_redirects_to_index(journalist_app, test_journo): ins.assert_redirects(resp, url_for('main.index')) -def test_admin_index(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_index(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.get(url_for('admin.index')) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "Admin Interface" in text + resp = app.get(url_for('admin.index', l=locale)) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Admin Interface'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') -def test_admin_delete_user(journalist_app, test_admin, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_delete_user(config, journalist_app, test_admin, test_journo, locale): # Verify journalist is in the database with journalist_app.app_context(): assert Journalist.query.get(test_journo['id']) is not None @@ -463,22 +544,28 @@ def test_admin_delete_user(journalist_app, test_admin, test_journo): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.delete_user', - user_id=test_journo['id']), - follow_redirects=True) + with InstrumentedApp(journalist_app) as ins: + resp = app.post( + url_for('admin.delete_user', user_id=test_journo['id'], l=locale), + follow_redirects=True + ) - # Assert correct interface behavior - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert escape("Deleted user '{}'".format(test_journo['username'])) \ - in text + assert page_language(resp.data) == language_tag(locale) + msgids = ["Deleted user '{user}'."] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + gettext(msgids[0]).format(user=test_journo['username']), + 'notification' + ) # Verify journalist is no longer in the database with journalist_app.app_context(): assert Journalist.query.get(test_journo['id']) is None -def test_admin_cannot_delete_self(journalist_app, test_admin, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_cannot_delete_self(config, journalist_app, test_admin, test_journo, locale): # Verify journalist is in the database with journalist_app.app_context(): assert Journalist.query.get(test_journo['id']) is not None @@ -486,48 +573,70 @@ def test_admin_cannot_delete_self(journalist_app, test_admin, test_journo): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.delete_user', - user_id=test_admin['id']), - follow_redirects=True) + resp = app.post( + url_for('admin.delete_user', user_id=test_admin['id'], l=locale), + follow_redirects=True + ) # Assert correct interface behavior assert resp.status_code == 403 - resp = app.get(url_for('admin.index'), follow_redirects=True) + resp = app.get(url_for('admin.index', l=locale), follow_redirects=True) assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "Admin Interface" in text - - # The user can be edited and deleted - assert escape("Edit user {}".format(test_journo['username'])) in text - assert escape("Delete user {}".format(test_journo['username'])) in text - # The admin can be edited but cannot deleted - assert escape("Edit user {}".format(test_admin['username'])) in text - assert escape("Delete user {}".format(test_admin['username'])) \ - not in text - - -def test_admin_edits_user_password_success_response(journalist_app, - test_admin, - test_journo): + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Admin Interface", + "Edit user {username}", + "Delete user {username}" + ] + with xfail_untranslated_messages(config, locale, msgids): + resp_text = resp.data.decode('utf-8') + assert gettext(msgids[0]) in resp_text + + # The user can be edited and deleted + assert escape( + gettext("Edit user {username}").format(username=test_journo['username']) + ) in resp_text + assert escape( + gettext("Delete user {username}").format(username=test_journo['username']) + ) in resp_text + # The admin can be edited but cannot deleted + assert escape( + gettext("Edit user {username}").format(username=test_admin['username']) + ) in resp_text + assert escape( + gettext("Delete user {username}").format(username=test_admin['username']) + ) not in resp_text + + +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_edits_user_password_success_response( + config, journalist_app, test_admin, test_journo, locale +): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.new_password', - user_id=test_journo['id']), + resp = app.post(url_for('admin.new_password', user_id=test_journo['id'], l=locale), data=dict(password=VALID_PASSWORD_2), follow_redirects=True) assert resp.status_code == 200 - - text = resp.data.decode('utf-8') - assert 'Password updated.' in text - assert VALID_PASSWORD_2 in text - - -def test_admin_edits_user_password_session_invalidate(journalist_app, - test_admin, - test_journo): + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Password updated. Don't forget to save it in your KeePassX database. New password:" + ] + with xfail_untranslated_messages(config, locale, msgids): + resp_text = resp.data.decode('utf-8') + assert escape(gettext(msgids[0])) in resp_text + assert VALID_PASSWORD_2 in resp_text + + +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_edits_user_password_session_invalidate( + config, journalist_app, test_admin, test_journo, locale +): # Start the journalist session. with journalist_app.test_client() as app: _login_user(app, test_journo['username'], test_journo['password'], @@ -538,20 +647,28 @@ def test_admin_edits_user_password_session_invalidate(journalist_app, _login_user(admin_app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = admin_app.post(url_for('admin.new_password', - user_id=test_journo['id']), - data=dict(password=VALID_PASSWORD_2), - follow_redirects=True) + resp = admin_app.post( + url_for('admin.new_password', user_id=test_journo['id'], l=locale), + data=dict(password=VALID_PASSWORD_2), + follow_redirects=True + ) assert resp.status_code == 200 - - text = resp.data.decode('utf-8') - assert 'Password updated.' in text - assert VALID_PASSWORD_2 in text + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Password updated. Don't forget to save it in your KeePassX database. New password:" + ] + with xfail_untranslated_messages(config, locale, msgids): + resp_text = resp.data.decode('utf-8') + assert escape(gettext(msgids[0])) in resp_text + assert VALID_PASSWORD_2 in resp_text # Now verify the password change error is flashed. - resp = app.get(url_for('main.index'), follow_redirects=True) - text = resp.data.decode('utf-8') - assert 'You have been logged out due to password change' in text + with InstrumentedApp(journalist_app) as ins: + resp = app.get(url_for('main.index', l=locale), follow_redirects=True) + msgids = ['You have been logged out due to password change'] + assert page_language(resp.data) == language_tag(locale) + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'error') # Also ensure that session is now invalid. session.pop('expires', None) @@ -571,26 +688,36 @@ def test_admin_deletes_invalid_user_404(journalist_app, test_admin): assert resp.status_code == 404 -def test_admin_edits_user_password_error_response(journalist_app, - test_admin, - test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_edits_user_password_error_response( + config, journalist_app, test_admin, test_journo, locale +): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) with patch('sqlalchemy.orm.scoping.scoped_session.commit', side_effect=Exception()): - resp = app.post(url_for('admin.new_password', - user_id=test_journo['id']), - data=dict(password=VALID_PASSWORD_2), - follow_redirects=True) - - text = resp.data.decode('utf-8') - assert ('There was an error, and the new password might not have ' - 'been saved correctly.') in text - - -def test_user_edits_password_success_response(journalist_app, test_journo): + with InstrumentedApp(journalist_app) as ins: + resp = app.post( + url_for('admin.new_password', user_id=test_journo['id'], l=locale), + data=dict(password=VALID_PASSWORD_2), + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = [ + 'There was an error, and the new password might not have been ' + 'saved correctly. To prevent you from getting locked ' + 'out of your account, you should reset your password again.' + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'error') + + +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_user_edits_password_success_response(config, journalist_app, test_journo, locale): original_hardening = models.LOGIN_HARDENING try: # Set this to false because we login then immediately reuse the same @@ -602,15 +729,20 @@ def test_user_edits_password_success_response(journalist_app, test_journo): _login_user(app, test_journo['username'], test_journo['password'], test_journo['otp_secret']) token = TOTP(test_journo['otp_secret']).now() - resp = app.post(url_for('account.new_password'), + resp = app.post(url_for('account.new_password', l=locale), data=dict(current_password=test_journo['password'], token=token, password=VALID_PASSWORD_2), follow_redirects=True) - text = resp.data.decode('utf-8') - assert "Password updated." in text - assert VALID_PASSWORD_2 in text + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Password updated. Don't forget to save it in your KeePassX database. New password:" + ] + with xfail_untranslated_messages(config, locale, msgids): + resp_text = resp.data.decode('utf-8') + assert escape(gettext(msgids[0])) in resp_text + assert VALID_PASSWORD_2 in resp_text finally: models.LOGIN_HARDENING = original_hardening @@ -643,7 +775,9 @@ def test_user_edits_password_expires_session(journalist_app, test_journo): models.LOGIN_HARDENING = original_hardening -def test_user_edits_password_error_reponse(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_user_edits_password_error_response(config, journalist_app, test_journo, locale): original_hardening = models.LOGIN_HARDENING try: # Set this to false because we login then immediately reuse the same @@ -660,31 +794,57 @@ def test_user_edits_password_error_reponse(journalist_app, test_journo): with patch.object(Journalist, 'verify_token', return_value=True): with patch.object(db.session, 'commit', side_effect=Exception()): - resp = app.post( - url_for('account.new_password'), - data=dict(current_password=test_journo['password'], - token='mocked', - password=VALID_PASSWORD_2), - follow_redirects=True) - - text = resp.data.decode('utf-8') - assert ('There was an error, and the new password might not have ' - 'been saved correctly.') in text + with InstrumentedApp(journalist_app) as ins: + resp = app.post( + url_for('account.new_password', l=locale), + data=dict( + current_password=test_journo['password'], + token='mocked', + password=VALID_PASSWORD_2 + ), + follow_redirects=True + ) + + assert page_language(resp.data) == language_tag(locale) + msgids = [ + ( + 'There was an error, and the new password might not have been ' + 'saved correctly. To prevent you from getting locked ' + 'out of your account, you should reset your password again.' + ) + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'error') finally: models.LOGIN_HARDENING = original_hardening -def test_admin_add_user_when_username_already_taken(journalist_app, test_admin): - with journalist_app.test_client() as app: - _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username=test_admin['username'], - first_name='', - last_name='', - password=VALID_PASSWORD, - is_admin=None)) - text = resp.data.decode('utf-8') - assert 'already taken' in text +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_when_username_already_taken(config, journalist_app, test_admin, locale): + with journalist_app.test_client() as client: + _login_user( + client, test_admin['username'], test_admin['password'], test_admin['otp_secret'] + ) + with InstrumentedApp(journalist_app) as ins: + resp = client.post( + url_for('admin.add_user', l=locale), + data=dict( + username=test_admin['username'], + first_name='', + last_name='', + password=VALID_PASSWORD, + is_admin=None + ), + ) + assert page_language(resp.data) == language_tag(locale) + + msgids = ['Username "{username}" already taken.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + gettext(msgids[0]).format(username=test_admin["username"]), + 'error' + ) def test_max_password_length(): @@ -708,8 +868,8 @@ def test_login_password_too_long(journalist_app, test_journo, mocker): text = resp.data.decode('utf-8') assert "Login failed" in text mocked_error_logger.assert_called_once_with( - "Login for '{}' failed: Password too long (len={})".format( - test_journo['username'], Journalist.MAX_PASSWORD_LEN + 1)) + "Login for '{}' failed: Password is too long.".format(test_journo['username']) + ) def test_min_password_length(): @@ -772,7 +932,9 @@ def test_user_edits_password_too_long_warning(journalist_app, test_journo): 'Password not changed.', 'error') -def test_admin_add_user_password_too_long_warning(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_password_too_long_warning(config, journalist_app, test_admin, locale): overly_long_password = VALID_PASSWORD + \ 'a' * (Journalist.MAX_PASSWORD_LEN - len(VALID_PASSWORD) + 1) with journalist_app.test_client() as app: @@ -780,71 +942,91 @@ def test_admin_add_user_password_too_long_warning(journalist_app, test_admin): test_admin['otp_secret']) with InstrumentedApp(journalist_app) as ins: - app.post( - url_for('admin.add_user'), - data=dict(username='dellsberg', - first_name='', - last_name='', - password=overly_long_password, - is_admin=None)) + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username='dellsberg', + first_name='', + last_name='', + password=overly_long_password, + is_admin=None + ), + ) - ins.assert_message_flashed( + assert page_language(resp.data) == language_tag(locale) + msgids = [ 'There was an error with the autogenerated password. User not ' - 'created. Please try again.', 'error') + 'created. Please try again.' + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'error') -def test_admin_add_user_first_name_too_long_warning(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_first_name_too_long_warning(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: overly_long_name = 'a' * (Journalist.MAX_NAME_LEN + 1) _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username=test_admin['username'], - first_name=overly_long_name, - last_name='', - password=VALID_PASSWORD, - is_admin=None)) - text = resp.data.decode('utf-8') - assert 'Cannot be longer than' in text + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username=test_admin['username'], + first_name=overly_long_name, + last_name='', + password=VALID_PASSWORD, + is_admin=None + ), + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Cannot be longer than {num} character.'] + with xfail_untranslated_messages(config, locale, msgids): + assert ( + ngettext( + 'Cannot be longer than {num} character.', + 'Cannot be longer than {num} characters.', + Journalist.MAX_NAME_LEN + ).format(num=Journalist.MAX_NAME_LEN) + in resp.data.decode('utf-8') + ) -def test_admin_add_user_last_name_too_long_warning(journalist_app, test_admin): + +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_last_name_too_long_warning(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: overly_long_name = 'a' * (Journalist.MAX_NAME_LEN + 1) _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username=test_admin['username'], - first_name='', - last_name=overly_long_name, - password=VALID_PASSWORD, - is_admin=None)) - text = resp.data.decode('utf-8') - assert 'Cannot be longer than' in text - - -def test_admin_edits_user_invalid_username( - journalist_app, test_admin, test_journo): - """Test expected error message when admin attempts to change a user's - username to a username that is taken by another user.""" - new_username = test_journo['username'] - with journalist_app.test_client() as app: - _login_user(app, test_admin['username'], test_admin['password'], - test_admin['otp_secret']) - with InstrumentedApp(journalist_app) as ins: - app.post( - url_for('admin.edit_user', user_id=test_admin['id']), - data=dict(username=new_username, - first_name='', - last_name='', - is_admin=None)) - - ins.assert_message_flashed( - 'Username "{}" already taken.'.format(new_username), - 'error') + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username=test_admin['username'], + first_name='', + last_name=overly_long_name, + password=VALID_PASSWORD, + is_admin=None + ), + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Cannot be longer than {num} character.'] + with xfail_untranslated_messages(config, locale, msgids): + assert ( + ngettext( + 'Cannot be longer than {num} character.', + 'Cannot be longer than {num} characters.', + Journalist.MAX_NAME_LEN + ).format(num=Journalist.MAX_NAME_LEN) + in resp.data.decode('utf-8') + ) +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) def test_admin_edits_user_invalid_username_deleted( - journalist_app, test_admin, test_journo): + config, journalist_app, test_admin, test_journo, locale +): """Test expected error message when admin attempts to change a user's username to deleted""" new_username = "deleted" @@ -853,17 +1035,27 @@ def test_admin_edits_user_invalid_username_deleted( test_admin['otp_secret']) with InstrumentedApp(journalist_app) as ins: - app.post( - url_for('admin.edit_user', user_id=test_admin['id']), - data=dict(username=new_username, - first_name='', - last_name='', - is_admin=None)) + resp = app.post( + url_for('admin.edit_user', user_id=test_admin['id'], l=locale), + data=dict( + username=new_username, + first_name='', + last_name='', + is_admin=None + ), + follow_redirects=True + ) - ins.assert_message_flashed( - 'Invalid username: This username is invalid because it ' - 'is reserved for internal use by the software.', - 'error') + assert page_language(resp.data) == language_tag(locale) + msgids = [ + 'Invalid username: {message}', + 'This username is invalid because it is reserved for internal use by the software.' + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + gettext(msgids[0]).format(message=gettext(msgids[1])), + 'error' + ) def test_admin_resets_user_hotp_format_non_hexa( @@ -1119,16 +1311,21 @@ def test_user_resets_totp(journalist_app, test_journo): assert new_secret != old_secret - -def test_admin_resets_hotp_with_missing_otp_secret_key(journalist_app, - test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_resets_hotp_with_missing_otp_secret_key(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.reset_two_factor_hotp'), - data=dict(uid=test_admin['id'])) + resp = app.post( + url_for('admin.reset_two_factor_hotp', l=locale), + data=dict(uid=test_admin['id']), + ) - assert 'Change Secret' in resp.data.decode('utf-8') + assert page_language(resp.data) == language_tag(locale) + msgids = ['Change Secret'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') def test_admin_new_user_2fa_redirect(journalist_app, test_admin, test_journo): @@ -1142,25 +1339,39 @@ def test_admin_new_user_2fa_redirect(journalist_app, test_admin, test_journo): ins.assert_redirects(resp, url_for('admin.index')) +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) def test_http_get_on_admin_new_user_two_factor_page( - journalist_app, - test_admin, - test_journo): + config, + journalist_app, + test_admin, + test_journo, + locale +): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.get(url_for('admin.new_user_two_factor', uid=test_journo['id'])) - # any GET req should take a user to the admin.new_user_two_factor page - assert 'FreeOTP' in resp.data.decode('utf-8') + resp = app.get( + url_for('admin.new_user_two_factor', uid=test_journo['id'], l=locale), + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Enable FreeOTP'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') -def test_http_get_on_admin_add_user_page(journalist_app, test_admin): + +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_http_get_on_admin_add_user_page(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.get(url_for('admin.add_user')) - # any GET req should take a user to the admin_add_user page - assert 'ADD USER' in resp.data.decode('utf-8') + resp = app.get(url_for('admin.add_user', l=locale)) + assert page_language(resp.data) == language_tag(locale) + msgids = ['ADD USER'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') def test_admin_add_user(journalist_app, test_admin): @@ -1182,24 +1393,35 @@ def test_admin_add_user(journalist_app, test_admin): uid=new_user.id)) -def test_admin_add_user_with_invalid_username(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_with_invalid_username(config, journalist_app, test_admin, locale): username = 'deleted' with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username=username, - first_name='', - last_name='', - password=VALID_PASSWORD, - is_admin=None)) - - assert "This username is invalid because it is reserved for internal use by the software." \ - in resp.data.decode('utf-8') + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username=username, + first_name='', + last_name='', + password=VALID_PASSWORD, + is_admin=None + ), + ) + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "This username is invalid because it is reserved for internal use by the software." + ] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode("utf-8") -def test_deleted_user_cannot_login(journalist_app): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_deleted_user_cannot_login(config, journalist_app, locale): username = 'deleted' uuid = 'deleted' @@ -1212,18 +1434,38 @@ def test_deleted_user_cannot_login(journalist_app): db.session.add(user) db.session.commit() - # Verify that deleted user is not able to login - with journalist_app.test_client() as app: - resp = app.post(url_for('main.login'), - data=dict(username=username, - password=password, - token=otp_secret)) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "Login failed" in text + with InstrumentedApp(journalist_app) as ins: + # Verify that deleted user is not able to login + with journalist_app.test_client() as app: + resp = app.post( + url_for('main.login', l=locale), + data=dict( + username=username, + password=password, + token=otp_secret + ), + ) + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Login failed.", + ( + "Please wait for a new code from your two-factor mobile" + " app or security key before trying again." + ) + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + "{} {}".format( + gettext(msgids[0]), + gettext(msgids[1]), + ), + 'error' + ) -def test_deleted_user_cannot_login_exception(journalist_app): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_deleted_user_cannot_login_exception(journalist_app, locale): username = 'deleted' uuid = 'deleted' @@ -1236,102 +1478,159 @@ def test_deleted_user_cannot_login_exception(journalist_app): db.session.add(user) db.session.commit() - with pytest.raises(InvalidUsernameException): - Journalist.login(username, - password, - TOTP(otp_secret).now()) + with journalist_app.test_request_context('/'): + with pytest.raises(InvalidUsernameException): + Journalist.login(username, password, TOTP(otp_secret).now()) -def test_admin_add_user_without_username(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_without_username(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username='', - password=VALID_PASSWORD, - is_admin=None)) + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict(username='', password=VALID_PASSWORD, is_admin=None), + ) - assert 'This field is required.' in resp.data.decode('utf-8') + assert page_language(resp.data) == language_tag(locale) + msgids = ['This field is required.'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') -def test_admin_add_user_too_short_username(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_too_short_username(config, journalist_app, test_admin, locale): username = 'a' * (Journalist.MIN_USERNAME_LEN - 1) with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username=username, - password='pentagonpapers', - password_again='pentagonpapers', - is_admin=None)) - msg = 'Must be at least {} characters long' - assert (msg.format(Journalist.MIN_USERNAME_LEN) in resp.data.decode('utf-8')) + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username=username, + password='pentagonpapers', + password_again='pentagonpapers', + is_admin=None + ), + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Must be at least {num} character long.'] + with xfail_untranslated_messages(config, locale, msgids): + assert( + ngettext( + 'Must be at least {num} character long.', + 'Must be at least {num} characters long.', + Journalist.MIN_USERNAME_LEN + ).format(num=Journalist.MIN_USERNAME_LEN) + in resp.data.decode('utf-8') + ) -def test_admin_add_user_yubikey_odd_length(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize( + "locale, secret", + ( + (locale, "a" * i) + for locale in get_test_locales() + for i in get_plural_tests()[locale] if i != 0 + ) +) +def test_admin_add_user_yubikey_odd_length(journalist_app, test_admin, locale, secret): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username='dellsberg', - first_name='', - last_name='', - password=VALID_PASSWORD, - password_again=VALID_PASSWORD, - is_admin=None, - is_hotp=True, - otp_secret='123')) - assert 'HOTP secrets are 40 characters' in resp.data.decode('utf-8') + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username='dellsberg', + first_name='', + last_name='', + password=VALID_PASSWORD, + password_again=VALID_PASSWORD, + is_admin=None, + is_hotp=True, + otp_secret=secret + ), + ) + journalist_app.logger.critical("response: %s", resp.data) + assert page_language(resp.data) == language_tag(locale) + msgids = ['HOTP secrets are 40 characters long - you have entered {num}.'] + with xfail_untranslated_messages(config, locale, msgids): + assert ( + ngettext( + 'HOTP secrets are 40 characters long - you have entered {num}.', + 'HOTP secrets are 40 characters long - you have entered {num}.', + len(secret) + ).format(num=len(secret)) + in resp.data.decode("utf-8") + ) -def test_admin_add_user_yubikey_valid_length(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_yubikey_valid_length(journalist_app, test_admin, locale): otp = '1234567890123456789012345678901234567890' with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username='dellsberg', - first_name='', - last_name='', - password=VALID_PASSWORD, - password_again=VALID_PASSWORD, - is_admin=None, - is_hotp=True, - otp_secret=otp), - follow_redirects=True) - - # Should redirect to the token verification page - assert 'Enable YubiKey (OATH-HOTP)' in resp.data.decode('utf-8') + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username='dellsberg', + first_name='', + last_name='', + password=VALID_PASSWORD, + password_again=VALID_PASSWORD, + is_admin=None, + is_hotp=True, + otp_secret=otp + ), + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Enable YubiKey (OATH-HOTP)'] + with xfail_untranslated_messages(config, locale, msgids): + # Should redirect to the token verification page + assert gettext(msgids[0]) in resp.data.decode('utf-8') -def test_admin_add_user_yubikey_correct_length_with_whitespace( - journalist_app, - test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_yubikey_correct_length_with_whitespace(journalist_app, test_admin, locale): otp = '12 34 56 78 90 12 34 56 78 90 12 34 56 78 90 12 34 56 78 90' with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) - resp = app.post(url_for('admin.add_user'), - data=dict(username='dellsberg', - first_name='', - last_name='', - password=VALID_PASSWORD, - password_again=VALID_PASSWORD, - is_admin=None, - is_hotp=True, - otp_secret=otp), - follow_redirects=True) - - # Should redirect to the token verification page - assert 'Enable YubiKey (OATH-HOTP)' in resp.data.decode('utf-8') + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username='dellsberg', + first_name='', + last_name='', + password=VALID_PASSWORD, + password_again=VALID_PASSWORD, + is_admin=None, + is_hotp=True, + otp_secret=otp + ), + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Enable YubiKey (OATH-HOTP)'] + with xfail_untranslated_messages(config, locale, msgids): + # Should redirect to the token verification page + assert gettext(msgids[0]) in resp.data.decode('utf-8') def test_admin_sets_user_to_admin(journalist_app, test_admin): @@ -1415,34 +1714,58 @@ def test_admin_adds_first_name_last_name_to_user(journalist_app, test_admin): Journalist.query.filter(Journalist.username == new_user).one() -def test_admin_adds_invalid_first_last_name_to_user(journalist_app, test_admin): - new_user = 'admin-invalid-first-name-last-name-user-test' +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_adds_invalid_first_last_name_to_user(config, journalist_app, test_admin, locale): + with journalist_app.test_client() as client: + new_user = 'admin-invalid-first-name-last-name-user-test' - with journalist_app.test_client() as app: - _login_user(app, test_admin['username'], test_admin['password'], - test_admin['otp_secret']) + _login_user( + client, test_admin['username'], test_admin['password'], test_admin['otp_secret'] + ) - resp = app.post(url_for('admin.add_user'), - data=dict(username=new_user, - first_name='', - last_name='', - password=VALID_PASSWORD, - is_admin=None)) - assert resp.status_code in (200, 302) + resp = client.post( + url_for('admin.add_user'), + data=dict( + username=new_user, + first_name='', + last_name='', + password=VALID_PASSWORD, + is_admin=None + ) + ) + assert resp.status_code == 302 journo = Journalist.query.filter(Journalist.username == new_user).one() overly_long_name = 'a' * (Journalist.MAX_NAME_LEN + 1) - resp = app.post(url_for('admin.edit_user', user_id=journo.id), - data=dict(username=overly_long_name, - first_name=overly_long_name, - last_name='test name'), - follow_redirects=True) - assert resp.status_code in (200, 302) - text = resp.data.decode('utf-8') - assert 'Name not updated' in text + with InstrumentedApp(journalist_app) as ins: + resp = client.post( + url_for('admin.edit_user', user_id=journo.id, l=locale), + data=dict( + username=new_user, + first_name=overly_long_name, + last_name='test name' + ), + follow_redirects=True, + ) + assert resp.status_code == 200 + assert page_language(resp.data) == language_tag(locale) + + msgids = [ + "Name not updated: {message}", + "Name too long" + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + gettext(msgids[0]).format(message=gettext("Name too long")), + 'error' + ) -def test_admin_add_user_integrity_error(journalist_app, test_admin, mocker): + +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_admin_add_user_integrity_error(config, journalist_app, test_admin, mocker, locale): mocked_error_logger = mocker.patch('journalist_app.admin.current_app.logger.error') mocker.patch('journalist_app.admin.Journalist', side_effect=IntegrityError('STATEMENT', 'PARAMETERS', None)) @@ -1452,23 +1775,32 @@ def test_admin_add_user_integrity_error(journalist_app, test_admin, mocker): test_admin['otp_secret']) with InstrumentedApp(journalist_app) as ins: - app.post(url_for('admin.add_user'), - data=dict(username='username', - first_name='', - last_name='', - password=VALID_PASSWORD, - is_admin=None)) - ins.assert_message_flashed( - "An error occurred saving this user to the database." - " Please inform your admin.", - "error") + resp = app.post( + url_for('admin.add_user', l=locale), + data=dict( + username='username', + first_name='', + last_name='', + password=VALID_PASSWORD, + is_admin=None + ), + ) + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "An error occurred saving this user to the database. " + "Please inform your admin." + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), "error") log_event = mocked_error_logger.call_args[0][0] assert ("Adding user 'username' failed: (builtins.NoneType) " "None\n[SQL: STATEMENT]\n[parameters: 'PARAMETERS']") in log_event -def test_prevent_document_uploads(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_prevent_document_uploads(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) @@ -1479,13 +1811,20 @@ def test_prevent_document_uploads(journalist_app, test_admin): follow_redirects=True) assert InstanceConfig.get_current().allow_document_uploads is False with InstrumentedApp(journalist_app) as ins: - app.post(url_for('admin.update_submission_preferences'), - data=form.data, - follow_redirects=True) - ins.assert_message_flashed('Preferences saved.', 'submission-preferences-success') + resp = app.post( + url_for('admin.update_submission_preferences', l=locale), + data=form.data, + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Preferences saved.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'submission-preferences-success') -def test_no_prevent_document_uploads(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_no_prevent_document_uploads(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) @@ -1493,10 +1832,15 @@ def test_no_prevent_document_uploads(journalist_app, test_admin): follow_redirects=True) assert InstanceConfig.get_current().allow_document_uploads is True with InstrumentedApp(journalist_app) as ins: - app.post(url_for('admin.update_submission_preferences'), - follow_redirects=True) - ins.assert_message_flashed('Preferences saved.', 'submission-preferences-success') + resp = app.post( + url_for('admin.update_submission_preferences', l=locale), + follow_redirects=True + ) assert InstanceConfig.get_current().allow_document_uploads is True + assert page_language(resp.data) == language_tag(locale) + msgids = ['Preferences saved.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'submission-preferences-success') def test_prevent_document_uploads_invalid(journalist_app, test_admin): @@ -1533,7 +1877,9 @@ class dummy_current(): assert g.organization_name == "SecureDrop" -def test_orgname_valid_succeeds(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_orgname_valid_succeeds(config, journalist_app, test_admin, locale): test_name = "Walden Inquirer" with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], @@ -1542,14 +1888,21 @@ def test_orgname_valid_succeeds(journalist_app, test_admin): organization_name=test_name) assert InstanceConfig.get_current().organization_name == "SecureDrop" with InstrumentedApp(journalist_app) as ins: - app.post(url_for('admin.update_org_name'), - data=form.data, - follow_redirects=True) - ins.assert_message_flashed('Preferences saved.', 'org-name-success') + resp = app.post( + url_for('admin.update_org_name', l=locale), + data=form.data, + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Preferences saved.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'org-name-success') assert InstanceConfig.get_current().organization_name == test_name -def test_orgname_null_fails(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_orgname_null_fails(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) @@ -1557,14 +1910,21 @@ def test_orgname_null_fails(journalist_app, test_admin): organization_name=None) assert InstanceConfig.get_current().organization_name == "SecureDrop" with InstrumentedApp(journalist_app) as ins: - app.post(url_for('admin.update_org_name'), - data=form.data, - follow_redirects=True) - ins.assert_message_flashed('This field is required.', 'org-name-error') + resp = app.post( + url_for('admin.update_org_name', l=locale), + data=form.data, + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['This field is required.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'org-name-error') assert InstanceConfig.get_current().organization_name == "SecureDrop" -def test_orgname_oversized_fails(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_orgname_oversized_fails(config, journalist_app, test_admin, locale): test_name = "1234567812345678123456781234567812345678123456781234567812345678a" with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], @@ -1572,15 +1932,28 @@ def test_orgname_oversized_fails(journalist_app, test_admin): form = journalist_app_module.forms.OrgNameForm( organization_name=test_name) assert InstanceConfig.get_current().organization_name == "SecureDrop" - with InstrumentedApp(journalist_app) as ins: - app.post(url_for('admin.update_org_name'), - data=form.data, - follow_redirects=True) - ins.assert_message_flashed('Cannot be longer than 64 characters.', 'org-name-error') - assert InstanceConfig.get_current().organization_name == "SecureDrop" + resp = app.post( + url_for('admin.update_org_name', l=locale), + data=form.data, + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Cannot be longer than {num} character.'] + with xfail_untranslated_messages(config, locale, msgids): + assert ( + ngettext( + 'Cannot be longer than {num} character.', + 'Cannot be longer than {num} characters.', + InstanceConfig.MAX_ORG_NAME_LEN + ).format(num=InstanceConfig.MAX_ORG_NAME_LEN) + in resp.data.decode('utf-8') + ) + assert InstanceConfig.get_current().organization_name == "SecureDrop" -def test_orgname_html_escaped(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_orgname_html_escaped(config, journalist_app, test_admin, locale): t_name = '"> ' with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], @@ -1589,10 +1962,15 @@ def test_orgname_html_escaped(journalist_app, test_admin): organization_name=t_name) assert InstanceConfig.get_current().organization_name == "SecureDrop" with InstrumentedApp(journalist_app) as ins: - app.post(url_for('admin.update_org_name'), - data=form.data, - follow_redirects=True) - ins.assert_message_flashed('Preferences saved.', 'org-name-success') + resp = app.post( + url_for('admin.update_org_name', l=locale), + data=form.data, + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Preferences saved.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'org-name-success') assert InstanceConfig.get_current().organization_name == htmlescape(t_name, quote=True) @@ -1609,7 +1987,9 @@ def test_logo_default_available(journalist_app): assert response.status_code == 200 -def test_logo_upload_with_valid_image_succeeds(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_logo_upload_with_valid_image_succeeds(config, journalist_app, test_admin, locale): # Save original logo to restore after test run logo_image_location = os.path.join(config.SECUREDROP_ROOT, "static/i/logo.png") @@ -1630,11 +2010,16 @@ def test_logo_upload_with_valid_image_succeeds(journalist_app, test_admin): logo=(BytesIO(logo_bytes), 'test.png') ) with InstrumentedApp(journalist_app) as ins: - app.post(url_for('admin.manage_config'), - data=form.data, - follow_redirects=True) + resp = app.post( + url_for('admin.manage_config', l=locale), + data=form.data, + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Image updated.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'logo-success') - ins.assert_message_flashed("Image updated.", "logo-success") with journalist_app.test_client() as app: logo_url = journalist_app_module.get_logo_url(journalist_app) assert logo_url.endswith("/static/i/custom_logo.png") @@ -1647,7 +2032,9 @@ def test_logo_upload_with_valid_image_succeeds(journalist_app, test_admin): logo_file.write(original_image) -def test_logo_upload_with_invalid_filetype_fails(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_logo_upload_with_invalid_filetype_fails(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) @@ -1656,16 +2043,21 @@ def test_logo_upload_with_invalid_filetype_fails(journalist_app, test_admin): logo=(BytesIO(b'filedata'), 'bad.exe') ) with InstrumentedApp(journalist_app) as ins: - resp = app.post(url_for('admin.manage_config'), - data=form.data, - follow_redirects=True) - ins.assert_message_flashed("You can only upload PNG image files.", - "logo-error") - text = resp.data.decode('utf-8') - assert "You can only upload PNG image files." in text + resp = app.post( + url_for('admin.manage_config', l=locale), + data=form.data, + follow_redirects=True + ) + + assert page_language(resp.data) == language_tag(locale) + msgids = ["You can only upload PNG image files."] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), "logo-error") -def test_logo_upload_save_fails(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_logo_upload_save_fails(config, journalist_app, test_admin, locale): # Save original logo to restore after test run logo_image_location = os.path.join(config.SECUREDROP_ROOT, "static/i/logo.png") @@ -1685,12 +2077,16 @@ def test_logo_upload_save_fails(journalist_app, test_admin): with InstrumentedApp(journalist_app) as ins: with patch('werkzeug.datastructures.FileStorage.save') as sMock: sMock.side_effect = Exception - app.post(url_for('admin.manage_config'), - data=form.data, - follow_redirects=True) + resp = app.post( + url_for('admin.manage_config', l=locale), + data=form.data, + follow_redirects=True + ) - ins.assert_message_flashed("Unable to process the image file." - " Try another one.", "logo-error") + assert page_language(resp.data) == language_tag(locale) + msgids = ["Unable to process the image file. Try another one."] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), "logo-error") finally: # Restore original image to logo location for subsequent tests with io.open(logo_image_location, 'wb') as logo_file: @@ -1709,7 +2105,9 @@ def test_creation_of_ossec_test_log_event(journalist_app, test_admin, mocker): ) -def test_logo_upload_with_empty_input_field_fails(journalist_app, test_admin): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_logo_upload_with_empty_input_field_fails(config, journalist_app, test_admin, locale): with journalist_app.test_client() as app: _login_user(app, test_admin['username'], test_admin['password'], test_admin['otp_secret']) @@ -1719,12 +2117,16 @@ def test_logo_upload_with_empty_input_field_fails(journalist_app, test_admin): ) with InstrumentedApp(journalist_app) as ins: - resp = app.post(url_for('admin.manage_config'), - data=form.data, - follow_redirects=True) + resp = app.post( + url_for('admin.manage_config', l=locale), + data=form.data, + follow_redirects=True + ) - ins.assert_message_flashed("File required.", "logo-error") - assert 'File required.' in resp.data.decode('utf-8') + assert page_language(resp.data) == language_tag(locale) + msgids = ["File required."] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), "logo-error") def test_admin_page_restriction_http_gets(journalist_app, test_journo): @@ -1785,18 +2187,31 @@ def test_user_authorization_for_posts(journalist_app): assert resp.status_code == 302 -def test_incorrect_current_password_change(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_incorrect_current_password_change(config, journalist_app, test_journo, locale): with journalist_app.test_client() as app: _login_user(app, test_journo['username'], test_journo['password'], test_journo['otp_secret']) - resp = app.post(url_for('account.new_password'), - data=dict(password=VALID_PASSWORD, - token='mocked', - current_password='badpw'), - follow_redirects=True) - - text = resp.data.decode('utf-8') - assert 'Incorrect password or two-factor code' in text + with InstrumentedApp(journalist_app) as ins: + resp = app.post( + url_for('account.new_password', l=locale), + data=dict( + password=VALID_PASSWORD, + token='mocked', + current_password='badpw' + ), + follow_redirects=True) + assert page_language(resp.data) == language_tag(locale) + msgids = [ + 'Incorrect password or two-factor code.', + ( + "Please wait for a new code from your two-factor mobile" + " app or security key before trying again." + ) + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]) + " " + gettext(msgids[1]), "error") # need a journalist app for the app context @@ -1863,7 +2278,9 @@ def test_journalist_reply_view(journalist_app, test_source, test_journo): assert resp.status_code == 302 -def test_too_long_user_password_change(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_too_long_user_password_change(config, journalist_app, test_journo, locale): overly_long_password = VALID_PASSWORD + \ 'a' * (Journalist.MAX_PASSWORD_LEN - len(VALID_PASSWORD) + 1) @@ -1872,56 +2289,96 @@ def test_too_long_user_password_change(journalist_app, test_journo): test_journo['otp_secret']) with InstrumentedApp(journalist_app) as ins: - app.post(url_for('account.new_password'), - data=dict(password=overly_long_password, - token=TOTP(test_journo['otp_secret']).now(), - current_password=test_journo['password']), - follow_redirects=True) + resp = app.post( + url_for('account.new_password', l=locale), + data=dict( + password=overly_long_password, + token=TOTP(test_journo['otp_secret']).now(), + current_password=test_journo['password'] + ), + follow_redirects=True) - ins.assert_message_flashed( - 'The password you submitted is invalid. ' - 'Password not changed.', 'error') + assert page_language(resp.data) == language_tag(locale) + msgids = ['The password you submitted is invalid. Password not changed.'] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed(gettext(msgids[0]), 'error') -def test_valid_user_password_change(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_valid_user_password_change(config, journalist_app, test_journo, locale): with journalist_app.test_client() as app: _login_user(app, test_journo['username'], test_journo['password'], test_journo['otp_secret']) - resp = app.post(url_for('account.new_password'), + resp = app.post(url_for('account.new_password', l=locale), data=dict(password=VALID_PASSWORD_2, token=TOTP(test_journo['otp_secret']).now(), current_password=test_journo['password']), follow_redirects=True) - assert 'Password updated.' in resp.data.decode('utf-8') + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Password updated. Don't forget to save it in your KeePassX database. New password:" + ] + with xfail_untranslated_messages(config, locale, msgids): + assert escape(gettext(msgids[0])) in resp.data.decode('utf-8') + -def test_valid_user_first_last_name_change(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_valid_user_first_last_name_change(config, journalist_app, test_journo, locale): with journalist_app.test_client() as app: _login_user(app, test_journo['username'], test_journo['password'], test_journo['otp_secret']) - resp = app.post(url_for('account.change_name'), - data=dict(first_name='test', - last_name='test'), - follow_redirects=True) + with InstrumentedApp(journalist_app) as ins: + resp = app.post( + url_for('account.change_name', l=locale), + data=dict(first_name='test', last_name='test'), + follow_redirects=True + ) - assert 'Name updated.' in resp.data.decode('utf-8') + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Name updated.", + "Name too long" + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + gettext(msgids[0]).format(gettext("Name too long")), + 'success' + ) -def test_valid_user_invalid_first_last_name_change(journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_valid_user_invalid_first_last_name_change(journalist_app, test_journo, locale): with journalist_app.test_client() as app: overly_long_name = 'a' * (Journalist.MAX_NAME_LEN + 1) _login_user(app, test_journo['username'], test_journo['password'], test_journo['otp_secret']) - resp = app.post(url_for('account.change_name'), - data=dict(first_name=overly_long_name, - last_name=overly_long_name), - follow_redirects=True) - - assert 'Name not updated' in resp.data.decode('utf-8') + with InstrumentedApp(journalist_app) as ins: + resp = app.post( + url_for('account.change_name', l=locale), + data=dict( + first_name=overly_long_name, + last_name=overly_long_name + ), + follow_redirects=True + ) + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "Name not updated: {message}", + "Name too long" + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + gettext(msgids[0]).format(message=gettext("Name too long")), + 'error' + ) def test_regenerate_totp(journalist_app, test_journo): @@ -2735,7 +3192,9 @@ def test_single_source_is_successfully_unstarred(journalist_app, assert not source.star.starred -def test_journalist_session_expiration(config, journalist_app, test_journo): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_journalist_session_expiration(config, journalist_app, test_journo, locale): # set the expiration to ensure we trigger an expiration config.SESSION_EXPIRATION_MINUTES = -1 with journalist_app.test_client() as app: @@ -2749,7 +3208,11 @@ def test_journalist_session_expiration(config, journalist_app, test_journo): ins.assert_redirects(resp, url_for('main.index')) assert 'uid' in session - resp = app.get(url_for('account.edit'), follow_redirects=True) + resp = app.get(url_for('account.edit', l=locale), follow_redirects=True) + # because the session is being cleared when it expires, the + # response should always be in English. + assert page_language(resp.data) == 'en-US' + assert 'You have been logged out due to inactivity.' in resp.data.decode('utf-8') # check that the session was cleared (apart from 'expires' # which is always present and 'csrf_token' which leaks no info) @@ -2757,11 +3220,19 @@ def test_journalist_session_expiration(config, journalist_app, test_journo): session.pop('csrf_token', None) session.pop('locale', None) assert not session, session - assert ('You have been logged out due to inactivity' in - resp.data.decode('utf-8')) -def test_csrf_error_page(journalist_app): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_csrf_error_page(config, journalist_app, locale): + # get the locale into the session + with journalist_app.test_client() as app: + resp = app.get(url_for('main.login', l=locale)) + assert page_language(resp.data) == language_tag(locale) + msgids = ['Show password'] + with xfail_untranslated_messages(config, locale, msgids): + assert gettext(msgids[0]) in resp.data.decode('utf-8') + journalist_app.config['WTF_CSRF_ENABLED'] = True with journalist_app.test_client() as app: with InstrumentedApp(journalist_app) as ins: @@ -2770,8 +3241,10 @@ def test_csrf_error_page(journalist_app): resp = app.post(url_for('main.login'), follow_redirects=True) - text = resp.data.decode('utf-8') - assert 'You have been logged out due to inactivity' in text + # because the session is being cleared when it expires, the + # response should always be in English. + assert page_language(resp.data) == 'en-US' + assert 'You have been logged out due to inactivity.' in resp.data.decode('utf-8') def test_col_process_aborts_with_bad_action(journalist_app, test_journo): diff --git a/securedrop/tests/test_source.py b/securedrop/tests/test_source.py index 0a880a2f08..fec6cde86d 100644 --- a/securedrop/tests/test_source.py +++ b/securedrop/tests/test_source.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# flake8: noqa: E741 import gzip import re import subprocess @@ -9,8 +10,11 @@ from io import BytesIO, StringIO from pathlib import Path +from flaky import flaky from flask import session, escape, url_for, g, request +from flask_babel import gettext from mock import patch, ANY +import pytest import crypto_util import source @@ -26,6 +30,7 @@ from source_app import api as source_app_api from source_app import get_logo_url from .utils.db_helper import new_codename, submit +from .utils.i18n import get_test_locales, language_tag, page_language, xfail_untranslated_messages from .utils.instrument import InstrumentedApp from sdconfig import config @@ -634,13 +639,30 @@ def test_submit_sanitizes_filename(source_app): mtime=0) -def test_tor2web_warning_headers(source_app): +@flaky(rerun_filter=utils.flaky_filter_xfail) +@pytest.mark.parametrize("locale", get_test_locales()) +def test_tor2web_warning_headers(config, source_app, locale): with source_app.test_client() as app: - resp = app.get(url_for('main.index'), - headers=[('X-tor2web', 'encrypted')]) - assert resp.status_code == 200 - text = resp.data.decode('utf-8') - assert "You appear to be using Tor2Web." in text + with InstrumentedApp(app) as ins: + resp = app.get(url_for('main.index', l=locale), headers=[('X-tor2web', 'encrypted')]) + assert resp.status_code == 200 + + assert page_language(resp.data) == language_tag(locale) + msgids = [ + "WARNING:", + "You appear to be using Tor2Web, which does not provide anonymity.", + "Why is this dangerous?", + ] + with xfail_untranslated_messages(config, locale, msgids): + ins.assert_message_flashed( + '{} {} {}'.format( + escape(gettext(msgids[0])), + escape(gettext(msgids[1])), + url_for('info.tor2web_warning'), + escape(gettext(msgids[2])), + ), + 'banner-warning' + ) def test_tor2web_warning(source_app): diff --git a/securedrop/tests/utils/__init__.py b/securedrop/tests/utils/__init__.py index e13df44842..507036d2da 100644 --- a/securedrop/tests/utils/__init__.py +++ b/securedrop/tests/utils/__init__.py @@ -8,6 +8,17 @@ from . import env # noqa +def flaky_filter_xfail(err, *args): + """ + Tell the pytest flaky plugin to not retry XFailed errors. + + If the test is expected to fail, let's not run it again. + """ + return '_pytest.outcomes.XFailed' == '{}.{}'.format( + err[0].__class__.__module__, err[0].__class__.__qualname__ + ) + + def login_user(app, test_user): resp = app.post('/login', data={'username': test_user['username'], diff --git a/securedrop/tests/utils/i18n.py b/securedrop/tests/utils/i18n.py new file mode 100644 index 0000000000..097e6af066 --- /dev/null +++ b/securedrop/tests/utils/i18n.py @@ -0,0 +1,106 @@ +import collections +import contextlib +import functools +import os +from typing import Dict, Generator, Iterable, List, Optional, Tuple + +import pytest +from babel.core import ( + get_locale_identifier, + parse_locale, +) +from babel.messages.catalog import Catalog +from babel.messages.pofile import read_po +from bs4 import BeautifulSoup +from flask_babel import force_locale + +from sdconfig import SDConfig + + +@functools.lru_cache(maxsize=None) +def get_test_locales(default_locale: str = "en_US") -> List[str]: + locales = set(os.environ.get("TEST_LOCALES", "ar en_US").split()) + locales.add(default_locale) + return sorted(list(locales)) + + +@functools.lru_cache(maxsize=None) +def get_plural_tests() -> Dict[str, Tuple[int, ...]]: + return collections.defaultdict( + lambda: (0, 1, 2), + ar=(0, 1, 2, 3, 11, 100), + cs=(1, 2, 5), + ro=(0, 1, 2, 20), + ru=(0, 1, 2, 20), + sk=(0, 1, 2, 5, 10), + zh_Hans=(1,), + zh_Hant=(1,), + ) + + +def language_tag(locale: str) -> str: + """ + Returns a BCP47/RFC5646 language tag for the locale. + + For example, it will convert "fr_FR" to "fr-FR". + """ + return get_locale_identifier(parse_locale(locale), sep="-") + + +@functools.lru_cache(maxsize=None) +def message_catalog(config: SDConfig, locale: str) -> Catalog: + """ + Returns the gettext message catalog for the given locale. + + With the catalog, tests can check whether a gettext call returned + an actual translation or merely the result of falling back to the + default locale. + + >>> german = message_catalog(config, 'de_DE') + >>> m = german.get("a string that has been added to the catalog but not translated") + >>> m.string + '' + >>> german.get("Password").string + 'Passwort' + """ + return read_po(open(str(config.TRANSLATION_DIRS / locale / "LC_MESSAGES/messages.po"))) + + +def page_language(page_text: str) -> Optional[str]: + """ + Returns the "lang" attribute of the page's "html" element. + """ + soup = BeautifulSoup(page_text, "html.parser") + return soup.find("html").get("lang") + + +@contextlib.contextmanager +def xfail_untranslated_messages(config: SDConfig, locale: str, msgids: Iterable[str]) -> Generator: + """ + Trigger pytest.xfail for untranslated strings. + + Given a list of gettext message IDs (strings marked for + translation with gettext or ngettext in source code) used in this + context manager's block, check that each has been translated in + `locale`. Call pytest.xfail if any has not. + + Without this, to detect when gettext fell back to English, we'd + have to hard-code the expected translations, which has obvious + maintenance problems. You also can't just check that the result of + a gettext call isn't the source string, because some translations + are the same. + """ + with force_locale(locale): + if locale != "en_US": + catalog = message_catalog(config, locale) + for msgid in msgids: + m = catalog.get(msgid) + if not m: + pytest.xfail( + "locale {} message catalog lacks msgid: {}".format(locale, msgid) + ) + if not m.string: + pytest.xfail( + "locale {} has no translation for msgid: {}".format(locale, msgid) + ) + yield From 4340070cbb8484d5fe37f99eae78c4a98bb4bacc Mon Sep 17 00:00:00 2001 From: John Hensley Date: Wed, 31 Mar 2021 10:35:30 -0400 Subject: [PATCH 3/3] Update translation message catalogs for source string changes --- securedrop/models.py | 2 +- .../translations/ar/LC_MESSAGES/messages.po | 80 ++++++++-- .../translations/ca/LC_MESSAGES/messages.po | 75 ++++++++-- .../translations/cs/LC_MESSAGES/messages.po | 73 +++++++-- .../de_DE/LC_MESSAGES/messages.po | 76 ++++++++-- .../translations/el/LC_MESSAGES/messages.po | 75 ++++++++-- .../es_ES/LC_MESSAGES/messages.po | 76 ++++++++-- .../fr_FR/LC_MESSAGES/messages.po | 76 ++++++++-- .../translations/hi/LC_MESSAGES/messages.po | 75 ++++++++-- .../translations/is/LC_MESSAGES/messages.po | 75 ++++++++-- .../it_IT/LC_MESSAGES/messages.po | 76 ++++++++-- securedrop/translations/messages.pot | 141 +++++++++++++----- .../nb_NO/LC_MESSAGES/messages.po | 76 ++++++++-- .../translations/nl/LC_MESSAGES/messages.po | 76 ++++++++-- .../pt_BR/LC_MESSAGES/messages.po | 79 ++++++++-- .../translations/ro/LC_MESSAGES/messages.po | 76 ++++++++-- .../translations/ru/LC_MESSAGES/messages.po | 76 ++++++++-- .../translations/sk/LC_MESSAGES/messages.po | 73 +++++++-- .../translations/sv/LC_MESSAGES/messages.po | 75 ++++++++-- .../translations/tr/LC_MESSAGES/messages.po | 75 ++++++++-- .../zh_Hans/LC_MESSAGES/messages.po | 74 +++++++-- .../zh_Hant/LC_MESSAGES/messages.po | 79 ++++++++-- 22 files changed, 1385 insertions(+), 274 deletions(-) diff --git a/securedrop/models.py b/securedrop/models.py index 5820349e86..cfddd930d5 100644 --- a/securedrop/models.py +++ b/securedrop/models.py @@ -494,7 +494,7 @@ def check_username_acceptable(cls, username: str) -> None: if len(username) < cls.MIN_USERNAME_LEN: raise InvalidUsernameException( ngettext( - 'Must be at least one character long.', + 'Must be at least {num} character long.', 'Must be at least {num} characters long.', cls.MIN_USERNAME_LEN ).format(num=cls.MIN_USERNAME_LEN) diff --git a/securedrop/translations/ar/LC_MESSAGES/messages.po b/securedrop/translations/ar/LC_MESSAGES/messages.po index 41ff1912ad..616939504f 100644 --- a/securedrop/translations/ar/LC_MESSAGES/messages.po +++ b/securedrop/translations/ar/LC_MESSAGES/messages.po @@ -4,7 +4,45 @@ # FIRST AUTHOR , 2017. # Ahmad Gharbeia أحمد غربية , 2017. msgid "" -msgstr "Project-Id-Version: SecureDrop 0.3.12\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPOT-Creation-Date: 2017-09-02 07:28+0000\nPO-Revision-Date: 2021-03-09 19:37+0000\nLast-Translator: Layla Taha \nLanguage-Team: Arabic \nLanguage: ar\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.3.12\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"POT-Creation-Date: 2017-09-02 07:28+0000\n" +"PO-Revision-Date: 2021-03-09 19:37+0000\n" +"Last-Translator: Layla Taha \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "إن نص الرسالة طويلٌ جداً." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "يجب ألا يحوي أي حرف." +msgstr[1] "يجب أن يحوي حرفًا واحدًا على الأقل." +msgstr[2] "يجب أن يحوي حرفين على الأقل." +msgstr[3] "يجب أن يحوي {num} حروف على الأقل." +msgstr[4] "يجب أن يحوي {num} حرفًا على الأقل." +msgstr[5] "يجب أن يحوي {num} حرفًا على الأقل." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "اسم المستخدم غير صالح لأن هذا الاسم محجوز للاستخدام الداخلي في البرنامج." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "تغيير اسم المستخدم" msgid "{time} ago" msgstr "{time} مضت" @@ -30,6 +68,10 @@ msgstr "هناك مشكلة في التأكد من رمز التسجيل في خ msgid "Image updated." msgstr "تم تحديث الصورة بنجاح." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "تم حفظ الاختيارات." @@ -42,7 +84,13 @@ msgstr "لم ينجح تحديث اسم المنظمة." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "تعذر إنشاء حساب المستخدم بسبب عطل في توليد كلمة السر. يرجى إعادة المحاولة." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "الاسم {user} سبق تخصيصه لمستخدم آخر." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +99,10 @@ msgstr "حدث عطل خلال حفظ الحساب في قاعدة البيان msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "لقد تم التحقق بنجاح من رمز الاستيثاق في خطوتين للمستخدم \"{user}\"." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "لم يتم تحديث الإسم: {}" msgid "Deleted user '{user}'." @@ -107,9 +158,6 @@ msgstr[3] "هذا الحقل لا يقبل أكثر من {num} حروف." msgstr[4] "هذا الحقل لا يقبل أكثر من {num} حرفًا." msgstr[5] "هذا الحقل لا يقبل أكثر من {num} حرفًا." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "اسم المستخدم غير صالح لأن هذا الاسم محجوز للاستخدام الداخلي في البرنامج." - msgid "This field is required." msgstr "هذا الحقل مطلوب." @@ -140,7 +188,9 @@ msgstr "لا توجد رسائل غير مقروءة من هذا المصدر." msgid "Account updated." msgstr "تم تحديث الحساب." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "فشل الولوج." msgid "Please wait at least {num} second before logging in again." @@ -677,8 +727,14 @@ msgstr "إظهار كلمة السر" msgid "LOG IN" msgstr "لِج" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "تنبيه: يبدو أنك تستخدم خدمة Tor2Web. يجب الانتباه إلى أنّ هذه الوسيلة لا تحقق المجهولية للمستخدم. ما الخطر في ذلك؟" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "يجب أن يتراوح طول الحقل بين محرف واحد و {max_codename_len} محرفا." @@ -981,6 +1037,9 @@ msgstr "هام: إذا كنت ترغب في البقاء مجه msgid "Back to submission page" msgstr "الرجوع إلى صفحة الإيداع" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "تنبيه: يبدو أنك تستخدم خدمة Tor2Web. يجب الانتباه إلى أنّ هذه الوسيلة لا تحقق المجهولية للمستخدم. ما الخطر في ذلك؟" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "المجموعة {source_name} حُذفت." @@ -1107,9 +1166,6 @@ msgstr "الرجوع إلى صفحة الإيداع" #~ msgid "Change Username & Admin Status" #~ msgstr "تغيير اسم المستخدم & الصفة الإدارية" -#~ msgid "Change username" -#~ msgstr "تغيير اسم المستخدم" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "يستخدم SecureDrop كلمات سرّ مولّدة تلقائيا بأسلوب دَيْسوِير." diff --git a/securedrop/translations/ca/LC_MESSAGES/messages.po b/securedrop/translations/ca/LC_MESSAGES/messages.po index dfc5b482d8..80338fdfca 100644 --- a/securedrop/translations/ca/LC_MESSAGES/messages.po +++ b/securedrop/translations/ca/LC_MESSAGES/messages.po @@ -4,7 +4,40 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.5-rc4\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-04 17:37+0000\nLast-Translator: Benet (BennyBeat) R. i Camps \nLanguage-Team: Catalan \nLanguage: ca\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.5-rc4\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-04 17:37+0000\n" +"Last-Translator: Benet (BennyBeat) R. i Camps \n" +"Language-Team: Catalan \n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "El missatge de text és massa llarg." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Cal que tingui com a mínim {num} caràcter." +msgstr[1] "Cal que tingui com a mínim {num} caràcters." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Aquest nom d'usuari no és vàlid perquè és reservat per a ús intern del programari." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Canvia el nom d'usuari" msgid "{time} ago" msgstr "fa {time}" @@ -30,6 +63,10 @@ msgstr "S'ha produït un error amb la contrasenya generada automàticament. No s msgid "Image updated." msgstr "S’ha actualitzat la imatge." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "S'han desat les preferències." @@ -42,7 +79,13 @@ msgstr "No s'ha pogut actualitzar el nom de l'organització." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "S'ha produït un error amb la contrasenya generada automàticament. No s'ha creat l'usuari. Torneu a provar." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "El nom d’usuari «{user}» ja existeix." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +94,10 @@ msgstr "S'ha produït un error en intentar desar aquest usuari en la base de dad msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "El codi de factor doble per a «{user}» s'ha verificat correctament." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "El nom no s'ha actualitzat: {}" msgid "Deleted user '{user}'." @@ -95,9 +141,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "No pot tenir més d'{num} caràcter." msgstr[1] "No pot tenir més de {num} caràcters." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Aquest nom d'usuari no és vàlid perquè és reservat per a ús intern del programari." - msgid "This field is required." msgstr "Aquest camp és obligatori." @@ -128,7 +171,9 @@ msgstr "Aquesta font no té cap enviament sense llegir." msgid "Account updated." msgstr "S’ha actualitzat el compte." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Ha fallat l’inici de la sessió." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +682,14 @@ msgstr "Mostra la contrasenya" msgid "LOG IN" msgstr "INICIA LA SESSIÓ" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "ATENCIÓ:  Sembla que feu servir el Tor2Web. Això  no  proporciona anonimat. Per què és perillós?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "El camp ha de tenir entre 1 i {max_codename_len} caràcters." @@ -941,6 +992,9 @@ msgstr "Important: si voleu romandre anònimament, noWARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "ATENCIÓ:  Sembla que feu servir el Tor2Web. Això  no  proporciona anonimat. Per què és perillós?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "S’ha suprimit la col·lecció de {source_name}." @@ -1056,9 +1110,6 @@ msgstr "Vés enrere a la pàgina d'enviaments" #~ msgid "Change Username & Admin Status" #~ msgstr "Canvia el nom d'usuari i l'estat d'administració" -#~ msgid "Change username" -#~ msgstr "Canvia el nom d'usuari" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "El SecureDrop ara usa contrasenyes generades automàticament amb el mètode «diceware»." diff --git a/securedrop/translations/cs/LC_MESSAGES/messages.po b/securedrop/translations/cs/LC_MESSAGES/messages.po index dc79bf04d4..b0f4f6f81d 100644 --- a/securedrop/translations/cs/LC_MESSAGES/messages.po +++ b/securedrop/translations/cs/LC_MESSAGES/messages.po @@ -4,7 +4,41 @@ # FIRST AUTHOR , 2019. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.14.0~rc1\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-03 17:37+0000\nLast-Translator: Honza Cibulka \nLanguage-Team: Czech \nLanguage: cs\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.5.1\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.14.0~rc1\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-03 17:37+0000\n" +"Last-Translator: Honza Cibulka \n" +"Language-Team: Czech \n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.5.1\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Zpráva je příliš dlouhá." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Uživatelské jméno musí mít alespoň {num} znak." +msgstr[1] "Uživatelské jméno musí mít alespoň {num} znaky." +msgstr[2] "Uživatelské jméno musí mít alespoň {num} znaků." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Toto uživatelské jméno je neplatné, protože je rezervováno pro interní použití." + +#, fuzzy +#| msgid "Invalid input." +msgid "Invalid username" +msgstr "Neplatný vstup." msgid "{time} ago" msgstr "před {time}" @@ -30,6 +64,10 @@ msgstr "Při ověřování dvoufaktorového kódu došlo k problému. Zkuste to msgid "Image updated." msgstr "Obrázek byl aktualizován." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Preference uložené." @@ -42,7 +80,13 @@ msgstr "Nepodařilo se aktualizovat jméno organizace." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Došlo k chybě s automaticky generovaným heslem. Uživatelský účet nebyl vytvořen. Zkuste to prosím znovu." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Uživatelské jméno „{user}“ už používá někdo jiný." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +95,10 @@ msgstr "Došlo k chybě při ukládání tohoto uživatele do databáze. Kontakt msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Dvoufaktorový kód pro uživatele \"{user}\" byl ověřen." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Jméno nebylo aktualizováno: {}" msgid "Deleted user '{user}'." @@ -98,9 +145,6 @@ msgstr[0] "Uživatelské jméno nemůže obsahovat více než {num} znak." msgstr[1] "Uživatelské jméno nemůže obsahovat více než {num} znaky." msgstr[2] "Uživatelské jméno nemůže obsahovat více než {num} znaků." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Toto uživatelské jméno je neplatné, protože je rezervováno pro interní použití." - msgid "This field is required." msgstr "Toto pole je povinné." @@ -131,7 +175,9 @@ msgstr "Žádné nepřečtené příspěvky z tohoto zdroje." msgid "Account updated." msgstr "Uživatelský účet aktualizován." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Přihlášení selhalo." msgid "Please wait at least {num} second before logging in again." @@ -647,8 +693,14 @@ msgstr "Zobrazit heslo" msgid "LOG IN" msgstr "PŘIHLÁSIT SE" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "VAROVÁNÍ:  Vypadá to, že používáte Tor2Web. To  neposkytuje anonymitu. Proč je to nebezpečné?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Pole musí mít mezi 1 a {max_codename_len} znaky." @@ -951,6 +1003,9 @@ msgstr "Důležité: Chcete-li zůstat v anonymitě, ne msgid "Back to submission page" msgstr "Zpátky na stránku podání" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "VAROVÁNÍ:  Vypadá to, že používáte Tor2Web. To  neposkytuje anonymitu. Proč je to nebezpečné?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "Sada {source_name} smazána." diff --git a/securedrop/translations/de_DE/LC_MESSAGES/messages.po b/securedrop/translations/de_DE/LC_MESSAGES/messages.po index ff8eaf2b2a..f189b1ea30 100644 --- a/securedrop/translations/de_DE/LC_MESSAGES/messages.po +++ b/securedrop/translations/de_DE/LC_MESSAGES/messages.po @@ -1,5 +1,39 @@ msgid "" -msgstr "Project-Id-Version: SecureDrop \\'0.3.5\\'\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPOT-Creation-Date: 2017-09-02 07:28+0000\nPO-Revision-Date: 2021-03-10 22:37+0000\nLast-Translator: kwadronaut \nLanguage-Team: German \nLanguage: de_DE\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop \\'0.3.5\\'\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"POT-Creation-Date: 2017-09-02 07:28+0000\n" +"PO-Revision-Date: 2021-03-10 22:37+0000\n" +"Last-Translator: kwadronaut \n" +"Language-Team: German \n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Nachricht zu lang." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Muss mindestens {num} Zeichen lang sein." +msgstr[1] "Muss mindestens {num} Zeichen lang sein." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Dieser Benutzername ist ungültig, da er für interne Nutzung durch die Software reserviert ist." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Benutzername ändern" msgid "{time} ago" msgstr "vor {time}" @@ -25,6 +59,10 @@ msgstr "Es gab ein Problem bei der Überprüfung des Zwei-Faktor-Codes. Bitte ve msgid "Image updated." msgstr "Bild aktualisiert." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Einstellungen gespeichert." @@ -37,7 +75,13 @@ msgstr "Organisationsname konnte nicht aktualisiert werden." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Es gab einen Fehler mit dem automatisch generierten Passwort. Der Benutzer wurde nicht erstellt. Bitte versuchen Sie es erneut." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Benutzername \"{user}\" ist bereits vergeben." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -46,7 +90,10 @@ msgstr "Beim Speichern dieses Benutzers in der Datenbank ist ein Fehler aufgetre msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Der Zwei-Faktor-Code für Benutzer \"{user}\" wurde erfolgreich überprüft." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Nicht aktualisierter Name: {}" msgid "Deleted user '{user}'." @@ -90,9 +137,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Darf nicht länger als {num} Zeichen sein." msgstr[1] "Darf nicht länger als {num} Zeichen sein." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Dieser Benutzername ist ungültig, da er für interne Nutzung durch die Software reserviert ist." - msgid "This field is required." msgstr "Dieses Feld ist erforderlich." @@ -123,7 +167,9 @@ msgstr "Keine ungelesenen Einreichungen für diese Quelle." msgid "Account updated." msgstr "Konto aktualisiert." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Anmeldung fehlgeschlagen." msgid "Please wait at least {num} second before logging in again." @@ -632,8 +678,14 @@ msgstr "Passwort anzeigen" msgid "LOG IN" msgstr "ANMELDEN" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "ACHTUNG:  Es scheint, dass Sie Tor2Web benutzen. Das garantiert  keine Anonymität.  Warum ist das gefährlich?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Feld muss zwischen 1 und {max_codename_len} Zeichen lang sein." @@ -936,6 +988,9 @@ msgstr "Wichtig: Verwenden Sie nicht GPG, um d msgid "Back to submission page" msgstr "Zurück zur Einreichungsseite" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "ACHTUNG:  Es scheint, dass Sie Tor2Web benutzen. Das garantiert  keine Anonymität.  Warum ist das gefährlich?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "{source_name}s Sammlung wurde gelöscht." @@ -1054,9 +1109,6 @@ msgstr "Zurück zur Einreichungsseite" #~ msgid "Change Username & Admin Status" #~ msgstr "Benutzernamen & Adminstatus ändern" -#~ msgid "Change username" -#~ msgstr "Benutzername ändern" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop benutzt jetzt automatisch generierte diceware-Passwörter." diff --git a/securedrop/translations/el/LC_MESSAGES/messages.po b/securedrop/translations/el/LC_MESSAGES/messages.po index a76d42556f..5035e34700 100644 --- a/securedrop/translations/el/LC_MESSAGES/messages.po +++ b/securedrop/translations/el/LC_MESSAGES/messages.po @@ -4,7 +4,40 @@ # FIRST AUTHOR , 2018. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.8.0~rc1\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-04 17:37+0000\nLast-Translator: Dimitris Maroulidis \nLanguage-Team: Greek \nLanguage: el\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.5.1\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.8.0~rc1\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-04 17:37+0000\n" +"Last-Translator: Dimitris Maroulidis \n" +"Language-Team: Greek \n" +"Language: el\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.5.1\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Το κείμενο του μηνύματος είναι πολύ μεγάλο." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Πρέπει να έχει τουλάχιστον {num} χαρακτήρα." +msgstr[1] "Πρέπει να έχει τουλάχιστον {num} χαρακτήρες." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Αυτό το όνομα χρήστη δεν είναι έγκυρο επειδή είναι κατοχυρωμένο για εσωτερική χρήση του λογισμικού." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Αλλαγή ονόματος χρήστη" msgid "{time} ago" msgstr "πριν από {time}" @@ -30,6 +63,10 @@ msgstr "Υπήρξε πρόβλημα με την επαλήθευση του κ msgid "Image updated." msgstr "Η φωτογραφία ενημερώθηκε." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Οι προτιμήσεις αποθηκεύτηκαν." @@ -42,7 +79,13 @@ msgstr "Αποτυχία ενημέρωσης ονόματος οργανισμ msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Υπήρξε κάποιο σφάλμα με τον αυτόματα παραγόμενο κωδικό. Ο χρήστης δεν δημιουργήθηκε. Παρακαλώ δοκιμάστε ξανά." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Το όνομα χρήστη «{user}» χρησιμοποιείται ήδη." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +94,10 @@ msgstr "Υπήρξε σφάλμα με την αποθήκευση αυτού τ msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Ο κωδικός πιστοποίησης δυο παραγόντων για τον/τη χρήστη \"{user}\" επαληθεύτηκε επιτυχώς." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Το όνομα δεν ενημερώθηκε: {}" msgid "Deleted user '{user}'." @@ -95,9 +141,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Δεν μπορεί να περιέχει παραπάνω από {num} χαρακτήρα." msgstr[1] "Δεν μπορεί να περιέχει παραπάνω από {num} χαρακτήρες." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Αυτό το όνομα χρήστη δεν είναι έγκυρο επειδή είναι κατοχυρωμένο για εσωτερική χρήση του λογισμικού." - msgid "This field is required." msgstr "Αυτό το πεδίο είναι υποχρεωτικό." @@ -128,7 +171,9 @@ msgstr "Δεν υπάρχουν μη αναγνωσμένες υποβολές msgid "Account updated." msgstr "Ο λογαριασμός ενημερώθηκε." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Η σύνδεση απέτυχε." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +682,14 @@ msgstr "Προβολή κωδικού" msgid "LOG IN" msgstr "ΣΥΝΔΕΣΗ" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "ΠΡΟΣΟΧΗ:  Φαίνεται πως χρησιμοποιείτε την υπηρεσία Tor2Web. Αυτή  δεν  παρέχει ανωνυμία. Γιατί είναι επικίνδυνο αυτό;" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Το πεδίο πρέπει να περιέχει μεταξύ 1 και {max_codename_len} χαρακτήρες." @@ -941,6 +992,9 @@ msgstr "Σημαντικό: Αν επιθυμείτε να δι msgid "Back to submission page" msgstr "Πίσω στη σελίδα υποβολών" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "ΠΡΟΣΟΧΗ:  Φαίνεται πως χρησιμοποιείτε την υπηρεσία Tor2Web. Αυτή  δεν  παρέχει ανωνυμία. Γιατί είναι επικίνδυνο αυτό;" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "Η συλλογή του/της {source_name} διαγράφηκε." @@ -1056,9 +1110,6 @@ msgstr "Πίσω στη σελίδα υποβολών" #~ msgid "Change Username & Admin Status" #~ msgstr "Αλλαγή ονόματος χρήστη & Κατάστασης διαχειριστή" -#~ msgid "Change username" -#~ msgstr "Αλλαγή ονόματος χρήστη" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "Το SecureDrop πλέον χρησιμοποιεί αυτόματα παραγόμενους κωδικούς τύπου diceware." diff --git a/securedrop/translations/es_ES/LC_MESSAGES/messages.po b/securedrop/translations/es_ES/LC_MESSAGES/messages.po index 294eef70f6..aefb3c179b 100644 --- a/securedrop/translations/es_ES/LC_MESSAGES/messages.po +++ b/securedrop/translations/es_ES/LC_MESSAGES/messages.po @@ -4,7 +4,41 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.3.12\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPOT-Creation-Date: 2017-09-02 07:28+0000\nPO-Revision-Date: 2021-03-09 00:37+0000\nLast-Translator: Gonzalo Bulnes Guilpain \nLanguage-Team: Spanish \nLanguage: es_ES\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.3.12\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"POT-Creation-Date: 2017-09-02 07:28+0000\n" +"PO-Revision-Date: 2021-03-09 00:37+0000\n" +"Last-Translator: Gonzalo Bulnes Guilpain \n" +"Language-Team: Spanish \n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Texto del mensaje demasiado largo." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Debe tener por lo menos {num} carácter de longitud." +msgstr[1] "Debe tener por lo menos {num} caracteres de longitud." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Este nombre de usuario es inválido porque está reservado para uso interno por el software." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Cambiar nombre de usuario" msgid "{time} ago" msgstr "hace {time}" @@ -30,6 +64,10 @@ msgstr "Hubo un problema al verificar el código de dos factores. Por favor int msgid "Image updated." msgstr "Imagen actualizada." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Preferencias guardadas." @@ -42,7 +80,13 @@ msgstr "Fallo al actualizar el nombre de la organización." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Hubo un error con la contraseña generada automáticamente. El usuario no fue creado. Por favor intenta nuevamente." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "El nombre de usuario \"{user}\" ya se encuentra en uso." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +95,10 @@ msgstr "Ocurrió un error al guardar este usuario en la base de datos. Por favor msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "El código de autenticación de dos factores para el usuario “{user}” fue verificado exitosamente." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Nombre no actualizado: {}" msgid "Deleted user '{user}'." @@ -95,9 +142,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "No puede tener más que {num} carácter." msgstr[1] "No puede tener más que {num} caracteres." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Este nombre de usuario es inválido porque está reservado para uso interno por el software." - msgid "This field is required." msgstr "Este campo es obligatorio." @@ -128,7 +172,9 @@ msgstr "No hay envíos sin leer para esta fuente." msgid "Account updated." msgstr "Cuenta actualizada." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "El inicio de sesión falló." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +683,14 @@ msgstr "Mostrar contraseña" msgid "LOG IN" msgstr "INICIAR SESIÓN" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "ADVERTENCIA:  Pareces estar usando Tor2Web. Esto   no   proporciona anonimato. ¿Por qué es peligroso? " +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Este campo debe tener de 1 a {max_codename_len} caracteres de longitud." @@ -941,6 +993,9 @@ msgstr "Importante: Si deseas permanecer anónimo, noWARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "ADVERTENCIA:  Pareces estar usando Tor2Web. Esto   no   proporciona anonimato. ¿Por qué es peligroso? " + #~ msgid "{source_name}'s collection deleted." #~ msgstr "Colección de {source_name} eliminada." @@ -1062,9 +1117,6 @@ msgstr "Regresar a la página de envío" #~ msgid "Change Username & Admin Status" #~ msgstr "Cambiar nombre de usuario y privilegio de administrador" -#~ msgid "Change username" -#~ msgstr "Cambiar nombre de usuario" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop ahora usa contraseñas automáticamente generadas con el método diceware." diff --git a/securedrop/translations/fr_FR/LC_MESSAGES/messages.po b/securedrop/translations/fr_FR/LC_MESSAGES/messages.po index 1a0fcc13dd..26098e7de1 100644 --- a/securedrop/translations/fr_FR/LC_MESSAGES/messages.po +++ b/securedrop/translations/fr_FR/LC_MESSAGES/messages.po @@ -4,7 +4,41 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.3.12\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPOT-Creation-Date: 2017-09-02 07:28+0000\nPO-Revision-Date: 2021-03-01 22:37+0000\nLast-Translator: AO Localization Lab \nLanguage-Team: French \nLanguage: fr_FR\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n > 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.3.12\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"POT-Creation-Date: 2017-09-02 07:28+0000\n" +"PO-Revision-Date: 2021-03-01 22:37+0000\n" +"Last-Translator: AO Localization Lab \n" +"Language-Team: French \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Le texte du message est trop long." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Doit comporter au moins {num} caractère." +msgstr[1] "Doit comporter au moins {num} caractères." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Ce nom d’utilisateur est invalide, car il est réservé pour une utilisation interne par le logiciel." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Changer le nom d’utilisateur" msgid "{time} ago" msgstr "il y a {time}" @@ -30,6 +64,10 @@ msgstr "Un problème est survenu lors de la vérification du code A2F. Veuillez msgid "Image updated." msgstr "L’image a été mise à jour." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Les préférences ont été enregistrées." @@ -42,7 +80,13 @@ msgstr "Échec de mise à jour du nom de l’organisme." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "La génération automatique du mot de passe a produit une erreur. L’utilisateur n’a pas été créé. Veuillez réessayer." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Le nom d’utilisateur « {user} » est déjà utilisé." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +95,10 @@ msgstr "Une erreur s’est produite lors de l’enregistrement de cet utilisateu msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Le code A2F de l’utilisateur « {user} » a été vérifié avec succès." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Le nom n’a pas été mis à jour : {}" msgid "Deleted user '{user}'." @@ -95,9 +142,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Ne peut pas dépasser {num} caractère." msgstr[1] "Ne peut pas dépasser {num} caractères." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Ce nom d’utilisateur est invalide, car il est réservé pour une utilisation interne par le logiciel." - msgid "This field is required." msgstr "Ce champ est exigé." @@ -128,7 +172,9 @@ msgstr "Aucun envoi non lu pour cette source." msgid "Account updated." msgstr "Le compte a été mis à jour." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Échec de connexion." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +683,14 @@ msgstr "Afficher le mot de passe" msgid "LOG IN" msgstr "CONNEXION" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "AVERTISSEMENT :  il semble que vous utilisez Tor2Web. Cela ne garantit pas votre anonymat. Pourquoi est-ce dangereux ?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Le champ doit comporter entre 1 et {max_codename_len} caractères." @@ -941,6 +993,9 @@ msgstr "Important : Si vous souhaitez rester anonyme, msgid "Back to submission page" msgstr "Revenir à la page d'envoi" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "AVERTISSEMENT :  il semble que vous utilisez Tor2Web. Cela ne garantit pas votre anonymat. Pourquoi est-ce dangereux ?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "La collection de {source_name} a été supprimée." @@ -1059,9 +1114,6 @@ msgstr "Revenir à la page d'envoi" #~ msgid "Change Username & Admin Status" #~ msgstr "Changer le nom d’utilisateur et les droits d’administration" -#~ msgid "Change username" -#~ msgstr "Changer le nom d’utilisateur" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop utilise désormais des mots de passe « diceware » générés automatiquement." diff --git a/securedrop/translations/hi/LC_MESSAGES/messages.po b/securedrop/translations/hi/LC_MESSAGES/messages.po index 22e16b1b63..551d2fc53f 100644 --- a/securedrop/translations/hi/LC_MESSAGES/messages.po +++ b/securedrop/translations/hi/LC_MESSAGES/messages.po @@ -4,7 +4,40 @@ # FIRST AUTHOR , 2018. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.6~rc2\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-08 22:37+0000\nLast-Translator: John Hensley \nLanguage-Team: Hindi \nLanguage: hi\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.5.1\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.6~rc2\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-08 22:37+0000\n" +"Last-Translator: John Hensley \n" +"Language-Team: Hindi \n" +"Language: hi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.5.1\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "संदेश पाठ बहुत लंबा है |" + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "कम से कम {num} वर्ण लंबा होना चाहिए।" +msgstr[1] "कम से कम {num} अक्षर लंबा होनी चाहिए।" + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "यह उपयोगकर्ता नाम अमान्य है क्योंकि यह सॉफ़्टवेयर द्वारा आंतरिक उपयोग के लिए आरक्षित है।" + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "उपयोगकर्ता नाम परिवर्तन करें" msgid "{time} ago" msgstr "{time} पहले" @@ -30,6 +63,10 @@ msgstr "दो कारक कोड की पुष्टि करने म msgid "Image updated." msgstr "छवि अपडेट की गई है।" +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "प्राथमिकताएं सेव हो गयी।" @@ -42,7 +79,13 @@ msgstr "संगठन का नाम अपडेट करने में msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "ऑटोग्रेनेरेटेड पासवर्ड के साथ एक त्रुटि थी| उपयोगकर्ता नहीं बना| कृपया पुन: प्रयास करें।" -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "उपयोगकर्ता नाम \"{user}\" पहले से ही लिया जा चूका है।" msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +94,10 @@ msgstr "इस उपयोगकर्ता को डेटाबेस म msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "उपयोगकर्ता \"{user}\" के लिए दो-कारक कोड सफलतापूर्वक सत्यापित किया गया था।" -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "नाम अपडेट नही हुआ: {}" msgid "Deleted user '{user}'." @@ -95,9 +141,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "{num} वर्ण से अधिक लंबा नहीं होना चाहिए।" msgstr[1] "{num} अक्षर से लंबा नहीं होना चाहिए।" -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "यह उपयोगकर्ता नाम अमान्य है क्योंकि यह सॉफ़्टवेयर द्वारा आंतरिक उपयोग के लिए आरक्षित है।" - msgid "This field is required." msgstr "यह फ़ील्ड आवश्यक है।" @@ -128,7 +171,9 @@ msgstr "इस स्रोत के लिए कोई अपठित सब msgid "Account updated." msgstr "खाता अपडेट किया गया।" -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "लॉगिन विफल।" msgid "Please wait at least {num} second before logging in again." @@ -637,8 +682,14 @@ msgstr "पासवर्ड दिखाएं" msgid "LOG IN" msgstr "लॉग इन" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "चेतावनी:  आप Tor2Web का उपयोग करते हुए दिखाई देते हैं| ये गुमनामी सुनिश्चित  नहीं   करता| यह घातक क्यों है?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "फ़ील्ड 1 और {max_codename_len} वर्णों के बीच लंबा होना चाहिए।" @@ -941,6 +992,9 @@ msgstr "महत्वपूर्ण: यदि आप गु msgid "Back to submission page" msgstr "सबमिशन पृष्ठ पर वापस जाएं" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "चेतावनी:  आप Tor2Web का उपयोग करते हुए दिखाई देते हैं| ये गुमनामी सुनिश्चित  नहीं   करता| यह घातक क्यों है?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "{source_name} का संग्रह हटाया गया|" @@ -1056,9 +1110,6 @@ msgstr "सबमिशन पृष्ठ पर वापस जाएं" #~ msgid "Change Username & Admin Status" #~ msgstr "उपयोगकर्ता नाम परिवर्तन करें & व्यवस्थापक स्थिति" -#~ msgid "Change username" -#~ msgstr "उपयोगकर्ता नाम परिवर्तन करें" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop अब स्वचालित रूप से बनाऐ गए डिसवेयर पासवर्ड का उपयोग करता है|" diff --git a/securedrop/translations/is/LC_MESSAGES/messages.po b/securedrop/translations/is/LC_MESSAGES/messages.po index 9c7c383742..47037a4266 100644 --- a/securedrop/translations/is/LC_MESSAGES/messages.po +++ b/securedrop/translations/is/LC_MESSAGES/messages.po @@ -4,7 +4,40 @@ # FIRST AUTHOR , 2019. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.12.0~rc1\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-02 16:37+0000\nLast-Translator: Oktavia \nLanguage-Team: Icelandic \nLanguage: is\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n % 10 != 1 || n % 100 == 11;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.5.1\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.12.0~rc1\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-02 16:37+0000\n" +"Last-Translator: Oktavia \n" +"Language-Team: Icelandic \n" +"Language: is\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n % 10 != 1 || n % 100 == 11;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.5.1\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Texti skilaboðanna er of langur." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Verður að vera a.m.k. {num} stafur að lengd." +msgstr[1] "Verður að vera a.m.k. {num} stafa langt." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Þetta notandanafn er ekki gilt því það er frátekið til innri nota í hugbúnaðnum." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Breyta notandanafni" msgid "{time} ago" msgstr "Fyrir {time} síðan" @@ -30,6 +63,10 @@ msgstr "Það kom upp villa við staðfestingu tvíþátta-kóðans. Reyndu aftu msgid "Image updated." msgstr "Mynd var uppfærð." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Kjörstillingar vistaðar." @@ -42,7 +79,13 @@ msgstr "Mistókst að uppfæra heiti stofnunar/félags." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Það kom upp villa með sjálfvirkt útbúna lykilorðið. Notandi var ekki búinn til. Vinsamlegast reyndu aftur." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Notandanafnið \"{user}\" er þegar í notkun." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +94,10 @@ msgstr "Villa kom upp við vistun þessa notanda í gagnagrunninn. Hafðu samban msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Það tókst að staðfesta tvíþátta-kóðann fyrir notandann \"{user}\"." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Nafn ekki uppfært: {}" msgid "Deleted user '{user}'." @@ -95,9 +141,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Má ekki vera meira en {num} stafur." msgstr[1] "Má ekki vera meira en {num} stafir." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Þetta notandanafn er ekki gilt því það er frátekið til innri nota í hugbúnaðnum." - msgid "This field is required." msgstr "Það er nauðsynlegt að fylla þennan reit út." @@ -128,7 +171,9 @@ msgstr "Engar ólesnar innsendingar hjá þessum heimildarmanni." msgid "Account updated." msgstr "Aðgangur uppfærður." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Innskráning mistókst." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +682,14 @@ msgstr "Birta lykilorð" msgid "LOG IN" msgstr "SKRÁ INN" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "AÐVÖRUN:  Það lítur út eins og þú sért að nota Tor2Web. Það  gefur þér ekki  nafnleysi. Hversvegna er það hættulegt?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Gagnasvið verður að vera á milli 1 og {max_codename_len} stafa langt." @@ -941,6 +992,9 @@ msgstr "Mikilvægt: Ef þú óskar áframhaldandi nafnleyndar, msgid "Back to submission page" msgstr "Til baka á innsendingasíðuna" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "AÐVÖRUN:  Það lítur út eins og þú sért að nota Tor2Web. Það  gefur þér ekki  nafnleysi. Hversvegna er það hættulegt?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "Safninu {source_name} var eytt." @@ -1059,9 +1113,6 @@ msgstr "Til baka á innsendingasíðuna" #~ msgid "Change Username & Admin Status" #~ msgstr "Breyta notandanafni & stjórnunarstöðu" -#~ msgid "Change username" -#~ msgstr "Breyta notandanafni" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop notar núna sjálfvirkt útbúin diceware-lykilorð." diff --git a/securedrop/translations/it_IT/LC_MESSAGES/messages.po b/securedrop/translations/it_IT/LC_MESSAGES/messages.po index 52ea1ef4ae..9d41c15140 100644 --- a/securedrop/translations/it_IT/LC_MESSAGES/messages.po +++ b/securedrop/translations/it_IT/LC_MESSAGES/messages.po @@ -7,7 +7,41 @@ # FIRST AUTHOR , 2014 # gio , 2014 msgid "" -msgstr "Project-Id-Version: SecureDrop\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPOT-Creation-Date: 2017-09-02 07:28+0000\nPO-Revision-Date: 2021-03-05 09:31+0000\nLast-Translator: Claudio Arseni \nLanguage-Team: Italian \nLanguage: it_IT\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"POT-Creation-Date: 2017-09-02 07:28+0000\n" +"PO-Revision-Date: 2021-03-05 09:31+0000\n" +"Last-Translator: Claudio Arseni \n" +"Language-Team: Italian \n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Testo del messaggio troppo lungo." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Deve essere lungo almeno {num} carattere." +msgstr[1] "Deve essere lungo almeno {num} caratteri." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Questo nome utente non è valido in quanto è riservato per uso interno da parte del software." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Cambia nome utente" msgid "{time} ago" msgstr "{time} fa" @@ -33,6 +67,10 @@ msgstr "C'è stato un problema nella verifica del codice a due fattori. Riprovar msgid "Image updated." msgstr "Immagine aggiornata." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Preferenze salvate." @@ -45,7 +83,13 @@ msgstr "Aggiornamento del nome dell'organizzazione non riuscito." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "C'è stato un errore con la password generata automaticamente. Utente non creato. Riprovare nuovamente." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Il nome utente \"{user}\" è già stato registrato." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -54,7 +98,10 @@ msgstr "Si è verificato un errore salvando questo utente nel database. Informar msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Il codice a due fattori per l'utente \"{user}\" è stato verificato con successo." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Nome non aggiornato: {}" msgid "Deleted user '{user}'." @@ -98,9 +145,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Non può contenere più di {num} carattere." msgstr[1] "Non può contenere più di {num} caratteri." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Questo nome utente non è valido in quanto è riservato per uso interno da parte del software." - msgid "This field is required." msgstr "Questo campo è obbligatorio." @@ -131,7 +175,9 @@ msgstr "Non ci sono invii non letti per questa fonte." msgid "Account updated." msgstr "Account aggiornato." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Accesso non riuscito." msgid "Please wait at least {num} second before logging in again." @@ -640,8 +686,14 @@ msgstr "Mostra password" msgid "LOG IN" msgstr "ACCEDI" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "ATTENZIONE: sembra che tu stia usando Tor2Web. Questo non fornisce anonimato. Perché è pericoloso?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Il campo deve avere una lunghezza tra 1 e {max_codename_len} caratteri." @@ -944,6 +996,9 @@ msgstr "Importante: per rimanere anonimi non usareWARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "ATTENZIONE: sembra che tu stia usando Tor2Web. Questo non fornisce anonimato. Perché è pericoloso?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "Archivio di {source_name} eliminato." @@ -1065,9 +1120,6 @@ msgstr "Torna alla pagina di invio documenti e messaggi" #~ msgid "Change Username & Admin Status" #~ msgstr "Modifica nome utente e ruolo di Amministratore" -#~ msgid "Change username" -#~ msgstr "Cambia nome utente" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop adesso usa password diceware generate automaticamente." diff --git a/securedrop/translations/messages.pot b/securedrop/translations/messages.pot index 7bbc4e98aa..89412790ca 100644 --- a/securedrop/translations/messages.pot +++ b/securedrop/translations/messages.pot @@ -6,7 +6,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: SecureDrop 1.8.0~rc1\n" +"Project-Id-Version: SecureDrop 1.9.0~rc1\n" "Report-Msgid-Bugs-To: securedrop@freedom.press\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" @@ -16,6 +16,20 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.5.1\n" +msgid "Name too long" +msgstr "" + +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "" +msgstr[1] "" + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "" + +msgid "Invalid username" +msgstr "" + msgid "{time} ago" msgstr "" @@ -40,6 +54,10 @@ msgstr "" msgid "Image updated." msgstr "" +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "" @@ -52,7 +70,11 @@ msgstr "" msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "" -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +msgid "Username \"{username}\" already taken." msgstr "" msgid "An error occurred saving this user to the database. Please inform your admin." @@ -61,7 +83,8 @@ msgstr "" msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "" -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +msgid "Name not updated: {message}" msgstr "" msgid "Deleted user '{user}'." @@ -70,10 +93,18 @@ msgstr "" msgid "Test alert sent. Please check your email." msgstr "" -msgid "{source_name}'s collection deleted." +#. Precedes a message confirming the success of an operation. +msgid "Success!" +msgstr "" + +msgid "The account and data for the source {} have been deleted." +msgstr "" + +#. Error shown when a user has not selected items to act on. +msgid "Nothing Selected" msgstr "" -msgid "No collections selected." +msgid "You must select one or more items." msgstr "" msgid "Your download failed because a file could not be found. An admin can find more information in the system and monitoring logs." @@ -97,9 +128,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "" msgstr[1] "" -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "" - msgid "This field is required." msgstr "" @@ -115,13 +143,13 @@ msgstr "" msgid "An unexpected error occurred! Please inform your admin." msgstr "" -msgid "Thanks. Your reply has been stored." +msgid "Your reply has been stored." msgstr "" -msgid "No collections selected for download." +msgid "You must select one or more items for download" msgstr "" -msgid "No collections selected for deletion." +msgid "You must select one or more items for deletion" msgstr "" msgid "No unread submissions for this source." @@ -147,16 +175,25 @@ msgstr "" msgid "Invalid secret format: odd-length secret. Did you mistype the secret?" msgstr "" -msgid "Submission deleted." -msgid_plural "{num} submissions deleted." +msgid "The item has been deleted." +msgid_plural "{num} items have been deleted." msgstr[0] "" msgstr[1] "" -msgid "{num} collection deleted" -msgid_plural "{num} collections deleted" +msgid "No collections selected for deletion." +msgstr "" + +msgid "The account and all data for {n} source have been deleted." +msgid_plural "The accounts and all data for {n} sources have been deleted." msgstr[0] "" msgstr[1] "" +msgid "You must select one or more items for deletion." +msgstr "" + +msgid "The files and messages have been deleted." +msgstr "" + msgid "Name updated." msgstr "" @@ -202,6 +239,33 @@ msgid_plural "{num} unread" msgstr[0] "" msgstr[1] "" +msgid "When the account for a source is deleted:" +msgstr "" + +msgid "The source will no longer be able to log in with their codename." +msgstr "" + +msgid "You will not be able to send them replies." +msgstr "" + +msgid "All files and messages from that source will also be destroyed." +msgstr "" + +msgid "Are you sure this is what you want?" +msgstr "" + +msgid "Yes, Delete Selected Source Accounts" +msgstr "" + +msgid "What would you like to delete?" +msgstr "" + +msgid "Files and Messages" +msgstr "" + +msgid "Source Accounts" +msgstr "" + msgid "Change Secret" msgstr "" @@ -328,10 +392,10 @@ msgstr "" msgid "Once you have configured your YubiKey, enter the 6-digit code below:" msgstr "" -msgid "Update Required  Set up v3 Onion Services before April 30 to keep your SecureDrop servers online. Please contact your administrator. Learn More" +msgid "Critical Security:  The operating system used by your SecureDrop servers has reached its end-of-life. A manual upgrade is required to re-enable the Source Interface and remain safe. Please contact your administrator. Learn More" msgstr "" -msgid "Update Required  Complete the v3 Onion Services setup before April 30. Please contact your administrator. Learn More" +msgid "Critical Security:  The operating system used by your SecureDrop servers will reach its end-of-life on April 30, 2021. A manual upgrade is urgently required to remain safe. Please contact your administrator. Learn More" msgstr "" msgid "Logged on as" @@ -349,7 +413,7 @@ msgstr "" msgid "All Sources" msgstr "" -msgid "The documents are stored encrypted for security. To read them, you will need to decrypt them using GPG." +msgid "All messages, files, and replies from sources are stored as encrypted files for security. To read them, you will need to decrypt them on your Secure Viewing Station." msgstr "" msgid "Download Selected" @@ -394,16 +458,16 @@ msgstr "" msgid "Delete Confirmation" msgstr "" -msgid "Are you sure you want to delete the selected documents?" +msgid "Are you sure you want to delete the selected submissions?" msgstr "" msgid "DELETE" msgstr "" -msgid "No documents to display." +msgid "No submissions to display." msgstr "" -msgid "You can write a secure reply to the person who submitted these documents:" +msgid "You can write a secure reply to the person who submitted these messages and/or files:" msgstr "" msgid "You've flagged this source for reply." @@ -418,13 +482,13 @@ msgstr "" msgid "FLAG THIS SOURCE FOR REPLY" msgstr "" -msgid "Click below to delete this source's collection. Warning: If you do this, the files seen here will be unrecoverable and the source will no longer be able to login using their previous codename." +msgid "Delete Source Account" msgstr "" -msgid "DELETE SOURCE AND SUBMISSIONS" +msgid "Are you sure?" msgstr "" -msgid "Are you sure you want to delete this collection?" +msgid "Yes, Delete Source Account" msgstr "" msgid "Instance Configuration" @@ -555,9 +619,6 @@ msgstr "" msgid "Continue to the list of documents for {codename}..." msgstr "" -msgid "Sources" -msgstr "" - msgid "Download Unread" msgstr "" @@ -570,12 +631,6 @@ msgstr "" msgid "Un-star" msgstr "" -msgid "Are you sure you want to delete the selected collections?" -msgstr "" - -msgid "Warning: If you do this, all files for the selected sources will be unrecoverable, and the sources will no longer be able to log in using their previous codename." -msgstr "" - msgid "No documents have been submitted!" msgstr "" @@ -597,6 +652,9 @@ msgstr "" msgid "Are you sure you want to reset two-factor authentication for {username}?" msgstr "" +msgid "Sources selected: " +msgstr "" + msgid "Login to access the journalist interface" msgstr "" @@ -609,7 +667,13 @@ msgstr "" msgid "LOG IN" msgstr "" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." @@ -666,6 +730,12 @@ msgstr "" msgid "SecureDrop Home" msgstr "" +msgid "We're sorry, our SecureDrop is currently offline." +msgstr "" + +msgid "Please try again later. Check our website for more information." +msgstr "" + msgid "LOG OUT" msgstr "" @@ -678,9 +748,6 @@ msgstr "" msgid "Look up a codename..." msgstr "" -msgid "Success!" -msgstr "" - msgid "Thank you for sending this information to us. Please check back later for replies." msgstr "" diff --git a/securedrop/translations/nb_NO/LC_MESSAGES/messages.po b/securedrop/translations/nb_NO/LC_MESSAGES/messages.po index e1acca0d15..f8dd80a4aa 100644 --- a/securedrop/translations/nb_NO/LC_MESSAGES/messages.po +++ b/securedrop/translations/nb_NO/LC_MESSAGES/messages.po @@ -4,7 +4,41 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.3.12\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPOT-Creation-Date: 2017-09-02 07:28+0000\nPO-Revision-Date: 2021-03-08 22:37+0000\nLast-Translator: John Hensley \nLanguage-Team: Norwegian Bokmål \nLanguage: nb_NO\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.3.12\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"POT-Creation-Date: 2017-09-02 07:28+0000\n" +"PO-Revision-Date: 2021-03-08 22:37+0000\n" +"Last-Translator: John Hensley \n" +"Language-Team: Norwegian Bokmål \n" +"Language: nb_NO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Meldingsteksten er for lang." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Må være minst {num} tegn langt." +msgstr[1] "Må være minst {num} tegn langt." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Dette brukernavnet er ugyldig fordi programvaren har reservert det for intern bruk." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Endre brukernavn" msgid "{time} ago" msgstr "{time} siden" @@ -30,6 +64,10 @@ msgstr "En feil oppstod ved bekreftelse av tofaktorkoden. Prøv igjen." msgid "Image updated." msgstr "Bilde oppdatert." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Innstillinger lagret." @@ -42,7 +80,13 @@ msgstr "Organisasjonsnavn kunne ikke oppdateres." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "En feil oppstod knyttet til det automatisk opprettede passordet. Brukeren ble derfor ikke opprettet. Prøv igjen." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Brukernavnet \"{user}\" er allerede i bruk." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +95,10 @@ msgstr "Kunne ikke lagre brukeren i databasen. Gi din administrator beskjed." msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Tofaktorkoden for brukeren \"{user}\" stemmer." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Navn ikke oppdatert: {}" msgid "Deleted user '{user}'." @@ -95,9 +142,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Kan ikke inneholde mer enn {num} tegn." msgstr[1] "Kan ikke inneholde mer enn {num} tegn." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Dette brukernavnet er ugyldig fordi programvaren har reservert det for intern bruk." - msgid "This field is required." msgstr "Dette feltet er påkrevd." @@ -128,7 +172,9 @@ msgstr "Ingen uleste innsendelser fra denne kilden." msgid "Account updated." msgstr "Konto oppdatert." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Innlogging mislyktes." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +683,14 @@ msgstr "Vis passord" msgid "LOG IN" msgstr "LOGG INN" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "ADVARSEL:  Det ser ut som du bruker Tor2Web. Dette  gir ikke  anonym kommunikasjon. Hvorfor kan dette være problematisk?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Feltet må være mellom ett og {max_codename_len} tegn langt." @@ -941,6 +993,9 @@ msgstr "Viktig: Om du ønsker å være helt anonym, ikk msgid "Back to submission page" msgstr "Tilbake til innsendelsesside" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "ADVARSEL:  Det ser ut som du bruker Tor2Web. Dette  gir ikke  anonym kommunikasjon. Hvorfor kan dette være problematisk?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "{source_name}s kildeoppføring slettet." @@ -1059,9 +1114,6 @@ msgstr "Tilbake til innsendelsesside" #~ msgid "Change Username & Admin Status" #~ msgstr "Endre brukernavn og admin-status" -#~ msgid "Change username" -#~ msgstr "Endre brukernavn" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop bruker nå automatisk opprettede Diceware-passord." diff --git a/securedrop/translations/nl/LC_MESSAGES/messages.po b/securedrop/translations/nl/LC_MESSAGES/messages.po index 0da7807bba..40c64f21f1 100644 --- a/securedrop/translations/nl/LC_MESSAGES/messages.po +++ b/securedrop/translations/nl/LC_MESSAGES/messages.po @@ -4,7 +4,41 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.3.12\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPOT-Creation-Date: 2017-09-02 07:28+0000\nPO-Revision-Date: 2021-03-10 14:19+0000\nLast-Translator: kwadronaut \nLanguage-Team: Dutch \nLanguage: nl\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.3.12\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"POT-Creation-Date: 2017-09-02 07:28+0000\n" +"PO-Revision-Date: 2021-03-10 14:19+0000\n" +"Last-Translator: kwadronaut \n" +"Language-Team: Dutch \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Bericht is te lang." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Moet ten minste {num} teken lang zijn." +msgstr[1] "Moet ten minste {num} tekens lang zijn." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Deze gebruikersnaam is ongeldig, omdat het voor intern gebruik door de software is gereserveerd." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Gebruikersnaam wijzigen" msgid "{time} ago" msgstr "{time} geleden" @@ -30,6 +64,10 @@ msgstr "Er was een probleem bij de verificatie van de tweestapstoken. Probeer he msgid "Image updated." msgstr "Afbeelding bijgewerkt." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Voorkeuren opgeslagen." @@ -42,7 +80,13 @@ msgstr "Het updaten van de organisatienaam is mislukt." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Er was een fout met het automatisch gegenereerde wachtwoord. Gebruiker niet aangemaakt. Probeer het nogmaals." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Gebruikersnaam \"{user}\" al in gebruik." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +95,10 @@ msgstr "Er trad een fout op bij het opslaan van deze gebruiker in de database. N msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "De tweestapstoken voor gebruiker \"{user}\" is succesvol geverifieerd." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Naam niet bijgewerkt: {}" msgid "Deleted user '{user}'." @@ -95,9 +142,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Kan niet langer dan {num} teken zijn." msgstr[1] "Kan niet langer dan {num} tekens zijn." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Deze gebruikersnaam is ongeldig, omdat het voor intern gebruik door de software is gereserveerd." - msgid "This field is required." msgstr "Dit veld is verplicht." @@ -128,7 +172,9 @@ msgstr "Geen ongelezen inzendingen van deze bron." msgid "Account updated." msgstr "Account bijgewerkt." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Inloggen mislukt." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +683,14 @@ msgstr "Toon wachtwoord" msgid "LOG IN" msgstr "INLOGGEN" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "WAARSCHUWING:  Het lijkt erop dat u Tor2Web gebruikt. Tor2Web zorgt  niet  voor anonimiteit. Waarom is dit gevaarlijk?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Veld moet tussen de 1 en {max_codename_len} tekens lang zijn." @@ -941,6 +993,9 @@ msgstr "Belangrijk: Als u anoniem wilt blijven, gebruik dan WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "WAARSCHUWING:  Het lijkt erop dat u Tor2Web gebruikt. Tor2Web zorgt  niet  voor anonimiteit. Waarom is dit gevaarlijk?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "{source_name}-collectie verwijderd." @@ -1059,9 +1114,6 @@ msgstr "Terug naar inzendingenpagina" #~ msgid "Change Username & Admin Status" #~ msgstr "Gebruikersnaam & Beheerderstatus wijzigen" -#~ msgid "Change username" -#~ msgstr "Gebruikersnaam wijzigen" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop gebruikt nu automatisch gegenereerde diceware wachtwoorden." diff --git a/securedrop/translations/pt_BR/LC_MESSAGES/messages.po b/securedrop/translations/pt_BR/LC_MESSAGES/messages.po index 2a68c383bc..f845bd643e 100644 --- a/securedrop/translations/pt_BR/LC_MESSAGES/messages.po +++ b/securedrop/translations/pt_BR/LC_MESSAGES/messages.po @@ -4,7 +4,40 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.4.3\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-02 19:59+0000\nLast-Translator: communiaa \nLanguage-Team: Portuguese (Brazil) \nLanguage: pt_BR\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n > 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.4.3\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-02 19:59+0000\n" +"Last-Translator: communiaa \n" +"Language-Team: Portuguese (Brazil) \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Mensagem de texto muito longa." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Deve conter pelo menos {num} caracteres." +msgstr[1] "Deve conter pelo menos {num} caracteres." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Este nome de usuário é inválido porque ele é reservado ao uso interno do software." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Alterar nome de usuário" msgid "{time} ago" msgstr "há {time}" @@ -30,6 +63,10 @@ msgstr "Houve um problema na verificação do código 2FA. Por favor, tente nova msgid "Image updated." msgstr "Imagem atualizada." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Preferências salvas." @@ -42,7 +79,13 @@ msgstr "Falha ao atualizar o nome da organização." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Houve um erro com a senha gerada automaticamente. O usuário não foi criado. Por favor, tente novamente." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "O nome de usuário \"{user}\" já está em uso." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +94,10 @@ msgstr "Erro ao salvar este usuário no banco de dados. Por favor, informe o adm msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "O código 2FA da.o usuária.o \"{user}\" foi verificado com sucesso." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Nome não atualizado: {}" msgid "Deleted user '{user}'." @@ -95,9 +141,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Não pode conter mais do que {num} caracteres." msgstr[1] "Não pode conter mais do que {num} caracteres." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Este nome de usuário é inválido porque ele é reservado ao uso interno do software." - msgid "This field is required." msgstr "Este campo é obrigatório." @@ -128,7 +171,9 @@ msgstr "Esta fonte não tem nenhum envio não lido." msgid "Account updated." msgstr "Conta atualizada." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Falha na autenticação." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +682,14 @@ msgstr "Mostrar senha" msgid "LOG IN" msgstr "ACESSAR" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "ATENÇÃO:  Parece que você está usando o Tor2Web, o que  não  garante o seu anonimato. Por que isso é perigoso?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Este campo deve conter entre 1 e {max_codename_len} caracteres." @@ -740,7 +791,9 @@ msgid "" "Because we do not track users of our SecureDrop\n" " service, in future visits, using this codename will be the only way we have to communicate with you should we have\n" " questions or are interested in additional information. Unlike passwords, there is no way to retrieve a lost codename." -msgstr "Como não rastreamos as pessoas que utilizam SecureDrop,\n este codinome é a nossa única maneira de entrar em contato com você para fazer perguntas ou pedir mais informações. Contrariamente a uma senha, não é possível recuperar um codinome perdido." +msgstr "" +"Como não rastreamos as pessoas que utilizam SecureDrop,\n" +" este codinome é a nossa única maneira de entrar em contato com você para fazer perguntas ou pedir mais informações. Contrariamente a uma senha, não é possível recuperar um codinome perdido." msgid "SUBMIT DOCUMENTS" msgstr "ENVIAR DOCUMENTOS" @@ -938,6 +991,9 @@ msgstr "Importante: Para manter o seu anonimato, nãoWARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "ATENÇÃO:  Parece que você está usando o Tor2Web, o que  não  garante o seu anonimato. Por que isso é perigoso?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "A coleção de {source_name} foi apagada." @@ -1059,9 +1115,6 @@ msgstr "Voltar à página de envio" #~ msgid "Change Username & Admin Status" #~ msgstr "Alterar nome de usuário e credenciais de administração" -#~ msgid "Change username" -#~ msgstr "Alterar nome de usuário" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "O SecureDrop agora usa senhas geradas automaticamente com o método Diceware." diff --git a/securedrop/translations/ro/LC_MESSAGES/messages.po b/securedrop/translations/ro/LC_MESSAGES/messages.po index 4d286f48f7..56a182fb6c 100644 --- a/securedrop/translations/ro/LC_MESSAGES/messages.po +++ b/securedrop/translations/ro/LC_MESSAGES/messages.po @@ -4,7 +4,41 @@ # FIRST AUTHOR , 2018. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.6~rc2\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-08 22:37+0000\nLast-Translator: John Hensley \nLanguage-Team: Romanian \nLanguage: ro\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.5.1\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.6~rc2\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-08 22:37+0000\n" +"Last-Translator: John Hensley \n" +"Language-Team: Romanian \n" +"Language: ro\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.5.1\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Textul mesajului este prea lung." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Trebuie să aibă cel puțin {num} caracter." +msgstr[1] "Trebuie să aibă cel puțin {num} caractere." +msgstr[2] "Trebuie să aibă cel puțin {num} de caractere." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Acest nume de utilizator este nevalid deoarece este rezervat pentru utilizare internă de către software." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Schimbă numele de utilizator" msgid "{time} ago" msgstr "acum {time}" @@ -30,6 +64,10 @@ msgstr "A apărut o problemă la verificarea datelor pentru autentificarea cu do msgid "Image updated." msgstr "Imaginea a fost actualizată." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Preferințele au fost salvate." @@ -42,7 +80,13 @@ msgstr "Eșec la actualizarea denumirii organizației." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "A apărut o eroare la parola generată automat. Utilizatorul nu a fost creat. Te rugăm să încerci din nou." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Numele de utilizator „{user}” este deja folosit." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +95,10 @@ msgstr "A apărut o eroare la salvarea utilizatorului în baza de date. Te rugă msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Codul de autentificare cu doi factori al utilizatorului „{user}” a fost verificat cu succes." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Nume neactualizat: {}" msgid "Deleted user '{user}'." @@ -98,9 +145,6 @@ msgstr[0] "Nu poate avea mai mult de {num} caracter." msgstr[1] "Nu poate avea mai mult de {num} caractere." msgstr[2] "Nu poate avea mai mult de {num} de caractere." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Acest nume de utilizator este nevalid deoarece este rezervat pentru utilizare internă de către software." - msgid "This field is required." msgstr "Acest câmp este obligatoriu." @@ -131,7 +175,9 @@ msgstr "Nu sunt trimiteri necitite de la această sursă." msgid "Account updated." msgstr "Contul a fost actualizat." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Autentificarea a eșuat." msgid "Please wait at least {num} second before logging in again." @@ -647,8 +693,14 @@ msgstr "Arată parola" msgid "LOG IN" msgstr "CONECTARE" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "AVERTISMENT:  Se pare că folosești Tor2Web. Acest serviciu  nu oferă  anonimitate. De ce este periculos?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Câmpul trebuie să aibă între 1 și {max_codename_len} caractere." @@ -951,6 +1003,9 @@ msgstr "Important: Dacă vrei să rămâi anonim(ă), n msgid "Back to submission page" msgstr "Înapoi la pagina de trimitere" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "AVERTISMENT:  Se pare că folosești Tor2Web. Acest serviciu  nu oferă  anonimitate. De ce este periculos?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "Colecția lui {source_name} a fost ștearsă." @@ -1068,9 +1123,6 @@ msgstr "Înapoi la pagina de trimitere" #~ msgid "Change Username & Admin Status" #~ msgstr "Schimbă numele de utilizator și drepturile de administrator" -#~ msgid "Change username" -#~ msgstr "Schimbă numele de utilizator" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop folosește acum parole generate automat de tip „diceware”." diff --git a/securedrop/translations/ru/LC_MESSAGES/messages.po b/securedrop/translations/ru/LC_MESSAGES/messages.po index 64b657e26b..ee0c8ed115 100644 --- a/securedrop/translations/ru/LC_MESSAGES/messages.po +++ b/securedrop/translations/ru/LC_MESSAGES/messages.po @@ -4,7 +4,41 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.4.3\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-08 22:37+0000\nLast-Translator: John Hensley \nLanguage-Team: Russian \nLanguage: ru\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.4.3\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-08 22:37+0000\n" +"Last-Translator: John Hensley \n" +"Language-Team: Russian \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Текст сообщения слишком длинный." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Должно быть не менее {num} символ." +msgstr[1] "Должно быть не менее {num} символа." +msgstr[2] "Должно быть не менее {num} символов." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Данное имя пользователя недоступно, так как оно предназначено для внутреннего использования в ПО." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Изменить имя пользователя" msgid "{time} ago" msgstr "{time} назад" @@ -30,6 +64,10 @@ msgstr "Произошла ошибка при подтверждении дву msgid "Image updated." msgstr "Изображение обновлено." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Настройки сохранены." @@ -42,7 +80,13 @@ msgstr "Не удалось обновить название организац msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Произошла ошибка с автоматически сгенерированным паролем. Пользователь не создан. Пожалуйста, попробуйте еще раз." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Имя пользователя \"{user}\" уже занято." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +95,10 @@ msgstr "Произошла ошибка при сохранении этого msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Двухфакторный код для пользователя \"{user}\" был успешно подтверждён." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Имя не обновлено: {}" msgid "Deleted user '{user}'." @@ -98,9 +145,6 @@ msgstr[0] "Длина не может превышать {num} символ." msgstr[1] "Длина не может превышать {num} символа." msgstr[2] "Длина не может превышать {num} символов." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Данное имя пользователя недоступно, так как оно предназначено для внутреннего использования в ПО." - msgid "This field is required." msgstr "Это обязательное поле." @@ -131,7 +175,9 @@ msgstr "Нет непрочитанных материалов для этого msgid "Account updated." msgstr "Аккаунт обновлен." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Авторизация не удалась." msgid "Please wait at least {num} second before logging in again." @@ -647,8 +693,14 @@ msgstr "Показать пароль" msgid "LOG IN" msgstr "Войти" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "ВНИМАНИЕ:  Кажется, вы используете Tor2Web. Это  не обеспечивает  анонимность. Почему это опасно?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Поле должно содержать от 1 до {max_codename_len} символов." @@ -951,6 +1003,9 @@ msgstr "Внимание: Если вы хотите сохра msgid "Back to submission page" msgstr "Вернуться на страницу отправки" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "ВНИМАНИЕ:  Кажется, вы используете Tor2Web. Это  не обеспечивает  анонимность. Почему это опасно?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "Коллекция {source_name} удалена." @@ -1071,9 +1126,6 @@ msgstr "Вернуться на страницу отправки" #~ msgid "Change Username & Admin Status" #~ msgstr "Изменение имени пользователя и статуса администратора" -#~ msgid "Change username" -#~ msgstr "Изменить имя пользователя" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop теперь использует автоматически создаваемые пароли." diff --git a/securedrop/translations/sk/LC_MESSAGES/messages.po b/securedrop/translations/sk/LC_MESSAGES/messages.po index 2f3eba515a..6f45d8de0c 100644 --- a/securedrop/translations/sk/LC_MESSAGES/messages.po +++ b/securedrop/translations/sk/LC_MESSAGES/messages.po @@ -4,7 +4,41 @@ # FIRST AUTHOR , 2019. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.14.0~rc1\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-07 21:37+0000\nLast-Translator: 1000101 \nLanguage-Team: Slovak \nLanguage: sk\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.5.1\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.14.0~rc1\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-07 21:37+0000\n" +"Last-Translator: 1000101 \n" +"Language-Team: Slovak \n" +"Language: sk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.5.1\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Správa je príliš dlhá." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Musí byť aspoň {num} znak dlhé." +msgstr[1] "Musí byť aspoň {num} znaky dlhé." +msgstr[2] "Musí byť aspoň {num} znakov dlhé." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Toto používateľské meno je neplatné, pretože je softvérom rezerované na interné použitie." + +#, fuzzy +#| msgid "Invalid input." +msgid "Invalid username" +msgstr "Neplatný vstup." msgid "{time} ago" msgstr "pred {time}" @@ -30,6 +64,10 @@ msgstr "Vyskytla sa chyba pri overovaní dvojfaktorového kódu. Prosím, skúst msgid "Image updated." msgstr "Obrázok bol aktualizovaný." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Preferencie uložené." @@ -42,7 +80,13 @@ msgstr "Aktualizácia názvu organizácie sa nepodarila." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Pri generovaní hesla sa vyskytla chyba. Účet nebol vytvorený. Prosím, skúste to znova." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Prihlasovacie meno „{user}“ je už obsadené." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +95,10 @@ msgstr "Vyskytla sa chyba pri ukladaní používateľa do databázy. Prosím, ko msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Kód dvojfaktorového overenia pre používateľa \"{user}\" bol úspešne overený." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Meno nebolo aktualizované: {}" msgid "Deleted user '{user}'." @@ -98,9 +145,6 @@ msgstr[0] "Nesmie byť dlhšie ako {num} znak." msgstr[1] "Nesmie byť dlhšie ako {num} znaky." msgstr[2] "Nesmie byť dlhšie ako {num} znakov." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Toto používateľské meno je neplatné, pretože je softvérom rezerované na interné použitie." - msgid "This field is required." msgstr "Toto pole je povinné." @@ -131,7 +175,9 @@ msgstr "Žiadne neprečítané podania od tohto zdroja." msgid "Account updated." msgstr "Účet bol aktualizovaný." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Prihlásenie zlyhalo." msgid "Please wait at least {num} second before logging in again." @@ -647,8 +693,14 @@ msgstr "Ukázať heslo" msgid "LOG IN" msgstr "PRIHLÁSIŤ SA" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "VAROVANIE:  Zdá sa, že používate Tor2Web. Toto  neposkytuje  anonymitu. Prečo je to nebezpečné?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Pole musí mať 1 až {max_codename_len} znakov." @@ -951,6 +1003,9 @@ msgstr "Dôležité: Ak chcete zostať v anonymite, nep msgid "Back to submission page" msgstr "Späť na stránku podania" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "VAROVANIE:  Zdá sa, že používate Tor2Web. Toto  neposkytuje  anonymitu. Prečo je to nebezpečné?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "Sada {source_name} zmazaná." diff --git a/securedrop/translations/sv/LC_MESSAGES/messages.po b/securedrop/translations/sv/LC_MESSAGES/messages.po index b5bdfdf959..76389619ec 100644 --- a/securedrop/translations/sv/LC_MESSAGES/messages.po +++ b/securedrop/translations/sv/LC_MESSAGES/messages.po @@ -4,7 +4,40 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.5-rc4\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-08 22:37+0000\nLast-Translator: John Hensley \nLanguage-Team: Swedish \nLanguage: sv\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.5-rc4\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-08 22:37+0000\n" +"Last-Translator: John Hensley \n" +"Language-Team: Swedish \n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "Meddelandet är för långt." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "Måste vara minst {num} tecken långt." +msgstr[1] "Måste vara minst {num} tecken långt." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Detta användarnamn får inte nyttas, av den anledning att det är reserverad av programvaran för intern bruk." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Ändra användarnamn" msgid "{time} ago" msgstr "{time} sedan" @@ -30,6 +63,10 @@ msgstr "Det uppstod ett fel vid bekräftelse tvåfaktorsautentisering. Vänligen msgid "Image updated." msgstr "Bild uppdaterad." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Inställningar sparade." @@ -42,7 +79,13 @@ msgstr "Uppdatering av organisationsnamn misslyckades." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Det uppstod ett fel med det autogenererade lösenordet. Användaren skapades inte. Vänligen försök på nytt." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "Användarnamnet \"{user}\" är redan använt." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +94,10 @@ msgstr "Det uppstod ett fel när användaren skulle sparas till databasen. Vänl msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "Tvåfaktorsautentisering för användaren \"{user}\" bekräftades." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Namn ej uppdaterat: {}" msgid "Deleted user '{user}'." @@ -95,9 +141,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "Kan inte vara längre än {num} tecken." msgstr[1] "Kan inte vara längre än {num} tecken." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Detta användarnamn får inte nyttas, av den anledning att det är reserverad av programvaran för intern bruk." - msgid "This field is required." msgstr "Detta fält är obligatoriskt." @@ -128,7 +171,9 @@ msgstr "Det finns inga olästa inlämningar från denna källa." msgid "Account updated." msgstr "Kontot uppdaterat." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Inloggningen misslyckades." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +682,14 @@ msgstr "Visa lösenord" msgid "LOG IN" msgstr "LOGGA IN" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "VARNING: Du verkar använda Tor2Web. Det gör dig   inte  anonym. Varför är det farligt?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Fältet måste innehålla mellan 1 och {max_codename_len} tecken." @@ -941,6 +992,9 @@ msgstr " Viktigt: Ifall du önskar att förbli anonym, msgid "Back to submission page" msgstr "Tillbaka till inlämningssidan" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "VARNING: Du verkar använda Tor2Web. Det gör dig   inte  anonym. Varför är det farligt?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "{source_name}s samling har raderats." @@ -1062,9 +1116,6 @@ msgstr "Tillbaka till inlämningssidan" #~ msgid "Change Username & Admin Status" #~ msgstr "Ändra användarnamn & administratörsstatus" -#~ msgid "Change username" -#~ msgstr "Ändra användarnamn" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop använder nu automatiskt genererade diceware-lösenord." diff --git a/securedrop/translations/tr/LC_MESSAGES/messages.po b/securedrop/translations/tr/LC_MESSAGES/messages.po index a75c2b8e3b..3967728d12 100644 --- a/securedrop/translations/tr/LC_MESSAGES/messages.po +++ b/securedrop/translations/tr/LC_MESSAGES/messages.po @@ -4,7 +4,40 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.5-rc4\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-08 10:37+0000\nLast-Translator: Volkan \nLanguage-Team: Turkish \nLanguage: tr\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.5-rc4\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-08 10:37+0000\n" +"Last-Translator: Volkan \n" +"Language-Team: Turkish \n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "İleti metni çok uzun." + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "{num} karakterden az olamaz." +msgstr[1] "{num} karakterden az olamaz." + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "Bu kullanıcı adı geçersiz, çünkü yazılım tarafından içsel olarak kullanıma tahsis edilmiştir." + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "Kullanıcı adını değiştir" msgid "{time} ago" msgstr "{time} önce" @@ -30,6 +63,10 @@ msgstr "İki aşamalı kimlik doğrulama kodu doğrulanırken bir sorun çıktı msgid "Image updated." msgstr "Görüntü güncellendi." +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "Tercihler kaydedildi." @@ -42,7 +79,13 @@ msgstr "Kuruluş ismi güncellemesi başarısız oldu." msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "Otomatik üretilen parolada bir sorun var. Kullanıcı eklenemedi. Lütfen yeniden deneyin." -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "\"{user}\" kullanıcı adı başkası tarafından kullanılıyor." msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +94,10 @@ msgstr "Bu kullanıcı veritabanına kaydedilirken bir sorun çıktı. Lütfen y msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "\"{user}\" kullanıcısı için iki aşamalı kimlik doğrulama kodu doğrulandı." -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "Ad güncellenmedi: {}" msgid "Deleted user '{user}'." @@ -95,9 +141,6 @@ msgid_plural "Cannot be longer than {num} characters." msgstr[0] "{num} karakterden fazla olamaz." msgstr[1] "{num} karakterden fazla olamaz." -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "Bu kullanıcı adı geçersiz, çünkü yazılım tarafından içsel olarak kullanıma tahsis edilmiştir." - msgid "This field is required." msgstr "Bu alanın doldurulması zorunludur." @@ -128,7 +171,9 @@ msgstr "Bu kaynağın okunmamış gönderimi yok." msgid "Account updated." msgstr "Hesap güncellendi." -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "Oturum açma başarısız." msgid "Please wait at least {num} second before logging in again." @@ -637,8 +682,14 @@ msgstr "Parolayı Göster" msgid "LOG IN" msgstr "OTURUM AÇ" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "UYARI:  Tor2Web kullanıyor gibi gözüküyorsunuz. Bu size anonimlik  sağlamaz . Bu neden tehlikeli?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "Alanda 1 ile {max_codename_len} arasında karakter bulunmalıdır." @@ -941,6 +992,9 @@ msgstr "Önemli: Anonim kalmak istiyorsanız, dosyanın GPG tar msgid "Back to submission page" msgstr "Gönderi sayfasına geri dön" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "UYARI:  Tor2Web kullanıyor gibi gözüküyorsunuz. Bu size anonimlik  sağlamaz . Bu neden tehlikeli?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "{source_name} derlemeleri silindi." @@ -1062,9 +1116,6 @@ msgstr "Gönderi sayfasına geri dön" #~ msgid "Change Username & Admin Status" #~ msgstr "Kullanıcı Adını ve Yönetici Durumunu değiştir" -#~ msgid "Change username" -#~ msgstr "Kullanıcı adını değiştir" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop artık otomatik olarak üretilmiş diceware (zar ile oluşturulmuş) parolaları kullanmaktadır." diff --git a/securedrop/translations/zh_Hans/LC_MESSAGES/messages.po b/securedrop/translations/zh_Hans/LC_MESSAGES/messages.po index cd4ae0fa78..1814a2bdfa 100644 --- a/securedrop/translations/zh_Hans/LC_MESSAGES/messages.po +++ b/securedrop/translations/zh_Hans/LC_MESSAGES/messages.po @@ -4,7 +4,39 @@ # FIRST AUTHOR , 2018. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.6~rc2\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPO-Revision-Date: 2021-03-08 22:37+0000\nLast-Translator: John Hensley \nLanguage-Team: Chinese (Simplified) \nLanguage: zh_Hans\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=1; plural=0;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.5.1\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.6~rc2\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"PO-Revision-Date: 2021-03-08 22:37+0000\n" +"Last-Translator: John Hensley \n" +"Language-Team: Chinese (Simplified) \n" +"Language: zh_Hans\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.5.1\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "消息文本太长。" + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "该字段需要至少 {num} 个字符。" + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "此用户名无效,因为其被保留为软件内部使用。" + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "更改用户名" msgid "{time} ago" msgstr "{time} 之前" @@ -30,6 +62,10 @@ msgstr "验证两步认证码时出现问题。请稍后重试。" msgid "Image updated." msgstr "图片已更新。" +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "设置已保存。" @@ -42,7 +78,13 @@ msgstr "无法更新组织名称。" msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "自动生成密码时发生错误。用户账户未建立。请重试。" -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "用户名 \"{user}\" 已经被占用。" msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +93,10 @@ msgstr "保存该用户到数据库时出错。请通知管理员。" msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "已成功验证用户 \"{user}\" 的两步验证代码。" -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "未更新名称: {}" msgid "Deleted user '{user}'." @@ -92,9 +137,6 @@ msgid "Cannot be longer than {num} character." msgid_plural "Cannot be longer than {num} characters." msgstr[0] "字段长度需小于 {num} 个字符。" -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "此用户名无效,因为其被保留为软件内部使用。" - msgid "This field is required." msgstr "这是必填项。" @@ -125,7 +167,9 @@ msgstr "该线人无未读内容。" msgid "Account updated." msgstr "账号已更新。" -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "登录失败。" msgid "Please wait at least {num} second before logging in again." @@ -627,8 +671,14 @@ msgstr "显示密码" msgid "LOG IN" msgstr "登录" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr "警告:  您似乎正使用 Tor2Web。此服务 无法 隐藏您的行踪。为何存在风险?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "字段长度必须介于 1 至 {max_codename_len} 字节之间。" @@ -931,6 +981,9 @@ msgstr "重要提示:若您想保持匿名身份, msgid "Back to submission page" msgstr "返回提交页面" +#~ msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr "警告:  您似乎正使用 Tor2Web。此服务 无法 隐藏您的行踪。为何存在风险?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "{source_name} 集合已删除。" @@ -1017,9 +1070,6 @@ msgstr "返回提交页面" #~ msgid "Change Username & Admin Status" #~ msgstr "更改账户名称 & 管理员状态" -#~ msgid "Change username" -#~ msgstr "更改用户名" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop 现使用虚拟骰子自动生成密码。" diff --git a/securedrop/translations/zh_Hant/LC_MESSAGES/messages.po b/securedrop/translations/zh_Hant/LC_MESSAGES/messages.po index c107cee88c..77c621e2b6 100644 --- a/securedrop/translations/zh_Hant/LC_MESSAGES/messages.po +++ b/securedrop/translations/zh_Hant/LC_MESSAGES/messages.po @@ -4,7 +4,40 @@ # FIRST AUTHOR , 2017. # msgid "" -msgstr "Project-Id-Version: SecureDrop 0.3.12\nReport-Msgid-Bugs-To: securedrop@freedom.press\nPOT-Creation-Date: 2017-09-02 07:28+0000\nPO-Revision-Date: 2021-03-08 22:37+0000\nLast-Translator: John Hensley \nLanguage-Team: Chinese (Traditional) \nLanguage: zh_Hant\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=1; plural=0;\nX-Generator: Weblate 4.3.2\nGenerated-By: Babel 2.4.0\n" +msgstr "" +"Project-Id-Version: SecureDrop 0.3.12\n" +"Report-Msgid-Bugs-To: securedrop@freedom.press\n" +"POT-Creation-Date: 2017-09-02 07:28+0000\n" +"PO-Revision-Date: 2021-03-08 22:37+0000\n" +"Last-Translator: John Hensley \n" +"Language-Team: Chinese (Traditional) \n" +"Language: zh_Hant\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.3.2\n" +"Generated-By: Babel 2.4.0\n" + +#, fuzzy +#| msgid "Message text too long." +msgid "Name too long" +msgstr "訊息文字過長。" + +#, fuzzy +#| msgid "Must be at least {num} character long." +#| msgid_plural "Must be at least {num} characters long." +msgid "Must be at least one character long." +msgid_plural "Must be at least {num} characters long." +msgstr[0] "须至少有 {num} 個字符長度。" + +msgid "This username is invalid because it is reserved for internal use by the software." +msgstr "因軟體本身保留自用故無法選用此帳戶名稱。" + +#, fuzzy +#| msgid "Change username" +msgid "Invalid username" +msgstr "更改使用者名稱" msgid "{time} ago" msgstr "{time} 之前" @@ -30,6 +63,10 @@ msgstr "確認雙重驗證碼時出現問題。請再次嘗試。" msgid "Image updated." msgstr "圖片已更新。" +#. This error is shown when an uploaded image cannot be used. +msgid "Unable to process the image file. Try another one." +msgstr "" + msgid "Preferences saved." msgstr "偏好已儲存。" @@ -42,7 +79,13 @@ msgstr "無法更新組織名稱。" msgid "There was an error with the autogenerated password. User not created. Please try again." msgstr "自動生成的密碼有誤。用户帳戶未建立。請再次嘗試。" -msgid "Username \"{user}\" already taken." +#. Here, "{message}" explains the problem with the username. +msgid "Invalid username: {message}" +msgstr "" + +#, fuzzy +#| msgid "Username \"{user}\" already taken." +msgid "Username \"{username}\" already taken." msgstr "用户名「{user}」已存在。" msgid "An error occurred saving this user to the database. Please inform your admin." @@ -51,7 +94,10 @@ msgstr "保存此用户到資料庫時出錯。請通知管理員。" msgid "The two-factor code for user \"{user}\" was verified successfully." msgstr "已成功驗證用户 \"{user}\" 的雙重驗證碼。" -msgid "Name not updated: {}" +#. Here, "{message}" explains the problem with the name. +#, fuzzy +#| msgid "Name not updated: {}" +msgid "Name not updated: {message}" msgstr "名稱未更新: {}" msgid "Deleted user '{user}'." @@ -92,9 +138,6 @@ msgid "Cannot be longer than {num} character." msgid_plural "Cannot be longer than {num} characters." msgstr[0] "不可多於 {num} 字元。" -msgid "This username is invalid because it is reserved for internal use by the software." -msgstr "因軟體本身保留自用故無法選用此帳戶名稱。" - msgid "This field is required." msgstr "這是必填項目。" @@ -125,7 +168,9 @@ msgstr "此位線人無未讀的提交。" msgid "Account updated." msgstr "帳戶已更新。" -msgid "Login failed." +#, fuzzy +#| msgid "Login failed." +msgid "Login failed." msgstr "登錄失敗。" msgid "Please wait at least {num} second before logging in again." @@ -627,8 +672,14 @@ msgstr "密碼" msgid "LOG IN" msgstr "登錄" -msgid "WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" -msgstr " 警告:  看來你正在使用 Tor2Web。 它  不能  提供匿名功能 這樣為什麼有危險?" +msgid "WARNING:" +msgstr "" + +msgid "You appear to be using Tor2Web, which does not provide anonymity." +msgstr "" + +msgid "Why is this dangerous?" +msgstr "" msgid "Field must be between 1 and {max_codename_len} characters long." msgstr "此處字段長度必須在 1 和 {max_codename_len} 個字符之間。" @@ -903,7 +954,9 @@ msgstr "如果您熟悉 GPG 加密軟體,您可以在上傳文件前先自行 msgid "" "Download the public key. It will be saved to a file called:\n" "

{submission_key_fpr_filename}

" -msgstr "下載公鑰。它會自動儲存為:\n

{submission_key_fpr_filename}

" +msgstr "" +"下載公鑰。它會自動儲存為:\n" +"

{submission_key_fpr_filename}

" msgid "Import it into your GPG keyring." msgstr "匯入至您的 GPG 鑰匙圈。" @@ -929,6 +982,9 @@ msgstr "重要:如果您希望保持匿名,請勿WARNING:  You appear to be using Tor2Web. This  does not  provide anonymity. Why is this dangerous?" +#~ msgstr " 警告:  看來你正在使用 Tor2Web。 它  不能  提供匿名功能 這樣為什麼有危險?" + #~ msgid "{source_name}'s collection deleted." #~ msgstr "{source_name} 集件已删除" @@ -1045,9 +1101,6 @@ msgstr "返回提交文件頁面" #~ msgid "Change Username & Admin Status" #~ msgstr "更改帳戶名稱 & 管理狀態" -#~ msgid "Change username" -#~ msgstr "更改使用者名稱" - #~ msgid "SecureDrop now uses automatically generated diceware passwords." #~ msgstr "SecureDrop 現使用骰子軟體來自動産生密碼。"