Added Locales to the webextention
Build LinkLog Development Image / development-image (push) Successful in 9s

This commit is contained in:
2026-08-26 12:14:30 +02:00
parent f0ae526a96
commit 3fdf146498
12 changed files with 229 additions and 51 deletions
+139
View File
@@ -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"
}
}
+21
View File
@@ -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);
});
+4 -3
View File
@@ -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
View File
@@ -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>
+8 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}