mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-17 22:27:48 -04:00
Add dynamic .json translations for content script (#1006)
This commit is contained in:
committed by
Leendert de Borst
parent
0c2de27f1a
commit
accc76d8a2
@@ -1,8 +1,8 @@
|
||||
import { type Browser } from '@wxt-dev/browser';
|
||||
import { sendMessage } from 'webext-bridge/background';
|
||||
|
||||
import { t } from '@/utils/contentTranslations';
|
||||
import { PasswordGenerator } from '@/utils/dist/shared/password-generator';
|
||||
import { tc } from '@/utils/i18n/StandaloneI18n';
|
||||
|
||||
import { browser } from "#imports";
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function setupContextMenus() : Promise<void> {
|
||||
browser.contextMenus.create({
|
||||
id: "aliasvault-activate-form",
|
||||
parentId: "aliasvault-root",
|
||||
title: await t('autofillWithAliasVault'),
|
||||
title: await tc('autofillWithAliasVault'),
|
||||
contexts: ["editable"],
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function setupContextMenus() : Promise<void> {
|
||||
browser.contextMenus.create({
|
||||
id: "aliasvault-generate-password",
|
||||
parentId: "aliasvault-root",
|
||||
title: await t('generateRandomPassword'),
|
||||
title: await tc('generateRandomPassword'),
|
||||
contexts: ["all"]
|
||||
});
|
||||
|
||||
@@ -83,7 +83,7 @@ export function handleContextMenuClick(info: Browser.contextMenus.OnClickData, t
|
||||
*/
|
||||
async function copyPasswordToClipboard(generatedPassword: string) : Promise<void> {
|
||||
navigator.clipboard.writeText(generatedPassword).then(async () => {
|
||||
showToast(await t('passwordCopiedToClipboard'));
|
||||
showToast(await tc('passwordCopiedToClipboard'));
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ import { BoolResponse as messageBoolResponse } from '@/utils/types/messaging/Boo
|
||||
|
||||
import { defineContentScript } from '#imports';
|
||||
import { createShadowRootUi } from '#imports';
|
||||
import { tc } from '@/utils/i18n/StandaloneI18n';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
@@ -159,13 +160,13 @@ export default defineContentScript({
|
||||
|
||||
if (authStatus.hasPendingMigrations) {
|
||||
// Show upgrade required popup
|
||||
createUpgradeRequiredPopup(inputElement, container, 'Vault upgrade required.');
|
||||
await createUpgradeRequiredPopup(inputElement, container, await tc('vaultUpgradeRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (authStatus.error) {
|
||||
// Show upgrade required popup for version-related errors
|
||||
createUpgradeRequiredPopup(inputElement, container, authStatus.error);
|
||||
await createUpgradeRequiredPopup(inputElement, container, authStatus.error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ import { filterCredentials } from '@/entrypoints/contentScript/Filter';
|
||||
import { fillCredential } from '@/entrypoints/contentScript/Form';
|
||||
|
||||
import { DISABLED_SITES_KEY, TEMPORARY_DISABLED_SITES_KEY, GLOBAL_AUTOFILL_POPUP_ENABLED_KEY, VAULT_LOCKED_DISMISS_UNTIL_KEY, LAST_CUSTOM_EMAIL_KEY, LAST_CUSTOM_USERNAME_KEY } from '@/utils/Constants';
|
||||
import { t } from '@/utils/contentTranslations';
|
||||
import { CreateIdentityGenerator } from '@/utils/dist/shared/identity-generator';
|
||||
import type { Credential } from '@/utils/dist/shared/models/vault';
|
||||
import { CreatePasswordGenerator, PasswordGenerator } from '@/utils/dist/shared/password-generator';
|
||||
import { FormDetector } from '@/utils/formDetector/FormDetector';
|
||||
import { tc } from '@/utils/i18n/StandaloneI18n';
|
||||
import { SqliteClient } from '@/utils/SqliteClient';
|
||||
import { CredentialsResponse } from '@/utils/types/messaging/CredentialsResponse';
|
||||
import { IdentitySettingsResponse } from '@/utils/types/messaging/IdentitySettingsResponse';
|
||||
@@ -47,8 +47,7 @@ export function openAutofillPopup(input: HTMLInputElement, container: HTMLElemen
|
||||
if (response.success) {
|
||||
await createAutofillPopup(input, response.credentials, container);
|
||||
} else {
|
||||
const vaultLockedText = await t('vaultLocked');
|
||||
createVaultLockedPopup(input, container, vaultLockedText);
|
||||
await createVaultLockedPopup(input, container);
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -158,13 +157,13 @@ export function removeExistingPopup(container: HTMLElement) : void {
|
||||
*/
|
||||
export async function createAutofillPopup(input: HTMLInputElement, credentials: Credential[] | undefined, rootContainer: HTMLElement) : Promise<void> {
|
||||
// Get all translations first
|
||||
const newText = await t('new');
|
||||
const searchPlaceholder = await t('searchVault');
|
||||
const hideFor1HourText = await t('hideFor1Hour');
|
||||
const hidePermanentlyText = await t('hidePermanently');
|
||||
const noMatchesText = await t('noMatchesFound');
|
||||
const creatingText = await t('creatingNewAlias');
|
||||
const failedText = await t('failedToCreateIdentity');
|
||||
const newText = await tc('new');
|
||||
const searchPlaceholder = await tc('searchVault');
|
||||
const hideFor1HourText = await tc('hideFor1Hour');
|
||||
const hidePermanentlyText = await tc('hidePermanently');
|
||||
const noMatchesText = await tc('noMatchesFound');
|
||||
const creatingText = await tc('creatingNewAlias');
|
||||
const failedText = await tc('failedToCreateIdentity');
|
||||
|
||||
// Disable browser's native autocomplete to avoid conflicts with AliasVault's autocomplete.
|
||||
input.setAttribute('autocomplete', 'false');
|
||||
@@ -449,7 +448,7 @@ export async function createAutofillPopup(input: HTMLInputElement, credentials:
|
||||
/**
|
||||
* Create vault locked popup.
|
||||
*/
|
||||
export function createVaultLockedPopup(input: HTMLInputElement, rootContainer: HTMLElement, vaultLockedText?: string): void {
|
||||
export async function createVaultLockedPopup(input: HTMLInputElement, rootContainer: HTMLElement): Promise<void> {
|
||||
/**
|
||||
* Handle unlock click.
|
||||
*/
|
||||
@@ -472,7 +471,7 @@ export function createVaultLockedPopup(input: HTMLInputElement, rootContainer: H
|
||||
// Add message
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.className = 'av-vault-locked-message';
|
||||
messageElement.textContent = vaultLockedText || 'AliasVault is locked.';
|
||||
messageElement.textContent = await tc('vaultLocked');
|
||||
container.appendChild(messageElement);
|
||||
|
||||
// Add unlock button with SVG icon
|
||||
@@ -772,10 +771,10 @@ export async function createAliasCreationPopup(suggestedNames: string[], rootCon
|
||||
<circle cx="16" cy="16" r="1"/>
|
||||
</svg>
|
||||
`;
|
||||
const randomIdentitySubtext = await t('randomIdentityDescription');
|
||||
const randomIdentityTitle = await t('createRandomAlias');
|
||||
const randomIdentityTitleDropdown = await t('randomAlias');
|
||||
const randomIdentitySubtextDropdown = 'Random identity with random email';
|
||||
const randomIdentitySubtext = await tc('randomIdentityDescription');
|
||||
const randomIdentityTitle = await tc('createRandomAlias');
|
||||
const randomIdentityTitleDropdown = await tc('randomAlias');
|
||||
const randomIdentitySubtextDropdown = await tc('randomIdentityDescriptionDropdown');
|
||||
|
||||
const manualUsernamePasswordIcon = `
|
||||
<svg class="av-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -783,22 +782,24 @@ export async function createAliasCreationPopup(suggestedNames: string[], rootCon
|
||||
<path d="M5.5 20a6.5 6.5 0 0 1 13 0"/>
|
||||
</svg>
|
||||
`;
|
||||
const manualUsernamePasswordSubtext = await t('manualCredentialDescription');
|
||||
const manualUsernamePasswordTitle = await t('createUsernamePassword');
|
||||
const manualUsernamePasswordTitleDropdown = await t('usernamePassword');
|
||||
const manualUsernamePasswordSubtextDropdown = 'Manual username and password';
|
||||
const manualUsernamePasswordSubtext = await tc('manualCredentialDescription');
|
||||
const manualUsernamePasswordTitle = await tc('createUsernamePassword');
|
||||
const manualUsernamePasswordTitleDropdown = await tc('usernamePassword');
|
||||
const manualUsernamePasswordSubtextDropdown = await tc('manualCredentialDescriptionDropdown');
|
||||
|
||||
// Get all translated strings first
|
||||
const serviceNameText = await t('serviceName');
|
||||
const enterServiceNameText = await t('enterServiceName');
|
||||
const cancelText = await t('cancel');
|
||||
const createAndSaveAliasText = await t('createAndSaveAlias');
|
||||
const emailText = await t('email');
|
||||
const enterEmailAddressText = await t('enterEmailAddress');
|
||||
const usernameText = await t('username');
|
||||
const enterUsernameText = await t('enterUsername');
|
||||
const generatedPasswordText = await t('generatedPassword');
|
||||
const createAndSaveCredentialText = await t('createAndSaveCredential');
|
||||
const serviceNameText = await tc('serviceName');
|
||||
const enterServiceNameText = await tc('enterServiceName');
|
||||
const cancelText = await tc('cancel');
|
||||
const createAndSaveAliasText = await tc('createAndSaveAlias');
|
||||
const emailText = await tc('email');
|
||||
const enterEmailAddressText = await tc('enterEmailAddress');
|
||||
const usernameText = await tc('username');
|
||||
const enterUsernameText = await tc('enterUsername');
|
||||
const generatedPasswordText = await tc('generatedPassword');
|
||||
const generateNewPasswordText = await tc('generateNewPassword');
|
||||
const togglePasswordVisibilityText = await tc('togglePasswordVisibility');
|
||||
const createAndSaveCredentialText = await tc('createAndSaveCredential');
|
||||
|
||||
// Create the main content
|
||||
popup.innerHTML = `
|
||||
@@ -893,13 +894,13 @@ export async function createAliasCreationPopup(suggestedNames: string[], rootCon
|
||||
class="av-create-popup-input"
|
||||
data-is-generated="true"
|
||||
>
|
||||
<button id="toggle-password-visibility" class="av-create-popup-visibility-btn" title="Toggle password visibility">
|
||||
<button id="toggle-password-visibility" class="av-create-popup-visibility-btn" title="${togglePasswordVisibilityText}">
|
||||
<svg class="av-icon" viewBox="0 0 24 24">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="regenerate-password" class="av-create-popup-regenerate-btn" title="Generate new password">
|
||||
<button id="regenerate-password" class="av-create-popup-regenerate-btn" title="${generateNewPasswordText}">
|
||||
<svg class="av-icon" viewBox="0 0 24 24">
|
||||
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
|
||||
<path d="M3 3v5h5"></path>
|
||||
@@ -1138,7 +1139,7 @@ export async function createAliasCreationPopup(suggestedNames: string[], rootCon
|
||||
/**
|
||||
* Handle custom save button click.
|
||||
*/
|
||||
const handleCustomSave = () : void => {
|
||||
const handleCustomSave = async () : Promise<void> => {
|
||||
const serviceName = inputServiceName.value.trim();
|
||||
if (serviceName) {
|
||||
const email = customEmail.value.trim();
|
||||
@@ -1162,14 +1163,14 @@ export async function createAliasCreationPopup(suggestedNames: string[], rootCon
|
||||
if (!emailLabel.querySelector('.av-create-popup-error-text')) {
|
||||
const emailError = document.createElement('span');
|
||||
emailError.className = 'av-create-popup-error-text';
|
||||
emailError.textContent = 'Enter email and/or username';
|
||||
emailError.textContent = await tc('enterEmailAndOrUsername');
|
||||
emailLabel.appendChild(emailError);
|
||||
}
|
||||
|
||||
if (!usernameLabel.querySelector('.av-create-popup-error-text')) {
|
||||
const usernameError = document.createElement('span');
|
||||
usernameError.className = 'av-create-popup-error-text';
|
||||
usernameError.textContent = 'Enter email and/or username';
|
||||
usernameError.textContent = await tc('enterEmailAndOrUsername');
|
||||
usernameLabel.appendChild(usernameError);
|
||||
}
|
||||
|
||||
@@ -1496,7 +1497,7 @@ function addReliableClickHandler(element: HTMLElement, handler: (e: Event) => vo
|
||||
/**
|
||||
* Create upgrade required popup.
|
||||
*/
|
||||
export function createUpgradeRequiredPopup(input: HTMLInputElement, rootContainer: HTMLElement, errorMessage: string): void {
|
||||
export async function createUpgradeRequiredPopup(input: HTMLInputElement, rootContainer: HTMLElement, errorMessage: string): Promise<void> {
|
||||
/**
|
||||
* Handle upgrade click.
|
||||
*/
|
||||
@@ -1524,7 +1525,7 @@ export function createUpgradeRequiredPopup(input: HTMLInputElement, rootContaine
|
||||
|
||||
// Add upgrade button with SVG icon
|
||||
const button = document.createElement('button');
|
||||
button.title = 'Open AliasVault to upgrade';
|
||||
button.title = await tc('openAliasVaultToUpgrade');
|
||||
button.className = 'av-upgrade-required-button';
|
||||
button.innerHTML = `
|
||||
<svg class="av-icon-upgrade" viewBox="0 0 24 24">
|
||||
@@ -1539,7 +1540,7 @@ export function createUpgradeRequiredPopup(input: HTMLInputElement, rootContaine
|
||||
// Add close button as a separate element positioned to the right
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.className = 'av-button av-button-close av-upgrade-required-close';
|
||||
closeButton.title = 'Dismiss popup';
|
||||
closeButton.title = await tc('dismissPopup');
|
||||
closeButton.innerHTML = `
|
||||
<svg class="av-icon" viewBox="0 0 24 24">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
|
||||
@@ -182,7 +182,7 @@ const Unlock: React.FC = () => {
|
||||
</Button>
|
||||
|
||||
<div className="text-sm font-medium text-gray-500 dark:text-gray-200 mt-6">
|
||||
Switch accounts? <button onClick={handleLogout} className="text-primary-700 hover:underline dark:text-primary-500">{t('logout')}</button>
|
||||
{t('switchAccounts')} <button onClick={handleLogout} className="text-primary-700 hover:underline dark:text-primary-500">{t('logout')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"sessionExpired": "Your session has expired. Please log in again.",
|
||||
"unlockSuccess": "Vault unlocked successfully!",
|
||||
"connectingTo": "Connecting to",
|
||||
"switchAccounts": "Switch accounts?",
|
||||
"errors": {
|
||||
"invalidCode": "Please enter a valid 6-digit authentication code.",
|
||||
"serverError": "Could not reach AliasVault server. Please try again later or contact support if the problem persists.",
|
||||
|
||||
@@ -42,7 +42,21 @@
|
||||
"NoVaultFound": "Your account does not have a vault yet. Please complete the tutorial in the AliasVault web client before using the browser extension.",
|
||||
"serverNotAvailable": "The AliasVault server is not available. Please try again later or contact support if the problem persists.",
|
||||
"clientVersionNotSupported": "This version of the AliasVault browser extension is not supported by the server anymore. Please update your browser extension to the latest version.",
|
||||
"serverVersionNotSupported": "The AliasVault server needs to be updated to a newer version in order to use this browser extension. Please contact support if you need help."
|
||||
"serverVersionNotSupported": "The AliasVault server needs to be updated to a newer version in order to use this browser extension. Please contact support if you need help.",
|
||||
"unknownError": "An unknown error occurred",
|
||||
"failedToStoreVault": "Failed to store vault",
|
||||
"vaultNotAvailable": "Vault not available",
|
||||
"failedToGetVault": "Failed to get vault",
|
||||
"vaultIsLocked": "Vault is locked",
|
||||
"failedToGetCredentials": "Failed to get credentials",
|
||||
"failedToCreateIdentity": "Failed to create identity",
|
||||
"failedToGetDefaultEmailDomain": "Failed to get default email domain",
|
||||
"failedToGetDefaultIdentitySettings": "Failed to get default identity settings",
|
||||
"failedToGetPasswordSettings": "Failed to get password settings",
|
||||
"failedToUploadVault": "Failed to upload vault",
|
||||
"noDerivedKeyAvailable": "No derived key available for encryption",
|
||||
"failedToUploadVaultToServer": "Failed to upload new vault to server",
|
||||
"noVaultOrDerivedKeyFound": "No vault or derived key found"
|
||||
},
|
||||
"apiErrors": {
|
||||
"UNKNOWN_ERROR": "An unknown error occurred. Please try again.",
|
||||
|
||||
39
apps/browser-extension/src/locales/en/content.json
Normal file
39
apps/browser-extension/src/locales/en/content.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"new": "New",
|
||||
"cancel": "Cancel",
|
||||
"search": "Search",
|
||||
"vaultLocked": "AliasVault is locked.",
|
||||
"creatingNewAlias": "Creating new alias...",
|
||||
"noMatchesFound": "No matches found",
|
||||
"searchVault": "Search vault...",
|
||||
"serviceName": "Service name",
|
||||
"email": "Email",
|
||||
"username": "Username",
|
||||
"generatedPassword": "Generated Password",
|
||||
"enterServiceName": "Enter service name",
|
||||
"enterEmailAddress": "Enter email address",
|
||||
"enterUsername": "Enter username",
|
||||
"hideFor1Hour": "Hide for 1 hour (current site)",
|
||||
"hidePermanently": "Hide permanently (current site)",
|
||||
"createRandomAlias": "Create random alias",
|
||||
"createUsernamePassword": "Create username/password",
|
||||
"randomAlias": "Random alias",
|
||||
"usernamePassword": "Username/password",
|
||||
"createAndSaveAlias": "Create and save alias",
|
||||
"createAndSaveCredential": "Create and save credential",
|
||||
"randomIdentityDescription": "Generate a random identity with a random email address accessible in AliasVault.",
|
||||
"randomIdentityDescriptionDropdown": "Random identity with random email",
|
||||
"manualCredentialDescription": "Specify your own email address and username.",
|
||||
"manualCredentialDescriptionDropdown": "Manual username and password",
|
||||
"failedToCreateIdentity": "Failed to create identity. Please try again.",
|
||||
"enterEmailAndOrUsername": "Enter email and/or username",
|
||||
"autofillWithAliasVault": "Autofill with AliasVault",
|
||||
"generateRandomPassword": "Generate random password (copy to clipboard)",
|
||||
"generateNewPassword": "Generate new password",
|
||||
"togglePasswordVisibility": "Toggle password visibility",
|
||||
"passwordCopiedToClipboard": "Password copied to clipboard",
|
||||
"enterEmailAndOrUsernameError": "Enter email and/or username",
|
||||
"openAliasVaultToUpgrade": "Open AliasVault to upgrade",
|
||||
"vaultUpgradeRequired": "Vault upgrade required.",
|
||||
"dismissPopup": "Dismiss popup"
|
||||
}
|
||||
@@ -42,7 +42,21 @@
|
||||
"NoVaultFound": "Uw account heeft nog geen vault. Voltooi eerst de tutorial in de AliasVault webclient voordat u de browserextensie gebruikt.",
|
||||
"serverNotAvailable": "De AliasVault server is niet beschikbaar. Probeer het later opnieuw of neem contact op met de ondersteuning als het probleem aanhoudt.",
|
||||
"clientVersionNotSupported": "Deze versie van de AliasVault browserextensie wordt niet meer ondersteund door de server. Update uw browserextensie naar de nieuwste versie.",
|
||||
"serverVersionNotSupported": "De AliasVault server moet worden bijgewerkt naar een nieuwere versie om deze browserextensie te kunnen gebruiken. Neem contact op met de ondersteuning als u hulp nodig hebt."
|
||||
"serverVersionNotSupported": "De AliasVault server moet worden bijgewerkt naar een nieuwere versie om deze browserextensie te kunnen gebruiken. Neem contact op met de ondersteuning als u hulp nodig hebt.",
|
||||
"unknownError": "Er is een onbekende fout opgetreden",
|
||||
"failedToStoreVault": "Vault opslaan mislukt",
|
||||
"vaultNotAvailable": "Vault niet beschikbaar",
|
||||
"failedToGetVault": "Vault ophalen mislukt",
|
||||
"vaultIsLocked": "Vault is vergrendeld",
|
||||
"failedToGetCredentials": "Inloggegevens ophalen mislukt",
|
||||
"failedToCreateIdentity": "Identiteit aanmaken mislukt",
|
||||
"failedToGetDefaultEmailDomain": "Standaard e-maildomein ophalen mislukt",
|
||||
"failedToGetDefaultIdentitySettings": "Standaard identiteitsinstellingen ophalen mislukt",
|
||||
"failedToGetPasswordSettings": "Wachtwoordinstellingen ophalen mislukt",
|
||||
"failedToUploadVault": "Vault uploaden mislukt",
|
||||
"noDerivedKeyAvailable": "Geen afgeleide sleutel beschikbaar voor versleuteling",
|
||||
"failedToUploadVaultToServer": "Nieuwe vault uploaden naar server mislukt",
|
||||
"noVaultOrDerivedKeyFound": "Geen vault of afgeleide sleutel gevonden"
|
||||
},
|
||||
"apiErrors": {
|
||||
"UNKNOWN_ERROR": "Er is een onbekende fout opgetreden. Probeer het opnieuw.",
|
||||
|
||||
31
apps/browser-extension/src/locales/nl/content.json
Normal file
31
apps/browser-extension/src/locales/nl/content.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"new": "Nieuw",
|
||||
"cancel": "Annuleren",
|
||||
"search": "Zoeken",
|
||||
"vaultLocked": "AliasVault is vergrendeld.",
|
||||
"creatingNewAlias": "Nieuwe alias aanmaken...",
|
||||
"noMatchesFound": "Geen resultaten gevonden",
|
||||
"searchVault": "Kluis doorzoeken...",
|
||||
"serviceName": "Servicenaam",
|
||||
"email": "E-mail",
|
||||
"username": "Gebruikersnaam",
|
||||
"generatedPassword": "Gegenereerd wachtwoord",
|
||||
"enterServiceName": "Voer servicenaam in",
|
||||
"enterEmailAddress": "Voer e-mailadres in",
|
||||
"enterUsername": "Voer gebruikersnaam in",
|
||||
"hideFor1Hour": "Verberg voor 1 uur (huidige site)",
|
||||
"hidePermanently": "Permanent verbergen (huidige site)",
|
||||
"createRandomAlias": "Willekeurige alias aanmaken",
|
||||
"createUsernamePassword": "Gebruikersnaam/wachtwoord aanmaken",
|
||||
"randomAlias": "Willekeurige alias",
|
||||
"usernamePassword": "Gebruikersnaam/wachtwoord",
|
||||
"createAndSaveAlias": "Alias aanmaken en opslaan",
|
||||
"createAndSaveCredential": "Inloggegevens aanmaken en opslaan",
|
||||
"randomIdentityDescription": "Genereer een willekeurige identiteit met een willekeurig e-mailadres toegankelijk in AliasVault.",
|
||||
"manualCredentialDescription": "Specificeer je eigen e-mailadres en gebruikersnaam.",
|
||||
"failedToCreateIdentity": "Identiteit aanmaken mislukt. Probeer opnieuw.",
|
||||
"enterEmailAndOrUsername": "Voer e-mail en/of gebruikersnaam in",
|
||||
"autofillWithAliasVault": "Autofill met AliasVault",
|
||||
"generateRandomPassword": "Willekeurig wachtwoord genereren (kopiëren naar klembord)",
|
||||
"passwordCopiedToClipboard": "Wachtwoord gekopieerd naar klembord"
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
import { storage } from '#imports';
|
||||
|
||||
/**
|
||||
* Translation keys for content scripts
|
||||
*/
|
||||
export interface IContentTranslations {
|
||||
// Common
|
||||
new: string;
|
||||
cancel: string;
|
||||
search: string;
|
||||
|
||||
// Status messages
|
||||
vaultLocked: string;
|
||||
creatingNewAlias: string;
|
||||
noMatchesFound: string;
|
||||
|
||||
// Form labels and placeholders
|
||||
searchVault: string;
|
||||
serviceName: string;
|
||||
email: string;
|
||||
username: string;
|
||||
generatedPassword: string;
|
||||
enterServiceName: string;
|
||||
enterEmailAddress: string;
|
||||
enterUsername: string;
|
||||
|
||||
// Context menu
|
||||
hideFor1Hour: string;
|
||||
hidePermanently: string;
|
||||
|
||||
// Create alias popup
|
||||
createRandomAlias: string;
|
||||
createUsernamePassword: string;
|
||||
randomAlias: string;
|
||||
usernamePassword: string;
|
||||
createAndSaveAlias: string;
|
||||
createAndSaveCredential: string;
|
||||
randomIdentityDescription: string;
|
||||
manualCredentialDescription: string;
|
||||
|
||||
// Error messages
|
||||
failedToCreateIdentity: string;
|
||||
enterEmailAndOrUsername: string;
|
||||
|
||||
// Context menu
|
||||
autofillWithAliasVault: string;
|
||||
generateRandomPassword: string;
|
||||
passwordCopiedToClipboard: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* English translations
|
||||
*/
|
||||
const enTranslations: IContentTranslations = {
|
||||
new: 'New',
|
||||
cancel: 'Cancel',
|
||||
search: 'Search',
|
||||
vaultLocked: 'AliasVault is locked.',
|
||||
creatingNewAlias: 'Creating new alias...',
|
||||
noMatchesFound: 'No matches found',
|
||||
searchVault: 'Search vault...',
|
||||
serviceName: 'Service name',
|
||||
email: 'Email',
|
||||
username: 'Username',
|
||||
generatedPassword: 'Generated Password',
|
||||
enterServiceName: 'Enter service name',
|
||||
enterEmailAddress: 'Enter email address',
|
||||
enterUsername: 'Enter username',
|
||||
hideFor1Hour: 'Hide for 1 hour (current site)',
|
||||
hidePermanently: 'Hide permanently (current site)',
|
||||
createRandomAlias: 'Create random alias',
|
||||
createUsernamePassword: 'Create username/password',
|
||||
randomAlias: 'Random alias',
|
||||
usernamePassword: 'Username/password',
|
||||
createAndSaveAlias: 'Create and save alias',
|
||||
createAndSaveCredential: 'Create and save credential',
|
||||
randomIdentityDescription: 'Generate a random identity with a random email address accessible in AliasVault.',
|
||||
manualCredentialDescription: 'Specify your own email address and username.',
|
||||
failedToCreateIdentity: 'Failed to create identity. Please try again.',
|
||||
enterEmailAndOrUsername: 'Enter email and/or username',
|
||||
autofillWithAliasVault: 'Autofill with AliasVault',
|
||||
generateRandomPassword: 'Generate random password (copy to clipboard)',
|
||||
passwordCopiedToClipboard: 'Password copied to clipboard'
|
||||
};
|
||||
|
||||
/**
|
||||
* Dutch translations
|
||||
*/
|
||||
const nlTranslations: IContentTranslations = {
|
||||
new: 'Nieuw',
|
||||
cancel: 'Annuleren',
|
||||
search: 'Zoeken',
|
||||
vaultLocked: 'AliasVault is vergrendeld.',
|
||||
creatingNewAlias: 'Nieuwe alias aanmaken...',
|
||||
noMatchesFound: 'Geen resultaten gevonden',
|
||||
searchVault: 'Kluis doorzoeken...',
|
||||
serviceName: 'Servicenaam',
|
||||
email: 'E-mail',
|
||||
username: 'Gebruikersnaam',
|
||||
generatedPassword: 'Gegenereerd wachtwoord',
|
||||
enterServiceName: 'Voer servicenaam in',
|
||||
enterEmailAddress: 'Voer e-mailadres in',
|
||||
enterUsername: 'Voer gebruikersnaam in',
|
||||
hideFor1Hour: 'Verberg voor 1 uur (huidige site)',
|
||||
hidePermanently: 'Permanent verbergen (huidige site)',
|
||||
createRandomAlias: 'Willekeurige alias aanmaken',
|
||||
createUsernamePassword: 'Gebruikersnaam/wachtwoord aanmaken',
|
||||
randomAlias: 'Willekeurige alias',
|
||||
usernamePassword: 'Gebruikersnaam/wachtwoord',
|
||||
createAndSaveAlias: 'Alias aanmaken en opslaan',
|
||||
createAndSaveCredential: 'Inloggegevens aanmaken en opslaan',
|
||||
randomIdentityDescription: 'Genereer een willekeurige identiteit met een willekeurig e-mailadres toegankelijk in AliasVault.',
|
||||
manualCredentialDescription: 'Specificeer je eigen e-mailadres en gebruikersnaam.',
|
||||
failedToCreateIdentity: 'Identiteit aanmaken mislukt. Probeer opnieuw.',
|
||||
enterEmailAndOrUsername: 'Voer e-mail en/of gebruikersnaam in',
|
||||
autofillWithAliasVault: 'Autofill met AliasVault',
|
||||
generateRandomPassword: 'Willekeurig wachtwoord genereren (kopiëren naar klembord)',
|
||||
passwordCopiedToClipboard: 'Wachtwoord gekopieerd naar klembord'
|
||||
};
|
||||
|
||||
/**
|
||||
* All available translations
|
||||
*/
|
||||
const translations = {
|
||||
en: enTranslations,
|
||||
nl: nlTranslations
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current language from storage
|
||||
*/
|
||||
export async function getCurrentLanguage(): Promise<string> {
|
||||
try {
|
||||
// Use extension storage API exclusively (reliable across all contexts)
|
||||
const langFromStorage = await storage.getItem('local:language') as string;
|
||||
if (langFromStorage && ['en', 'nl'].includes(langFromStorage)) {
|
||||
return langFromStorage;
|
||||
}
|
||||
|
||||
// If no language is set in storage, detect browser language and save it
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
const detectedLanguage = ['en', 'nl'].includes(browserLang) ? browserLang : 'en';
|
||||
|
||||
// Save the detected language to storage for future use
|
||||
await storage.setItem('local:language', detectedLanguage);
|
||||
|
||||
return detectedLanguage;
|
||||
} catch (error) {
|
||||
console.error('Failed to get current language:', error);
|
||||
return 'en';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation for a key
|
||||
*/
|
||||
export async function t(key: keyof IContentTranslations): Promise<string> {
|
||||
const lang = await getCurrentLanguage();
|
||||
return translations[lang as keyof typeof translations]?.[key] || translations.en[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all translations for current language
|
||||
*/
|
||||
export async function getTranslations(): Promise<IContentTranslations> {
|
||||
const lang = await getCurrentLanguage();
|
||||
return translations[lang as keyof typeof translations] || translations.en;
|
||||
}
|
||||
196
apps/browser-extension/src/utils/i18n/StandaloneI18n.ts
Normal file
196
apps/browser-extension/src/utils/i18n/StandaloneI18n.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { storage } from '#imports';
|
||||
|
||||
/**
|
||||
* Type for content translations
|
||||
*/
|
||||
export type ContentTranslations = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache for loaded translations to avoid repeated file reads
|
||||
*/
|
||||
const translationCache = new Map<string, ContentTranslations>();
|
||||
|
||||
/**
|
||||
* Get current language from storage
|
||||
*/
|
||||
export async function getCurrentLanguage(): Promise<string> {
|
||||
try {
|
||||
// Use extension storage API exclusively (reliable across all contexts)
|
||||
const langFromStorage = await storage.getItem('local:language') as string;
|
||||
if (langFromStorage && ['en', 'nl', 'de', 'es', 'fr', 'uk'].includes(langFromStorage)) {
|
||||
return langFromStorage;
|
||||
}
|
||||
|
||||
// If no language is set in storage, detect browser language and save it
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
const detectedLanguage = ['en', 'nl', 'de', 'es', 'fr', 'uk'].includes(browserLang) ? browserLang : 'en';
|
||||
|
||||
// Save the detected language to storage for future use
|
||||
await storage.setItem('local:language', detectedLanguage);
|
||||
|
||||
return detectedLanguage;
|
||||
} catch (error) {
|
||||
console.error('Failed to get current language:', error);
|
||||
return 'en';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load translations for a specific namespace and language
|
||||
*/
|
||||
async function loadTranslations(namespace: string, language: string): Promise<ContentTranslations> {
|
||||
const cacheKey = `${namespace}:${language}`;
|
||||
|
||||
// Check cache first
|
||||
if (translationCache.has(cacheKey)) {
|
||||
return translationCache.get(cacheKey)!;
|
||||
}
|
||||
|
||||
try {
|
||||
// Dynamically import the translation file
|
||||
const translations = await import(`../../locales/${language}/${namespace}.json`);
|
||||
const translationData = translations.default || translations;
|
||||
|
||||
// Cache the translations
|
||||
translationCache.set(cacheKey, translationData);
|
||||
|
||||
return translationData;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load translations for ${namespace}:${language}`, error);
|
||||
|
||||
// Fallback to English if available
|
||||
if (language !== 'en') {
|
||||
try {
|
||||
const fallbackTranslations = await import(`../../locales/en/${namespace}.json`);
|
||||
const fallbackData = fallbackTranslations.default || fallbackTranslations;
|
||||
translationCache.set(cacheKey, fallbackData);
|
||||
return fallbackData;
|
||||
} catch (fallbackError) {
|
||||
console.error('Failed to load English fallback translations', fallbackError);
|
||||
}
|
||||
}
|
||||
|
||||
// Return empty object as last resort
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation function for non-React contexts
|
||||
*
|
||||
* @param key - Translation key (supports nested keys like 'errors.networkError')
|
||||
* @param namespace - Translation namespace (default: 'content')
|
||||
* @param fallback - Fallback text if translation is not found
|
||||
* @returns Promise<string> - Translated text
|
||||
*/
|
||||
export async function t(
|
||||
key: string,
|
||||
namespace: string = 'content',
|
||||
fallback?: string
|
||||
): Promise<string> {
|
||||
try {
|
||||
const language = await getCurrentLanguage();
|
||||
const translations = await loadTranslations(namespace, language);
|
||||
|
||||
// Support nested keys like 'errors.networkError'
|
||||
const value = getNestedValue(translations, key);
|
||||
|
||||
if (value && typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
// If translation not found and we're not using English, try English fallback
|
||||
if (language !== 'en') {
|
||||
const englishTranslations = await loadTranslations(namespace, 'en');
|
||||
const englishValue = getNestedValue(englishTranslations, key);
|
||||
|
||||
if (englishValue && typeof englishValue === 'string') {
|
||||
return englishValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Return fallback or key if no translation found
|
||||
return fallback || key;
|
||||
} catch (error) {
|
||||
console.error('Translation error:', error);
|
||||
return fallback || key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nested value from object using dot notation
|
||||
*/
|
||||
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
||||
return path.split('.').reduce((current, key) => {
|
||||
return current && current[key] !== undefined ? current[key] : undefined;
|
||||
}, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation function specifically for content scripts
|
||||
* This is a convenience wrapper around the main t() function
|
||||
*/
|
||||
export async function tc(key: string, fallback?: string): Promise<string> {
|
||||
return t(key, 'content', fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation function for errors namespace
|
||||
*/
|
||||
export async function te(key: string, fallback?: string): Promise<string> {
|
||||
return t(key, 'errors', fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation function for common namespace
|
||||
*/
|
||||
export async function tcommon(key: string, fallback?: string): Promise<string> {
|
||||
return t(key, 'common', fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear translation cache (useful for language changes)
|
||||
*/
|
||||
export function clearTranslationCache(): void {
|
||||
translationCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-load common translations for use in synchronous contexts
|
||||
*/
|
||||
export async function preloadTranslationsForSync(
|
||||
namespaces: string[] = ['common', 'errors']
|
||||
): Promise<Record<string, ContentTranslations>> {
|
||||
const languages = ['en', 'nl'];
|
||||
const translations: Record<string, ContentTranslations> = {};
|
||||
|
||||
for (const lang of languages) {
|
||||
const langTranslations: ContentTranslations = {};
|
||||
|
||||
for (const namespace of namespaces) {
|
||||
try {
|
||||
const nsTranslations = await loadTranslations(namespace, lang);
|
||||
// Flatten the namespace structure for easier access
|
||||
Object.keys(nsTranslations).forEach(key => {
|
||||
if (namespace === 'common' && key === 'errors') {
|
||||
// Handle nested errors object
|
||||
const errors = nsTranslations[key] as Record<string, string>;
|
||||
Object.keys(errors).forEach(errorKey => {
|
||||
langTranslations[`errors.${errorKey}`] = errors[errorKey];
|
||||
});
|
||||
} else {
|
||||
langTranslations[key] = nsTranslations[key];
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(`Failed to preload ${namespace} translations for ${lang}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
translations[lang] = langTranslations;
|
||||
}
|
||||
|
||||
return translations;
|
||||
}
|
||||
Reference in New Issue
Block a user