Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
40e0960
chore(l10n): move fluent files
LeoMcA Feb 3, 2026
23d5342
chore(l10n): load locales on demand
LeoMcA Feb 3, 2026
82e1a3b
chore(l10n): require l10n tags to have an id
LeoMcA Feb 3, 2026
f38b6c8
fix(l10n): don't return html type from l10n template tag
LeoMcA Feb 3, 2026
3903375
chore(l10n): add string scraper/extractor script
LeoMcA Feb 3, 2026
8407f21
chore(l10n): add lint mode to extractor
LeoMcA Feb 3, 2026
3245e89
chore(l10n): add generated qa locale
LeoMcA Feb 3, 2026
538881a
chore(l10n): add docs
LeoMcA Feb 3, 2026
f30f28b
chore(l10n): script to generate missing ids
LeoMcA Jan 30, 2026
82dcb10
run l10n:extract
LeoMcA Feb 3, 2026
ea56bde
run scripts/migrate-l10n/index.js
LeoMcA Feb 3, 2026
b5974db
run prettier
LeoMcA Feb 3, 2026
3dba163
run l10n:extract
LeoMcA Feb 3, 2026
153f4db
chore(l10n): remove script to generate missing ids
LeoMcA Feb 3, 2026
a1fe237
extract.js -> cli.js
LeoMcA Feb 10, 2026
1e27dbf
add comments to explain pseudo-locales better
LeoMcA Feb 10, 2026
0a17c28
add --gen-pseudo option to l10n script
LeoMcA Feb 10, 2026
992ecd9
explain why we check if localizedStringOrHtml is a string
LeoMcA Feb 10, 2026
2f2d567
move warning to constant
LeoMcA Feb 10, 2026
e5e897d
split up extractor function and add unit tests
LeoMcA Feb 10, 2026
b6c47ad
add newline to end of file
LeoMcA Feb 10, 2026
1b57f8d
move locale loading on the client into a hook
LeoMcA Feb 10, 2026
662edc0
add --help to l10n cli
LeoMcA Mar 11, 2026
b7641d0
EDIT_WARNING -> TEMPLATE_HEADER; update references to generator
LeoMcA Mar 11, 2026
9b3e8fc
add newline
LeoMcA Mar 11, 2026
8d9a454
Revert "run prettier"
LeoMcA Mar 11, 2026
ae6fe33
Revert "run scripts/migrate-l10n/index.js"
LeoMcA Mar 11, 2026
ebb592e
Merge remote-tracking branch 'origin/main' into fluent-ast
LeoMcA Mar 11, 2026
103fd7d
revert unnecessary lets to consts
LeoMcA Mar 11, 2026
652560b
chore(l10n): script to generate missing ids
LeoMcA Jan 30, 2026
df84148
run scripts/migrate-l10n/index.js
LeoMcA Mar 11, 2026
eb31d2a
run prettier
LeoMcA Mar 11, 2026
9f70f9e
regenerate template.ftl
LeoMcA Mar 11, 2026
52c589b
Remove components/left-sidebar/inline.js
LeoMcA Mar 11, 2026
e402016
Revert "chore(l10n): script to generate missing ids"
LeoMcA Mar 11, 2026
1f3ec40
don't shadow path import
LeoMcA Mar 11, 2026
7df3367
fail on duplicated scraped ids with different text
LeoMcA Mar 11, 2026
08e3ce4
fail on duplicated ids with different text across manual and scraped …
LeoMcA Mar 11, 2026
446cf83
fix duplicates
LeoMcA Mar 11, 2026
d9aedcd
remove duplicates where ids and text match from template.ftl
LeoMcA Mar 11, 2026
5dea7c4
add l10n lint to lefthook
LeoMcA Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
- name: Run tsc
run: npx tsc --noEmit

- name: Lint l10n strings
run: npm run l10n -- --lint

test:
runs-on: ubuntu-latest
defaults:
Expand Down
2 changes: 2 additions & 0 deletions .lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pre-push:
run: npx stylelint '**/*.css' --cache --ignore-path .gitignore
- name: tsc
run: npx tsc
- name: lint l10n strings
run: npm run l10n -- --lint
Comment on lines +48 to +49
Copy link
Copy Markdown
Contributor

