def test_ignore_warnings(entity_properties_plurals_mock, ): """ Check if logic of ignore_warnings works when there are errors. """ assert run_checks( entity_properties_plurals_mock, "en-US", entity_properties_plurals_mock.string, "plural1;plural2;plural3;plural4;plural5", True, ) == { "clWarnings": ["expecting 2 plurals, found 5"], "ttWarnings": ["Simple capitalization", "Starting capitalization"], } # Warnings can be ignored for Translate Toolkit if user decides to do so assert run_checks( entity_properties_plurals_mock, "en-US", entity_properties_plurals_mock.string, "plural1;plural2;plural3;plural4;plural5", False, ) == { "clWarnings": ["expecting 2 plurals, found 5"] }
def test_tt_disabled_checks( entity_properties_mock, entity_dtd_mock, run_tt_checks_mock, ): """ Check if overlapping checks are disabled in Translate Toolkit. """ assert run_checks( entity_properties_mock, 'en-US', entity_properties_mock.string, 'invalid translation \q', True, False, ).content == json.dumps({ 'failedChecks': { 'clWarnings': ['unknown escape sequence, \q'] }, }) run_tt_checks_mock.assert_called_with(ANY, ANY, ANY, {'escapes', 'nplurals', 'printf'}) assert run_checks(entity_dtd_mock, 'en-US', entity_properties_mock.string, 'Translated string', True, False) is None assert not run_tt_checks_mock.assert_called_with( ANY, ANY, ANY, {'acronyms', 'gconf', 'kdecomments'})
def test_ignore_warnings(entity_properties_plurals_mock, ): """ Check if logic of ignore_warnings works when there are errors. """ assert run_checks( entity_properties_plurals_mock, 'en-US', entity_properties_plurals_mock.string, 'plural1;plural2;plural3;plural4;plural5', True, False, ).content == json.dumps({ 'failedChecks': { 'clWarnings': ['expecting 2 plurals, found 5'], 'ttWarnings': ['Simple capitalization', 'Starting capitalization'] } }) # Warnings can be ignored for Translate Toolkit if user decides to do so assert run_checks( entity_properties_plurals_mock, 'en-US', entity_properties_plurals_mock.string, 'plural1;plural2;plural3;plural4;plural5', False, False, ).content == json.dumps( {'failedChecks': { 'clWarnings': ['expecting 2 plurals, found 5'], }})
def test_ignore_warnings( entity_properties_plurals_mock, ): """ Check if logic of ignore_warnings works when there are errors. """ assert run_checks( entity_properties_plurals_mock, 'en-US', entity_properties_plurals_mock.string, 'plural1;plural2;plural3;plural4;plural5', True, ) == { 'clWarnings': ['expecting 2 plurals, found 5'], 'ttWarnings': ['Simple capitalization', 'Starting capitalization'] } # Warnings can be ignored for Translate Toolkit if user decides to do so assert run_checks( entity_properties_plurals_mock, 'en-US', entity_properties_plurals_mock.string, 'plural1;plural2;plural3;plural4;plural5', False, ) == { 'clWarnings': ['expecting 2 plurals, found 5'], }
def test_invalid_resource_compare_locales(entity_invalid_resource_mock, ): """ Unsupported resource shouldn't raise an error. """ assert run_checks(entity_invalid_resource_mock, 'en-US', entity_invalid_resource_mock.string, 'Translation', False, False) is None
def perform_checks(request): """Perform quality checks and return a list of any failed ones.""" try: entity = request.POST['entity'] locale_code = request.POST['locale_code'] original = request.POST['original'] string = request.POST['string'] ignore_warnings = request.POST.get('ignore_warnings', 'false') == 'true' except MultiValueDictKeyError as e: return HttpResponseBadRequest('Bad Request: {error}'.format(error=e)) try: entity = Entity.objects.get(pk=entity) except Entity.DoesNotExist as e: return HttpResponseBadRequest('Bad Request: {error}'.format(error=e)) try: use_ttk_checks = UserProfile.objects.get(user=request.user).quality_checks except UserProfile.DoesNotExist: use_ttk_checks = True failed_checks = run_checks( entity, locale_code, original, string, use_ttk_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ 'failedChecks': failed_checks, }) else: return HttpResponse('ok')
def perform_checks(request): """Perform quality checks and return a list of any failed ones.""" try: entity = request.POST["entity"] locale_code = request.POST["locale_code"] original = request.POST["original"] string = request.POST["string"] ignore_warnings = request.POST.get("ignore_warnings", "false") == "true" except MultiValueDictKeyError as e: return JsonResponse( {"status": False, "message": "Bad Request: {error}".format(error=e)}, status=400, ) try: entity = Entity.objects.get(pk=entity) except Entity.DoesNotExist as e: return JsonResponse( {"status": False, "message": "Bad Request: {error}".format(error=e)}, status=400, ) failed_checks = run_checks( entity, locale_code, original, string, request.user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({"failedChecks": failed_checks}) else: return JsonResponse({"status": True})
def bulk_run_checks(translations): """ Run checks on a list of translations *Important* To avoid performance problems, translations have to prefetch entities and locales objects. """ warnings, errors = [], [] if not translations: return for translation in translations: warnings_, errors_ = get_failed_checks_db_objects( translation, run_checks(translation.entity, translation.locale.code, translation.entity.string, translation.string, use_tt_checks=False)) warnings.extend(warnings_) errors.extend(errors_) # Remove old warnings and errors Warning.objects.filter( translation__pk__in=[t.pk for t in translations]).delete() Error.objects.filter(translation__pk__in=[t.pk for t in translations]).delete() # Insert new warnings and errors Warning.objects.bulk_create(warnings) Error.objects.bulk_create(errors) return warnings, errors
def test_tt_checks_simple_ftl( entity_ftl_mock, run_tt_checks_mock, ): """ Check translate toolkit checks for a simple ftl entity """ translated_string = dedent(""" windowTitle = Translated string .pontoon = is kewl """) run_checks( entity_ftl_mock, "en-US", entity_ftl_mock.string, translated_string, True, ) run_tt_checks_mock.assert_called_with( "Untranslated string is cool", "Translated string is kewl", ANY, { "acronyms", "gconf", "kdecomments", "untranslated", "doublespacing", "endwhitespace", "escapes", "newlines", "numbers", "printf", "singlequoting", "startwhitespace", "pythonbraceformat", "doublequoting", }, None, {"varmatches": [("{$", "}")]}, )
def test_invalid_resource_compare_locales(entity_invalid_resource_mock, ): """ Unsupported resource shouldn't raise an error. """ assert (run_checks( entity_invalid_resource_mock, "en-US", entity_invalid_resource_mock.string, "Translation", False, ) == {})
def test_tt_disabled_checks( entity_properties_mock, entity_dtd_mock, run_tt_checks_mock, ): """ Check if overlapping checks are disabled in Translate Toolkit. """ assert run_checks( entity_properties_mock, "en-US", entity_properties_mock.string, "invalid translation \\q", True, ) == { "clWarnings": ["unknown escape sequence, \\q"] } run_tt_checks_mock.assert_called_with( ANY, ANY, ANY, { "escapes", "acronyms", "printf", "gconf", "kdecomments", "nplurals", "untranslated", }, ) assert (run_checks( entity_dtd_mock, "en-US", entity_properties_mock.string, "Translated string", True, ) == {}) assert not run_tt_checks_mock.assert_called_with( ANY, ANY, ANY, {"acronyms", "gconf", "kdecomments", "untranslated"})
def test_tt_disabled_checks( entity_properties_mock, entity_dtd_mock, run_tt_checks_mock, ): """ Check if overlapping checks are disabled in Translate Toolkit. """ assert run_checks( entity_properties_mock, 'en-US', entity_properties_mock.string, 'invalid translation \q', True, ) == { 'clWarnings': [ 'unknown escape sequence, \q' ] } run_tt_checks_mock.assert_called_with( ANY, ANY, ANY, {'escapes', 'acronyms', 'printf', 'gconf', 'kdecomments', 'nplurals'}, ) assert run_checks( entity_dtd_mock, 'en-US', entity_properties_mock.string, 'Translated string', True, ) == {} assert not run_tt_checks_mock.assert_called_with( ANY, ANY, ANY, {'acronyms', 'gconf', 'kdecomments'} )
def test_invalid_resource_compare_locales( entity_invalid_resource_mock, ): """ Unsupported resource shouldn't raise an error. """ assert run_checks( entity_invalid_resource_mock, 'en-US', entity_invalid_resource_mock.string, 'Translation', False ) == {}
def test_tt_disabled_checks( entity_properties_mock, entity_dtd_mock, run_tt_checks_mock, ): """ Check if overlapping checks are disabled in Translate Toolkit. """ assert run_checks( entity_properties_mock, 'en-US', entity_properties_mock.string, 'invalid translation \q', True, ) == { 'clWarnings': ['unknown escape sequence, \q'] } run_tt_checks_mock.assert_called_with( ANY, ANY, ANY, { 'escapes', 'acronyms', 'printf', 'gconf', 'kdecomments', 'nplurals', 'untranslated' }, ) assert run_checks( entity_dtd_mock, 'en-US', entity_properties_mock.string, 'Translated string', True, ) == {} assert not run_tt_checks_mock.assert_called_with( ANY, ANY, ANY, {'acronyms', 'gconf', 'kdecomments', 'untranslated'})
def perform_checks(request): """Perform quality checks and return a list of any failed ones.""" try: entity = request.POST['entity'] locale_code = request.POST['locale_code'] original = request.POST['original'] string = request.POST['string'] ignore_warnings = request.POST.get('ignore_warnings', 'false') == 'true' except MultiValueDictKeyError as e: return JsonResponse( { 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) try: entity = Entity.objects.get(pk=entity) except Entity.DoesNotExist as e: return JsonResponse( { 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) failed_checks = run_checks( entity, locale_code, original, string, request.user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ 'failedChecks': failed_checks, }) else: return JsonResponse({ 'status': True, })
def perform_checks(request): """Perform quality checks and return a list of any failed ones.""" try: entity = request.POST['entity'] locale_code = request.POST['locale_code'] original = request.POST['original'] string = request.POST['string'] ignore_warnings = request.POST.get('ignore_warnings', 'false') == 'true' except MultiValueDictKeyError as e: return JsonResponse({ 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) try: entity = Entity.objects.get(pk=entity) except Entity.DoesNotExist as e: return JsonResponse({ 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) failed_checks = run_checks( entity, locale_code, original, string, request.user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ 'failedChecks': failed_checks, }) else: return JsonResponse({ 'status': True, })
def approve_translation(request): """Approve given translation.""" try: t = request.POST["translation"] ignore_warnings = request.POST.get("ignore_warnings", "false") == "true" paths = request.POST.getlist("paths[]") except MultiValueDictKeyError as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) translation = get_object_or_404(Translation, pk=t) entity = translation.entity project = entity.resource.project locale = translation.locale user = request.user # Read-only translations cannot be approved if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) if translation.approved: return JsonResponse( { "status": False, "message": "Forbidden: This translation is already approved.", }, status=403, ) # Only privileged users can approve translations if not user.can_translate(locale, project): return JsonResponse( { "status": False, "message": "Forbidden: You don't have permission to approve this translation.", }, status=403, ) # Check for errors. # Checks are disabled for the tutorial. use_checks = project.slug != "tutorial" if use_checks: failed_checks = run_checks( entity, locale.code, entity.string, translation.string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ "string": translation.string, "failedChecks": failed_checks }) translation.approve(user) log_action("translation:approved", user, translation=translation) active_translation = translation.entity.reset_active_translation( locale=locale, plural_form=translation.plural_form, ) return JsonResponse({ "translation": active_translation.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), })
def update_translation(request): """Update entity translation for the specified locale and user. Note that this view is also used to approve a translation by the old Translate app. Once we migrate to Translate.Next, we'll want to rework this view to remove the bits about approving a translation, because that has been delegated to the `approve_translation` view. """ try: entity = request.POST['entity'] string = request.POST['translation'] locale = request.POST['locale'] plural_form = request.POST['plural_form'] original = request.POST['original'] ignore_warnings = request.POST.get('ignore_warnings', 'false') == 'true' approve = request.POST.get('approve', 'false') == 'true' force_suggestions = request.POST.get('force_suggestions', 'false') == 'true' paths = request.POST.getlist('paths[]') except MultiValueDictKeyError as e: return JsonResponse( { 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) try: e = Entity.objects.get(pk=entity) except Entity.DoesNotExist as e: return JsonResponse( { 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) try: locale = Locale.objects.get(code=locale) except Locale.DoesNotExist as e: return JsonResponse( { 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) if plural_form == "-1": plural_form = None user = request.user project = e.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return JsonResponse( { 'status': False, 'message': 'Forbidden: This string is in read-only mode.', }, status=403) now = timezone.now() can_translate = (request.user.can_translate(project=project, locale=locale) and (not force_suggestions or approve)) translations = Translation.objects.filter(entity=e, locale=locale, plural_form=plural_form) same_translations = translations.filter(string=string).order_by( '-active', 'rejected', '-date') # If same translation exists in the DB, don't save it again. if utils.is_same(same_translations, can_translate): return JsonResponse({ 'same': True, }) # Check for errors. # Checks are disabled for the tutorial. use_checks = project.slug != 'tutorial' if use_checks: failed_checks = run_checks( e, locale.code, original, string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ 'failedChecks': failed_checks, }) # Translations exist if len(translations) > 0: # Same translation exists if len(same_translations) > 0: t = same_translations[0] # If added by privileged user, approve and unfuzzy it if can_translate and (t.fuzzy or not t.approved): if not t.active: translations.filter(active=True).update(active=False) t.active = True t.approved = True t.fuzzy = False t.rejected = False t.rejected_user = None t.rejected_date = None if t.approved_user is None: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) return JsonResponse({ 'type': 'updated', 'translation': t.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), }) # Different translation added else: t = Translation( entity=e, locale=locale, plural_form=plural_form, string=string, user=user, date=now, approved=can_translate, ) if can_translate: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) active_translation = e.reset_active_translation( locale=locale, plural_form=plural_form, ) return JsonResponse({ 'type': 'added', 'translation': active_translation.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), }) # No translations saved yet else: t = Translation( entity=e, locale=locale, plural_form=plural_form, string=string, user=user, date=now, active=True, approved=can_translate, ) if can_translate: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) return JsonResponse({ 'type': 'saved', 'translation': t.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), })
def approve_translation(request): """Approve given translation.""" try: t = request.POST['translation'] ignore_warnings = request.POST.get('ignore_warnings', 'false') == 'true' paths = request.POST.getlist('paths[]') except MultiValueDictKeyError as e: return JsonResponse( { 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) translation = get_object_or_404(Translation, pk=t) entity = translation.entity project = entity.resource.project locale = translation.locale user = request.user # Read-only translations cannot be approved if utils.readonly_exists(project, locale): return JsonResponse( { 'status': False, 'message': 'Forbidden: This string is in read-only mode.', }, status=403) if translation.approved: return JsonResponse( { 'status': False, 'message': 'Forbidden: This translation is already approved.', }, status=403) # Only privileged users can approve translations if not user.can_translate(locale, project): return JsonResponse( { 'status': False, 'message': "Forbidden: You don't have permission to approve this translation.", }, status=403) # Check for errors. # Checks are disabled for the tutorial. use_checks = project.slug != 'tutorial' if use_checks: failed_checks = run_checks( entity, locale.code, entity.string, translation.string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ 'string': translation.string, 'failedChecks': failed_checks, }) translation.approve(user) active_translation = translation.entity.reset_active_translation( locale=locale, plural_form=translation.plural_form, ) return JsonResponse({ 'translation': active_translation.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), })
def approve_translation(request): """Approve given translation.""" try: t = request.POST['translation'] ignore_warnings = request.POST.get('ignore_warnings', 'false') == 'true' paths = request.POST.getlist('paths[]') except MultiValueDictKeyError as e: return JsonResponse({ 'status': False, 'message': 'Bad Request: {error}'.format(error=e), }, status=400) translation = get_object_or_404(Translation, pk=t) entity = translation.entity project = entity.resource.project locale = translation.locale user = request.user # Read-only translations cannot be approved if utils.readonly_exists(project, locale): return JsonResponse({ 'status': False, 'message': 'Forbidden: This string is in read-only mode.', }, status=403) if translation.approved: return JsonResponse({ 'status': False, 'message': 'Forbidden: This translation is already approved.', }, status=403) # Only privileged users can approve translations if not user.can_translate(locale, project): return JsonResponse({ 'status': False, 'message': "Forbidden: You don't have permission to approve this translation.", }, status=403) # Check for errors. # Checks are disabled for the tutorial. use_checks = project.slug != 'tutorial' if use_checks: failed_checks = run_checks( entity, locale.code, entity.string, translation.string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ 'string': translation.string, 'failedChecks': failed_checks, }) translation.approve(user) active_translation = translation.entity.reset_active_translation( locale=locale, plural_form=translation.plural_form, ) return JsonResponse({ 'translation': active_translation.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), })
def update_translation(request): """Update entity translation for the specified locale and user.""" try: entity = request.POST['entity'] string = request.POST['translation'] locale = request.POST['locale'] plural_form = request.POST['plural_form'] original = request.POST['original'] ignore_warnings = request.POST.get('ignore_warnings', 'false') == 'true' approve = request.POST.get('approve', 'false') == 'true' force_suggestions = request.POST.get('force_suggestions', 'false') == 'true' paths = request.POST.getlist('paths[]') except MultiValueDictKeyError as e: return HttpResponseBadRequest('Bad Request: {error}'.format(error=e)) try: e = Entity.objects.get(pk=entity) except Entity.DoesNotExist as error: log.error(str(error)) return HttpResponse("error") try: locale = Locale.objects.get(code=locale) except Locale.DoesNotExist as error: log.error(str(error)) return HttpResponse("error") if plural_form == "-1": plural_form = None user = request.user project = e.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return HttpResponseForbidden( "Forbidden: This string is in read-only mode" ) try: use_ttk_checks = UserProfile.objects.get(user=user).quality_checks except UserProfile.DoesNotExist as error: use_ttk_checks = True # Disable checks for tutorial project. if project.slug == 'tutorial': use_ttk_checks = False now = timezone.now() can_translate = ( request.user.can_translate(project=project, locale=locale) and (not force_suggestions or approve) ) translations = Translation.objects.filter( entity=e, locale=locale, plural_form=plural_form) same_translations = translations.filter(string=string).order_by( '-approved', 'rejected', '-date' ) # If same translation exists in the DB, don't save it again. if utils.is_same(same_translations, can_translate): return JsonResponse({ 'same': True, }) failed_checks = run_checks( e, locale.code, original, string, use_ttk_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ 'failedChecks': failed_checks, }) # Translations exist if len(translations) > 0: if len(same_translations) > 0: t = same_translations[0] # If added by privileged user, approve and unfuzzy it if can_translate and (t.fuzzy or not t.approved): translations.update( approved=False, approved_user=None, approved_date=None, rejected=True, rejected_user=request.user, rejected_date=timezone.now(), fuzzy=False, ) t.approved = True t.fuzzy = False t.rejected = False t.rejected_user = None t.rejected_date = None if t.approved_user is None: t.approved_user = user t.approved_date = now # If added by non-privileged user and fuzzy, unfuzzy it elif t.fuzzy: t.approved = False t.approved_user = None t.approved_date = None t.fuzzy = False t.save() t.warnings.all().delete() t.errors.all().delete() save_failed_checks(t, failed_checks) return JsonResponse({ 'type': 'updated', 'translation': t.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), }) # Different translation added else: if can_translate: translations.update(approved=False, approved_user=None, approved_date=None) translations.update(fuzzy=False) t = Translation( entity=e, locale=locale, user=user, string=string, plural_form=plural_form, date=now, approved=can_translate) if can_translate: t.approved_user = user t.approved_date = now t.save() save_failed_checks(t, failed_checks) # Return active (approved or latest) translation try: active = translations.filter(approved=True).latest("date") except Translation.DoesNotExist: active = translations.latest("date") return JsonResponse({ 'type': 'added', 'translation': active.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), }) # No translations saved yet else: t = Translation( entity=e, locale=locale, user=user, string=string, plural_form=plural_form, date=now, approved=can_translate) if can_translate: t.approved_user = user t.approved_date = now t.save() save_failed_checks(t, failed_checks) return JsonResponse({ 'type': 'saved', 'translation': t.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), })
def create_translation(request): """ Create a new translation. """ form = forms.CreateTranslationForm(request.POST) if not form.is_valid(): problems = [] for field, errors in form.errors.items(): problems.append('Error validating field `{0}`: "{1}"'.format( field, " ".join(errors))) return JsonResponse({ "status": False, "message": "\n".join(problems) }, status=400) entity = form.cleaned_data["entity"] string = form.cleaned_data["translation"] locale = form.cleaned_data["locale"] plural_form = form.cleaned_data["plural_form"] original = form.cleaned_data["original"] ignore_warnings = form.cleaned_data["ignore_warnings"] approve = form.cleaned_data["approve"] force_suggestions = form.cleaned_data["force_suggestions"] resources = form.cleaned_data["paths"] project = entity.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) translations = Translation.objects.filter(entity=entity, locale=locale, plural_form=plural_form) same_translations = translations.filter(string=string) # If same translation exists in the DB, don't save it again. if same_translations: return JsonResponse({"status": False, "same": True}) # Look for failed checks. # Checks are disabled for the tutorial. use_checks = project.slug != "tutorial" user = request.user failed_checks = None if use_checks: failed_checks = run_checks( entity, locale.code, original, string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ "status": False, "failedChecks": failed_checks }) now = timezone.now() can_translate = user.can_translate( project=project, locale=locale) and (not force_suggestions or approve) translation = Translation( entity=entity, locale=locale, plural_form=plural_form, string=string, user=user, date=now, approved=can_translate, ) if can_translate: translation.approved_user = user translation.approved_date = now translation.save(failed_checks=failed_checks) log_action("translation:created", user, translation=translation) if translations: translation = entity.reset_active_translation( locale=locale, plural_form=plural_form, ) return JsonResponse({ "status": True, "translation": translation.serialize(), "stats": TranslatedResource.objects.stats(project, resources, locale), })
def find_and_replace(translations, find, replace, user): """Replace text in a set of translation. :arg QuerySet translations: a list of Translation objects in which to search :arg string find: a string to search for, and replace, in translations :arg string replace: what to replace the original string with :arg User user: the User making the change :returns: a tuple with: - a queryset of old translations to be changed - a list of new translations to be created - a list of PKs of translations with errors """ translations = translations.filter(string__contains=find) # No matches found if translations.count() == 0: return translations, [], [] # Create translations' clones and replace strings now = timezone.now() translations_to_create = [] translations_with_errors = [] # To speed-up error checks, translations will prefetch additional fields translations = ( translations.for_checks(only_db_formats=False) ) for translation in translations: # Cache the old value to identify changed translations string = translation.string old_translation_pk = translation.pk if translation.entity.resource.format == 'ftl': translation.string = ftl_find_and_replace(string, find, replace) else: translation.string = string.replace(find, replace) # Quit early if no changes are made if translation.string == string: continue translation.pk = None # Create new translation translation.user = translation.approved_user = user translation.date = translation.approved_date = now translation.approved = True translation.rejected = False translation.rejected_date = None translation.rejected_user = None translation.fuzzy = False if translation.entity.resource.format in DB_FORMATS: errors = run_checks( translation.entity, translation.locale.code, translation.entity.string, translation.string, use_tt_checks=False, ) else: errors = {} if errors: translations_with_errors.append(old_translation_pk) else: translations_to_create.append(translation) if translations_with_errors: translations = translations.exclude( pk__in=translations_with_errors ) return ( translations, translations_to_create, translations_with_errors, )
def find_and_replace(translations, find, replace, user): """Replace text in a set of translation. :arg QuerySet translations: a list of Translation objects in which to search :arg string find: a string to search for, and replace, in translations :arg string replace: what to replace the original string with :arg User user: the User making the change :returns: a tuple with: - a queryset of old translations to be changed - a list of new translations to be created - a list of PKs of translations with errors """ translations = translations.filter(string__contains=find) # No matches found if translations.count() == 0: return translations, [], [] # Create translations' clones and replace strings now = timezone.now() translations_to_create = [] translations_with_errors = [] # To speed-up error checks, translations will prefetch additional fields translations = translations.for_checks(only_db_formats=False) for translation in translations: # Cache the old value to identify changed translations new_translation = deepcopy(translation) if translation.entity.resource.format == "ftl": new_translation.string = ftl_find_and_replace( translation.string, find, replace) else: new_translation.string = translation.string.replace(find, replace) # Quit early if no changes are made if new_translation.string == translation.string: continue new_translation.pk = None # Create new translation new_translation.user = new_translation.approved_user = user new_translation.date = new_translation.approved_date = now new_translation.approved = True new_translation.rejected = False new_translation.rejected_date = None new_translation.rejected_user = None new_translation.fuzzy = False if new_translation.entity.resource.format in DB_FORMATS: errors = run_checks( new_translation.entity, new_translation.locale.code, new_translation.entity.string, new_translation.string, use_tt_checks=False, ) else: errors = {} if errors: translations_with_errors.append(translation.pk) else: translations_to_create.append(new_translation) if translations_with_errors: translations = translations.exclude(pk__in=translations_with_errors) return ( translations, translations_to_create, translations_with_errors, )
def update_translation(request): """Update entity translation for the specified locale and user. Note that this view is also used to approve a translation by the old Translate app. Once we migrate to Translate.Next, we'll want to rework this view to remove the bits about approving a translation, because that has been delegated to the `approve_translation` view. """ try: entity = request.POST["entity"] string = request.POST["translation"] locale = request.POST["locale"] plural_form = request.POST["plural_form"] original = request.POST["original"] ignore_warnings = request.POST.get("ignore_warnings", "false") == "true" approve = request.POST.get("approve", "false") == "true" force_suggestions = request.POST.get("force_suggestions", "false") == "true" paths = request.POST.getlist("paths[]") except MultiValueDictKeyError as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) try: e = Entity.objects.get(pk=entity) except Entity.DoesNotExist as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) try: locale = Locale.objects.get(code=locale) except Locale.DoesNotExist as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) if plural_form == "-1": plural_form = None user = request.user project = e.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) now = timezone.now() can_translate = request.user.can_translate( project=project, locale=locale) and (not force_suggestions or approve) translations = Translation.objects.filter(entity=e, locale=locale, plural_form=plural_form) same_translations = translations.filter(string=string).order_by( "-active", "rejected", "-date") # If same translation exists in the DB, don't save it again. if utils.is_same(same_translations, can_translate): return JsonResponse({"same": True}) # Check for errors. # Checks are disabled for the tutorial. use_checks = project.slug != "tutorial" failed_checks = None if use_checks: failed_checks = run_checks( e, locale.code, original, string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({"failedChecks": failed_checks}) # Translations exist if len(translations) > 0: # Same translation exists if len(same_translations) > 0: t = same_translations[0] # If added by privileged user, approve and unfuzzy it if can_translate and (t.fuzzy or not t.approved): if not t.active: translations.filter(active=True).update(active=False) t.active = True t.approved = True t.fuzzy = False t.rejected = False t.rejected_user = None t.rejected_date = None if t.approved_user is None: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) log_action("translation:approved", user, translation=t) return JsonResponse({ "type": "updated", "translation": t.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), }) # Different translation added else: t = Translation( entity=e, locale=locale, plural_form=plural_form, string=string, user=user, date=now, approved=can_translate, ) if can_translate: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) log_action("translation:created", user, translation=t) active_translation = e.reset_active_translation( locale=locale, plural_form=plural_form, ) return JsonResponse({ "type": "added", "translation": active_translation.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), }) # No translations saved yet else: t = Translation( entity=e, locale=locale, plural_form=plural_form, string=string, user=user, date=now, active=True, approved=can_translate, ) if can_translate: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) log_action("translation:created", user, translation=t) return JsonResponse({ "type": "saved", "translation": t.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), })
def create_translation(request): """ Create a new translation. """ form = forms.CreateTranslationForm(request.POST) if not form.is_valid(): problems = [] for field, errors in form.errors.items(): problems.append( 'Error validating field `{}`: "{}"'.format(field, " ".join(errors)) ) return JsonResponse( {"status": False, "message": "\n".join(problems)}, status=400 ) entity = form.cleaned_data["entity"] string = form.cleaned_data["translation"] locale = form.cleaned_data["locale"] plural_form = form.cleaned_data["plural_form"] original = form.cleaned_data["original"] ignore_warnings = form.cleaned_data["ignore_warnings"] approve = form.cleaned_data["approve"] force_suggestions = form.cleaned_data["force_suggestions"] paths = form.cleaned_data["paths"] machinery_sources = form.cleaned_data["machinery_sources"] project = entity.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) translations = Translation.objects.filter( entity=entity, locale=locale, plural_form=plural_form, ) same_translations = translations.filter(string=string) # If same translation exists in the DB, don't save it again. if same_translations: return JsonResponse({"status": False, "same": True}) # Look for failed checks. # Checks are disabled for the tutorial. use_checks = project.slug != "tutorial" user = request.user first_contribution = user.is_new_contributor(locale) failed_checks = None if use_checks: failed_checks = run_checks( entity, locale.code, original, string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({"status": False, "failedChecks": failed_checks}) now = timezone.now() can_translate = user.can_translate(project=project, locale=locale) and ( not force_suggestions or approve ) translation = Translation( entity=entity, locale=locale, plural_form=plural_form, string=string, user=user, date=now, approved=can_translate, machinery_sources=machinery_sources, ) if can_translate: translation.approved_user = user translation.approved_date = now translation.save(failed_checks=failed_checks) log_action(ActionLog.ActionType.TRANSLATION_CREATED, user, translation=translation) if translations: translation = entity.reset_active_translation( locale=locale, plural_form=plural_form, ) # When user makes their first contribution to the team, notify team managers if first_contribution: desc = """ <a href="{user_href}">{user}</a> has made their first contribution to <a href="{locale_href}">{locale} ({locale_code})</a>. Please welcome them to the team, and make sure to <a href="{review_href}">review their suggestions</a>. """.format( user=user.name_or_email, user_href=reverse( "pontoon.contributors.contributor.username", kwargs={ "username": user.username, }, ), locale=locale.name, locale_code=locale.code, locale_href=reverse( "pontoon.teams.team", kwargs={ "locale": locale.code, }, ), review_href=reverse( "pontoon.translate", kwargs={ "locale": locale.code, "project": project.slug, "resource": entity.resource.path, }, ) + f"?string={entity.pk}", ) for manager in locale.managers_group.user_set.filter( profile__new_contributor_notifications=True ): notify.send( sender=manager, recipient=manager, verb="has reviewed suggestions", # Triggers render of description only description=desc, ) return JsonResponse( { "status": True, "translation": translation.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), } )
def update_translation(request): """Update entity translation for the specified locale and user. Note that this view is also used to approve a translation by the old Translate app. Once we migrate to Translate.Next, we'll want to rework this view to remove the bits about approving a translation, because that has been delegated to the `approve_translation` view. """ try: entity = request.POST['entity'] string = request.POST['translation'] locale = request.POST['locale'] plural_form = request.POST['plural_form'] original = request.POST['original'] ignore_warnings = request.POST.get('ignore_warnings', 'false') == 'true' approve = request.POST.get('approve', 'false') == 'true' force_suggestions = request.POST.get('force_suggestions', 'false') == 'true' paths = request.POST.getlist('paths[]') except MultiValueDictKeyError as e: return HttpResponseBadRequest('Bad Request: {error}'.format(error=e)) try: e = Entity.objects.get(pk=entity) except Entity.DoesNotExist as error: log.error(str(error)) return HttpResponse("error") try: locale = Locale.objects.get(code=locale) except Locale.DoesNotExist as error: log.error(str(error)) return HttpResponse("error") if plural_form == "-1": plural_form = None user = request.user project = e.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return HttpResponseForbidden( "Forbidden: This string is in read-only mode" ) try: use_ttk_checks = UserProfile.objects.get(user=user).quality_checks except UserProfile.DoesNotExist as error: use_ttk_checks = True # Disable checks for tutorial project. if project.slug == 'tutorial': use_ttk_checks = False now = timezone.now() can_translate = ( request.user.can_translate(project=project, locale=locale) and (not force_suggestions or approve) ) translations = Translation.objects.filter( entity=e, locale=locale, plural_form=plural_form) same_translations = translations.filter(string=string).order_by( '-active', 'rejected', '-date' ) # If same translation exists in the DB, don't save it again. if utils.is_same(same_translations, can_translate): return JsonResponse({ 'same': True, }) failed_checks = run_checks( e, locale.code, original, string, use_ttk_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ 'failedChecks': failed_checks, }) # Translations exist if len(translations) > 0: # Same translation exists if len(same_translations) > 0: t = same_translations[0] # If added by privileged user, approve and unfuzzy it if can_translate and (t.fuzzy or not t.approved): if not t.active: translations.filter(active=True).update(active=False) t.active = True t.approved = True t.fuzzy = False t.rejected = False t.rejected_user = None t.rejected_date = None if t.approved_user is None: t.approved_user = user t.approved_date = now t.save() t.warnings.all().delete() t.errors.all().delete() save_failed_checks(t, failed_checks) return JsonResponse({ 'type': 'updated', 'translation': t.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), }) # Different translation added else: t = Translation( entity=e, locale=locale, plural_form=plural_form, string=string, user=user, date=now, approved=can_translate, ) if can_translate: t.approved_user = user t.approved_date = now t.save() save_failed_checks(t, failed_checks) active_translation = e.reset_active_translation( locale=locale, plural_form=plural_form, ) return JsonResponse({ 'type': 'added', 'translation': active_translation.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), }) # No translations saved yet else: t = Translation( entity=e, locale=locale, plural_form=plural_form, string=string, user=user, date=now, active=True, approved=can_translate, ) if can_translate: t.approved_user = user t.approved_date = now t.save() save_failed_checks(t, failed_checks) return JsonResponse({ 'type': 'saved', 'translation': t.serialize(), 'stats': TranslatedResource.objects.stats(project, paths, locale), })