Add Pretranslated status and drop Fuzzy as status (#2463)

* Fix Time Range layout in local environment

* Remove duplicate CSS rule

* Fix typo in the spec

* Move Fuzzy filter from Status to Extra

* Show Fuzzy strings as Missing and support Pretranslated in the string list

* Set fuzzy stats to 0

* Link to Missing strings only in new string notifications (which now includes Fuzzy)

* Model changes:
- Rename fuzzy_strings to pretranslated_strings in AggregatedStats models
- Add Locale.pretranslated field
- Male relevant logic changes (for stats/insights calculation, active translation selection, etc.)

* Replace fuzzy with pretranslated on dashboards and charts

* Add missing fuzzy property on EntityTranslation type

* Show failed checks for pretranslated

* Add Pretranslated status

* Remove Fuzzy from Contributors and Profile pages

* Render Pretranslated in History panel

* Last bit of translate app changes

* Batch actions: set Pretranslated flag to false

* Do not send suggestion notifications for Pretranslated

* More robust tests

* Do not approve pretranslated strings on sync

* Disable Pretranslated in Dashboards, Filter and Translate view progress chart

* Attempt to fix pytest DB error

* Add comments explaining why Pretranslated status and filter are disabled

* Q objects don't need a wrapping call when they're combined via | or &

* Rebase migrations
This commit is contained in:
Matjaž Horvat 2022-04-11 15:35:17 +02:00 коммит произвёл GitHub
Родитель 9e77a9404e
Коммит 17138c608c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
64 изменённых файлов: 682 добавлений и 375 удалений

Просмотреть файл

@ -37,7 +37,7 @@ class ProjectLocale(DjangoObjectType, Stats):
"locale",
"total_strings",
"approved_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",
@ -62,7 +62,7 @@ class Project(DjangoObjectType, Stats):
"contact",
"total_strings",
"approved_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",
@ -91,7 +91,7 @@ class Locale(DjangoObjectType, Stats):
"population",
"total_strings",
"approved_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",

Просмотреть файл

@ -20,7 +20,7 @@ from pontoon.teams.utils import log_user_groups
AGGREGATED_STATS_FIELDS = (
"total_strings",
"approved_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",

Просмотреть файл

@ -0,0 +1,41 @@
# Generated by Django 3.2.10 on 2022-03-23 22:54
from django.db import migrations
def adjust_stats(model):
model.fuzzy_strings = 0
model.save(update_fields=["fuzzy_strings"])
def fuzzy_to_missing(apps, schema_editor):
ProjectLocale = apps.get_model("base", "ProjectLocale")
TranslatedResource = apps.get_model("base", "TranslatedResource")
for tr in TranslatedResource.objects.filter(fuzzy_strings__gt=0):
project = tr.resource.project
locale = tr.locale
adjust_stats(tr)
adjust_stats(project)
adjust_stats(locale)
try:
project_locale = ProjectLocale.objects.get(project=project, locale=locale)
adjust_stats(project_locale)
except ProjectLocale.DoesNotExist:
return None
class Migration(migrations.Migration):
dependencies = [
("base", "0022_reviewed_suggestions"),
]
operations = [
migrations.RunPython(
code=fuzzy_to_missing,
reverse_code=migrations.RunPython.noop,
),
]

Просмотреть файл

@ -0,0 +1,51 @@
# Generated by Django 3.2.10 on 2022-03-24 01:12
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("base", "0023_fuzzy_to_missing"),
]
operations = [
migrations.RenameField(
model_name="locale",
old_name="fuzzy_strings",
new_name="pretranslated_strings",
),
migrations.RenameField(
model_name="project",
old_name="fuzzy_strings",
new_name="pretranslated_strings",
),
migrations.RenameField(
model_name="projectlocale",
old_name="fuzzy_strings",
new_name="pretranslated_strings",
),
migrations.RenameField(
model_name="translatedresource",
old_name="fuzzy_strings",
new_name="pretranslated_strings",
),
migrations.AddField(
model_name="translation",
name="pretranslated",
field=models.BooleanField(default=False),
),
migrations.AlterIndexTogether(
name="translation",
index_together={
("entity", "user", "approved", "pretranslated"),
("locale", "user", "entity"),
("date", "locale"),
("entity", "locale", "approved"),
("entity", "locale", "fuzzy"),
("entity", "locale", "pretranslated"),
},
),
]

Просмотреть файл

@ -300,7 +300,7 @@ def serialized_notifications(self):
"part": "all-resources",
},
)
+ "?status=missing,fuzzy",
+ "?status=missing",
}
else:
actor = {
@ -443,7 +443,7 @@ class PermissionChangelog(models.Model):
class AggregatedStats(models.Model):
total_strings = models.PositiveIntegerField(default=0)
approved_strings = models.PositiveIntegerField(default=0)
fuzzy_strings = models.PositiveIntegerField(default=0)
pretranslated_strings = models.PositiveIntegerField(default=0)
strings_with_errors = models.PositiveIntegerField(default=0)
strings_with_warnings = models.PositiveIntegerField(default=0)
unreviewed_strings = models.PositiveIntegerField(default=0)
@ -459,7 +459,7 @@ class AggregatedStats(models.Model):
return {
"total_strings": sum(x.total_strings for x in qs),
"approved_strings": sum(x.approved_strings for x in qs),
"fuzzy_strings": sum(x.fuzzy_strings for x in qs),
"pretranslated_strings": sum(x.pretranslated_strings for x in qs),
"strings_with_errors": sum(x.strings_with_errors for x in qs),
"strings_with_warnings": sum(x.strings_with_warnings for x in qs),
"unreviewed_strings": sum(x.unreviewed_strings for x in qs),
@ -481,14 +481,16 @@ class AggregatedStats(models.Model):
self,
total_strings_diff,
approved_strings_diff,
fuzzy_strings_diff,
pretranslated_strings_diff,
strings_with_errors_diff,
strings_with_warnings_diff,
unreviewed_strings_diff,
):
self.total_strings = F("total_strings") + total_strings_diff
self.approved_strings = F("approved_strings") + approved_strings_diff
self.fuzzy_strings = F("fuzzy_strings") + fuzzy_strings_diff
self.pretranslated_strings = (
F("pretranslated_strings") + pretranslated_strings_diff
)
self.strings_with_errors = F("strings_with_errors") + strings_with_errors_diff
self.strings_with_warnings = (
F("strings_with_warnings") + strings_with_warnings_diff
@ -499,7 +501,7 @@ class AggregatedStats(models.Model):
update_fields=[
"total_strings",
"approved_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",
@ -511,7 +513,7 @@ class AggregatedStats(models.Model):
return (
self.total_strings
- self.approved_strings
- self.fuzzy_strings
- self.pretranslated_strings
- self.strings_with_errors
- self.strings_with_warnings
)
@ -533,8 +535,8 @@ class AggregatedStats(models.Model):
return self.percent_of_total(self.approved_strings)
@property
def fuzzy_percent(self):
return self.percent_of_total(self.fuzzy_strings)
def pretranslated_percent(self):
return self.percent_of_total(self.pretranslated_strings)
@property
def errors_percent(self):
@ -973,7 +975,7 @@ class Locale(AggregatedStats):
"title": "all-resources",
"resource__path": [],
"resource__total_strings": self.total_strings,
"fuzzy_strings": self.fuzzy_strings,
"pretranslated_strings": self.pretranslated_strings,
"strings_with_errors": self.strings_with_errors,
"strings_with_warnings": self.strings_with_warnings,
"unreviewed_strings": self.unreviewed_strings,
@ -991,7 +993,7 @@ class Locale(AggregatedStats):
"resource__path",
"resource__deadline",
"resource__total_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",
@ -1028,8 +1030,8 @@ class Locale(AggregatedStats):
resource__path=F("resources__path"),
resource__deadline=F("resources__deadline"),
resource__total_strings=F("resources__total_strings"),
fuzzy_strings=F(
"resources__translatedresources__fuzzy_strings"
pretranslated_strings=F(
"resources__translatedresources__pretranslated_strings"
),
strings_with_errors=F(
"resources__translatedresources__strings_with_errors"
@ -1056,8 +1058,8 @@ class Locale(AggregatedStats):
resource__path=F("project__resources__path"),
resource__deadline=F("project__resources__deadline"),
resource__total_strings=F("project__resources__total_strings"),
fuzzy_strings=F(
"project__resources__translatedresources__fuzzy_strings"
pretranslated_strings=F(
"project__resources__translatedresources__pretranslated_strings"
),
strings_with_errors=F(
"project__resources__translatedresources__strings_with_errors"
@ -1092,7 +1094,7 @@ class Locale(AggregatedStats):
"resource__path": [],
"resource__deadline": [],
"resource__total_strings": all_resources.total_strings,
"fuzzy_strings": all_resources.fuzzy_strings,
"pretranslated_strings": all_resources.pretranslated_strings,
"strings_with_errors": all_resources.strings_with_errors,
"strings_with_warnings": all_resources.strings_with_warnings,
"unreviewed_strings": all_resources.unreviewed_strings,
@ -1637,7 +1639,7 @@ class ProjectLocaleQuerySet(models.QuerySet):
return self.aggregate(
total_strings=Sum("total_strings"),
approved_strings=Sum("approved_strings"),
fuzzy_strings=Sum("fuzzy_strings"),
pretranslated_strings=Sum("pretranslated_strings"),
strings_with_errors=Sum("strings_with_errors"),
strings_with_warnings=Sum("strings_with_warnings"),
unreviewed_strings=Sum("unreviewed_strings"),
@ -1776,12 +1778,12 @@ class ProjectLocale(AggregatedStats):
return {
"total_strings": obj.total_strings,
"approved_strings": obj.approved_strings,
"fuzzy_strings": obj.fuzzy_strings,
"pretranslated_strings": obj.pretranslated_strings,
"strings_with_errors": obj.strings_with_errors,
"strings_with_warnings": obj.strings_with_warnings,
"unreviewed_strings": obj.unreviewed_strings,
"approved_share": round(obj.approved_percent),
"fuzzy_share": round(obj.fuzzy_percent),
"pretranslated_share": round(obj.pretranslated_percent),
"errors_share": round(obj.errors_percent),
"warnings_share": round(obj.warnings_percent),
"unreviewed_share": round(obj.unreviewed_percent),
@ -2252,7 +2254,7 @@ class EntityQuerySet(models.QuerySet):
"""Return a filter to be used to select entities marked as "missing".
An entity is marked as "missing" if at least one of its plural forms
has no approved or fuzzy translations.
has no approved or pretranslated translations.
:arg Locale locale: a Locale object to get translations for
@ -2262,28 +2264,8 @@ class EntityQuerySet(models.QuerySet):
return ~Q(
pk__in=self.get_filtered_entities(
locale,
Q(approved=True) | Q(fuzzy=True),
lambda x: x.approved or x.fuzzy,
project=project,
)
)
def fuzzy(self, locale, project=None):
"""Return a filter to be used to select entities marked as "fuzzy".
An entity is marked as "fuzzy" if all of its plural forms have a fuzzy
translation.
:arg Locale locale: a Locale object to get translations for
:returns: a django ORM Q object to use as a filter
"""
return Q(
pk__in=self.get_filtered_entities(
locale,
Q(fuzzy=True, warnings__isnull=True, errors__isnull=True),
lambda x: x.fuzzy,
Q(approved=True) | Q(pretranslated=True),
lambda x: x.approved or x.pretranslated,
project=project,
)
)
@ -2292,7 +2274,7 @@ class EntityQuerySet(models.QuerySet):
"""Return a filter to be used to select entities with translations with warnings.
This filter will return an entity if at least one of its plural forms
has an approved or fuzzy translation with a warning.
has an approved, pretranslated or fuzzy translation with a warning.
:arg Locale locale: a Locale object to get translations for
@ -2302,8 +2284,10 @@ class EntityQuerySet(models.QuerySet):
return Q(
pk__in=self.get_filtered_entities(
locale,
Q(Q(Q(approved=True) | Q(fuzzy=True)) & Q(warnings__isnull=False)),
lambda x: (x.approved or x.fuzzy) and x.warnings.count(),
(Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True))
& Q(warnings__isnull=False),
lambda x: (x.approved or x.pretranslated or x.fuzzy)
and x.warnings.count(),
match_all=False,
prefetch=Prefetch("warnings"),
project=project,
@ -2314,7 +2298,7 @@ class EntityQuerySet(models.QuerySet):
"""Return a filter to be used to select entities with translations with errors.
This filter will return an entity if at least one of its plural forms
has an approved or fuzzy translation with an error.
has an approved, pretranslated or fuzzy translation with an error.
:arg Locale locale: a Locale object to get translations for
@ -2324,19 +2308,39 @@ class EntityQuerySet(models.QuerySet):
return Q(
pk__in=self.get_filtered_entities(
locale,
Q(Q(Q(approved=True) | Q(fuzzy=True)) & Q(errors__isnull=False)),
lambda x: (x.approved or x.fuzzy) and x.errors.count(),
(Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True))
& Q(errors__isnull=False),
lambda x: (x.approved or x.pretranslated or x.fuzzy)
and x.errors.count(),
match_all=False,
prefetch=Prefetch("errors"),
project=project,
)
)
def pretranslated(self, locale, project=None):
"""Return a filter to be used to select entities marked as "pretranslated".
An entity is marked as "pretranslated" if all of its plural forms have a pretranslated translation.
:arg Locale locale: a Locale object to get translations for
:returns: a django ORM Q object to use as a filter
"""
return Q(
pk__in=self.get_filtered_entities(
locale,
Q(pretranslated=True, warnings__isnull=True, errors__isnull=True),
lambda x: x.pretranslated,
project=project,
)
)
def translated(self, locale, project):
"""Return a filter to be used to select entities marked as "approved".
An entity is marked as "approved" if all of its plural forms have an approved
translation.
An entity is marked as "approved" if all of its plural forms have an approved translation.
:arg Locale locale: a Locale object to get translations for
@ -2356,7 +2360,7 @@ class EntityQuerySet(models.QuerySet):
"""Return a filter to be used to select entities with suggested translations.
An entity is said to have suggestions if at least one of its plural forms
has at least one unreviewed suggestion (not fuzzy, not approved, not rejected).
has at least one unreviewed suggestion (not approved, not rejected, not pretranslated, not fuzzy).
:arg Locale locale: a Locale object to get translations for
@ -2366,8 +2370,11 @@ class EntityQuerySet(models.QuerySet):
return Q(
pk__in=self.get_filtered_entities(
locale,
Q(approved=False, rejected=False, fuzzy=False),
lambda x: not x.approved and not x.rejected and not x.fuzzy,
Q(approved=False, rejected=False, pretranslated=False, fuzzy=False),
lambda x: not x.approved
and not x.rejected
and not x.pretranslated
and not x.fuzzy,
match_all=False,
project=project,
)
@ -2376,8 +2383,7 @@ class EntityQuerySet(models.QuerySet):
def rejected(self, locale, project=None):
"""Return a filter to be used to select entities with rejected translations.
This filter will return all entities that have a rejected translation, whether
they have approved or fuzzy translations or not.
This filter will return all entities that have a rejected translation.
:arg Locale locale: a Locale object to get translations for
@ -2406,8 +2412,28 @@ class EntityQuerySet(models.QuerySet):
return ~Q(
pk__in=self.get_filtered_entities(
locale,
Q(approved=True) | Q(fuzzy=True) | Q(rejected=False),
lambda x: x.approved or x.fuzzy or not x.rejected,
Q(approved=True) | Q(pretranslated=True) | Q(rejected=False),
lambda x: x.approved or x.pretranslated or not x.rejected,
project=project,
)
)
def fuzzy(self, locale, project=None):
"""Return a filter to be used to select entities marked as "fuzzy".
An entity is marked as "fuzzy" if all of its plural forms have a fuzzy
translation.
:arg Locale locale: a Locale object to get translations for
:returns: a django ORM Q object to use as a filter
"""
return Q(
pk__in=self.get_filtered_entities(
locale,
Q(fuzzy=True, warnings__isnull=True, errors__isnull=True),
lambda x: x.fuzzy,
project=project,
)
)
@ -2444,12 +2470,9 @@ class EntityQuerySet(models.QuerySet):
return Q(
pk__in=self.get_filtered_entities(
locale,
Q(
Q(active=True)
& Q(
Q(string=F("entity__string"))
| Q(string=F("entity__string_plural"))
)
Q(active=True)
& (
Q(string=F("entity__string")) | Q(string=F("entity__string_plural"))
),
lambda x: x.active
and (x.string == x.entity.string or x.string == x.entity.string_plural),
@ -2529,14 +2552,17 @@ class EntityQuerySet(models.QuerySet):
# First, deactivate all translations
translations.update(active=False)
# Mark all approved and fuzzy translations as active.
translations.filter(Q(approved=True) | Q(fuzzy=True)).update(active=True)
# Mark all approved, pretranslated and fuzzy translations as active.
translations.filter(
Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True)
).update(active=True)
# Mark most recent unreviewed suggestions without active siblings
# for any given combination of (locale, entity, plural_form) as active.
unreviewed_pks = set()
unreviewed = translations.filter(
approved=False,
pretranslated=False,
fuzzy=False,
rejected=False,
).values_list("entity", "plural_form")
@ -2641,35 +2667,36 @@ class Entity(DirtyFieldsMixin, models.Model):
]
)
fuzzy_strings_count = len(
pretranslated_strings_count = len(
[
t
for t in translations
if t.fuzzy and not (t.errors.exists() or t.warnings.exists())
if t.pretranslated and not (t.errors.exists() or t.warnings.exists())
]
)
if self.string_plural:
approved = int(approved_strings_count == locale.nplurals)
fuzzy = int(fuzzy_strings_count == locale.nplurals)
pretranslated = int(pretranslated_strings_count == locale.nplurals)
else:
approved = int(approved_strings_count > 0)
fuzzy = int(fuzzy_strings_count > 0)
pretranslated = int(pretranslated_strings_count > 0)
if not (approved or fuzzy):
if not (approved or pretranslated):
has_errors = bool(
[
t
for t in translations
if (t.approved or t.fuzzy) and t.errors.exists()
if (t.approved or t.pretranslated or t.fuzzy) and t.errors.exists()
]
)
has_warnings = bool(
[
t
for t in translations
if (t.approved or t.fuzzy) and t.warnings.exists()
if (t.approved or t.pretranslated or t.fuzzy)
and t.warnings.exists()
]
)
@ -2681,13 +2708,17 @@ class Entity(DirtyFieldsMixin, models.Model):
warnings = 0
unreviewed_count = len(
[t for t in translations if not (t.approved or t.fuzzy or t.rejected)]
[
t
for t in translations
if not (t.approved or t.pretranslated or t.fuzzy or t.rejected)
]
)
return {
"total_strings_diff": 0,
"approved_strings_diff": approved,
"fuzzy_strings_diff": fuzzy,
"pretranslated_strings_diff": pretranslated,
"strings_with_errors_diff": errors,
"strings_with_warnings_diff": warnings,
"unreviewed_strings_diff": unreviewed_count,
@ -2735,7 +2766,7 @@ class Entity(DirtyFieldsMixin, models.Model):
def reset_active_translation(self, locale, plural_form=None):
"""
Reset active translation for given entity, locale and plural for.
Reset active translation for given entity, locale and plural form.
Return active translation if exists or empty Translation instance.
"""
translations = self.translation_set.filter(locale=locale)
@ -2746,7 +2777,7 @@ class Entity(DirtyFieldsMixin, models.Model):
translations.update(active=False)
candidates = translations.filter(rejected=False).order_by(
"-approved", "-fuzzy", "-date"
"-approved", "-pretranslated", "-fuzzy", "-date"
)
if candidates:
@ -2847,9 +2878,9 @@ class Entity(DirtyFieldsMixin, models.Model):
# Apply a combination of filters based on the list of statuses the user sent.
status_filter_choices = (
"missing",
"fuzzy",
"warnings",
"errors",
"pretranslated",
"translated",
"unreviewed",
)
@ -2865,6 +2896,7 @@ class Entity(DirtyFieldsMixin, models.Model):
"rejected",
"unchanged",
"empty",
"fuzzy",
"missing-without-unreviewed",
)
post_filters.append(
@ -3094,6 +3126,7 @@ class Translation(DirtyFieldsMixin, models.Model):
# each (entity, locale, plural_form) combination. See bug 1481175.
active = models.BooleanField(default=False)
pretranslated = models.BooleanField(default=False)
fuzzy = models.BooleanField(default=False)
approved = models.BooleanField(default=False)
@ -3158,8 +3191,9 @@ class Translation(DirtyFieldsMixin, models.Model):
class Meta:
index_together = (
("entity", "user", "approved", "fuzzy"),
("entity", "user", "approved", "pretranslated"),
("entity", "locale", "approved"),
("entity", "locale", "pretranslated"),
("entity", "locale", "fuzzy"),
("locale", "user", "entity"),
("date", "locale"),
@ -3279,6 +3313,7 @@ class Translation(DirtyFieldsMixin, models.Model):
rejected=True,
rejected_user=self.approved_user,
rejected_date=self.approved_date,
pretranslated=False,
fuzzy=False,
)
@ -3357,6 +3392,7 @@ class Translation(DirtyFieldsMixin, models.Model):
self.approved_user = user
self.approved_date = timezone.now()
self.pretranslated = False
self.fuzzy = False
self.unapproved_user = None
@ -3396,9 +3432,9 @@ class Translation(DirtyFieldsMixin, models.Model):
"""
Reject translation.
"""
# Check if translation was approved or fuzzy.
# We must do this before unapproving/unfuzzying it.
if self.approved or self.fuzzy:
# Check if translation was approved or pretranslated or fuzzy.
# We must do this before rejecting it.
if self.approved or self.pretranslated or self.fuzzy:
TranslationMemoryEntry.objects.filter(translation=self).delete()
self.entity.mark_changed(self.locale)
@ -3408,6 +3444,7 @@ class Translation(DirtyFieldsMixin, models.Model):
self.approved = False
self.approved_user = None
self.approved_date = None
self.pretranslated = False
self.fuzzy = False
self.save()
@ -3426,6 +3463,7 @@ class Translation(DirtyFieldsMixin, models.Model):
"string": self.string,
"approved": self.approved,
"rejected": self.rejected,
"pretranslated": self.pretranslated,
"fuzzy": self.fuzzy,
"errors": [error.message for error in self.errors.all()],
"warnings": [warning.message for warning in self.warnings.all()],
@ -3585,7 +3623,7 @@ class TranslatedResourceQuerySet(models.QuerySet):
return self.aggregate(
total=Sum("resource__total_strings"),
approved=Sum("approved_strings"),
fuzzy=Sum("fuzzy_strings"),
pretranslated=Sum("pretranslated_strings"),
errors=Sum("strings_with_errors"),
warnings=Sum("strings_with_warnings"),
unreviewed=Sum("unreviewed_strings"),
@ -3596,7 +3634,7 @@ class TranslatedResourceQuerySet(models.QuerySet):
instance.total_strings = aggregated_stats["total"] or 0
instance.approved_strings = aggregated_stats["approved"] or 0
instance.fuzzy_strings = aggregated_stats["fuzzy"] or 0
instance.pretranslated_strings = aggregated_stats["pretranslated"] or 0
instance.strings_with_errors = aggregated_stats["errors"] or 0
instance.strings_with_warnings = aggregated_stats["warnings"] or 0
instance.unreviewed_strings = aggregated_stats["unreviewed"] or 0
@ -3605,7 +3643,7 @@ class TranslatedResourceQuerySet(models.QuerySet):
update_fields=[
"total_strings",
"approved_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",
@ -3665,7 +3703,7 @@ class TranslatedResourceQuerySet(models.QuerySet):
fields=[
"total_strings",
"approved_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",
@ -3750,15 +3788,18 @@ class TranslatedResource(AggregatedStats):
warnings__isnull=True,
).count()
fuzzy = translations.filter(
fuzzy=True,
pretranslated = translations.filter(
pretranslated=True,
errors__isnull=True,
warnings__isnull=True,
).count()
errors = (
translations.filter(
Q(Q(Q(approved=True) | Q(fuzzy=True)) & Q(errors__isnull=False)),
Q(
Q(Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True))
& Q(errors__isnull=False)
),
)
.distinct()
.count()
@ -3766,7 +3807,10 @@ class TranslatedResource(AggregatedStats):
warnings = (
translations.filter(
Q(Q(Q(approved=True) | Q(fuzzy=True)) & Q(warnings__isnull=False)),
Q(
Q(Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True))
& Q(warnings__isnull=False)
),
)
.distinct()
.count()
@ -3774,8 +3818,9 @@ class TranslatedResource(AggregatedStats):
unreviewed = translations.filter(
approved=False,
fuzzy=False,
rejected=False,
pretranslated=False,
fuzzy=False,
).count()
# Plural
@ -3792,21 +3837,21 @@ class TranslatedResource(AggregatedStats):
warnings__isnull=True,
).count()
plural_fuzzy_count = translations.filter(
fuzzy=True,
plural_pretranslated_count = translations.filter(
pretranslated=True,
errors__isnull=True,
warnings__isnull=True,
).count()
if plural_approved_count == nplurals:
approved += 1
elif plural_fuzzy_count == nplurals:
fuzzy += 1
elif plural_pretranslated_count == nplurals:
pretranslated += 1
else:
plural_errors_count = (
translations.filter(
Q(
Q(Q(approved=True) | Q(fuzzy=True))
Q(Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True))
& Q(errors__isnull=False)
),
)
@ -3817,7 +3862,7 @@ class TranslatedResource(AggregatedStats):
plural_warnings_count = (
translations.filter(
Q(
Q(Q(approved=True) | Q(fuzzy=True))
Q(Q(approved=True) | Q(pretranslated=True) | Q(fuzzy=True))
& Q(warnings__isnull=False)
),
)
@ -3831,7 +3876,7 @@ class TranslatedResource(AggregatedStats):
warnings += 1
plural_unreviewed_count = translations.filter(
approved=False, fuzzy=False, rejected=False
approved=False, pretranslated=False, fuzzy=False, rejected=False
).count()
if plural_unreviewed_count:
unreviewed += plural_unreviewed_count
@ -3839,7 +3884,7 @@ class TranslatedResource(AggregatedStats):
if not save:
self.total_strings = resource.total_strings
self.approved_strings = approved
self.fuzzy_strings = fuzzy
self.pretranslated_strings = pretranslated
self.strings_with_errors = errors
self.strings_with_warnings = warnings
self.unreviewed_strings = unreviewed
@ -3849,7 +3894,7 @@ class TranslatedResource(AggregatedStats):
# Calculate diffs to reduce DB queries
total_strings_diff = resource.total_strings - self.total_strings
approved_strings_diff = approved - self.approved_strings
fuzzy_strings_diff = fuzzy - self.fuzzy_strings
pretranslated_strings_diff = pretranslated - self.pretranslated_strings
strings_with_errors_diff = errors - self.strings_with_errors
strings_with_warnings_diff = warnings - self.strings_with_warnings
unreviewed_strings_diff = unreviewed - self.unreviewed_strings
@ -3857,7 +3902,7 @@ class TranslatedResource(AggregatedStats):
self.adjust_all_stats(
total_strings_diff,
approved_strings_diff,
fuzzy_strings_diff,
pretranslated_strings_diff,
strings_with_errors_diff,
strings_with_warnings_diff,
unreviewed_strings_diff,

Просмотреть файл

@ -25,8 +25,8 @@
color: #7bc876;
}
.fuzzy .status.fa:before {
color: #fed271;
.pretranslated .status.fa:before {
color: #c0ff00;
}
.warnings .status.fa:before {
@ -461,8 +461,8 @@ tfoot td a {
background: #7bc876;
}
.select .menu ul li .chart span.fuzzy {
background: #fed271;
.select .menu ul li .chart span.pretranslated {
background: #c0ff00;
}
.select .menu ul li .chart span.missing {
@ -621,55 +621,6 @@ img.rounded {
border: 2px solid #4d5967;
}
.details div {
border-top: 5px solid;
color: #aaaaaa;
display: inline-block;
margin-right: 1px;
width: 89px;
}
.details div:last-child {
margin-right: 0;
width: 90px;
}
.details div.translated {
border-color: #7bc876;
}
.details div.fuzzy {
border-color: #fed271;
}
.details div.warnings {
border-color: #ffa10f;
}
.details div.errors {
border-color: #f36;
}
.details div.missing {
border-color: #5f7285;
}
.details div.unreviewed {
border-color: #4fc4f6;
}
.details div span {
font-size: 10px;
text-transform: uppercase;
}
.details div p {
color: #ffffff;
font-size: 28px;
padding: 10px 0;
position: relative;
}
#error {
background: #333941;
bottom: 0;

Просмотреть файл

@ -287,8 +287,8 @@ table.table.project-list.hidden {
background: #7bc876;
}
.table .progress .chart-wrapper .fuzzy {
background: #fed271;
.table .progress .chart-wrapper .pretranslated {
background: #c0ff00;
}
.table .progress .chart-wrapper .warnings {
@ -325,11 +325,11 @@ table.table.project-list.hidden {
display: inline-block;
padding: 0;
text-align: center;
width: 51px;
width: 60px;
}
.table .progress .legend li:last-child {
width: 54px;
width: 60px;
}
.table .progress .legend li a {
@ -349,8 +349,8 @@ table.table.project-list.hidden {
color: #7bc876;
}
.table .progress .legend li.fuzzy .title {
color: #fed271;
.table .progress .legend li.pretranslated .title {
color: #c0ff00;
}
.table .progress .legend li.warnings .title {

Просмотреть файл

@ -21,7 +21,7 @@ $(function () {
var fraction = {
translated: stats.all ? stats.translated / stats.all : 0,
fuzzy: stats.all ? stats.fuzzy / stats.all : 0,
pretranslated: stats.all ? stats.pretranslated / stats.all : 0,
warnings: stats.all ? stats.warnings / stats.all : 0,
errors: stats.all ? stats.errors / stats.all : 0,
missing: stats.all

Просмотреть файл

@ -124,7 +124,8 @@ var Pontoon = (function (my) {
all = legend.find('.all .value').data('value') || 0,
translated =
legend.find('.translated .value').data('value') / all || 0,
fuzzy = legend.find('.fuzzy .value').data('value') / all || 0;
pretranslated =
legend.find('.pretranslated .value').data('value') / all || 0;
if ($(el).find('.progress .not-ready').length) {
return 'not-ready';
@ -132,7 +133,7 @@ var Pontoon = (function (my) {
return {
translated: translated,
fuzzy: fuzzy,
pretranslated: pretranslated,
};
}
@ -188,7 +189,7 @@ var Pontoon = (function (my) {
node.addClass(cls);
items.sort(function (a, b) {
// Sort by translated, then by fuzzy percentage
// Sort by translated, then by pretranslated percentage
if (node.is('.progress')) {
var chartA = getProgress(a),
chartB = getProgress(b);
@ -206,7 +207,7 @@ var Pontoon = (function (my) {
return (
(chartA.translated - chartB.translated) * dir ||
(chartA.fuzzy - chartB.fuzzy) * dir
(chartA.pretranslated - chartB.pretranslated) * dir
);
// Sort by unreviewed state

Просмотреть файл

@ -87,12 +87,15 @@
value=obj.approved_strings,
link=(link + '?status=translated') if link else None)
}}
<!-- Pretranslation feature is not ready yet, so we're disabling the Pretranslated status
in dashboards to avoid showing unnecessary zeros.
{{ legend_item(
title='Fuzzy',
class='fuzzy',
value=obj.fuzzy_strings,
link=(link + '?status=fuzzy') if link else None)
title='Pretranslated',
class='pretranslated',
value=obj.pretranslated_strings,
link=(link + '?status=pretranslated') if link else None)
}}
-->
{{ legend_item(
title='Warnings',
class='warnings',
@ -108,7 +111,7 @@
{{ legend_item(
title='Missing',
class='missing',
value=(obj.total_strings - obj.approved_strings - obj.fuzzy_strings - obj.strings_with_warnings - obj.strings_with_errors),
value=(obj.total_strings - obj.approved_strings - obj.pretranslated_strings - obj.strings_with_warnings - obj.strings_with_errors),
link=(link + '?status=missing') if link else None)
}}
</ul>

Просмотреть файл

@ -4,10 +4,10 @@
<span class="percent">{{ chart.completion_percent }}%</span>
<span class="chart">
<span class="translated" style="width:{{ chart.approved_share }}%;"></span>
<span class="fuzzy" style="width:{{ chart.fuzzy_share }}%;"></span>
<span class="pretranslated" style="width:{{ chart.pretranslated_share }}%;"></span>
<span class="warnings" style="width:{{ chart.warnings_share }}%;"></span>
<span class="errors" style="width:{{ chart.errors_share }}%;"></span>
<span class="missing" style="width:{{ 100 - chart.approved_share - chart.fuzzy_share - chart.warnings_share - chart.errors_share }}%;"></span>
<span class="missing" style="width:{{ 100 - chart.approved_share - chart.pretranslated_share - chart.warnings_share - chart.errors_share }}%;"></span>
</span>
<span class="unreviewed-status fa fa-lightbulb{% if chart.unreviewed_strings > 0 %} pending{% endif %}"></span>
</div>
@ -25,12 +25,15 @@
<div class="value" data-value="{{ chart.approved_strings }}">{{ chart.approved_strings|comma_or_prefix }}</div>
</a>
</li>
<li class="fuzzy">
<a href="{{ link }}{% if link_parameter %}{% if not has_params %}?{% else %}&{% endif %}status=fuzzy{% endif %}">
<div class="title">Fuzzy</div>
<div class="value" data-value="{{ chart.fuzzy_strings }}">{{ chart.fuzzy_strings|comma_or_prefix }}</div>
<!-- Pretranslation feature is not ready yet, so we're disabling the Pretranslated status
in dashboards to avoid showing unnecessary zeros.
<li class="pretranslated">
<a href="{{ link }}{% if link_parameter %}{% if not has_params %}?{% else %}&{% endif %}status=pretranslated{% endif %}">
<div class="title">Pre</div>
<div class="value" data-value="{{ chart.pretranslated_strings }}">{{ chart.pretranslated_strings|comma_or_prefix }}</div>
</a>
</li>
-->
<li class="warnings">
<a href="{{ link }}{% if link_parameter %}{% if not has_params %}?{% else %}&{% endif %}status=warnings{% endif %}">
<div class="title">Wrngs</div>
@ -46,7 +49,7 @@
<li class="missing">
<a href="{{ link }}{% if link_parameter %}{% if not has_params %}?{% else %}&{% endif %}status=missing{% endif %}">
<div class="title">Missing</div>
{% set missing_strings = chart.total_strings - chart.approved_strings - chart.fuzzy_strings - chart.strings_with_warnings - chart.strings_with_errors %}
{% set missing_strings = chart.total_strings - chart.approved_strings - chart.pretranslated_strings - chart.strings_with_warnings - chart.strings_with_errors %}
<div class="value" data-value="{{ missing_strings }}">{{ missing_strings|comma_or_prefix }}</div>
</a>
</li>

Просмотреть файл

@ -301,19 +301,19 @@ def test_mgr_entity_filter_warnings_plural(resource_a, locale_a):
locale=locale_a,
entity=entities[0],
plural_form=1,
fuzzy=True,
pretranslated=True,
)
translation20 = TranslationFactory.create(
locale=locale_a,
entity=entities[2],
plural_form=0,
fuzzy=True,
pretranslated=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
plural_form=1,
fuzzy=True,
pretranslated=True,
)
WarningFactory.create(translation=translation20)
@ -402,6 +402,85 @@ def test_mgr_entity_filter_fuzzy_plurals(resource_a, locale_a):
}
@pytest.mark.django_db
def test_mgr_entity_filter_pretranslated(resource_a, locale_a):
entities = [
EntityFactory.create(
resource=resource_a,
string="testentity%s" % i,
)
for i in range(0, 3)
]
TranslationFactory.create(
locale=locale_a,
entity=entities[0],
pretranslated=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[1],
approved=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
pretranslated=True,
)
assert set(Entity.objects.filter(Entity.objects.pretranslated(locale_a))) == {
entities[0],
entities[2],
}
@pytest.mark.django_db
def test_mgr_entity_filter_pretranslated_plurals(resource_a, locale_a):
locale_a.cldr_plurals = "1,5"
locale_a.save()
entities = [
EntityFactory.create(
resource=resource_a,
string="testentity%s" % i,
string_plural="testpluralentity%s" % i,
)
for i in range(0, 3)
]
TranslationFactory.create(
locale=locale_a,
entity=entities[0],
plural_form=0,
pretranslated=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[0],
plural_form=1,
pretranslated=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[1],
plural_form=0,
pretranslated=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
plural_form=0,
pretranslated=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
plural_form=1,
pretranslated=True,
)
assert set(Entity.objects.filter(Entity.objects.pretranslated(locale_a))) == {
entities[0],
entities[2],
}
@pytest.mark.django_db
def test_mgr_entity_filter_missing(resource_a, locale_a):
entities = [
@ -420,7 +499,7 @@ def test_mgr_entity_filter_missing(resource_a, locale_a):
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
fuzzy=True,
pretranslated=True,
)
TranslationFactory.create(
locale=locale_a,
@ -492,6 +571,11 @@ def test_mgr_entity_filter_unreviewed(resource_a, locale_a):
entity=entities[1],
string="translation for 1",
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
pretranslated=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
@ -510,7 +594,7 @@ def test_mgr_entity_filter_unchanged(resource_a, locale_a):
resource=resource_a,
string="Unchanged string",
)
for i in range(0, 3)
for i in range(0, 4)
]
TranslationFactory.create(
locale=locale_a,
@ -519,6 +603,13 @@ def test_mgr_entity_filter_unchanged(resource_a, locale_a):
approved=True,
string="Unchanged string",
)
TranslationFactory.create(
locale=locale_a,
entity=entities[1],
active=False,
approved=True,
string="Unchanged string",
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
@ -528,14 +619,15 @@ def test_mgr_entity_filter_unchanged(resource_a, locale_a):
)
TranslationFactory.create(
locale=locale_a,
entity=entities[1],
active=False,
approved=True,
entity=entities[3],
active=True,
pretranslated=True,
string="Unchanged string",
)
assert set(Entity.objects.filter(Entity.objects.unchanged(locale_a))) == {
entities[0],
entities[2],
entities[3],
}
@ -555,13 +647,13 @@ def test_mgr_entity_filter_missing_plural(resource_a, locale_a):
TranslationFactory.create(
locale=locale_a,
entity=entities[0],
fuzzy=True,
pretranslated=True,
plural_form=0,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[0],
fuzzy=True,
pretranslated=True,
plural_form=1,
)
TranslationFactory.create(
@ -598,28 +690,28 @@ def test_mgr_entity_filter_unreviewed_plural(resource_a, locale_a):
locale=locale_a,
entity=entities[0],
approved=False,
fuzzy=False,
pretranslated=False,
plural_form=0,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[0],
approved=False,
fuzzy=False,
pretranslated=False,
plural_form=1,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
approved=False,
fuzzy=False,
pretranslated=False,
plural_form=0,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[2],
approved=False,
fuzzy=False,
pretranslated=False,
plural_form=1,
)
assert set(Entity.objects.filter(Entity.objects.unreviewed(locale_a))) == {
@ -661,7 +753,7 @@ def test_mgr_entity_filter_unchanged_plural(resource_a, locale_a):
locale=locale_a,
entity=entities[1],
active=False,
fuzzy=True,
pretranslated=True,
plural_form=0,
string="Unchanged string",
)
@ -669,7 +761,7 @@ def test_mgr_entity_filter_unchanged_plural(resource_a, locale_a):
locale=locale_a,
entity=entities[1],
active=False,
fuzzy=True,
pretranslated=True,
plural_form=1,
string="Unchanged plural string",
)
@ -677,7 +769,7 @@ def test_mgr_entity_filter_unchanged_plural(resource_a, locale_a):
locale=locale_a,
entity=entities[2],
active=True,
fuzzy=True,
pretranslated=True,
plural_form=0,
string="Unchanged string",
)
@ -685,7 +777,7 @@ def test_mgr_entity_filter_unchanged_plural(resource_a, locale_a):
locale=locale_a,
entity=entities[2],
active=True,
fuzzy=True,
pretranslated=True,
plural_form=1,
string="Unchanged plural string",
)
@ -1131,7 +1223,7 @@ def test_mgr_entity_reset_active_translations(resource_a, locale_a):
resource=resource_a,
string="testentity%s" % i,
)
for i in range(0, 4)
for i in range(0, 5)
] + [
EntityFactory(
resource=resource_a,
@ -1186,27 +1278,41 @@ def test_mgr_entity_reset_active_translations(resource_a, locale_a):
fuzzy=True,
)
# Translations for Entity 4 - pluralized:
# Translations for Entity 4:
# Pretranslated and unreviewed translation
TranslationFactory.create(
locale=locale_a,
entity=entities[4],
string=entities[4].string + " translation1",
)
TranslationFactory.create(
locale=locale_a,
entity=entities[4],
string=entities[4].string + " translation2",
pretranslated=True,
)
# Translations for Entity 5 - pluralized:
# Approved and unreviewed translation for first form,
# a single unreviewed translation for second form
TranslationFactory.create(
locale=locale_a,
entity=entities[4],
entity=entities[5],
plural_form=0,
string=entities[4].string + " translation1",
string=entities[5].string + " translation1",
approved=True,
)
TranslationFactory.create(
locale=locale_a,
entity=entities[4],
entity=entities[5],
plural_form=0,
string=entities[4].string + " translation2",
string=entities[5].string + " translation2",
)
TranslationFactory.create(
locale=locale_a,
entity=entities[4],
entity=entities[5],
plural_form=1,
string=entities[4].string_plural + " translation1plural",
string=entities[5].string_plural + " translation1plural",
)
entities_qs.reset_active_translations(locale=locale_a)
@ -1236,12 +1342,19 @@ def test_mgr_entity_reset_active_translations(resource_a, locale_a):
== entities[3].string + " translation2"
)
# Active translations for Entity 4 - pluralized:
# Active translations for Entity 4:
# pretranslated translation is active
assert (
entities[4].translation_set.get(active=True).string
== entities[4].string + " translation2"
)
# Active translations for Entity 5 - pluralized:
# Approved translation for first form,
# a single unreviewed translation for second form
active = entities[4].translation_set.filter(active=True)
assert active[0].string == entities[4].string + " translation1"
assert active[1].string == entities[4].string_plural + " translation1plural"
active = entities[5].translation_set.filter(active=True)
assert active[0].string == entities[5].string + " translation1"
assert active[1].string == entities[5].string_plural + " translation1plural"
@pytest.mark.parametrize(

Просмотреть файл

@ -125,20 +125,17 @@ def test_mgr_user_translation_counts(
assert top_contribs[0].translations_count == 16
assert top_contribs[0].translations_approved_count == 5
assert top_contribs[0].translations_rejected_count == 9
assert top_contribs[0].translations_unapproved_count == 0
assert top_contribs[0].translations_needs_work_count == 2
assert top_contribs[0].translations_unapproved_count == 2
assert top_contribs[1].translations_count == 12
assert top_contribs[1].translations_approved_count == 7
assert top_contribs[1].translations_rejected_count == 3
assert top_contribs[1].translations_unapproved_count == 0
assert top_contribs[1].translations_needs_work_count == 2
assert top_contribs[1].translations_unapproved_count == 2
assert top_contribs[2].translations_count == 8
assert top_contribs[2].translations_approved_count == 1
assert top_contribs[2].translations_rejected_count == 0
assert top_contribs[2].translations_unapproved_count == 2
assert top_contribs[2].translations_needs_work_count == 5
assert top_contribs[2].translations_unapproved_count == 7
@pytest.mark.django_db
@ -235,33 +232,28 @@ def test_mgr_user_period_filters(
assert top_contribs[0].translations_approved_count == 5
assert top_contribs[0].translations_rejected_count == 0
assert top_contribs[0].translations_unapproved_count == 0
assert top_contribs[0].translations_needs_work_count == 0
top_contribs = users_with_translations_counts(aware_datetime(2015, 5, 10))
assert len(top_contribs) == 2
assert top_contribs[0].translations_count == 15
assert top_contribs[0].translations_approved_count == 2
assert top_contribs[0].translations_rejected_count == 0
assert top_contribs[0].translations_unapproved_count == 11
assert top_contribs[0].translations_needs_work_count == 2
assert top_contribs[0].translations_unapproved_count == 13
assert top_contribs[1].translations_count == 5
assert top_contribs[1].translations_approved_count == 5
assert top_contribs[1].translations_rejected_count == 0
assert top_contribs[1].translations_unapproved_count == 0
assert top_contribs[1].translations_needs_work_count == 0
top_contribs = users_with_translations_counts(aware_datetime(2015, 1, 10))
assert len(top_contribs) == 2
assert top_contribs[0].translations_count == 20
assert top_contribs[0].translations_approved_count == 17
assert top_contribs[0].translations_rejected_count == 0
assert top_contribs[0].translations_unapproved_count == 1
assert top_contribs[0].translations_needs_work_count == 2
assert top_contribs[0].translations_unapproved_count == 3
assert top_contribs[1].translations_count == 15
assert top_contribs[1].translations_approved_count == 2
assert top_contribs[1].translations_rejected_count == 0
assert top_contribs[1].translations_unapproved_count == 11
assert top_contribs[1].translations_needs_work_count == 2
assert top_contribs[1].translations_unapproved_count == 13
@pytest.mark.django_db
@ -314,14 +306,12 @@ def test_mgr_user_query_args_filtering(
assert top_contribs[0].translations_count == 24
assert top_contribs[0].translations_approved_count == 10
assert top_contribs[0].translations_rejected_count == 0
assert top_contribs[0].translations_unapproved_count == 12
assert top_contribs[0].translations_needs_work_count == 2
assert top_contribs[0].translations_unapproved_count == 14
assert top_contribs[1] == contributors[0]
assert top_contribs[1].translations_count == 15
assert top_contribs[1].translations_approved_count == 12
assert top_contribs[1].translations_rejected_count == 0
assert top_contribs[1].translations_unapproved_count == 1
assert top_contribs[1].translations_needs_work_count == 2
assert top_contribs[1].translations_unapproved_count == 3
top_contribs = users_with_translations_counts(
aware_datetime(2015, 1, 1),
@ -332,5 +322,4 @@ def test_mgr_user_query_args_filtering(
assert top_contribs[0].translations_count == 14
assert top_contribs[0].translations_approved_count == 11
assert top_contribs[0].translations_rejected_count == 0
assert top_contribs[0].translations_unapproved_count == 1
assert top_contribs[0].translations_needs_work_count == 2
assert top_contribs[0].translations_unapproved_count == 3

Просмотреть файл

@ -108,6 +108,21 @@ def test_reset_active_translation_single_approved(translation_a):
assert entity.reset_active_translation(locale) == translation_a
@pytest.mark.django_db
def test_reset_active_translation_single_pretranslated(translation_a):
"""
Test if active translations gets set properly for an entity
with a single pretranslated translation.
"""
entity = translation_a.entity
locale = translation_a.locale
translation_a.pretranslated = True
translation_a.save()
assert entity.reset_active_translation(locale) == translation_a
@pytest.mark.django_db
def test_reset_active_translation_single_fuzzy(translation_a):
"""
@ -292,6 +307,7 @@ def test_entity_project_locale_no_paths(
"translation": [
{
"pk": tr0.pk,
"pretranslated": False,
"fuzzy": False,
"string": str(tr0.string),
"approved": False,
@ -301,6 +317,7 @@ def test_entity_project_locale_no_paths(
},
{
"pk": tr0pl.pk,
"pretranslated": False,
"fuzzy": False,
"string": str(tr0pl.string),
"approved": False,

Просмотреть файл

@ -78,7 +78,7 @@ def test_translation_approved(stats_update, get_stats, translation_a):
assert get_stats(translation_a) == {
"total": 1,
"approved": 1,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 0,
"warnings": 0,
"errors": 0,
@ -90,34 +90,34 @@ def test_translation_approved(stats_update, get_stats, translation_a):
assert get_stats(translation_a) == {
"total": 1,
"approved": 0,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 1,
"warnings": 0,
"errors": 0,
}
def test_translation_fuzzy(stats_update, get_stats, translation_a):
translation_a.fuzzy = True
def test_translation_pretranslated(stats_update, get_stats, translation_a):
translation_a.pretranslated = True
stats_update(translation_a)
assert get_stats(translation_a) == {
"total": 1,
"approved": 0,
"fuzzy": 1,
"pretranslated": 1,
"unreviewed": 0,
"warnings": 0,
"errors": 0,
}
translation_a.fuzzy = False
translation_a.pretranslated = False
translation_a.rejected = True
stats_update(translation_a)
assert get_stats(translation_a) == {
"total": 1,
"approved": 0,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 0,
"warnings": 0,
"errors": 0,
@ -131,32 +131,32 @@ def test_translation_with_error(stats_update, get_stats, translation_with_error)
assert get_stats(translation_with_error) == {
"total": 1,
"approved": 0,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 0,
"warnings": 0,
"errors": 1,
}
translation_with_error.approved = False
translation_with_error.fuzzy = True
translation_with_error.pretranslated = True
stats_update(translation_with_error)
assert get_stats(translation_with_error) == {
"total": 1,
"approved": 0,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 0,
"warnings": 0,
"errors": 1,
}
translation_with_error.fuzzy = False
translation_with_error.pretranslated = False
stats_update(translation_with_error)
assert get_stats(translation_with_error) == {
"total": 1,
"approved": 0,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 1,
"warnings": 0,
"errors": 0,
@ -170,32 +170,32 @@ def test_translation_with_warning(stats_update, get_stats, translation_with_warn
assert get_stats(translation_with_warning) == {
"total": 1,
"approved": 0,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 0,
"warnings": 1,
"errors": 0,
}
translation_with_warning.approved = False
translation_with_warning.fuzzy = True
translation_with_warning.pretranslated = True
stats_update(translation_with_warning)
assert get_stats(translation_with_warning) == {
"total": 1,
"approved": 0,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 0,
"warnings": 1,
"errors": 0,
}
translation_with_warning.fuzzy = False
translation_with_warning.pretranslated = False
stats_update(translation_with_warning)
assert get_stats(translation_with_warning) == {
"total": 1,
"approved": 0,
"fuzzy": 0,
"pretranslated": 0,
"unreviewed": 1,
"warnings": 0,
"errors": 0,

Просмотреть файл

@ -88,6 +88,7 @@ def approve_translations(form, user, translations, locale):
rejected=False,
rejected_user=None,
rejected_date=None,
pretranslated=False,
fuzzy=False,
)
@ -143,6 +144,7 @@ def reject_translations(form, user, translations, locale):
approved=False,
approved_user=None,
approved_date=None,
pretranslated=False,
fuzzy=False,
)
@ -200,6 +202,7 @@ def replace_translations(form, user, translations, locale):
rejected=True,
rejected_user=user,
rejected_date=timezone.now(),
pretranslated=False,
fuzzy=False,
)

Просмотреть файл

@ -115,6 +115,7 @@ def find_and_replace(translations, find, replace, user):
new_translation.rejected = False
new_translation.rejected_date = None
new_translation.rejected_user = None
new_translation.pretranslated = False
new_translation.fuzzy = False
if new_translation.entity.resource.format in DB_FORMATS:

Просмотреть файл

@ -26,7 +26,7 @@ body.top-contributors #heading .legend li .status.fa {
.contributor {
display: inline-block;
position: relative;
width: 580px;
width: 670px;
}
.contributor img {
@ -57,10 +57,24 @@ body.top-contributors #heading .legend li .status.fa {
top: 42px;
}
.details div {
display: inline-block;
width: 90px;
}
.details div span {
font-size: 10px;
text-transform: uppercase;
}
.details div p {
color: #ffffff;
}
th:last-child,
.stats .details {
text-align: center;
width: 360px;
width: 270px;
}
th:last-child {
@ -80,10 +94,6 @@ th:last-child sup {
color: #7bc876;
}
.stats .details div.fuzzy {
color: #fed271;
}
.stats .details div.unreviewed {
color: #4fc4f6;
}

Просмотреть файл

@ -2,16 +2,37 @@
padding: 1em 0;
}
#middle .container p {
line-height: 18px;
}
#middle .container div {
width: 244px;
width: 326px;
border-top: 5px solid;
color: #aaaaaa;
display: inline-block;
margin-right: 1px;
}
#middle .container div:last-child {
width: 245px;
margin-right: 0;
}
#middle .container div.translated {
border-color: #7bc876;
}
#middle .container div.unreviewed {
border-color: #4fc4f6;
}
#middle .container div span {
font-size: 10px;
text-transform: uppercase;
}
#middle .container div p {
color: #ffffff;
font-size: 28px;
line-height: 18px;
padding: 10px 0;
position: relative;
}
#main {

Просмотреть файл

@ -70,11 +70,6 @@
class='translated',
value=top_contributor.translations_approved_count)
}}
{{ HeadingInfo.legend_item(
title='Fuzzy strings',
class='fuzzy',
value=top_contributor.translations_needs_work_count)
}}
{{ HeadingInfo.legend_item(
title='Unreviewed suggestions',
class='unreviewed',

Просмотреть файл

@ -62,12 +62,9 @@
</div><div class="translated">
<span>Translated</span>
<p>{{ translations.filter(approved=True).count()|intcomma }}</p>
</div><div class="fuzzy">
<span>Fuzzy</span>
<p>{{ translations.filter(fuzzy=True).count()|intcomma }}</p>
</div><div class="unreviewed">
<span>Unreviewed</span>
<p>{{ translations.exclude(approved=True).exclude(fuzzy=True).exclude(rejected=True).count()|intcomma }}</p>
<p>{{ translations.exclude(approved=True).exclude(rejected=True).count()|intcomma }}</p>
</div>
</div>
</section>

Просмотреть файл

@ -38,9 +38,6 @@
</div><div class="approved">
<span>Translated</span>
<p>{{ contributor.translations_approved_count|intcomma }}</p>
</div><div class="fuzzy">
<span>Fuzzy</span>
<p>{{ contributor.translations_needs_work_count|intcomma }}</p>
</div><div class="unreviewed">
<span>Unreviewed</span>
<p>{{ contributor.translations_unapproved_count|intcomma }}</p>

Просмотреть файл

@ -34,7 +34,7 @@
{% if notification.actor.slug %}
{% set actor_anchor = notification.actor %}
{% if "new string" in notification.verb and user.profile.custom_homepage %}
{% set actor_url = url('pontoon.translate.locale.agnostic', notification.actor.slug, "all-resources") + '?status=missing,fuzzy' %}
{% set actor_url = url('pontoon.translate.locale.agnostic', notification.actor.slug, "all-resources") + '?status=missing' %}
{% else %}
{% set actor_url = url('pontoon.projects.project', notification.actor.slug) %}
{% endif %}

Просмотреть файл

@ -46,7 +46,6 @@ def users_with_translations_counts(start_date=None, query_filters=None, limit=10
* translations_approved_count
* translations_rejected_count
* translations_unapproved_count
* translations_needs_work_count
* user_role
All counts will be returned from start_date to now().
@ -66,9 +65,9 @@ def users_with_translations_counts(start_date=None, query_filters=None, limit=10
# Count('user') returns 0 if the user is None.
# See https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#values.
translations = translations.values(
"user", "approved", "fuzzy", "rejected"
).annotate(count=Count("approved"))
translations = translations.values("user", "approved", "rejected").annotate(
count=Count("approved")
)
for translation in translations:
count = translation["count"]
@ -76,8 +75,6 @@ def users_with_translations_counts(start_date=None, query_filters=None, limit=10
if translation["approved"]:
status = "approved"
elif translation["fuzzy"]:
status = "fuzzy"
elif translation["rejected"]:
status = "rejected"
else:
@ -88,7 +85,6 @@ def users_with_translations_counts(start_date=None, query_filters=None, limit=10
"total": 0,
"approved": 0,
"unreviewed": 0,
"fuzzy": 0,
"rejected": 0,
}
@ -128,7 +124,6 @@ def users_with_translations_counts(start_date=None, query_filters=None, limit=10
contributor.translations_approved_count = user["approved"]
contributor.translations_rejected_count = user["rejected"]
contributor.translations_unapproved_count = user["unreviewed"]
contributor.translations_needs_work_count = user["fuzzy"]
if contributor.pk is None:
contributor.user_role = "System User"

Просмотреть файл

@ -0,0 +1,29 @@
# Generated by Django 3.2.10 on 2022-03-24 14:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("base", "0024_fuzzy_to_pretranslated_strings"),
("insights", "0009_fix_projectlocale_insights_data"),
]
operations = [
migrations.RenameField(
model_name="localeinsightssnapshot",
old_name="fuzzy_strings",
new_name="pretranslated_strings",
),
migrations.RenameField(
model_name="projectinsightssnapshot",
old_name="fuzzy_strings",
new_name="pretranslated_strings",
),
migrations.RenameField(
model_name="projectlocaleinsightssnapshot",
old_name="fuzzy_strings",
new_name="pretranslated_strings",
),
]

Просмотреть файл

@ -154,6 +154,7 @@ def get_suggestions():
# Make sure TranslatedResource is still enabled for the locale
locale=F("entity__resource__translatedresources__locale"),
approved=False,
pretranslated=False,
fuzzy=False,
rejected=False,
entity__obsolete=False,
@ -270,7 +271,7 @@ def get_locale_insights_snapshot(
# AggregatedStats
total_strings=locale.total_strings,
approved_strings=locale.approved_strings,
fuzzy_strings=locale.fuzzy_strings,
pretranslated_strings=locale.pretranslated_strings,
strings_with_errors=locale.strings_with_errors,
strings_with_warnings=locale.strings_with_warnings,
unreviewed_strings=locale.unreviewed_strings,
@ -323,7 +324,7 @@ def get_project_locale_insights_snapshot(
# AggregatedStats
total_strings=project_locale.total_strings,
approved_strings=project_locale.approved_strings,
fuzzy_strings=project_locale.fuzzy_strings,
pretranslated_strings=project_locale.pretranslated_strings,
strings_with_errors=project_locale.strings_with_errors,
strings_with_warnings=project_locale.strings_with_warnings,
unreviewed_strings=project_locale.unreviewed_strings,

Просмотреть файл

@ -33,7 +33,7 @@ def test_latest_activity(mock_parts_stats, mock_render, client, project_a, local
"resource__total_strings": 1,
"approved_strings": 0,
"unreviewed_strings": 1,
"fuzzy_strings": 0,
"pretranslated_strings": 0,
"strings_with_warnings": 0,
"strings_with_errors": 0,
"resource__deadline": None,
@ -45,7 +45,7 @@ def test_latest_activity(mock_parts_stats, mock_render, client, project_a, local
"resource__total_strings": 1,
"approved_strings": 0,
"unreviewed_strings": 0,
"fuzzy_strings": 0,
"pretranslated_strings": 0,
"strings_with_warnings": 0,
"strings_with_errors": 0,
"resource__deadline": None,
@ -73,11 +73,11 @@ def test_latest_activity(mock_parts_stats, mock_render, client, project_a, local
"resource__total_strings": 1,
"approved_strings": 0,
"unreviewed_strings": 1,
"fuzzy_strings": 0,
"pretranslated_strings": 0,
"strings_with_errors": 0,
"strings_with_warnings": 0,
"chart": {
"fuzzy_strings": 0,
"pretranslated_strings": 0,
"total_strings": 1,
"approved_strings": 0,
"unreviewed_strings": 1,
@ -85,7 +85,7 @@ def test_latest_activity(mock_parts_stats, mock_render, client, project_a, local
"strings_with_warnings": 0,
"approved_share": 0.0,
"unreviewed_share": 100.0,
"fuzzy_share": 0.0,
"pretranslated_share": 0.0,
"warnings_share": 0.0,
"errors_share": 0.0,
"completion_percent": 0,
@ -100,11 +100,11 @@ def test_latest_activity(mock_parts_stats, mock_render, client, project_a, local
"resource__total_strings": 1,
"approved_strings": 0,
"unreviewed_strings": 0,
"fuzzy_strings": 0,
"pretranslated_strings": 0,
"strings_with_errors": 0,
"strings_with_warnings": 0,
"chart": {
"fuzzy_strings": 0,
"pretranslated_strings": 0,
"total_strings": 1,
"approved_strings": 0,
"unreviewed_strings": 0,
@ -112,7 +112,7 @@ def test_latest_activity(mock_parts_stats, mock_render, client, project_a, local
"strings_with_warnings": 0,
"approved_share": 0.0,
"unreviewed_share": 0.0,
"fuzzy_share": 0.0,
"pretranslated_share": 0.0,
"warnings_share": 0.0,
"errors_share": 0.0,
"completion_percent": 0,

Просмотреть файл

@ -109,7 +109,7 @@ def ajax_resources(request, code, slug):
part["chart"] = {
"unreviewed_strings": part["unreviewed_strings"],
"fuzzy_strings": part["fuzzy_strings"],
"pretranslated_strings": part["pretranslated_strings"],
"strings_with_errors": part["strings_with_errors"],
"strings_with_warnings": part["strings_with_warnings"],
"total_strings": part["resource__total_strings"],
@ -120,8 +120,8 @@ def ajax_resources(request, code, slug):
"unreviewed_share": round(
part["unreviewed_strings"] / part["resource__total_strings"] * 100
),
"fuzzy_share": round(
part["fuzzy_strings"] / part["resource__total_strings"] * 100
"pretranslated_share": round(
part["pretranslated_strings"] / part["resource__total_strings"] * 100
),
"errors_share": round(
part["strings_with_errors"] / part["resource__total_strings"] * 100

Просмотреть файл

@ -116,7 +116,7 @@ def pretranslate(self, project_pk, locales=None, entities=None):
string=string,
user=user,
approved=False,
fuzzy=True,
pretranslated=True,
active=True,
plural_form=plural_form,
)

Просмотреть файл

@ -49,8 +49,8 @@ def test_pretranslate(gt_mock, project_a, locale_a, resource_a, locale_b):
# Total pretranslations = 2(tr_ax) + 2(tr_bx) + 2(tr_ay)
assert len(translations) == 6
# fuzzy count == total pretranslations.
assert project_a.fuzzy_strings == 6
# pretranslated count == total pretranslations.
assert project_a.pretranslated_strings == 6
# latest_translation belongs to pretranslations.
assert project_a.latest_translation in translations

Просмотреть файл

@ -75,7 +75,7 @@ class Command(BaseCommand):
start = timezone.now() - timedelta(days=7)
return Translation.objects.filter(
approved=False, rejected=False, fuzzy=False
approved=False, pretranslated=False, rejected=False, fuzzy=False
).filter(
Q(date__gt=start)
| Q(unapproved_date__gt=start)

Просмотреть файл

@ -280,7 +280,11 @@ class ChangeSet:
# Modify existing translation.
if db_translation:
new_action = None
if not db_translation.approved and not vcs_translation.fuzzy:
if (
not db_translation.approved
and not db_translation.pretranslated
and not vcs_translation.fuzzy
):
new_action = ActionLog(
action_type=ActionLog.ActionType.TRANSLATION_APPROVED,
performed_by=user or self.sync_user,
@ -299,7 +303,7 @@ class ChangeSet:
self.actions_to_log.append(new_action)
if db_translation.fuzzy:
fuzzy_translations.append(db_translation)
else:
elif not db_translation.pretranslated:
approved_translations.append(db_translation)
# Create new translation.

Просмотреть файл

@ -90,7 +90,7 @@ class SyncLog(BaseLog):
for pl in ProjectLocale.objects.filter(project=p):
pl.aggregate_stats()
# approved + fuzzy + errors + warnings > total in TranslatedResource
# approved + pretranslated + errors + warnings > total in TranslatedResource
for t in (
TranslatedResource.objects.filter(
resource__project__disabled=False,
@ -99,7 +99,7 @@ class SyncLog(BaseLog):
.annotate(
total=Sum(
F("approved_strings")
+ F("fuzzy_strings")
+ F("pretranslated_strings")
+ F("strings_with_errors")
+ F("strings_with_warnings")
)

2
pontoon/tags/tests/fixtures/site.py поставляемый
Просмотреть файл

@ -124,7 +124,7 @@ def _get_translated_resources(locales, project, resources):
"locale": locale,
"approved_strings": (i + 2) ** 3,
"unreviewed_strings": (i + 1) ** 3,
"fuzzy_strings": i ** 3,
"pretranslated_strings": i ** 3,
}
)
return _kwargs

4
pontoon/tags/tests/fixtures/tags.py поставляемый
Просмотреть файл

@ -32,7 +32,7 @@ def _assert_tags(expected, data):
"total_strings",
"approved_strings",
"unreviewed_strings",
"fuzzy_strings",
"pretranslated_strings",
]
for slug, stats in results.items():
_exp = expected[slug]
@ -105,7 +105,7 @@ def _calculate_tags(**kwargs):
attrs = [
"approved_strings",
"unreviewed_strings",
"fuzzy_strings",
"pretranslated_strings",
]
totals = {}
resource_attrs = [

Просмотреть файл

@ -191,5 +191,5 @@ def test_util_tags_stats_tool_groupby_locale(
for locale in data:
locale_exp = exp[locale["locale"]]
assert locale_exp["total_strings"] == locale["total_strings"]
assert locale_exp["fuzzy_strings"] == locale["fuzzy_strings"]
assert locale_exp["pretranslated_strings"] == locale["pretranslated_strings"]
assert locale_exp["approved_strings"] == locale["approved_strings"]

Просмотреть файл

@ -173,7 +173,7 @@ def test_util_tag_chart():
chart = TagChart()
assert chart.approved_strings is None
assert chart.fuzzy_strings is None
assert chart.pretranslated_strings is None
assert chart.total_strings is None
assert chart.unreviewed_strings is None
@ -187,12 +187,12 @@ def test_util_tag_chart():
chart = TagChart(
total_strings=73,
fuzzy_strings=7,
pretranslated_strings=7,
approved_strings=13,
strings_with_warnings=0,
unreviewed_strings=23,
)
assert chart.approved_share == 18.0
assert chart.fuzzy_share == 10.0
assert chart.pretranslated_share == 10.0
assert chart.unreviewed_share == 32.0
assert chart.completion_percent == 17

Просмотреть файл

@ -5,7 +5,7 @@ class TagChart:
def __init__(self, **kwargs):
self.kwargs = kwargs
self.approved_strings = kwargs.get("approved_strings")
self.fuzzy_strings = kwargs.get("fuzzy_strings")
self.pretranslated_strings = kwargs.get("pretranslated_strings")
self.strings_with_warnings = kwargs.get("strings_with_warnings")
self.strings_with_errors = kwargs.get("strings_with_errors")
self.total_strings = kwargs.get("total_strings")
@ -26,8 +26,8 @@ class TagChart:
return self._share(self.approved_strings)
@property
def fuzzy_share(self):
return self._share(self.fuzzy_strings)
def pretranslated_share(self):
return self._share(self.pretranslated_strings)
@property
def warnings_share(self):

Просмотреть файл

@ -20,7 +20,7 @@ class TagsStatsTool(TagsTRTool):
# from the perspective of translated resources
_default_annotations = (
("total_strings", Coalesce(Sum("resource__total_strings"), Value(0))),
("fuzzy_strings", Coalesce(Sum("fuzzy_strings"), Value(0))),
("pretranslated_strings", Coalesce(Sum("pretranslated_strings"), Value(0))),
("strings_with_warnings", Coalesce(Sum("strings_with_warnings"), Value(0))),
("strings_with_errors", Coalesce(Sum("strings_with_errors"), Value(0))),
("approved_strings", Coalesce(Sum("approved_strings"), Value(0))),

Просмотреть файл

@ -10,7 +10,7 @@ class Tagged:
def __init__(self, **kwargs):
self._latest_translation = kwargs.pop("latest_translation", None)
self.approved_strings = kwargs.get("approved_strings")
self.fuzzy_strings = kwargs.get("fuzzy_strings")
self.pretranslated_strings = kwargs.get("pretranslated_strings")
self.strings_with_warnings = kwargs.get("strings_with_warnings")
self.strings_with_errors = kwargs.get("strings_with_errors")
self.total_strings = kwargs.get("total_strings")

Просмотреть файл

@ -22,7 +22,7 @@ def update_terminology_project_stats():
[
"total_strings",
"approved_strings",
"fuzzy_strings",
"pretranslated_strings",
"strings_with_errors",
"strings_with_warnings",
"unreviewed_strings",

Просмотреть файл

@ -34,7 +34,7 @@ Let's have a closer look at each of them.
We treat Gettext Fuzzy strings as Missing instead of Pretranslated on dashboards, in string list, in progress chart, i.e. everywhere.
Internally, we keep using the fuzzy=True flag for Fuzzy strings, which allows us to distinct them from Missing and:
Internally, we keep using the fuzzy=True flag for Fuzzy strings, which allows us to distinguish them from Missing and:
1. Sync them with version control system.
2. Run quality checks on them.
3. Set fuzzy flag accordingly in the .po file.

Просмотреть файл

@ -84,7 +84,7 @@ batchactions-ReplaceAll--error = OOPS, SOMETHING WENT WRONG
resourceprogress-ResourceProgress--all-strings = ALL STRINGS
resourceprogress-ResourceProgress--unreviewed = UNREVIEWED
resourceprogress-ResourceProgress--translated = TRANSLATED
resourceprogress-ResourceProgress--fuzzy = FUZZY
resourceprogress-ResourceProgress--pretranslated = PRETRANSLATED
resourceprogress-ResourceProgress--warnings = WARNINGS
resourceprogress-ResourceProgress--errors = ERRORS
resourceprogress-ResourceProgress--missing = MISSING
@ -621,13 +621,13 @@ search-FiltersPanel--heading-extra = EXTRA FILTERS
search-FiltersPanel--heading-authors = TRANSLATION AUTHORS
search-FiltersPanel--status-name-all = All
search-FiltersPanel--status-name-translated = Translated
search-FiltersPanel--status-name-fuzzy = Fuzzy
search-FiltersPanel--status-name-warnings = Warnings
search-FiltersPanel--status-name-errors = Errors
search-FiltersPanel--status-name-missing = Missing
search-FiltersPanel--status-name-unreviewed = Unreviewed
search-FiltersPanel--extra-name-unchanged = Unchanged
search-FiltersPanel--extra-name-empty = Empty
search-FiltersPanel--extra-name-fuzzy = Fuzzy
search-FiltersPanel--extra-name-rejected = Rejected
search-FiltersPanel--clear-selection = <glyph></glyph>CLEAR

Просмотреть файл

@ -5,6 +5,7 @@ export type EntityTranslation = {
readonly pk: number;
readonly string: string | null | undefined;
readonly approved: boolean;
readonly pretranslated: boolean;
readonly fuzzy: boolean;
readonly rejected: boolean;
readonly errors: Array<string>;

Просмотреть файл

@ -43,7 +43,7 @@ export function _getTranslationForSelectedEntity(
* Return the active translation for the currently selected entity
* and plural form.
*
* The active translation is either the approved one, the fuzzy one, or the
* The active translation is either the approved one, the pretranslated one, the fuzzy one, or the
* most recent non-rejected one.
*/
export const getTranslationForSelectedEntity = createSelector(
@ -64,7 +64,7 @@ export function _getTranslationStringForSelectedEntity(
* Return the active translation *string* for the currently selected entity
* and plural form.
*
* The active translation is either the approved one, the fuzzy one, or the
* The active translation is either the approved one, the pretranslated one, the fuzzy one, or the
* most recent non-rejected one.
*/
export const getTranslationStringForSelectedEntity = createSelector(

Просмотреть файл

@ -2,7 +2,7 @@ export const UPDATE: 'stats/UPDATE' = 'stats/UPDATE';
export type APIStats = {
approved: number;
fuzzy: number;
pretranslated: number;
warnings: number;
errors: number;
unreviewed: number;
@ -23,7 +23,7 @@ export function update(stats: APIStats): UpdateAction {
missing:
stats.total -
stats.approved -
stats.fuzzy -
stats.pretranslated -
stats.errors -
stats.warnings,
};

Просмотреть файл

@ -6,7 +6,7 @@ type Action = UpdateAction;
const initial: Stats = {
approved: 0,
fuzzy: 0,
pretranslated: 0,
warnings: 0,
errors: 0,
missing: 0,

Просмотреть файл

@ -92,8 +92,8 @@
color: #7bc876;
}
.entity.fuzzy .status:before {
color: #fed271;
.entity.pretranslated .status:before {
color: #c0ff00;
}
.entity.errors .status:before {

Просмотреть файл

@ -27,7 +27,7 @@ describe('<Entity>', () => {
translation: [
{
string: 'chaine b',
fuzzy: true,
pretranslated: true,
errors: [],
warnings: [],
},
@ -62,7 +62,7 @@ describe('<Entity>', () => {
translation: [
{
string: 'chaine e',
fuzzy: true,
pretranslated: true,
errors: [],
warnings: ['warning'],
},
@ -80,7 +80,7 @@ describe('<Entity>', () => {
},
{
string: 'chaine f2',
fuzzy: true,
pretranslated: true,
errors: [],
warnings: [],
},
@ -102,7 +102,7 @@ describe('<Entity>', () => {
expect(wrapper.find('.approved')).toHaveLength(1);
wrapper = shallow(<Entity entity={ENTITY_B} parameters={{}} />);
expect(wrapper.find('.fuzzy')).toHaveLength(1);
expect(wrapper.find('.pretranslated')).toHaveLength(1);
wrapper = shallow(<Entity entity={ENTITY_C} parameters={{}} />);
expect(wrapper.find('.missing')).toHaveLength(1);

Просмотреть файл

@ -27,17 +27,17 @@ type Props = {
* The format of this element is: "[Status] Source (Translation)"
*
* "Status" is the current status of the translation. Can be:
* - "errors": one of the plural forms has errors and is approved or fuzzy
* - "warnings": one of the plural forms has warnings and is approved or fuzzy
* - "errors": one of the plural forms has errors and is approved, pretranslated or fuzzy
* - "warnings": one of the plural forms has warnings and is approved, pretranslated or fuzzy
* - "approved": all plural forms are approved and don't have errors or warnings
* - "fuzzy": all plural forms are fuzzy and don't have errors or warnings
* - "partial": some plural forms have either approved or fuzzy translations, but not all
* - "missing": none of the plural forms have an approved or fuzzy translation
* - "pretranslated": all plural forms are pretranslated and don't have errors or warnings
* - "partial": some plural forms have either approved or pretranslated translations, but not all
* - "missing": none of the plural forms have an approved or pretranslated translation
*
* "Source" is the original string from the project. Usually it's the en-US string.
*
* "Translation" is the current "best" translation. It shows either the approved
* translation, or the fuzzy translation, or the last suggested translation.
* "Translation" is the current "best" translation. It shows either the approved,
* pretranslated or fuzzy translation, or the last suggested translation.
*/
export function Entity({
checkedForBatchEditing,
@ -152,19 +152,24 @@ function translationStatus(translations: EntityTranslation[]): string {
let errors = false;
let warnings = false;
let approved = 0;
let fuzzy = 0;
let pretranslated = 0;
for (const tx of translations) {
if (tx.errors.length && (tx.approved || tx.fuzzy)) errors = true;
else if (tx.warnings.length && (tx.approved || tx.fuzzy)) warnings = true;
if (tx.errors.length && (tx.approved || tx.pretranslated || tx.fuzzy))
errors = true;
else if (
tx.warnings.length &&
(tx.approved || tx.pretranslated || tx.fuzzy)
)
warnings = true;
else if (tx.approved) approved += 1;
else if (tx.fuzzy) fuzzy += 1;
else if (tx.pretranslated) pretranslated += 1;
}
if (errors) return 'errors';
if (warnings) return 'warnings';
if (approved === translations.length) return 'approved';
if (fuzzy === translations.length) return 'fuzzy';
if (approved > 0 || fuzzy > 0) return 'partial';
if (pretranslated === translations.length) return 'pretranslated';
if (approved > 0 || pretranslated > 0) return 'partial';
return 'missing';
}

Просмотреть файл

@ -140,7 +140,7 @@ describe('<EntityDetailsBase>', () => {
// enzyme shallow rendering doesn't support useEffect()
// https://github.com/enzymejs/enzyme/issues/2086
it.skip('shows failed checks for approved (or fuzzy) translations with errors or warnings', () => {
it.skip('shows failed checks for approved translations with errors or warnings', () => {
const wrapper = createShallowEntityDetails();
// componentDidMount(): reset failed checks
@ -170,7 +170,7 @@ describe('<EntityDetailsBase>', () => {
// enzyme shallow rendering doesn't support useEffect()
// https://github.com/enzymejs/enzyme/issues/2086
it.skip('hides failed checks for approved (or fuzzy) translations without errors or warnings', () => {
it.skip('hides failed checks for pretranslated translations without errors or warnings', () => {
const wrapper = createShallowEntityDetails();
// componentDidMount(): reset failed checks
@ -184,7 +184,7 @@ describe('<EntityDetailsBase>', () => {
original: 'something',
translation: [
{
approved: true,
pretranslated: true,
string: 'quelque chose',
errors: [],
warnings: [],

Просмотреть файл

@ -136,12 +136,13 @@ export function EntityDetailsBase({
const plural = pluralForm === -1 ? 0 : pluralForm;
const translation = selectedEntity.translation[plural];
// Only show failed checks for active translations that are approved or fuzzy,
// i.e. when their status icon is colored as error/warning in the string list
// Only show failed checks for active translations that are approved,
// pretranslated or fuzzy, i.e. when their status icon is colored as
// error/warning in the string list
if (
translation &&
(translation.errors.length || translation.warnings.length) &&
(translation.approved || translation.fuzzy)
(translation.approved || translation.pretranslated || translation.fuzzy)
) {
const failedChecks: FailedChecks = {
clErrors: translation.errors,

Просмотреть файл

@ -17,6 +17,7 @@ export const UPDATE: 'history/UPDATE' = 'history/UPDATE';
export type HistoryTranslation = {
readonly approved: boolean;
readonly approvedUser: string;
readonly pretranslated: boolean;
readonly date: string;
readonly dateIso: string;
readonly fuzzy: boolean;

Просмотреть файл

@ -135,10 +135,14 @@
opacity: 1;
}
.history .translation.fuzzy .content > header button.approve:before {
.history .translation.pretranslated .content > header button.approve:before {
color: #fed271;
}
.history .translation.fuzzy .content > header button.approve:before {
color: #c0ff00;
}
.history .translation .content > header button.approve:before,
.history .translation .content > header button.unapprove:before {
content: '';

Просмотреть файл

@ -13,6 +13,7 @@ describe('<TranslationBase>', () => {
const DEFAULT_TRANSLATION = {
approved: false,
approvedUser: '',
pretranslated: false,
date: '',
dateIso: '',
fuzzy: false,
@ -67,6 +68,22 @@ describe('<TranslationBase>', () => {
expect(wrapper.find('.rejected')).toHaveLength(1);
});
it('returns the correct status for pretranslated translations', () => {
const translation = {
...DEFAULT_TRANSLATION,
...{ pretranslated: true },
};
const wrapper = shallow(
<TranslationBase
translation={translation}
entity={DEFAULT_ENTITY}
user={DEFAULT_USER}
/>,
);
expect(wrapper.find('.pretranslated')).toHaveLength(1);
});
it('returns the correct status for fuzzy translations', () => {
const translation = {
...DEFAULT_TRANSLATION,

Просмотреть файл

@ -209,6 +209,8 @@ export function TranslationBase({
'translation',
translation.approved
? 'approved'
: translation.pretranslated
? 'pretranslated'
: translation.fuzzy
? 'fuzzy'
: translation.rejected

Просмотреть файл

@ -45,7 +45,7 @@ export default class ProgressChart extends React.Component<Props> {
}
drawCanvas() {
const { approved, fuzzy, warnings, errors, missing, total } =
const { approved, pretranslated, warnings, errors, missing, total } =
this.props.stats;
const dpr = window.devicePixelRatio || 1;
const canvas = this.canvas.current;
@ -60,8 +60,8 @@ export default class ProgressChart extends React.Component<Props> {
color: '#7BC876',
},
{
type: total ? fuzzy / total : 0,
color: '#FED271',
type: total ? pretranslated / total : 0,
color: '#C0FF00',
},
{
type: total ? warnings / total : 0,

Просмотреть файл

@ -96,20 +96,20 @@
color: #aaaaaa;
display: inline-block;
margin-right: 1px;
width: 79px;
width: 99px;
}
.progress-chart .menu .details div:last-child {
margin-right: 0;
width: 80px;
width: 100px;
}
.progress-chart .menu .details div.approved {
border-color: #7bc876;
}
.progress-chart .menu .details div.fuzzy {
border-color: #fed271;
.progress-chart .menu .details div.pretranslated {
border-color: #c0ff00;
}
.progress-chart .menu .details div.warnings {

Просмотреть файл

@ -6,7 +6,7 @@ import ResourceProgressBase from './ResourceProgress';
describe('<ResourceProgressBase>', () => {
const STATS = {
approved: 5,
fuzzy: 4,
pretranslated: 4,
unreviewed: 5,
warnings: 3,
errors: 2,

Просмотреть файл

@ -33,8 +33,15 @@ function ResourceProgress({
stats,
onDiscard,
}: ResourceProgressProps) {
const { approved, fuzzy, warnings, errors, missing, unreviewed, total } =
stats;
const {
approved,
pretranslated,
warnings,
errors,
missing,
unreviewed,
total,
} = stats;
const ref = React.useRef(null);
useOnDiscard(ref, onDiscard);
@ -77,23 +84,26 @@ function ResourceProgress({
</Link>
</p>
</div>
<div className='fuzzy'>
{/* Pretranslation feature is not ready yet, so we're disabling the
Pretranslated filter, which wouldn't catch anything.
<div className='pretranslated'>
<span className='title'>
<Localized id='resourceprogress-ResourceProgress--fuzzy'>
FUZZY
<Localized id='resourceprogress-ResourceProgress--pretranslated'>
PRETRANSLATED
</Localized>
</span>
<p className='value' onClick={onDiscard}>
<Link
to={{
pathname: currentPath,
search: '?status=fuzzy',
search: '?status=pretranslated',
}}
>
{asLocaleString(fuzzy)}
{asLocaleString(pretranslated)}
</Link>
</p>
</div>
*/}
<div className='warnings'>
<span className='title'>
<Localized id='resourceprogress-ResourceProgress--warnings'>

Просмотреть файл

@ -71,11 +71,6 @@
background-color: #3f4752;
}
.filters-panel .menu li:not(.horizontal-separator) {
padding: 4px 0 4px 30px;
position: relative;
}
.filters-panel .menu li .status {
font-size: 16px;
margin: -3px -13px -3px -16px;

Просмотреть файл

@ -66,8 +66,8 @@
color: #7bc876;
}
.search-box .fuzzy .status:before {
color: #fed271;
.search-box .pretranslated .status:before {
color: #c0ff00;
}
.search-box .warnings .status:before {
@ -102,6 +102,10 @@
font-weight: 100;
}
.search-box .fuzzy .status:before {
content: '\f059';
}
.search-box .menu .empty.selected .status:before,
.search-box .menu .empty .status:hover:before {
font-weight: 900;

Просмотреть файл

@ -20,7 +20,7 @@
padding-right: 5px;
}
.filters-panel .menu li.time-range {
.filters-panel .menu li:not(.horizontal-separator).time-range {
padding: 4px 0;
}

Просмотреть файл

@ -11,10 +11,6 @@ export const FILTERS_STATUS = [
slug: 'translated',
stat: 'approved',
},
{
name: 'Fuzzy',
slug: 'fuzzy',
},
{
name: 'Warnings',
slug: 'warnings',
@ -44,6 +40,10 @@ export const FILTERS_EXTRA = [
name: 'Empty',
slug: 'empty',
},
{
name: 'Fuzzy',
slug: 'fuzzy',
},
{
name: 'Rejected',
slug: 'rejected',