From 3fdf146498471cf2d4001b79e3c6ca06595eeed0 Mon Sep 17 00:00:00 2001 From: Kolkman Date: Wed, 26 Aug 2026 12:14:30 +0200 Subject: [PATCH] Added Locales to the webextention --- Makefile | 2 +- VIBE/CHAT_LOG.md | 8 ++ VIBE/PROMPTS.md | 1 + XPI/unsigned/LinkLog-0.1.0.xpi | Bin 39787 -> 41759 bytes scripts/release/validate_xpi.py | 2 + webextension/_locales/en-US/messages.json | 139 ++++++++++++++++++++++ webextension/l10n.js | 21 ++++ webextension/manifest.json | 7 +- webextension/options.html | 41 +++---- webextension/options.js | 14 ++- webextension/popup.html | 27 +++-- webextension/popup.js | 18 +-- 12 files changed, 229 insertions(+), 51 deletions(-) create mode 100644 webextension/_locales/en-US/messages.json create mode 100644 webextension/l10n.js diff --git a/Makefile b/Makefile index f2fc82a..051b602 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ XPI_FILE := LinkLog-$(EXTENSION_VERSION).xpi XPI_UNSIGNED_DIR := XPI/unsigned XPI_SIGNED_DIR := XPI/signed XPI_OUTPUT := $(XPI_UNSIGNED_DIR)/$(XPI_FILE) -EXTENSION_FILES := manifest.json logo.svg icon-16.png icon-32.png icon-48.png icon-96.png options.css options.html options.js popup.css popup.html popup.js +EXTENSION_FILES := manifest.json logo.svg icon-16.png icon-32.png icon-48.png icon-96.png options.css options.html options.js popup.css popup.html popup.js l10n.js _locales/en-US/messages.json EXTENSION_SOURCES := $(addprefix webextension/,$(EXTENSION_FILES)) XPI_VALIDATOR := scripts/release/validate_xpi.py diff --git a/VIBE/CHAT_LOG.md b/VIBE/CHAT_LOG.md index f2003d0..7315aa5 100644 --- a/VIBE/CHAT_LOG.md +++ b/VIBE/CHAT_LOG.md @@ -232,6 +232,14 @@ On the home page, when clicking on the avatar or the user, show the profile info ### Assistant outcome Made each home-feed avatar and username link to the corresponding `//` profile page, added matching link layout styling, and added a regression assertion for the generated profile URL. +## 2026-08-26 + +### User +Localize the firefox plugin. + +### Assistant outcome +Added Firefox `browser.i18n` support with an `en-US` message catalog, localized popup and settings page labels and runtime messages, localized manifest metadata, and included the localization resources in XPI packaging and validation. + ### User Show the avatar with each entry on the home page. On the specific // page don't show the avatar and user name with each entry diff --git a/VIBE/PROMPTS.md b/VIBE/PROMPTS.md index adab5a5..25caeac 100644 --- a/VIBE/PROMPTS.md +++ b/VIBE/PROMPTS.md @@ -125,6 +125,7 @@ 116. When the user is authorized and on its // page show a Mastodon post button with logo; after posting keep it functional but change its color. 117. Continue to log interactions to the VIBE directory. 118. On the home page, when clicking on the avatar or the user, show the profile information. +119. Localize the firefox plugin. ## Future entries diff --git a/XPI/unsigned/LinkLog-0.1.0.xpi b/XPI/unsigned/LinkLog-0.1.0.xpi index 23cfaf0d08c751592d0d56a80c164286577fba90..d004638ba7f12a61463d36229ca35613aa4beaa9 100644 GIT binary patch delta 7693 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 B*J1zw diff --git a/scripts/release/validate_xpi.py b/scripts/release/validate_xpi.py index 1a87b51..170e7e1 100644 --- a/scripts/release/validate_xpi.py +++ b/scripts/release/validate_xpi.py @@ -20,6 +20,8 @@ REQUIRED_FILES = { 'popup.css', 'popup.html', 'popup.js', + 'l10n.js', + '_locales/en-US/messages.json', } diff --git a/webextension/_locales/en-US/messages.json b/webextension/_locales/en-US/messages.json new file mode 100644 index 0000000..4cb6096 --- /dev/null +++ b/webextension/_locales/en-US/messages.json @@ -0,0 +1,139 @@ +{ + "extensionName": { + "message": "LinkLog" + }, + "extensionDescription": { + "message": "Capture and submit page links to LinkLog." + }, + "settingsTitle": { + "message": "LinkLog Settings" + }, + "titleLabel": { + "message": "Title" + }, + "urlLabel": { + "message": "URL" + }, + "commentLabel": { + "message": "Comment" + }, + "commentPlaceholder": { + "message": "Your comment" + }, + "tagsLabel": { + "message": "Tags" + }, + "loadingTags": { + "message": "Loading tags..." + }, + "newTagsPlaceholder": { + "message": "#new-tag, #another-tag" + }, + "saveLink": { + "message": "Save link" + }, + "settings": { + "message": "Settings" + }, + "signOut": { + "message": "Sign out" + }, + "backendUrlLabel": { + "message": "Backend URL" + }, + "usernameLabel": { + "message": "Username" + }, + "usernamePlaceholder": { + "message": "alice" + }, + "passwordLabel": { + "message": "Password" + }, + "saveAndLogIn": { + "message": "Save and log in" + }, + "thisPlugin": { + "message": "This Plugin" + }, + "pluginDescription": { + "message": "This plugin will log links to a backend LinkLog server which will publish them on a website and potentially as an entry on a fediverse service or other locations, depending on the plugins configured." + }, + "accountRequirement": { + "message": "Users of this plugin will need an account on a LinkLog server" + }, + "privacy": { + "message": "Privacy" + }, + "privacyDescription": { + "message": "This plugin respects your privacy and does not collect any personal data beyond what is necessary to log links to your LinkLog server." + }, + "storedData": { + "message": "The URL - after a best effort try to remove tracking parameters -, the title of the webpage, a possible comment, and a timestamp of visit will be stored on the backend server, and will be made public in combination with the user's profile as registered on the backend server." + }, + "backendHeading": { + "message": "LinkLog Backend" + }, + "backendDescription": { + "message": "The backend URL is a selfhosted linklog application. See its" + }, + "backendSource": { + "message": "for its source." + }, + "copyright": { + "message": "Copyright © 2026 Olaf Kolkman" + }, + "repository": { + "message": "Repository" + }, + "signInToSelectTags": { + "message": "Sign in to select existing tags." + }, + "loadTagsFailed": { + "message": "Could not load existing tags." + }, + "submitting": { + "message": "Submitting..." + }, + "configureAndLogIn": { + "message": "Please configure the backend URL and log in first." + }, + "sessionExpired": { + "message": "Session expired. Re-authenticate in settings." + }, + "submissionFailed": { + "message": "Submission failed" + }, + "linkSaved": { + "message": "Link saved successfully" + }, + "submissionFailedConnection": { + "message": "Submission failed. Check your backend connection." + }, + "loggedInAt": { + "message": "$USERNAME$ logged in at $BACKEND$", + "placeholders": { + "username": { + "content": "$1" + }, + "backend": { + "content": "$2" + } + } + }, + "fillAllFields": { + "message": "Please fill in all fields" + }, + "loginFailed": { + "message": "Login failed" + }, + "loggedInSuccessfully": { + "message": "Logged in successfully" + }, + "unableToLogIn": { + "message": "Unable to log in. Check backend URL and credentials." + }, + "signedOut": { + "message": "Signed out" + } +} \ No newline at end of file diff --git a/webextension/l10n.js b/webextension/l10n.js new file mode 100644 index 0000000..4b3bec3 --- /dev/null +++ b/webextension/l10n.js @@ -0,0 +1,21 @@ +// Copyright © 2026 Olaf Kolkman +// SPDX-License-Identifier: GPL-3.0-or-later + +function getLocalizedMessage(messageId, substitutions = []) { + const message = browser.i18n.getMessage(messageId, substitutions); + return message || messageId; +} + +window.linklogI18n = getLocalizedMessage; + +document.querySelectorAll('[data-i18n]').forEach((element) => { + element.textContent = getLocalizedMessage(element.dataset.i18n); +}); + +document.querySelectorAll('[data-i18n-placeholder]').forEach((element) => { + element.placeholder = getLocalizedMessage(element.dataset.i18nPlaceholder); +}); + +document.querySelectorAll('[data-i18n-alt]').forEach((element) => { + element.alt = getLocalizedMessage(element.dataset.i18nAlt); +}); \ No newline at end of file diff --git a/webextension/manifest.json b/webextension/manifest.json index 52d6c9d..e2d42b5 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -1,8 +1,9 @@ { "manifest_version": 3, - "name": "LinkLog", + "name": "__MSG_extensionName__", "version": "0.1.0", - "description": "Capture and submit page links to LinkLog.", + "description": "__MSG_extensionDescription__", + "default_locale": "en-US", "permissions": [ "activeTab", "storage", @@ -12,7 +13,7 @@ "" ], "action": { - "default_title": "LinkLog", + "default_title": "__MSG_extensionName__", "default_popup": "popup.html", "default_icon": { "16": "icon-16.png", diff --git a/webextension/options.html b/webextension/options.html index ed66068..513f48f 100644 --- a/webextension/options.html +++ b/webextension/options.html @@ -5,65 +5,66 @@ - LinkLog Settings + LinkLog Settings
- -

