diff --git a/.github/ISSUE_TEMPLATE/localization-request.yml b/.github/ISSUE_TEMPLATE/localization-request.yml
index e2f2c8c050..b1f7a6c403 100644
--- a/.github/ISSUE_TEMPLATE/localization-request.yml
+++ b/.github/ISSUE_TEMPLATE/localization-request.yml
@@ -1,7 +1,7 @@
name: Localization Request
description: Request a new language or translation.
title: "[L10N]: "
-labels: ["enhancement"]
+labels: ["localization"]
body:
- type: textarea
attributes:
diff --git a/frontend/docs/docs/develop/localization.md b/frontend/docs/docs/develop/localization.md
index bfd9963569..f704cb7497 100644
--- a/frontend/docs/docs/develop/localization.md
+++ b/frontend/docs/docs/develop/localization.md
@@ -35,6 +35,8 @@ To add a new language directly through code change:
3. Open a pull request with the changes.
4. Once the pull request is merged, manually refresh the language list in the [Weblate Browsertrix project](https://hosted.weblate.org/projects/browsertrix). Translations are managed entirely through the Weblate interface.
+New languages will be available in user preferences only after the app is redeployed.
+
## Making Strings Localizable
All text should be wrapped in the `msg` helper to make them localizable:
diff --git a/frontend/src/assets/icons/flask-fill.svg b/frontend/src/assets/icons/flask-fill.svg
new file mode 100644
index 0000000000..c0a2265a81
--- /dev/null
+++ b/frontend/src/assets/icons/flask-fill.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/components/beta-badges.ts b/frontend/src/components/beta-badges.ts
index 40c8b17bdc..cbe9d521c5 100644
--- a/frontend/src/components/beta-badges.ts
+++ b/frontend/src/components/beta-badges.ts
@@ -14,12 +14,14 @@ const styles = unsafeCSS(stylesheet);
export class BetaIcon extends TailwindElement {
static styles = styles;
render() {
- return html`
+ return html`
+ ${msg("Beta feature")}`;
}
}
@@ -34,15 +36,20 @@ export class BetaBadge extends TailwindElement {
render() {
return html`
-
- ${msg("This part of Browsertrix is in beta!")}
- ${msg(
- "Parts might change or be broken. Please share your thoughts with us!",
- )}
+
+
+ ${msg("This part of Browsertrix is in beta!")}
+
+ ${msg(
+ "Parts might change or be broken. Please share your thoughts with us!",
+ )}
+
+
{
- this.localeNames![locale] = new Intl.DisplayNames([locale], {
- type: "language",
- }).of(locale.toUpperCase())!;
- };
-
- firstUpdated() {
- this.localeNames = {} as LocaleNames;
- allLocales.forEach(this.setLocaleName);
- }
-
- render() {
- if (!this.localeNames) {
- return;
- }
-
- const selectedLocale =
- this.appState.userPreferences?.locale || sourceLocale;
-
- return html`
-
-
- ${this.localeNames[selectedLocale as LocaleCodeEnum]}
-
-
- ${allLocales.map(
- (locale) =>
- html`
- ${this.localeNames![locale]}
- `,
- )}
-
-
- `;
- }
-
- async localeChanged(event: SlSelectEvent) {
- const newLocale = event.detail.item.value as LocaleCodeEnum;
-
- AppStateService.partialUpdateUserPreferences({ locale: newLocale });
-
- if (newLocale !== getLocale()) {
- void setLocale(newLocale);
- }
- }
-}
diff --git a/frontend/src/components/ui/user-language-select.ts b/frontend/src/components/ui/user-language-select.ts
new file mode 100644
index 0000000000..eba4547ad3
--- /dev/null
+++ b/frontend/src/components/ui/user-language-select.ts
@@ -0,0 +1,91 @@
+import type { SlSelectEvent } from "@shoelace-style/shoelace";
+import { html } from "lit";
+import { customElement, state } from "lit/decorators.js";
+
+import { sourceLocale } from "@/__generated__/locale-codes";
+import { BtrixElement } from "@/classes/BtrixElement";
+import { allLocales, type LocaleCodeEnum } from "@/types/localization";
+import { getLocale, setLocale } from "@/utils/localization";
+import { AppStateService } from "@/utils/state";
+
+/**
+ * Select language that Browsertrix app will be shown in
+ */
+@customElement("btrix-user-language-select")
+export class LocalePicker extends BtrixElement {
+ @state()
+ private localeNames: { [locale: string]: string } = {};
+
+ firstUpdated() {
+ this.setLocaleNames();
+ }
+
+ private setLocaleNames() {
+ const localeNames: LocalePicker["localeNames"] = {};
+
+ // TODO Add browser-preferred languages
+ // https://github.com/webrecorder/browsertrix/issues/2143
+ allLocales.forEach((locale) => {
+ const name = new Intl.DisplayNames([locale], {
+ type: "language",
+ }).of(locale);
+
+ if (!name) return;
+
+ localeNames[locale] = name;
+ });
+
+ this.localeNames = localeNames;
+ }
+
+ render() {
+ const selectedLocale =
+ this.appState.userPreferences?.locale || sourceLocale;
+
+ return html`
+
+
+
+ ${this.localeNames[selectedLocale as LocaleCodeEnum]}
+
+
+ ${Object.keys(this.localeNames)
+ .sort()
+ .map(
+ (locale) =>
+ html`
+ ${this.localeNames[locale]}
+ `,
+ )}
+
+
+ `;
+ }
+
+ async localeChanged(event: SlSelectEvent) {
+ const newLocale = event.detail.item.value as LocaleCodeEnum;
+
+ AppStateService.partialUpdateUserPreferences({ locale: newLocale });
+
+ if (newLocale !== getLocale()) {
+ void setLocale(newLocale);
+ }
+ }
+}
diff --git a/frontend/src/features/crawl-workflows/workflow-list.ts b/frontend/src/features/crawl-workflows/workflow-list.ts
index 74f8a1172f..7ecc34f50e 100644
--- a/frontend/src/features/crawl-workflows/workflow-list.ts
+++ b/frontend/src/features/crawl-workflows/workflow-list.ts
@@ -27,7 +27,6 @@ import type { ListWorkflow } from "@/types/crawler";
import { humanizeSchedule } from "@/utils/cron";
import { srOnly, truncate } from "@/utils/css";
import { formatNumber, getLocale } from "@/utils/localization";
-import { numberFormatter } from "@/utils/number";
import { pluralOf } from "@/utils/pluralize";
const formatNumberCompact = (v: number) =>
@@ -245,13 +244,9 @@ export class WorkflowListItem extends LitElement {
${this.safeRender((workflow) => {
if (workflow.schedule) {
return msg(
- str`${humanizeSchedule(
- workflow.schedule,
- {
- length: "short",
- },
- numberFormatter,
- )}`,
+ str`${humanizeSchedule(workflow.schedule, {
+ length: "short",
+ })}`,
);
}
if (workflow.lastStartedByName) {
diff --git a/frontend/src/index.ts b/frontend/src/index.ts
index 2dbb983c2c..495303353c 100644
--- a/frontend/src/index.ts
+++ b/frontend/src/index.ts
@@ -30,7 +30,7 @@ import type { NavigateEventDetail } from "@/controllers/navigate";
import type { NotifyEventDetail } from "@/controllers/notify";
import { theme } from "@/theme";
import { type Auth } from "@/types/auth";
-import { type LocaleCodeEnum } from "@/types/localization";
+import { translatedLocales, type LocaleCodeEnum } from "@/types/localization";
import { type AppSettings } from "@/utils/app";
import {
getLocale,
@@ -454,9 +454,13 @@ export class App extends BtrixElement {
`
: html`
${this.renderSignUpLink()}
-
+ ${(translatedLocales as unknown as string[]).length > 1
+ ? html`
+
+ `
+ : nothing}
`}