Add identity generator options to browser extension settings (#1571)
This commit is contained in:
committed by
Leendert de Borst
parent
63bc50d862
commit
75d74d46b1
@@ -31,6 +31,7 @@ import AutofillSettings from '@/entrypoints/popup/pages/settings/AutofillSetting
|
||||
import AutoLockSettings from '@/entrypoints/popup/pages/settings/AutoLockSettings';
|
||||
import ClipboardSettings from '@/entrypoints/popup/pages/settings/ClipboardSettings';
|
||||
import ContextMenuSettings from '@/entrypoints/popup/pages/settings/ContextMenuSettings';
|
||||
import IdentityGeneratorSettings from '@/entrypoints/popup/pages/settings/IdentityGeneratorSettings';
|
||||
import LanguageSettings from '@/entrypoints/popup/pages/settings/LanguageSettings';
|
||||
import PasskeySettings from '@/entrypoints/popup/pages/settings/PasskeySettings';
|
||||
import Settings from '@/entrypoints/popup/pages/settings/Settings';
|
||||
@@ -203,6 +204,7 @@ const App: React.FC = () => {
|
||||
{ path: '/settings/language', element: <LanguageSettings />, showBackButton: true, title: t('settings.language') },
|
||||
{ path: '/settings/auto-lock', element: <AutoLockSettings />, showBackButton: true, title: t('settings.autoLockTimeout') },
|
||||
{ path: '/settings/passkeys', element: <PasskeySettings />, showBackButton: true, title: t('settings.passkeySettings') },
|
||||
{ path: '/settings/identity-generator', element: <IdentityGeneratorSettings />, showBackButton: true, title: t('settings.identityGenerator') },
|
||||
], [t]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||
|
||||
import { ClipboardCountdownBar } from '@/entrypoints/popup/components/ClipboardCountdownBar';
|
||||
import BottomNav from '@/entrypoints/popup/components/Layout/BottomNav';
|
||||
@@ -30,6 +30,16 @@ type DefaultLayoutProps = {
|
||||
* This is the main layout used for most pages in the extension.
|
||||
*/
|
||||
const DefaultLayout: React.FC<DefaultLayoutProps> = ({ routes, headerButtons, message, children }) => {
|
||||
const mainRef = useRef<HTMLElement>(null);
|
||||
const location = useLocation();
|
||||
|
||||
// Reset scroll position when route changes
|
||||
useEffect(() => {
|
||||
if (mainRef.current) {
|
||||
mainRef.current.scrollTop = 0;
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen min-w-[350px] bg-white dark:bg-gray-900 flex flex-col max-h-[600px]">
|
||||
<ClipboardCountdownBar />
|
||||
@@ -40,6 +50,7 @@ const DefaultLayout: React.FC<DefaultLayoutProps> = ({ routes, headerButtons, me
|
||||
/>
|
||||
|
||||
<main
|
||||
ref={mainRef}
|
||||
className="flex-1 overflow-y-auto bg-gray-100 dark:bg-gray-900"
|
||||
style={{
|
||||
paddingTop: '64px',
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useDb } from '@/entrypoints/popup/context/DbContext';
|
||||
import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
|
||||
import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate';
|
||||
|
||||
import { getAvailableLanguages, getAvailableAgeRanges, ILanguageOption, IAgeRangeOption } from '@/utils/dist/core/identity-generator';
|
||||
|
||||
/**
|
||||
* Identity Generator Settings page component.
|
||||
* Allows users to configure default language, gender, and age range for identity generation.
|
||||
*/
|
||||
const IdentityGeneratorSettings: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { setIsInitialLoading } = useLoading();
|
||||
const dbContext = useDb();
|
||||
const { executeVaultMutationAsync } = useVaultMutate();
|
||||
|
||||
const [language, setLanguage] = useState<string>('en');
|
||||
const [gender, setGender] = useState<string>('random');
|
||||
const [ageRange, setAgeRange] = useState<string>('random');
|
||||
const [languageOptions, setLanguageOptions] = useState<ILanguageOption[]>([]);
|
||||
const [ageRangeOptions, setAgeRangeOptions] = useState<IAgeRangeOption[]>([]);
|
||||
|
||||
const GENDER_OPTIONS = [
|
||||
{ label: t('settings.identityGeneratorSettings.genderOptions.random'), value: 'random' },
|
||||
{ label: t('settings.identityGeneratorSettings.genderOptions.male'), value: 'male' },
|
||||
{ label: t('settings.identityGeneratorSettings.genderOptions.female'), value: 'female' }
|
||||
];
|
||||
|
||||
/**
|
||||
* Load settings on mount.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const languages = getAvailableLanguages();
|
||||
const ranges = getAvailableAgeRanges();
|
||||
setLanguageOptions(languages);
|
||||
setAgeRangeOptions(ranges);
|
||||
|
||||
if (dbContext?.sqliteClient) {
|
||||
const currentLanguage = dbContext.sqliteClient.settings.getEffectiveIdentityLanguage();
|
||||
const currentGender = dbContext.sqliteClient.settings.getDefaultIdentityGender();
|
||||
const currentAgeRange = dbContext.sqliteClient.settings.getDefaultIdentityAgeRange();
|
||||
|
||||
setLanguage(currentLanguage);
|
||||
setGender(currentGender);
|
||||
setAgeRange(currentAgeRange);
|
||||
}
|
||||
|
||||
setIsInitialLoading(false);
|
||||
}, [dbContext?.sqliteClient, setIsInitialLoading]);
|
||||
|
||||
/**
|
||||
* Handle language change.
|
||||
*/
|
||||
const handleLanguageChange = useCallback(async (newLanguage: string): Promise<void> => {
|
||||
setLanguage(newLanguage);
|
||||
if (dbContext?.sqliteClient) {
|
||||
await executeVaultMutationAsync(async () => {
|
||||
dbContext.sqliteClient!.settings.updateSetting('DefaultIdentityLanguage', newLanguage);
|
||||
});
|
||||
}
|
||||
}, [dbContext?.sqliteClient, executeVaultMutationAsync]);
|
||||
|
||||
/**
|
||||
* Handle gender change.
|
||||
*/
|
||||
const handleGenderChange = useCallback(async (newGender: string): Promise<void> => {
|
||||
setGender(newGender);
|
||||
if (dbContext?.sqliteClient) {
|
||||
await executeVaultMutationAsync(async () => {
|
||||
dbContext.sqliteClient!.settings.updateSetting('DefaultIdentityGender', newGender);
|
||||
});
|
||||
}
|
||||
}, [dbContext?.sqliteClient, executeVaultMutationAsync]);
|
||||
|
||||
/**
|
||||
* Handle age range change.
|
||||
*/
|
||||
const handleAgeRangeChange = useCallback(async (newAgeRange: string): Promise<void> => {
|
||||
setAgeRange(newAgeRange);
|
||||
if (dbContext?.sqliteClient) {
|
||||
await executeVaultMutationAsync(async () => {
|
||||
dbContext.sqliteClient!.settings.updateSetting('DefaultIdentityAgeRange', newAgeRange);
|
||||
});
|
||||
}
|
||||
}, [dbContext?.sqliteClient, executeVaultMutationAsync]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Language Section */}
|
||||
<section>
|
||||
<h3 className="text-md font-semibold text-gray-900 dark:text-white mb-3">
|
||||
{t('settings.identityGeneratorSettings.languageSection')}
|
||||
</h3>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="p-4">
|
||||
<p className="font-medium text-gray-900 dark:text-white mb-2">
|
||||
{t('settings.identityGeneratorSettings.languageSection')}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
{t('settings.identityGeneratorSettings.languageDescription')}
|
||||
</p>
|
||||
<select
|
||||
value={language}
|
||||
onChange={(e) => handleLanguageChange(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-gray-900 dark:text-white focus:ring-primary-500 focus:border-primary-500"
|
||||
>
|
||||
{languageOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.flag} {option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Gender Section */}
|
||||
<section>
|
||||
<h3 className="text-md font-semibold text-gray-900 dark:text-white mb-3">
|
||||
{t('settings.identityGeneratorSettings.genderSection')}
|
||||
</h3>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="p-4">
|
||||
<p className="font-medium text-gray-900 dark:text-white mb-2">
|
||||
{t('settings.identityGeneratorSettings.genderSection')}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
{t('settings.identityGeneratorSettings.genderDescription')}
|
||||
</p>
|
||||
<select
|
||||
value={gender}
|
||||
onChange={(e) => handleGenderChange(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-gray-900 dark:text-white focus:ring-primary-500 focus:border-primary-500"
|
||||
>
|
||||
{GENDER_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Age Range Section */}
|
||||
<section>
|
||||
<h3 className="text-md font-semibold text-gray-900 dark:text-white mb-3">
|
||||
{t('settings.identityGeneratorSettings.ageRangeSection')}
|
||||
</h3>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="p-4">
|
||||
<p className="font-medium text-gray-900 dark:text-white mb-2">
|
||||
{t('settings.identityGeneratorSettings.ageRangeSection')}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
{t('settings.identityGeneratorSettings.ageRangeDescription')}
|
||||
</p>
|
||||
<select
|
||||
value={ageRange}
|
||||
onChange={(e) => handleAgeRangeChange(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-gray-900 dark:text-white focus:ring-primary-500 focus:border-primary-500"
|
||||
>
|
||||
{ageRangeOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.value === 'random' ? t('settings.identityGeneratorSettings.genderOptions.random') : option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IdentityGeneratorSettings;
|
||||
@@ -192,6 +192,13 @@ const Settings: React.FC = () => {
|
||||
navigate('/settings/passkeys');
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to identity generator settings.
|
||||
*/
|
||||
const navigateToIdentityGeneratorSettings = () : void => {
|
||||
navigate('/settings/identity-generator');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Logout Confirmation Modal */}
|
||||
@@ -279,60 +286,6 @@ const Settings: React.FC = () => {
|
||||
<section>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{/* Vault Unlock Method */}
|
||||
<button
|
||||
onClick={navigateToUnlockMethodSettings}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<svg className="w-5 h-5 mr-3 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14" />
|
||||
</svg>
|
||||
<span className="text-gray-900 dark:text-white text-left">{t('settings.unlockMethod.title')}</span>
|
||||
</div>
|
||||
<svg
|
||||
className="w-4 h-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Auto-lock Settings */}
|
||||
<button
|
||||
onClick={navigateToAutoLockSettings}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="w-5 h-5 mr-3 text-gray-600 dark:text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-gray-900 dark:text-white text-left">{t('settings.autoLockTimeout')}</span>
|
||||
</div>
|
||||
<svg
|
||||
className="w-4 h-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Autofill Settings */}
|
||||
<button
|
||||
onClick={navigateToAutofillSettings}
|
||||
@@ -396,14 +349,61 @@ const Settings: React.FC = () => {
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Additional Settings Section */}
|
||||
<section>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{/* Vault Unlock Method */}
|
||||
<button
|
||||
onClick={navigateToUnlockMethodSettings}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<svg className="w-5 h-5 mr-3 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14" />
|
||||
</svg>
|
||||
<span className="text-gray-900 dark:text-white text-left">{t('settings.unlockMethod.title')}</span>
|
||||
</div>
|
||||
<svg
|
||||
className="w-4 h-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Auto-lock Settings */}
|
||||
<button
|
||||
onClick={navigateToAutoLockSettings}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="w-5 h-5 mr-3 text-gray-600 dark:text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-gray-900 dark:text-white text-left">{t('settings.autoLockTimeout')}</span>
|
||||
</div>
|
||||
<svg
|
||||
className="w-4 h-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Clipboard Settings */}
|
||||
<button
|
||||
onClick={navigateToClipboardSettings}
|
||||
@@ -436,38 +436,6 @@ const Settings: React.FC = () => {
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Context Menu Settings */}
|
||||
<button
|
||||
onClick={navigateToContextMenuSettings}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="w-5 h-5 mr-3 text-gray-600 dark:text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4 6h16M4 12h16m-7 6h7"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-gray-900 dark:text-white text-left">{t('settings.contextMenuSettings')}</span>
|
||||
</div>
|
||||
<svg
|
||||
className="w-4 h-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Language Settings */}
|
||||
<button
|
||||
onClick={navigateToLanguageSettings}
|
||||
@@ -503,6 +471,77 @@ const Settings: React.FC = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Generator Settings Section */}
|
||||
<section>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{/* Identity Generator Settings */}
|
||||
<button
|
||||
onClick={navigateToIdentityGeneratorSettings}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="w-5 h-5 mr-3 text-gray-600 dark:text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-gray-900 dark:text-white text-left">{t('settings.identityGenerator')}</span>
|
||||
</div>
|
||||
<svg
|
||||
className="w-4 h-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Context Menu Settings */}
|
||||
<button
|
||||
onClick={navigateToContextMenuSettings}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="w-5 h-5 mr-3 text-gray-600 dark:text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4 6h16M4 12h16m-7 6h7"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-gray-900 dark:text-white text-left">{t('settings.contextMenuSettings')}</span>
|
||||
</div>
|
||||
<svg
|
||||
className="w-4 h-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Appearance Settings Section */}
|
||||
<section>
|
||||
<h3 className="text-md font-semibold text-gray-900 dark:text-white mb-3">{t('settings.appearance')}</h3>
|
||||
|
||||
@@ -403,6 +403,21 @@
|
||||
"clientUrlRequired": "Client URL is required",
|
||||
"clientUrlInvalid": "Please enter a valid client URL"
|
||||
},
|
||||
"identityGenerator": "Identity Generator",
|
||||
"identityGeneratorSettings": {
|
||||
"description": "Configure the default language and gender preference for generating new identities.",
|
||||
"languageSection": "Language",
|
||||
"languageDescription": "Set the language that will be used when generating new identities.",
|
||||
"genderSection": "Gender",
|
||||
"genderDescription": "Set the gender preference for generating new identities.",
|
||||
"ageRangeSection": "Age Range",
|
||||
"ageRangeDescription": "Set the age range for generating new identities.",
|
||||
"genderOptions": {
|
||||
"random": "Random",
|
||||
"male": "Male",
|
||||
"female": "Female"
|
||||
}
|
||||
},
|
||||
"unlockMethod": {
|
||||
"title": "Vault Unlock Method",
|
||||
"introText": "Choose how you want to unlock your vault. You can use your master password (always available) or set up a PIN code for faster access. After 3 failed PIN attempts, you'll need to use your master password.",
|
||||
|
||||
Reference in New Issue
Block a user