diff --git a/package-lock.json b/package-lock.json
index afc642c49ff..c6da2aa4660 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1277,9 +1277,9 @@
}
},
"node_modules/@lit/localize-tools": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@lit/localize-tools/-/localize-tools-0.8.1.tgz",
- "integrity": "sha512-bWjKXdcTDW0v3w/errna+bzJ2eDfpuz+V10sswF5oXd6C0mHw20sRU+wrXOoj1avVcI7g8cWVQHUJHkQs9UxAQ==",
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/@lit/localize-tools/-/localize-tools-0.8.2.tgz",
+ "integrity": "sha512-Hf7jMfamdX26FN4SBRtM7zoToCUeShzI8NQcLdHLdpPdNPlqbEYMjqRaTQPm/PsbzTl6uZwfE3/kV+NvvrJS2A==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -10824,6 +10824,8 @@
"devDependencies": {
"@cfworker/json-schema": "^4.1.1",
"@google-labs/tsconfig": "^0.0.2",
+ "@lit/localize": "^0.12.2",
+ "@lit/localize-tools": "^0.8.2",
"@tweakpane/core": "^2.0.5",
"@types/flubber": "^0.4.0",
"@types/gapi": "^0.0.47",
diff --git a/packages/folio/package.json b/packages/folio/package.json
index cac3bb683c7..21ed5ff46c1 100644
--- a/packages/folio/package.json
+++ b/packages/folio/package.json
@@ -27,7 +27,7 @@
},
"devDependencies": {
"@google-labs/tsconfig": "^0.0.2",
- "@lit/localize-tools": "^0.8.1",
+ "@lit/localize-tools": "^0.8.2",
"vite": "^7.3.2",
"wireit": "^0.15.0-pre.2"
},
diff --git a/packages/visual-editor/localization/de.xlb b/packages/visual-editor/localization/de.xlb
new file mode 100644
index 00000000000..6833112093d
--- /dev/null
+++ b/packages/visual-editor/localization/de.xlb
@@ -0,0 +1,10 @@
+
+
+
+ Dieses Gem wird von Google Labs bereitgestellt, nicht von Gemini Apps. Es kann
+ Fehler machen. Bitte überprüfen Sie die Ergebnisse.
+ <a href="https://support.google.com/gemini?p=ai_apps">Weitere Informationen</a>
+ Ich möchte E-Mails zu Modellaktualisierungen, Angeboten, nützlichen Tipps und Neuigkeiten zu Opal erhalten
+ Ich möchte zu künftigen Nutzerstudien von Opal eingeladen werden
+
+
diff --git a/packages/visual-editor/localization/fr.xlb b/packages/visual-editor/localization/fr.xlb
new file mode 100644
index 00000000000..57cbac3df3f
--- /dev/null
+++ b/packages/visual-editor/localization/fr.xlb
@@ -0,0 +1,10 @@
+
+
+
+ Ce Gem est fourni par Google Labs, et non par Gemini Apps. Il peut
+ faire des erreurs, veuillez donc vérifier les résultats.
+ <a href="https://support.google.com/gemini?p=ai_apps">En savoir plus</a>
+ Je souhaite recevoir des e-mails concernant les mises à jour des modèles, les offres, les conseils utiles et les actualités d'Opal
+ Je souhaite être invité(e) à participer à de futures études de recherche d'Opal
+
+
diff --git a/packages/visual-editor/localization/lit-localize.json b/packages/visual-editor/localization/lit-localize.json
new file mode 100644
index 00000000000..62a4958d91c
--- /dev/null
+++ b/packages/visual-editor/localization/lit-localize.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
+ "sourceLocale": "en",
+ "targetLocales": ["fr", "de"],
+ "tsConfig": "../tsconfig.json",
+ "output": {
+ "mode": "runtime",
+ "outputDir": "../src/generated/localization",
+ "language": "ts",
+ "localeCodesModule": "src/generated/localization/locale-codes.ts"
+ },
+ "interchange": {
+ "format": "xlb",
+ "outputFile": "messages.xmb",
+ "translationsGlob": "*.xlb"
+ }
+}
diff --git a/packages/visual-editor/localization/messages.xmb b/packages/visual-editor/localization/messages.xmb
new file mode 100644
index 00000000000..206edfea9c7
--- /dev/null
+++ b/packages/visual-editor/localization/messages.xmb
@@ -0,0 +1,10 @@
+
+
+
+ This Gem is powered by Google Labs, not Gemini Apps. It can
+ make mistakes, so double-check it.
+ <a href="https://support.google.com/gemini?p=ai_apps">Learn more</a>
+ I’d like to receive emails for model updates, offers, useful tips and news about Opal
+ I’m interested in being invited to future research studies from Opal
+
+
diff --git a/packages/visual-editor/package.json b/packages/visual-editor/package.json
index a7143749249..11adfc1a525 100644
--- a/packages/visual-editor/package.json
+++ b/packages/visual-editor/package.json
@@ -31,7 +31,9 @@
"lint": "wireit",
"export-functions": "wireit",
"import-declarations": "wireit",
- "build:opie-instructions": "wireit"
+ "build:opie-instructions": "wireit",
+ "localize:extract": "wireit",
+ "localize:build": "wireit"
},
"wireit": {
"build": {
@@ -269,6 +271,29 @@
"output": [
"src/a2/agent/graph-editing/instructions/generated.ts"
]
+ },
+ "localize:extract": {
+ "command": "lit-localize extract --config localization/lit-localize.json",
+ "files": [
+ "src/**/*.ts",
+ "!src/generated/localization/",
+ "localization/lit-localize.json"
+ ],
+ "output": [
+ "localization/messages.xmb"
+ ]
+ },
+ "localize:build": {
+ "command": "lit-localize build --config localization/lit-localize.json",
+ "files": [
+ "src/**/*.ts",
+ "!src/generated/localization/",
+ "localization/*.xlb",
+ "localization/lit-localize.json"
+ ],
+ "output": [
+ "src/generated/localization/"
+ ]
}
},
"repository": {
@@ -291,6 +316,8 @@
"devDependencies": {
"@cfworker/json-schema": "^4.1.1",
"@google-labs/tsconfig": "^0.0.2",
+ "@lit/localize": "^0.12.2",
+ "@lit/localize-tools": "^0.8.2",
"@tweakpane/core": "^2.0.5",
"@types/flubber": "^0.4.0",
"@types/gapi": "^0.0.47",
diff --git a/packages/visual-editor/src/bootstrap.ts b/packages/visual-editor/src/bootstrap.ts
index aa99b261ae1..d37026d4e7d 100644
--- a/packages/visual-editor/src/bootstrap.ts
+++ b/packages/visual-editor/src/bootstrap.ts
@@ -15,6 +15,7 @@ import { makeUrl, OAUTH_REDIRECT, parseUrl } from "./ui/navigation/urls.js";
import { CLIENT_DEPLOYMENT_CONFIG } from "./ui/config/client-deployment-configuration.js";
import { connectToOpalShellHost } from "./ui/utils/opal-shell-guest.js";
import { Utils } from "./sca/utils.js";
+import { setLocale } from "./localization/config.js";
export { bootstrap };
@@ -68,6 +69,9 @@ async function bootstrap(bootstrapArgs: BootstrapArguments) {
const guestConfiguration = await shellHost.getConfiguration();
const parsedUrl = parseUrl(window.location.href);
+ if (parsedUrl.hl) {
+ await setLocale(parsedUrl.hl);
+ }
const { lite, page, colorScheme } = parsedUrl;
if (
// Signed-out users can access public graphs.
diff --git a/packages/visual-editor/src/generated/localization/de.ts b/packages/visual-editor/src/generated/localization/de.ts
new file mode 100644
index 00000000000..92465e17016
--- /dev/null
+++ b/packages/visual-editor/src/generated/localization/de.ts
@@ -0,0 +1,18 @@
+
+ // Do not modify this file by hand!
+ // Re-generate this file by running lit-localize
+
+ import {html} from 'lit';
+
+
+ /* eslint-disable no-irregular-whitespace */
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+
+ export const templates = {
+ 'hbeb436a6f6a2a826': html`Dieses Gem wird von Google Labs bereitgestellt, nicht von Gemini Apps. Es kann
+ Fehler machen. Bitte überprüfen Sie die Ergebnisse.
+ Weitere Informationen`,
+'s018656f690df2a0e': `Ich möchte zu künftigen Nutzerstudien von Opal eingeladen werden`,
+'s718207f6b5b1ed0b': `Ich möchte E-Mails zu Modellaktualisierungen, Angeboten, nützlichen Tipps und Neuigkeiten zu Opal erhalten`,
+ };
+
\ No newline at end of file
diff --git a/packages/visual-editor/src/generated/localization/fr.ts b/packages/visual-editor/src/generated/localization/fr.ts
new file mode 100644
index 00000000000..61d715383e0
--- /dev/null
+++ b/packages/visual-editor/src/generated/localization/fr.ts
@@ -0,0 +1,18 @@
+
+ // Do not modify this file by hand!
+ // Re-generate this file by running lit-localize
+
+ import {html} from 'lit';
+
+
+ /* eslint-disable no-irregular-whitespace */
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+
+ export const templates = {
+ 'hbeb436a6f6a2a826': html`Ce Gem est fourni par Google Labs, et non par Gemini Apps. Il peut
+ faire des erreurs, veuillez donc vérifier les résultats.
+ En savoir plus`,
+'s018656f690df2a0e': `Je souhaite être invité(e) à participer à de futures études de recherche d'Opal`,
+'s718207f6b5b1ed0b': `Je souhaite recevoir des e-mails concernant les mises à jour des modèles, les offres, les conseils utiles et les actualités d'Opal`,
+ };
+
\ No newline at end of file
diff --git a/packages/visual-editor/src/generated/localization/locale-codes.ts b/packages/visual-editor/src/generated/localization/locale-codes.ts
new file mode 100644
index 00000000000..f6a094041d8
--- /dev/null
+++ b/packages/visual-editor/src/generated/localization/locale-codes.ts
@@ -0,0 +1,25 @@
+// Do not modify this file by hand!
+// Re-generate this file by running lit-localize.
+
+/**
+ * The locale code that templates in this source code are written in.
+ */
+export const sourceLocale = `en`;
+
+/**
+ * The other locale codes that this application is localized into. Sorted
+ * lexicographically.
+ */
+export const targetLocales = [
+ `de`,
+ `fr`,
+] as const;
+
+/**
+ * All valid project locale codes. Sorted lexicographically.
+ */
+export const allLocales = [
+ `de`,
+ `en`,
+ `fr`,
+] as const;
diff --git a/packages/visual-editor/src/index-lite.ts b/packages/visual-editor/src/index-lite.ts
index b11e031c2ce..b34092c3a96 100644
--- a/packages/visual-editor/src/index-lite.ts
+++ b/packages/visual-editor/src/index-lite.ts
@@ -27,7 +27,7 @@ import { blankBoard } from "./ui/utils/blank-board.js";
import { repeat } from "lit/directives/repeat.js";
import { createRef, ref, Ref } from "lit/directives/ref.js";
-import { markdown } from "./ui/directives/markdown.js";
+import { msg } from "@lit/localize";
import { type SharePanel } from "./ui/elements/elements.js";
import { deriveLiteViewType } from "./sca/utils/lite-view-type.js";
import {
@@ -665,7 +665,9 @@ export class LiteMain extends MainBase implements LiteEditInputController {
#renderMessage() {
return html`
- ${markdown(Strings.from("LABEL_DISCLAIMER_LITE"))}
+ ${msg(html`This Gem is powered by Google Labs, not Gemini Apps. It can
+ make mistakes, so double-check it.
+ Learn more`)}
`;
}
diff --git a/packages/visual-editor/src/localization/config.ts b/packages/visual-editor/src/localization/config.ts
new file mode 100644
index 00000000000..45cea521527
--- /dev/null
+++ b/packages/visual-editor/src/localization/config.ts
@@ -0,0 +1,28 @@
+/**
+ * @license
+ * Copyright 2026 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { configureLocalization, type LocaleModule } from "@lit/localize";
+import {
+ sourceLocale,
+ targetLocales,
+} from "../generated/localization/locale-codes.js";
+
+// Non-standard Vite function. Detects all available locales at build time and
+// substitutes static module names for the dynamic imports.
+const localeModules = import.meta.glob("../generated/localization/*.ts");
+
+const { setLocale } = configureLocalization({
+ sourceLocale,
+ targetLocales,
+ loadLocale: (locale) => {
+ const loader = localeModules[`../generated/localization/${locale}.ts`];
+ if (!loader) {
+ return Promise.reject(new Error(`Unknown locale: ${locale}`));
+ }
+ return loader() as Promise;
+ },
+});
+export { setLocale };
diff --git a/packages/visual-editor/src/opal-lang.ts b/packages/visual-editor/src/opal-lang.ts
index a3ead1be76b..694c747d592 100644
--- a/packages/visual-editor/src/opal-lang.ts
+++ b/packages/visual-editor/src/opal-lang.ts
@@ -76,9 +76,7 @@ export default {
LABEL_DISCLAIMER: {
str: "Opal can make mistakes, so double-check it",
},
- LABEL_DISCLAIMER_LITE: {
- str: "This Gem is powered by Google Labs, not Gemini Apps. It can make mistakes, so double-check it. [Learn more](https://support.google.com/gemini?p=ai_apps)",
- },
+
LABEL_FIRST_RUN: {
str: "Click start to try this Opal",
},
diff --git a/packages/visual-editor/src/sca/types.ts b/packages/visual-editor/src/sca/types.ts
index e4ccf398e60..318cc82be73 100644
--- a/packages/visual-editor/src/sca/types.ts
+++ b/packages/visual-editor/src/sca/types.ts
@@ -145,6 +145,7 @@ export interface BaseUrlInit {
forceSurveySelection?: "true";
};
oauthRedirect?: string;
+ hl?: string;
lite?: boolean;
colorScheme?: "light" | "dark";
guestPrefixed: boolean;
diff --git a/packages/visual-editor/src/ui/elements/shell/global-settings.ts b/packages/visual-editor/src/ui/elements/shell/global-settings.ts
index f68de54f2af..3aed5236d99 100644
--- a/packages/visual-editor/src/ui/elements/shell/global-settings.ts
+++ b/packages/visual-editor/src/ui/elements/shell/global-settings.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { LitElement, html, css, nothing } from "lit";
+import { msg } from "@lit/localize";
import { customElement, property, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { baseColors } from "../../styles/host/base-colors.js";
@@ -189,7 +190,7 @@ export class VEGlobalSettingsModal extends SignalWatcher(LitElement) {
["OPAL_MARKETING_UPDATES", target.checked],
])}
>
- ${Strings.from("LABEL_EMAIL_UPDATES")}
+ ${msg("I’d like to receive emails for model updates, offers, useful tips and news about Opal")}
`,
},
[TabId.INTEGRATIONS]: {
diff --git a/packages/visual-editor/src/ui/elements/shell/warm-welcome.ts b/packages/visual-editor/src/ui/elements/shell/warm-welcome.ts
index 46fe12a3e22..13158d0ba52 100644
--- a/packages/visual-editor/src/ui/elements/shell/warm-welcome.ts
+++ b/packages/visual-editor/src/ui/elements/shell/warm-welcome.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { LitElement, html, css } from "lit";
+import { msg } from "@lit/localize";
import { customElement, state, property } from "lit/decorators.js";
import { baseColors } from "../../styles/host/base-colors.js";
import { type } from "../../styles/host/type.js";
@@ -96,7 +97,7 @@ export class VEWarmWelcomeModal extends LitElement {
@change=${({ target }: { target: MdCheckbox }) =>
(this.emailUpdates = target.checked)}
>
- ${Strings.from("LABEL_EMAIL_UPDATES")}
+ ${msg("I’d like to receive emails for model updates, offers, useful tips and news about Opal")}
`;
}
diff --git a/packages/visual-editor/src/ui/navigation/urls.ts b/packages/visual-editor/src/ui/navigation/urls.ts
index 969678cab8f..e1daf809f9f 100644
--- a/packages/visual-editor/src/ui/navigation/urls.ts
+++ b/packages/visual-editor/src/ui/navigation/urls.ts
@@ -35,6 +35,7 @@ const MISSING_SCOPES = "missing-scopes";
const AUTO_SIGN_IN = "auto-sign-in";
const RESOURCE_KEY = "resourcekey";
export const OAUTH_REDIRECT = "oauth_redirect";
+const HL = "hl";
const DEV_PREFIX = "dev-";
/**
@@ -173,6 +174,9 @@ export function makeUrl(
`unhandled page ${JSON.stringify(page)} from ${JSON.stringify(init)}`
);
}
+ if (init.hl) {
+ url.searchParams.set(HL, init.hl);
+ }
if (init.dev) {
for (const [key, val] of Object.entries(
init.dev as Record
@@ -200,6 +204,7 @@ export function parseUrl(url: string | URL): MakeUrlInit {
}
let dev: BaseUrlInit["dev"];
const oauthRedirect = url.searchParams.get(OAUTH_REDIRECT);
+ const hl = url.searchParams.get(HL);
for (const [key, val] of url.searchParams) {
if (key.startsWith(DEV_PREFIX)) {
dev ??= {};
@@ -250,6 +255,9 @@ export function parseUrl(url: string | URL): MakeUrlInit {
if (dev) {
landing.dev = dev;
}
+ if (hl) {
+ landing.hl = hl;
+ }
return landing;
} else if (pathname.startsWith("/open/")) {
const open: OpenUrlInit = {
@@ -291,6 +299,9 @@ export function parseUrl(url: string | URL): MakeUrlInit {
if (oauthRedirect) {
home.oauthRedirect = oauthRedirect;
}
+ if (hl) {
+ home.hl = hl;
+ }
return home;
}
const mode =
@@ -325,6 +336,9 @@ export function parseUrl(url: string | URL): MakeUrlInit {
if (oauthRedirect) {
graph.oauthRedirect = oauthRedirect;
}
+ if (hl) {
+ graph.hl = hl;
+ }
return graph;
}
}
diff --git a/packages/visual-editor/src/ui/strings/en_US/global.ts b/packages/visual-editor/src/ui/strings/en_US/global.ts
index fe38e5b5ec4..9678f3ef1ad 100644
--- a/packages/visual-editor/src/ui/strings/en_US/global.ts
+++ b/packages/visual-editor/src/ui/strings/en_US/global.ts
@@ -146,12 +146,7 @@ export default {
LABEL_SHARE: {
str: "A Breadboard mini-app has been shared with you",
},
- LABEL_EMAIL_UPDATES: {
- str: "I’d like to receive emails for model updates, offers, useful tips and news about Opal",
- },
- LABEL_EMAIL_RESEARCH: {
- str: "I’m interested in being invited to future research studies from Opal",
- },
+
LABEL_SETTINGS_GENERAL: {
str: "General",
},