Skip to content

Commit

Permalink
Lang: Rename plural equation to formula
Browse files Browse the repository at this point in the history
It is not actually an euqation and formula is better fit here.
  • Loading branch information
nijel committed Apr 4, 2020
1 parent 5454c0a commit e6ae842
Show file tree
Hide file tree
Showing 20 changed files with 479 additions and 451 deletions.
4 changes: 2 additions & 2 deletions docs/user/translating.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ example Czech or Arabic have more plurals and also their rules for plurals are
different.

Weblate has full support for each of these forms, in each respective language
by translating every plural separately. The number of fields and how it is
used in the translated application depends on the configured plural equation.
by translating every plural separately. The number of fields and how it is
used in the translated application depends on the configured plural formula.
Weblate shows the basic information, but you can find a more detailed description in
the `Language Plural Rules`_ by the Unicode Consortium.

Expand Down
8 changes: 3 additions & 5 deletions weblate/formats/tests/test_exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,11 @@ def test_dictionary_special(self):

def check_unit(self, nplurals=3, template=None, source_info=None, **kwargs):
if nplurals == 3:
equation = "n==0 ? 0 : n==1 ? 1 : 2"
formula = "n==0 ? 0 : n==1 ? 1 : 2"
else:
equation = "0"
formula = "0"
lang = Language.objects.create(code="zz")
plural = Plural.objects.create(
language=lang, number=nplurals, equation=equation
)
plural = Plural.objects.create(language=lang, number=nplurals, formula=formula)
project = Project(slug="test", source_language=Language.objects.get(code="en"))
component = Component(
slug="comp", project=project, file_format="xliff", template=template
Expand Down
8 changes: 4 additions & 4 deletions weblate/formats/tests/test_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,16 +317,16 @@ def load_plural(self, filename):

def test_plurals(self):
self.assertEqual(
self.load_plural(TEST_HE_CLDR).equation,
self.load_plural(TEST_HE_CLDR).formula,
"(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && n % 10 == 0) ? 2 : 3))",
)
self.assertEqual(
self.load_plural(TEST_HE_CUSTOM).equation,
self.load_plural(TEST_HE_CUSTOM).formula,
"(n == 1) ? 0 : ((n == 2) ? 1 : ((n == 10) ? 2 : 3))",
)
self.assertEqual(self.load_plural(TEST_HE_SIMPLE).equation, "(n != 1)")
self.assertEqual(self.load_plural(TEST_HE_SIMPLE).formula, "(n != 1)")
self.assertEqual(
self.load_plural(TEST_HE_THREE).equation, "n==1 ? 0 : n==2 ? 2 : 1"
self.load_plural(TEST_HE_THREE).formula, "n==1 ? 0 : n==2 ? 2 : 1"
)


Expand Down
6 changes: 3 additions & 3 deletions weblate/formats/ttkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -747,21 +747,21 @@ def get_plural(self, language):

header = self.store.parseheader()
try:
number, equation = Plural.parse_formula(header["Plural-Forms"])
number, formula = Plural.parse_plural_forms(header["Plural-Forms"])
except (ValueError, KeyError):
return super().get_plural(language)

# Find matching one
for plural in language.plural_set.iterator():
if plural.same_plural(number, equation):
if plural.same_plural(number, formula):
return plural

# Create new one
return Plural.objects.create(
language=language,
source=Plural.SOURCE_GETTEXT,
number=number,
equation=equation,
formula=formula,
)

@classmethod
Expand Down
4 changes: 2 additions & 2 deletions weblate/lang/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ def save_related(self, request, form, formsets, change):
lang.plural_set.create(
source=Plural.SOURCE_DEFAULT,
number=baseplural.number,
equation=baseplural.equation,
formula=baseplural.formula,
)
except (Language.DoesNotExist, IndexError):
lang.plural_set.create(
source=Plural.SOURCE_DEFAULT, number=2, equation="n != 1"
source=Plural.SOURCE_DEFAULT, number=2, formula="n != 1"
)
4 changes: 2 additions & 2 deletions weblate/lang/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# List of RTL languages
RTL_LANGS = {"ar", "arc", "ckb", "dv", "fa", "ha", "he", "ks", "ps", "ug", "ur", "yi"}

# Following variables are used to map Gettext plural equations
# Following variables are used to map Gettext plural formulas
# to one/few/may/other like rules

ONE_OTHER_PLURALS = (
Expand Down Expand Up @@ -147,7 +147,7 @@ def nospace_set(source):
return {item.replace(" ", "") for item in source}


# Plural equation - type mappings
# Plural formula - type mappings
PLURAL_MAPPINGS = (
(nospace_set(ONE_OTHER_PLURALS), PLURAL_ONE_OTHER),
(nospace_set(ONE_FEW_OTHER_PLURALS), PLURAL_ONE_FEW_OTHER),
Expand Down
2 changes: 1 addition & 1 deletion weblate/lang/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ class Meta:
class PluralForm(forms.ModelForm):
class Meta:
model = Plural
fields = ["number", "equation"]
fields = ["number", "formula"]
2 changes: 1 addition & 1 deletion weblate/lang/management/commands/move_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def handle(self, *args, **options):

for plural in source.plural_set.iterator():
try:
new_plural = target.plural_set.get(equation=plural.equation)
new_plural = target.plural_set.get(formula=plural.formula)
plural.translation_set.update(plural=new_plural)
except Plural.DoesNotExist:
plural.language = target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class Migration(migrations.Migration):
models.CharField(
default="n != 1",
max_length=400,
validators=[weblate.utils.validators.validate_pluraleq],
validators=[weblate.utils.validators.validate_plural_formula],
verbose_name="Plural equation",
),
),
Expand Down
2 changes: 1 addition & 1 deletion weblate/lang/migrations/0005_auto_20200212_1239.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Migration(migrations.Migration):
field=models.CharField(
default="n != 1",
max_length=600,
validators=[weblate.utils.validators.validate_pluraleq],
validators=[weblate.utils.validators.validate_plural_formula],
verbose_name="Plural equation",
),
)
Expand Down
28 changes: 28 additions & 0 deletions weblate/lang/migrations/0007_auto_20200404_1111.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 3.0.5 on 2020-04-04 11:11

from django.db import migrations, models

import weblate.utils.validators


class Migration(migrations.Migration):

dependencies = [
("lang", "0006_auto_20200309_1436"),
]

operations = [
migrations.RenameField(
model_name="plural", old_name="equation", new_name="formula",
),
migrations.AlterField(
model_name="plural",
name="formula",
field=models.CharField(
default="n != 1",
max_length=600,
validators=[weblate.utils.validators.validate_plural_formula],
verbose_name="Plural formula",
),
),
]
76 changes: 39 additions & 37 deletions weblate/lang/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from weblate.trans.util import sort_objects
from weblate.utils.stats import LanguageStats
from weblate.utils.templatetags.icons import icon
from weblate.utils.validators import validate_pluraleq
from weblate.utils.validators import validate_plural_formula

PLURAL_RE = re.compile(
r"\s*nplurals\s*=\s*([0-9]+)\s*;\s*plural\s*=\s*([()n0-9!=|&<>+*/%\s?:-]+)"
Expand All @@ -55,30 +55,30 @@
COPY_RE = re.compile(r"\([0-9]+\)")


def get_plural_type(base_code, pluralequation):
def get_plural_type(base_code, plural_formula):
"""Get correct plural type for language."""
# Remove not needed parenthesis
if pluralequation[-1] == ";":
pluralequation = pluralequation[:-1]
if plural_formula[-1] == ";":
plural_formula = plural_formula[:-1]

# No plural
if pluralequation == "0":
if plural_formula == "0":
return data.PLURAL_NONE

# Remove whitespace
equation = pluralequation.replace(" ", "")
formula = plural_formula.replace(" ", "")

# Standard plural equations
# Standard plural formulas
for mapping in data.PLURAL_MAPPINGS:
if equation in mapping[0]:
if formula in mapping[0]:
return mapping[1]

# Arabic special case
if base_code in ("ar",):
return data.PLURAL_ARABIC

# Log error in case of uknown mapping
LOGGER.error("Can not guess type of plural for %s: %s", base_code, pluralequation)
LOGGER.error("Can not guess type of plural for %s: %s", base_code, plural_formula)

return data.PLURAL_UNKNOWN

Expand Down Expand Up @@ -270,11 +270,11 @@ def auto_create(self, code, create=True):
lang.plural_set.create(
source=Plural.SOURCE_DEFAULT,
number=baseplural.number,
equation=baseplural.equation,
formula=baseplural.formula,
)
elif create:
lang.plural_set.create(
source=Plural.SOURCE_DEFAULT, number=2, equation="n != 1"
source=Plural.SOURCE_DEFAULT, number=2, formula="n != 1"
)

return lang
Expand All @@ -285,13 +285,13 @@ def setup(self, update, logger=lambda x: x):
It is based on languages defined in the languages-data repo.
"""
# Create Weblate languages
for code, name, nplurals, pluraleq in LANGUAGES:
for code, name, nplurals, plural_formula in LANGUAGES:
lang, created = self.get_or_create(code=code, defaults={"name": name})
if created:
logger("Created language {}".format(code))

# Get plural type
plural_type = get_plural_type(lang.base_code, pluraleq)
plural_type = get_plural_type(lang.base_code, plural_formula)

# Should we update existing?
if update and lang.name != name:
Expand All @@ -302,7 +302,7 @@ def setup(self, update, logger=lambda x: x):
plural_data = {
"type": plural_type,
"number": nplurals,
"equation": pluraleq,
"formula": plural_formula,
}
try:
plural, created = lang.plural_set.get_or_create(
Expand All @@ -311,7 +311,7 @@ def setup(self, update, logger=lambda x: x):
if created:
logger(
"Created default plural {} for language {}".format(
pluraleq, code
plural_formula, code
)
)
else:
Expand All @@ -323,38 +323,40 @@ def setup(self, update, logger=lambda x: x):
if modified:
logger(
"Updated default plural {} for language {}".format(
pluraleq, code
plural_formula, code
)
)
plural.save()
except Plural.MultipleObjectsReturned:
continue

# Create addditiona plurals
for code, _unused, nplurals, pluraleq in EXTRAPLURALS:
for code, _unused, nplurals, plural_formula in EXTRAPLURALS:
lang = self.get(code=code)

# Get plural type
plural_type = get_plural_type(lang.base_code, pluraleq)
plural_type = get_plural_type(lang.base_code, plural_formula)

plural_data = {"type": plural_type}
plural, created = lang.plural_set.get_or_create(
source=Plural.SOURCE_GETTEXT,
language=lang,
number=nplurals,
equation=pluraleq,
formula=plural_formula,
defaults=plural_data,
)
if created:
logger("Created plural {} for language {}".format(pluraleq, code))
logger("Created plural {} for language {}".format(plural_formula, code))
else:
modified = False
for item in plural_data:
if getattr(plural, item) != plural_data[item]:
modified = True
setattr(plural, item, plural_data[item])
if modified:
logger("Updated plural {} for language {}".format(pluraleq, code))
logger(
"Updated plural {} for language {}".format(plural_formula, code)
)
plural.save()

def have_translation(self):
Expand Down Expand Up @@ -518,12 +520,12 @@ class Plural(models.Model):
number = models.SmallIntegerField(
default=2, verbose_name=gettext_lazy("Number of plurals")
)
equation = models.CharField(
formula = models.CharField(
max_length=600,
default="n != 1",
validators=[validate_pluraleq],
validators=[validate_plural_formula],
blank=False,
verbose_name=gettext_lazy("Plural equation"),
verbose_name=gettext_lazy("Plural formula"),
)
type = models.IntegerField(
choices=PLURAL_CHOICES,
Expand All @@ -544,11 +546,11 @@ def __str__(self):

@cached_property
def plural_form(self):
return "nplurals={0:d}; plural={1};".format(self.number, self.equation)
return "nplurals={0:d}; plural={1};".format(self.number, self.formula)

@cached_property
def plural_function(self):
return gettext.c2py(self.equation if self.equation else "0")
return gettext.c2py(self.formula if self.formula else "0")

@cached_property
def examples(self):
Expand All @@ -562,10 +564,10 @@ def examples(self):
return result

@staticmethod
def parse_formula(plurals):
def parse_plural_form(plurals):
matches = PLURAL_RE.match(plurals)
if matches is None:
raise ValueError("Failed to parse formula")
raise ValueError("Failed to parse plural forms")

number = int(matches.group(1))
formula = matches.group(2)
Expand All @@ -576,16 +578,16 @@ def parse_formula(plurals):

return number, formula

def same_plural(self, number, equation):
def same_plural(self, number, formula):
"""Compare whether given plurals formula matches."""
if number != self.number or not equation:
if number != self.number or not formula:
return False

# Convert formulas to functions
ours = self.plural_function
theirs = gettext.c2py(equation)
theirs = gettext.c2py(formula)

# Compare equation results
# Compare formula results
# It would be better to compare formulas,
# but this was easier to implement and the performance
# is still okay.
Expand Down Expand Up @@ -626,12 +628,12 @@ def list_plurals(self):
}

def save(self, *args, **kwargs):
self.type = get_plural_type(self.language.base_code, self.equation)
# Try to calculate based on equation
self.type = get_plural_type(self.language.base_code, self.formula)
# Try to calculate based on formula
if self.type == data.PLURAL_UNKNOWN:
for equations, plural in data.PLURAL_MAPPINGS:
for equation in equations:
if self.same_plural(self.number, equation):
for formulas, plural in data.PLURAL_MAPPINGS:
for formula in formulas:
if self.same_plural(self.number, formula):
self.type = plural
break
if self.type != data.PLURAL_UNKNOWN:
Expand Down

0 comments on commit e6ae842

Please sign in to comment.