Revision control

Copy as Markdown

Other Tools

# Migrating Strings to Fluent Files
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. Recipies are
After a patch with migrations landed, it will be run for all locales as part of
the [Thunderbird Cross-Channel string quarantining process](cross_channel.md).
Be sure to read [Migrating Legacy Formats](https://firefox-source-docs.mozilla.org/l10n/migrations/legacy.html)
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`**
```dtd
<!ENTITY calendar.day.button.tooltip "Switch to day view" >
<!ENTITY calendar.day.button.label "Day" >
```
**The (simplified) XUL code**
```xml
<radio id="calendar-day-view-button"
label="&calendar.day.button.label;"
tooltiptext="&calendar.day.button.tooltip;" />
```
**The new HTML**
```html
<button id="calTabDay" data-l10n-id="calendar-view-toggle-day"></button>
```
**The new FTL string**
```fluent
calendar-view-toggle-day = Day
.title = Switch to day view
```
**Renders as:**
```html
<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:**
```python
# Any copyright is dedicated to the Public Domain.
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
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:
```python
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
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:
```xml
<!ENTITY update.updateButton.label3 "Restart to update &brandShorterName;">
```
```python
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:
```ftl
update-update-button = Restart to update { -brand-shorter-name }
.accesskey = R
```