@caugner caugner Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given how fast this runs, should we not also run it on pre-commit?

% time npm run l10n -- --lint

> @mdn/fred@2.2.1 l10n
> node l10n/cli.js --lint

npm run l10n -- --lint  0.64s user 0.07s system 113% cpu 0.627 total

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The glob option supports multiple values, so we could run it only when relevant files were changed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The failure case is something like:

  • add string to components/a.js
  • add string to components/b.js
  • stage a.js
  • commit
  • new strings from both a and b are in template.ftl


post-merge:
only:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ MDN's next fr(ont)e(n)d.
- `npm run preview`
- runs the preview server: using the production bundles with the rari server: useful for testing our prod rspack config

## L10n

See [the l10n README](./l10n/README.md).

### Accessing from non-localhost

If you want to access fred from a different machine, you'll need to run with certain options:
Expand Down
14 changes: 12 additions & 2 deletions components/a11y-menu/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@ export class A11yMenu extends ServerComponent {
*/
render(context) {
return html`<ul class="a11y-menu">
<li><a href="#content">${context.l10n`Skip to main content`}</a></li>
<li><a href="#search">${context.l10n`Skip to search`}</a></li>
<li>
<a href="#content"
>${context.l10n(
"a11y-menu-skip-to-main-content",
)`Skip to main content`}</a
>
</li>
<li>
<a href="#search"
>${context.l10n("a11y-menu-skip-to-search")`Skip to search`}</a
>
</li>
</ul>`;
}
}
16 changes: 12 additions & 4 deletions components/article-footer/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ function Contribute(context) {
return html`<a
class="article-footer__contribute"
href=${`/${context.locale}/docs/MDN/Community/Getting_started`}
>${context.l10n`Learn how to contribute`}</a
>${context.l10n(
"article-footer-learn-how-to-contribute",
)`Learn how to contribute`}</a
>`;
}

