Migrating Strings to Fluent Files

Like Firefox, Thunderbird developers are working on migrating strings from legacy formats to Fluent. The process is very similar to how migrations are done for Firefox. The differences are detailed below.

Migration Recipes

When part of Thunderbird’s UI is migrated to Fluent, a migration recipe should be included in the same patch that adds new strings to .ftl files. Recipes are stored in comm-central. After a patch with migrations landed, it will be run for all locales as part of the Thunderbird Cross-Channel string quarantining process.

Be sure to read Migrating Legacy Formats along with the below example.

The migration recipe’s filename should start with a reference to the associated bug number, and include a brief description of the bug, e.g. bug_1805746_calendar_view.py for the below example.

Example: Migrate Multiple DTD strings to Fluent

Often, strings are migrated as part of ongoing UI work to convert XUL code to HTML. Multiple DTD strings may convert to a single Fluent string with attributes. It really depends on the document structure and what the UI changes are doing.

Legacy strings are in comm/calendar/locales/en-US/chrome/calendar/calendar.dtd

<!ENTITY calendar.day.button.tooltip            "Switch to day view" >
<!ENTITY calendar.day.button.label              "Day" >

The (simplified) XUL code

<radio id="calendar-day-view-button"
       label="&calendar.day.button.label;"
       tooltiptext="&calendar.day.button.tooltip;" />

The new HTML

<button id="calTabDay" data-l10n-id="calendar-view-toggle-day"></button>

The new FTL string

calendar-view-toggle-day = Day
  .title = Switch to day view

Renders as:

<button id="calTabDay" title="Switch to day view">Day</button>

This case migrates two DTD strings, calendar.day.button.label and calendar.day.button.tooltip to create a single FTL string, calendar-view-toggle-day, with one (title) attribute.

The migration recipe:

# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/

from fluent.migratetb.helpers import transforms_from
from fluent.migratetb import COPY

def migrate(ctx):
    """Bug 1805746 - Update Calendar View selection part {index}."""
    target = reference = "calendar/calendar/calendar-widgets.ftl"
    source = "calendar/chrome/calendar/calendar.dtd"

    ctx.add_transforms(
        target,
        reference,
        transforms_from(
            """
calendar-view-toggle-day = { COPY(from_path, "calendar.day.button.label") }
    .title = { COPY(from_path, "calendar.day.button.tooltip") }
""",
            from_path=source,
        ),
    )

Migrations are Python modules, and implement a single migrate(MigrationContext) function. The migrate() function makes calls into MigrationContext.add_transforms().

The add_transforms() function takes three arguments:

  • target_path: Path to the target l10n file

  • reference_path: Path to the reference (en-US) file

  • A list of Transforms, the source_path (legacy translated strings file) is set here

Note

For Thunderbird migrations, the target and reference path are the same.

Transforms are rather dense AST nodes. See Transforms for the exact details.

There are some helper functions that simplify creating the ASTs. The above example uses the transforms_from() helper function. It is equivalent to:

target = reference = "calendar/calendar/calendar-widgets.ftl"
source = "calendar/chrome/calendar/calendar.dtd"

ctx.add_transforms(
    target,
    reference,
    [
        FTL.Message(
            id=FTL.Identifier("calendar-view-toggle-day"),
            value=COPY(source, "calendar.day.button.label"),
            attributes=[
                FTL.Attribute(
                    id=FTL.Identifier("title"),
                    value=COPY(
                        source,
                        "calendar.day.button.tooltip"
                    )
                )
            ]
        )
    ]
)

transforms_from() allows copying reference FTL strings, and replacing the value of each message with a COPY Transform that copies values from the DTD file at from_path.

There are other Transforms like COPY. See Migrating Legacy Formats for usage information.

Thunderbird migration helpers

The REPLACE works with transforms_from if provided the extra context that it needs.

Starting with aboutDialog.dtd containing:

<!ENTITY update.updateButton.label3               "Restart to update &brandShorterName;">
from fluent.migratetb.helpers import TERM_REFERENCE, transforms_from

# This can't just be a straight up literal dict (eg: {"a":"b"}) because the
# validator fails... so make it a function call that returns a dict.. it works
about_replacements = dict({
    "&brandShorterName;": TERM_REFERENCE("brand-shorter-name"),
})


def migrate(ctx):
    """Bug 1816532 - Migrate aboutDialog.dtd strings to Fluent, part {index}"""
    target = reference = "mail/messenger/aboutDialog.ftl"
    source = "mail/chrome/messenger/aboutDialog.dtd"

    ctx.add_transforms(
        target,
        reference,
        transforms_from(
          """
update-update-button = { REPLACE(source, "update.updateButton.label3", about_replacements) }
    .accesskey = { COPY(source, "update.updateButton.accesskey") }
""", source=source, about_replacements=about_replacements))

The resulting aboutDialog.ftl will get:

update-update-button = Restart to update { -brand-shorter-name }
    .accesskey = R