Repository: mozilla/pontoon

Issue:
Add ability for locales to opt out of access key localization
Fluent Rich editor has a special UI for messages with access keys, which lists access key candidates.

Some locales don't translate access keys, mostly because they use non-Latin scripts.

We should add the ability for Locales to opt out of access key localization.

For those, we should use the original string as an access key candidate or even prefill the field with its value.


Discussion:

Resolving patch:
diff --git a/pontoon/base/migrations/0042_locale_accesskey_localization.py b/pontoon/base/migrations/0042_locale_accesskey_localization.py
new file mode 100644
--- /dev/null
+++ b/pontoon/base/migrations/0042_locale_accesskey_localization.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.2.15 on 2023-05-11 00:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("base", "0041_alter_userprofile_visibility_email"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="locale",
+            name="accesskey_localization",
+            field=models.BooleanField(
+                default=True,
+                help_text="\n        Allow localization of access keys if they are part of a string.\n        Some locales don't translate access keys, mostly because they use non-Latin scripts.\n    ",
+            ),
+        ),
+    ]
diff --git a/pontoon/base/models.py b/pontoon/base/models.py
--- a/pontoon/base/models.py
+++ b/pontoon/base/models.py
@@ -888,6 +888,14 @@ class Direction(models.TextChoices):
         related_name="locale_latest",
     )
 
+    accesskey_localization = models.BooleanField(
+        default=True,
+        help_text="""
+        Allow localization of access keys if they are part of a string.
+        Some locales don't translate access keys, mostly because they use non-Latin scripts.
+    """,
+    )
+
     objects = LocaleQuerySet.as_manager()
 
     class Meta:
diff --git a/pontoon/pretranslation/transformer.py b/pontoon/pretranslation/transformer.py
--- a/pontoon/pretranslation/transformer.py
+++ b/pontoon/pretranslation/transformer.py
@@ -96,8 +96,8 @@ def create_locale_plural_variants(node: FTL.SelectExpression, locale: Locale):
 class PreparePretranslation(Transformer):
     """
     Flattens the given Pattern, uplifting selectors to the highest possible level and
-    duplicating shared parts in the variants. All other Placeables are serialised as
-    TextElements.
+    duplicating shared parts in the variants. Transforms plural variants to match the
+    locale.
     """
 
     def __init__(self, locale: Locale):
@@ -135,6 +135,14 @@ def __init__(
         self.locale = locale
         self.services: list[str] = []
 
+    def visit_Attribute(self, node):
+        if (
+            node.id.name.endswith("accesskey")
+            and not self.locale.accesskey_localization
+        ):
+            return node
+        return self.generic_visit(node)
+
     def visit_Pattern(self, node: FTL.Pattern):
         has_selects = False
         source = ""


Test patch:
diff --git a/pontoon/pretranslation/tests/test_pretranslate.py b/pontoon/pretranslation/tests/test_pretranslate.py
--- a/pontoon/pretranslation/tests/test_pretranslate.py
+++ b/pontoon/pretranslation/tests/test_pretranslate.py
@@ -171,6 +171,77 @@ def test_get_pretranslations_fluent_whitespace(
     assert response == [(pretranslated_string, None, tm_user)]
 
 
+@patch("pontoon.pretranslation.pretranslate.get_google_translate_data")
+@pytest.mark.django_db
+def test_get_pretranslations_fluent_accesskeys(
+    gt_mock, fluent_resource, google_translate_locale, gt_user
+):
+    # By default, pretranslate accesskeys
+    input_string = dedent(
+        """
+        title =
+            .foo = Bar
+            .accesskey = B
+    """
+    )
+    fluent_entity = EntityFactory(resource=fluent_resource, string=input_string)
+
+    gt_mock.return_value = {
+        "status": True,
+        "translation": "gt_translation",
+    }
+
+    expected = dedent(
+        """
+        title =
+            .foo = gt_translation
+            .accesskey = gt_translation
+    """
+    )
+
+    # Re-serialize to match whitespace
+    pretranslated_string = serializer.serialize_entry(parser.parse_entry(expected))
+
+    response = get_pretranslations(fluent_entity, google_translate_locale)
+    assert response == [(pretranslated_string, None, gt_user)]
+
+
+@patch("pontoon.pretranslation.pretranslate.get_google_translate_data")
+@pytest.mark.django_db
+def test_get_pretranslations_fluent_accesskeys_opt_out(
+    gt_mock, fluent_resource, google_translate_locale, gt_user
+):
+    # For locales that opt-out of accesskey localization, do not pretranslate them
+    google_translate_locale.accesskey_localization = False
+    input_string = dedent(
+        """
+        title =
+            .foo = Bar
+            .accesskey = B
+    """
+    )
+    fluent_entity = EntityFactory(resource=fluent_resource, string=input_string)
+
+    gt_mock.return_value = {
+        "status": True,
+        "translation": "gt_translation",
+    }
+
+    expected = dedent(
+        """
+        title =
+            .foo = gt_translation
+            .accesskey = B
+    """
+    )
+
+    # Re-serialize to match whitespace
+    pretranslated_string = serializer.serialize_entry(parser.parse_entry(expected))
+
+    response = get_pretranslations(fluent_entity, google_translate_locale)
+    assert response == [(pretranslated_string, None, gt_user)]
+
+
 @pytest.mark.django_db
 def test_get_pretranslations_fluent_multiline(
     fluent_resource, entity_b, locale_b, tm_user