Expand Down Expand Up @@ -112,7 +114,9 @@ function GitHubSourceLink(context) {
rel="noopener"
>${locale === "de"
? "Übersetzung auf GitHub anzeigen"
: context.l10n`View this page on GitHub`}</a
: context.l10n(
"article-footer-view-this-page-on-github",
)`View this page on GitHub`}</a
>`;
}

Expand Down Expand Up @@ -146,12 +150,16 @@ function GitHubIssueLink(context) {
return html`<a
class="external"
href=${url.href}
title=${context.l10n`This will take you to GitHub to file a new issue.`}
title=${context.l10n(
"article-footer-this-will-take-you-to-github-to",
)`This will take you to GitHub to file a new issue.`}
target="_blank"
rel="noopener"
>${locale === "de"
? "Fehler mit dieser Übersetzung melden"
: context.l10n`Report a problem with this content`}</a
: context.l10n(
"article-footer-report-a-problem-with-this-conte",
)`Report a problem with this content`}</a
>`;
}

Expand Down
32 changes: 22 additions & 10 deletions components/baseline-indicator/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,26 +137,34 @@ export class BaselineIndicator extends ServerComponent {
class="indicator"
role="img"
aria-label=${level === "not"
? context.l10n`Baseline Cross`
: context.l10n`Baseline Check`}
? context.l10n("baseline-indicator-baseline-cross")`Baseline Cross`
: context.l10n("baseline-indicator-baseline-check")`Baseline Check`}
></span>
<div class="status-title">
${level === "not"
? html`<span class="not-bold"
>${context.l10n`Limited availability`}</span
>${context.l10n(
"baseline-indicator-limited-availability",
)`Limited availability`}</span
>`
: html`
${context.l10n`Baseline`}
${context.l10n("baseline-indicator-baseline")`Baseline`}
<span class="not-bold">
${level === "high"
? context.l10n`Widely available`
? context.l10n(
"baseline-indicator-widely-available",
)`Widely available`
: low_date?.getFullYear()}
</span>
${status.asterisk && " *"}
`}
</div>
${level === "low"
? html`<div class="pill">${context.l10n`Newly available`}</div>`
? html`<div class="pill">
${context.l10n(
"baseline-indicator-newly-available",
)`Newly available`}
</div>`
: nothing}
<div class="browsers">
${ENGINES.map(
Expand All @@ -174,7 +182,7 @@ export class BaselineIndicator extends ServerComponent {
isBrowserSupported(browser) ? "supported" : ""
}`}
role="img"
aria-label=${`${browser.name} ${isBrowserSupported(browser) ? context.l10n`check` : context.l10n`cross`}`}
aria-label=${`${browser.name} ${isBrowserSupported(browser) ? context.l10n("baseline-indicator-check")`check` : context.l10n("baseline-indicator-cross")`cross`}`}
></span>`,
)}
</span>`,
Expand Down Expand Up @@ -219,12 +227,14 @@ export class BaselineIndicator extends ServerComponent {
target="_blank"
class="learn-more"
>
${context.l10n`Learn more`}
${context.l10n("baseline-indicator-learn-more")`Learn more`}
</a>
</li>
<li>
<a href=${bcdLink} data-glean-id="baseline_link_bcd_table">
${context.l10n`See full compatibility`}
${context.l10n(
"baseline-indicator-see-full-compatibility",
)`See full compatibility`}
</a>
</li>
<li>
Expand All @@ -235,7 +245,9 @@ export class BaselineIndicator extends ServerComponent {
target="_blank"
rel="noreferrer"
>
${context.l10n`Report feedback`}
${context.l10n(
"baseline-indicator-report-feedback",
)`Report feedback`}
</a>
</li>
</ul>
Expand Down
4 changes: 3 additions & 1 deletion components/blog-index/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ export class BlogIndex extends ServerComponent {
html`
<div id="content" class="blog-index">
<header class="blog-index__header">
<h1>${context.l10n`Blog it better`}</h1>
<h1>
${context.l10n("blog-index-blog-it-better")`Blog it better`}
</h1>
</header>
<div class="blog-index__main">
<section class="blog-index__articles">
Expand Down
2 changes: 1 addition & 1 deletion components/blog-post/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class BlogPost extends ServerComponent {
return PageLayout.render(
context,
html`<p id="content">
${context.l10n("blog-post-not-found")`Blog post not found`}
${context.l10n("blog-post-not-found")`Blog post not found.`}
</p>`,
);
}
Expand Down
2 changes: 1 addition & 1 deletion components/blog/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function PrevNextLinks(context, { blogMeta }) {
>
<article>
<h2>
<strong>${context.l10n("blog-previous")`Previous Post`}</strong>
<strong>${context.l10n("blog-previous")`Previous post`}</strong>
${blogMeta.links.previous.title}
</h2>
</article>
Expand Down
47 changes: 33 additions & 14 deletions components/collection-save-button/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,22 @@ export class MDNCollectionSaveButton extends L10nMixin(LitElement) {
<button
class="collection-save-button"
data-state=${this._bookmarks.value?.length ? "remove" : "save"}
title=${this.l10n`Save in collection`}
title=${this.l10n(
"collection-save-button-save-in-collection",
)`Save in collection`}
@click=${this._open}
>
<span
>${this._bookmarks.value?.length
? this.l10n`Remove`
: this.l10n`Save`}</span
? this.l10n("collection-save-button-remove")`Remove`
: this.l10n("collection-save-button-save")`Save`}</span
>
</button>
<mdn-modal modal-title=${this.l10n`Add to collection`}>
<mdn-modal
modal-title=${this.l10n(
"collection-save-button-add-to-collection",
)`Add to collection`}
>
${this._bookmarks.render({
initial: () => html`<progress></progress>`,
pending: () => html`<progress></progress>`,
Expand All @@ -218,7 +224,9 @@ export class MDNCollectionSaveButton extends L10nMixin(LitElement) {
pending: () => html`<progress></progress>`,
complete: (collections) => html`
<label>
${this.l10n`Collection:`}
${this.l10n(
"collection-save-button-collection",
)`Collection:`}
<select
.value=${this._item?.collection_id}
@change=${this._selectChange}
Expand All @@ -237,7 +245,9 @@ export class MDNCollectionSaveButton extends L10nMixin(LitElement) {
? "★"
: "☆"}
${collection.name === "Default"
? this.l10n`Saved articles`
? this.l10n(
"collection-save-button-saved-articles",
)`Saved articles`
: collection.name}
</option>
`,
Expand All @@ -246,31 +256,36 @@ export class MDNCollectionSaveButton extends L10nMixin(LitElement) {
——————————
</option>
<option value=${ADD_VALUE}>
+ ${this.l10n`New collection`}
+
${this.l10n(
"collection-save-button-new-collection",
)`New collection`}
</option>
</select>
</label>
<label>
${this.l10n`Name:`}
${this.l10n("collection-save-button-name")`Name:`}
<input .value=${this._item?.title || this.docTitle} />
</label>
<label>
${this.l10n`Note:`}
${this.l10n("collection-save-button-note")`Note:`}
<textarea
.value=${this._item?.notes || ""}
></textarea>
</label>
<mdn-button @click=${this._submit}>
${this._pending && this._lastAction === "save"
? this.l10n`Saving…`
: this.l10n`Save`}
? this.l10n(
"collection-save-button-saving",
)`Saving…`
: this.l10n("collection-save-button-save")`Save`}
</mdn-button>
<mdn-button
@click=${this._cancel}
?disabled=${this._pending}
variant="secondary"
>
${this.l10n`Cancel`}
${this.l10n("collection-save-button-cancel")`Cancel`}
</mdn-button>
${bookmarks?.length
? html`<mdn-button
Expand All @@ -282,8 +297,12 @@ export class MDNCollectionSaveButton extends L10nMixin(LitElement) {
!isCurrentInCollection}
>
${this._pending && this._lastAction === "delete"
? this.l10n`Deleting…`
: this.l10n`Delete`}
? this.l10n(
"collection-save-button-deleting",
)`Deleting…`
: this.l10n(
"collection-save-button-delete",
)`Delete`}
</mdn-button>`
: nothing}
`,
Expand Down
10 changes: 6 additions & 4 deletions components/color-theme/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export class MDNColorTheme extends L10nMixin(LitElement) {
this._mode = "light dark";
this._options = Object.entries({
"light dark": this.l10n("theme-default")`OS default`,
light: this.l10n`Light`,
dark: this.l10n`Dark`,
light: this.l10n("color-theme-light")`Light`,
dark: this.l10n("color-theme-dark")`Dark`,
});
}

Expand Down Expand Up @@ -68,9 +68,11 @@ export class MDNColorTheme extends L10nMixin(LitElement) {
class="color-theme__button"
data-mode=${this._mode}
type="button"
aria-label=${this.l10n`Switch color theme`}
aria-label=${this.l10n(
"color-theme-switch-color-theme",
)`Switch color theme`}
>
<span>${this.l10n`Theme`}</span>
<span>${this.l10n("color-theme-theme")`Theme`}</span>
</button>
<div
slot="dropdown"
Expand Down
6 changes: 4 additions & 2 deletions components/compat-table/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ export class MDNCompatTable extends L10nMixin(LitElement) {
if (status.deprecated) {
icons.push({
title: this._getLegendLabel("deprecated"),
text: this.l10n("compat-deprecated")`Experimental`,
text: this.l10n("compat-deprecated")`Deprecated`,
iconClassName: "icon-deprecated",
});
}
Expand Down Expand Up @@ -744,7 +744,9 @@ export class MDNCompatTable extends L10nMixin(LitElement) {
if (versionIsPreview(item.version_added, browser)) {
supportNotes.push({
iconName: "footnote",
label: this.l10n("compat-support-preview")`Preview browser support`,
label: this.l10n(
"compat-support-preview-browser",
)`Preview browser support`,
});
}

Expand Down
Loading
Loading