Migration Recipes and Their Lifecycle
The actual migrations are performed running Python modules called migration recipes, which contain directives on how to migrate strings, which files are involved, transformations to apply, etc. These recipes are stored in mozilla-central.
When part of Firefox’s UI is migrated to Fluent, a migration recipe should be attached to the same patch that adds new strings to .ftl files.
Migration recipes can quickly become obsolete, because the referenced strings and files are removed from repositories as part of ongoing development. For these reasons, l10n-drivers periodically clean up the fluent_migrations folder in mozilla-central, keeping only recipes for 2 shipping versions (Nightly and Beta).
Hint
As a developer you don’t need to bother about updating migration recipes already in mozilla-central: if a new patch removes a string or file that is used in a migration recipe, simply ignore it, since the entire recipe will be removed within a couple of cycles.
How to Write Migration Recipes
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_1451992_preferences_applicationManager.py
is the migration recipe
used to migrate the Application Manager window in preferences. It’s also
possible to look at existing recipes in mozilla-central for inspiration.
General Recipe Structure
A migration recipe is a Python module, implementing the migrate()
function, which takes a MigrationContext
as input. The API provided
by the context is
class MigrationContext:
def add_transforms(self, target, reference, transforms):
"""Define transforms for target using reference as template.
`target` is a path of the destination FTL file relative to the
localization directory. `reference` is a path to the template FTL
file relative to the reference directory.
Each transform is an extended FTL node with `Transform` nodes as some
values.
For transforms that merely copy legacy messages or Fluent patterns,
using `fluent.migrate.helpers.transforms_from` is recommended.
"""
The skeleton of a migration recipe just implements the migrate()
function calling into ctx.add_transforms()
, and looks like
# coding=utf8
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
from __future__ import absolute_import
def migrate(ctx):
"""Bug 1552333 - Migrate feature to Fluent, part {index}"""
target = 'browser/browser/feature.ftl'
reference = 'browser/browser/feature.ftl'
ctx.add_transforms(
target,
reference,
[], # Actual transforms go here.
)
One can call into ctx.add_transforms()
multiple times. In particular, one
can create migrated content in multiple files as part of a single migration
recipe by calling ctx.add_transforms()
with different target-reference
pairs.
The docstring for this function will be used as a commit message in VCS, that’s why it’s important to make sure the bug reference is correct, and to keep the part {index} section: multiple strings could have multiple authors, and would be migrated in distinct commits (part 1, part 2, etc.).
Transforms
The work of the migrations is done by the transforms that are passed as
last argument to ctx.add_transforms()
. They’re instances of either Fluent
fluent.syntax.ast.Message
or Term
, and their content
can depend on existing translation sources. The skeleton of a Message looks like
FTL.Message(
id=FTL.Identifier(
name="msg",
),
value=FTL.Pattern(
elements=[
FTL.TextElement(
value="A string",
),
],
),
)
When migrating existing legacy translations, you’ll replace an
FTL.TextElement
with a COPY(legacy_path, "old_id")
, or one of its
variations we detail next. When migrating existing Fluent
translations, an FTL.Pattern
is replaced with a
COPY_PATTERN(old_path, "old-id")
.