Added Locales to the webextention
Build LinkLog Development Image / development-image (push) Successful in 9s
Build LinkLog Development Image / development-image (push) Successful in 9s
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 `/<username>/` 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 /<user>/ page don't show the avatar and user name with each entry
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
116. When the user is authorized and on its /<user>/ 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
|
||||
|
||||
|
||||
Binary file not shown.
@@ -20,6 +20,8 @@ REQUIRED_FILES = {
|
||||
'popup.css',
|
||||
'popup.html',
|
||||
'popup.js',
|
||||
'l10n.js',
|
||||
'_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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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 @@
|
||||
"<all_urls>"
|
||||
],
|
||||
"action": {
|
||||
"default_title": "LinkLog",
|
||||
"default_title": "__MSG_extensionName__",
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"16": "icon-16.png",
|
||||
|
||||
+21
-20
@@ -5,65 +5,66 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>LinkLog Settings</title>
|
||||
<title data-i18n="settingsTitle">LinkLog Settings</title>
|
||||
<link rel="stylesheet" href="options.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="options-shell">
|
||||
<img class="extension-logo" src="logo.svg" alt="LinkLog" />
|
||||
<h1>Settings</h1>
|
||||
<img class="extension-logo" src="logo.svg" data-i18n-alt="extensionName" alt="LinkLog" />
|
||||
<h1 data-i18n="settings">Settings</h1>
|
||||
|
||||
<div id="status" class="status hidden" aria-live="polite"></div>
|
||||
|
||||
<section id="logged-in" class="logged-in hidden" aria-live="polite">
|
||||
<p id="session-summary"></p>
|
||||
<button type="button" id="sign-out">Sign out</button>
|
||||
<button type="button" id="sign-out" data-i18n="signOut">Sign out</button>
|
||||
</section>
|
||||
|
||||
<form id="settings-form">
|
||||
<label>
|
||||
Backend URL
|
||||
<span data-i18n="backendUrlLabel">Backend URL</span>
|
||||
<input id="backend-url" type="url" placeholder="https://example.com" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Username
|
||||
<input id="username" type="text" placeholder="alice" />
|
||||
<span data-i18n="usernameLabel">Username</span>
|
||||
<input id="username" type="text" data-i18n-placeholder="usernamePlaceholder" placeholder="alice" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Password
|
||||
<span data-i18n="passwordLabel">Password</span>
|
||||
<input id="password" type="password" />
|
||||
</label>
|
||||
|
||||
<button type="submit">Save and log in</button>
|
||||
<button type="submit" data-i18n="saveAndLogIn">Save and log in</button>
|
||||
</form>
|
||||
|
||||
<section class="about-plugin">
|
||||
<h2>This Plugin</h2>
|
||||
<p>This plugin will log links to a backend LinkLog server which will publish them on a website and potentially as
|
||||
<h2 data-i18n="thisPlugin">This Plugin</h2>
|
||||
<p data-i18n="pluginDescription">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.</p>
|
||||
<p>Users of this plugin will need an account on a LinkLog server</p>
|
||||
<h2>Privacy</h2>
|
||||
<p>This plugin respects your privacy and does not collect any personal data beyond what is necessary to log links
|
||||
<p data-i18n="accountRequirement">Users of this plugin will need an account on a LinkLog server</p>
|
||||
<h2 data-i18n="privacy">Privacy</h2>
|
||||
<p data-i18n="privacyDescription">This plugin respects your privacy and does not collect any personal data beyond what is necessary to log links
|
||||
to your LinkLog server.</p>
|
||||
<p>The URL - after a best effort try to remove tracking parameters -, the title of the webpage, a possible
|
||||
<p data-i18n="storedData">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.
|
||||
|
||||
</p>
|
||||
|
||||
<h2>LinkLog Backend</h2>
|
||||
<p>The backend URL is a selfhosted linklog application. See its <a
|
||||
href="https://git.kolkman.org/olaf/Link-Log">repository</a> for its source.</p>
|
||||
<h2 data-i18n="backendHeading">LinkLog Backend</h2>
|
||||
<p><span data-i18n="backendDescription">The backend URL is a selfhosted linklog application. See its</span> <a
|
||||
href="https://git.kolkman.org/olaf/Link-Log" data-i18n="repository">repository</a> <span data-i18n="backendSource">for its source.</span></p>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="extension-footer">Copyright © 2026 Olaf Kolkman · <a
|
||||
href="https://git.kolkman.org/olaf/Link-Log">Repository</a></footer>
|
||||
<footer class="extension-footer"><span data-i18n="copyright">Copyright © 2026 Olaf Kolkman</span> · <a
|
||||
href="https://git.kolkman.org/olaf/Link-Log" data-i18n="repository">Repository</a></footer>
|
||||
|
||||
<script src="l10n.js"></script>
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+14
-13
@@ -4,48 +4,49 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>LinkLog</title>
|
||||
<title data-i18n="extensionName">LinkLog</title>
|
||||
<link rel="stylesheet" href="popup.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="popup-shell">
|
||||
<header>
|
||||
<img class="extension-logo" src="logo.svg" alt="LinkLog" />
|
||||
<a id="feed-link" class="feed-link hidden" href="/" target="_blank" rel="noopener noreferrer">LinkLog</a>
|
||||
<img class="extension-logo" src="logo.svg" data-i18n-alt="extensionName" alt="LinkLog" />
|
||||
<a id="feed-link" class="feed-link hidden" href="/" target="_blank" rel="noopener noreferrer" data-i18n="extensionName">LinkLog</a>
|
||||
</header>
|
||||
|
||||
<div id="status" class="status hidden" aria-live="polite"></div>
|
||||
|
||||
<form id="link-form">
|
||||
<label>
|
||||
Title
|
||||
<span data-i18n="titleLabel">Title</span>
|
||||
<input id="title" name="title" type="text" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
URL
|
||||
<span data-i18n="urlLabel">URL</span>
|
||||
<input id="url" name="url" type="url" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Comment
|
||||
<textarea id="comment" name="comment" rows="3" placeholder="Your comment"></textarea>
|
||||
<span data-i18n="commentLabel">Comment</span>
|
||||
<textarea id="comment" name="comment" rows="3" data-i18n-placeholder="commentPlaceholder" placeholder="Your comment"></textarea>
|
||||
</label>
|
||||
|
||||
<fieldset>
|
||||
<legend>Tags</legend>
|
||||
<div id="existing-tags" class="tag-options">Loading tags...</div>
|
||||
<input id="new-tags" type="text" pattern="#[^, ]+(,\s*#[^, ]+)*" placeholder="#new-tag, #another-tag" />
|
||||
<legend data-i18n="tagsLabel">Tags</legend>
|
||||
<div id="existing-tags" class="tag-options" data-i18n="loadingTags">Loading tags...</div>
|
||||
<input id="new-tags" type="text" pattern="#[^, ]+(,\s*#[^, ]+)*" data-i18n-placeholder="newTagsPlaceholder" placeholder="#new-tag, #another-tag" />
|
||||
</fieldset>
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" id="submit-link">Save link</button>
|
||||
<button type="button" id="open-settings" class="secondary">Settings</button>
|
||||
<button type="submit" id="submit-link" data-i18n="saveLink">Save link</button>
|
||||
<button type="button" id="open-settings" class="secondary" data-i18n="settings">Settings</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
<footer class="extension-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">Repository</a></footer>
|
||||
<footer class="extension-footer"><span data-i18n="copyright">Copyright © 2026 Olaf Kolkman</span> · <a href="https://git.kolkman.org/olaf/Link-Log" data-i18n="repository">Repository</a></footer>
|
||||
|
||||
<script src="l10n.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-8
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user