Settings

+ +

Settings

- +
-

This Plugin

-

This plugin will log links to a backend LinkLog server which will publish them on a website and potentially as +

This Plugin

+

This plugin will log links to a backend LinkLog server which will publish them on a website and potentially as an entry on a fediverse service or other locations, depending on the plugins configured.

-

Users of this plugin will need an account on a LinkLog server

-

Privacy

-

This plugin respects your privacy and does not collect any personal data beyond what is necessary to log links +

Users of this plugin will need an account on a LinkLog server

+

Privacy

+

This plugin respects your privacy and does not collect any personal data beyond what is necessary to log links to your LinkLog server.

-

The URL - after a best effort try to remove tracking parameters -, the title of the webpage, a possible +

The URL - after a best effort try to remove tracking parameters -, the title of the webpage, a possible comment, and a timestamp of visit will be stored on the backend server, and will be made public in combination with the user's profile as registered on the backend server.

-

LinkLog Backend

-

The backend URL is a selfhosted linklog application. See its repository for its source.

+

LinkLog Backend

+

The backend URL is a selfhosted linklog application. See its repository for its source.

- + + diff --git a/webextension/options.js b/webextension/options.js index c1f4cce..a5ec85a 100644 --- a/webextension/options.js +++ b/webextension/options.js @@ -12,6 +12,8 @@ const session = document.getElementById('logged-in'); const sessionSummary = document.getElementById('session-summary'); const signOutButton = document.getElementById('sign-out'); +const t = window.linklogI18n; + function setStatus(message, isError = false) { statusEl.textContent = message; statusEl.classList.remove('hidden'); @@ -43,7 +45,7 @@ async function loadSettings() { } function showLoggedIn(username, backendUrl) { - sessionSummary.textContent = `${username} logged in at ${backendUrl}`; + sessionSummary.textContent = t('loggedInAt', [username, backendUrl]); session.classList.remove('hidden'); form.classList.add('hidden'); } @@ -64,7 +66,7 @@ async function saveSettingsAndLogin(event) { const password = passwordInput.value; if (!backendUrl || !username || !password) { - setStatus('Please fill in all fields', true); + setStatus(t('fillAllFields'), true); return; } @@ -76,7 +78,7 @@ async function saveSettingsAndLogin(event) { }); if (!response.ok) { - throw new Error('Login failed'); + throw new Error(t('loginFailed')); } const data = await response.json(); @@ -91,9 +93,9 @@ async function saveSettingsAndLogin(event) { showLoggedIn(data.user?.username || username, backendUrl); passwordInput.value = ''; - setStatus('Logged in successfully'); + setStatus(t('loggedInSuccessfully')); } catch (error) { - setStatus('Unable to log in. Check backend URL and credentials.', true); + setStatus(t('unableToLogIn'), true); } } @@ -108,7 +110,7 @@ async function signOut() { } await clearSession(); showLoggedOut(); - setStatus('Signed out'); + setStatus(t('signedOut')); } form.addEventListener('submit', saveSettingsAndLogin); diff --git a/webextension/popup.html b/webextension/popup.html index 253d5ba..a5d59b0 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -4,48 +4,49 @@ - LinkLog + LinkLog
- - + +
- + + diff --git a/webextension/popup.js b/webextension/popup.js index e19600d..2ae82a4 100644 --- a/webextension/popup.js +++ b/webextension/popup.js @@ -11,6 +11,8 @@ const existingTags = document.getElementById('existing-tags'); const newTagsInput = document.getElementById('new-tags'); const feedLink = document.getElementById('feed-link'); +const t = window.linklogI18n; + function setStatus(message, isError = false) { statusEl.textContent = message; statusEl.classList.remove('hidden'); @@ -42,14 +44,14 @@ async function updateFeedLink() { async function loadExistingTags() { const settings = await getSettings(); if (!settings.backendUrl || !settings.accessToken) { - existingTags.textContent = 'Sign in to select existing tags.'; + existingTags.textContent = t('signInToSelectTags'); return; } const response = await fetch(`${settings.backendUrl}/api/tags`, { headers: {'Authorization': `Bearer ${settings.accessToken}`}, }); if (!response.ok) { - existingTags.textContent = 'Could not load existing tags.'; + existingTags.textContent = t('loadTagsFailed'); return; } const tags = await response.json(); @@ -96,14 +98,14 @@ async function populateCurrentTab() { async function handleSubmit(event) { event.preventDefault(); - setStatus('Submitting...', false); + setStatus(t('submitting'), false); const settings = await getSettings(); const token = settings.accessToken; const backendUrl = settings.backendUrl; if (!token || !backendUrl) { - setStatus('Please configure the backend URL and log in first.', true); + setStatus(t('configureAndLogIn'), true); browser.runtime.openOptionsPage(); return; } @@ -125,18 +127,18 @@ async function handleSubmit(event) { }); if (response.status === 401) { - setStatus('Session expired. Re-authenticate in settings.', true); + setStatus(t('sessionExpired'), true); browser.runtime.openOptionsPage(); return; } if (!response.ok) { - throw new Error('Submission failed'); + throw new Error(t('submissionFailed')); } - setStatus('Link saved successfully'); + setStatus(t('linkSaved')); } catch (error) { - setStatus('Submission failed. Check your backend connection.', true); + setStatus(t('submissionFailedConnection'), true); } }