mirror of
https://github.com/LogicLabs-OU/OpenArchiver.git
synced 2026-04-06 00:31:57 +02:00
System settings: adding multi-language support for frontend (#72)
* System settings setup * Multi-language support * feat: Add internationalization (i18n) support to frontend This commit introduces internationalization (i18n) to the frontend using the `sveltekit-i18n` library, allowing the user interface to be translated into multiple languages. Key changes: - Added translation files for 10 languages (en, de, es, fr, etc.). - Replaced hardcoded text strings throughout the frontend components and pages with translation keys. - Added a language selector to the system settings page, allowing administrators to set the default application language. - Updated the backend settings API to store and expose the new language configuration. * Adding greek translation * feat(backend): Implement i18n for API responses This commit introduces internationalization (i18n) to the backend API using the `i18next` library. Hardcoded error and response messages in the API controllers have been replaced with translation keys, which are processed by the new i18next middleware. This allows for API responses to be translated into different languages. The following dependencies were added: - `i18next` - `i18next-fs-backend` - `i18next-http-middleware` * Formatting code * Translation revamp for frontend and backend, adding systems docs * Docs site title --------- Co-authored-by: Wayne <5291640+ringoinca@users.noreply.github.com>
This commit is contained in:
@@ -12,7 +12,7 @@ export default defineConfig({
|
||||
],
|
||||
['link', { rel: 'icon', href: '/logo-sq.svg' }],
|
||||
],
|
||||
title: 'Open Archiver',
|
||||
title: 'Open Archiver Docs',
|
||||
description: 'Official documentation for the Open Archiver project.',
|
||||
themeConfig: {
|
||||
search: {
|
||||
@@ -54,6 +54,16 @@ export default defineConfig({
|
||||
{ text: 'PST Import', link: '/user-guides/email-providers/pst' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Settings',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: 'System',
|
||||
link: '/user-guides/settings/system',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -75,9 +85,8 @@ export default defineConfig({
|
||||
{ text: 'Overview', link: '/services/' },
|
||||
{ text: 'Storage Service', link: '/services/storage-service' },
|
||||
{
|
||||
text: 'IAM Service', items: [
|
||||
{ text: 'IAM Policies', link: '/services/iam-service/iam-policy' }
|
||||
]
|
||||
text: 'IAM Service',
|
||||
items: [{ text: 'IAM Policies', link: '/services/iam-service/iam-policy' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
32
docs/user-guides/settings/system.md
Normal file
32
docs/user-guides/settings/system.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# System Settings
|
||||
|
||||
System settings allow administrators to configure the global look and theme of the application. These settings apply to all users.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Language
|
||||
|
||||
This setting determines the default display language for the application UI. The selected language will be used for all interface elements, including menus, labels, and messages.
|
||||
|
||||
> **Important:** When the language is changed, the backend (API) language will only change after a restart of the server. The frontend will update immediately.
|
||||
|
||||
Supported languages:
|
||||
|
||||
- English
|
||||
- German
|
||||
- French
|
||||
- Estonian
|
||||
- Spanish
|
||||
- Italian
|
||||
- Portuguese
|
||||
- Dutch
|
||||
- Greek
|
||||
- Japanese
|
||||
|
||||
### Default Theme
|
||||
|
||||
This setting controls the default color theme for the application. Users can choose between light, dark, or system default. The system default theme will sync with the user's operating system theme.
|
||||
|
||||
### Support Email
|
||||
|
||||
This setting allows administrators to provide a public-facing email address for user support inquiries. This email address may be displayed on error pages or in other areas where users may need to contact support.
|
||||
@@ -40,6 +40,9 @@
|
||||
"express-validator": "^7.2.1",
|
||||
"google-auth-library": "^10.1.0",
|
||||
"googleapis": "^152.0.0",
|
||||
"i18next": "^25.4.2",
|
||||
"i18next-fs-backend": "^2.6.0",
|
||||
"i18next-http-middleware": "^3.8.0",
|
||||
"imapflow": "^1.0.191",
|
||||
"jose": "^6.0.11",
|
||||
"mailparser": "^3.7.4",
|
||||
|
||||
@@ -11,7 +11,7 @@ export class ArchivedEmailController {
|
||||
const userId = req.user?.sub;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: 'Unauthorized' });
|
||||
return res.status(401).json({ message: req.t('errors.unauthorized') });
|
||||
}
|
||||
|
||||
const result = await ArchivedEmailService.getArchivedEmails(
|
||||
@@ -23,7 +23,7 @@ export class ArchivedEmailController {
|
||||
return res.status(200).json(result);
|
||||
} catch (error) {
|
||||
console.error('Get archived emails error:', error);
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,23 +33,23 @@ export class ArchivedEmailController {
|
||||
const userId = req.user?.sub;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: 'Unauthorized' });
|
||||
return res.status(401).json({ message: req.t('errors.unauthorized') });
|
||||
}
|
||||
|
||||
const email = await ArchivedEmailService.getArchivedEmailById(id, userId);
|
||||
if (!email) {
|
||||
return res.status(404).json({ message: 'Archived email not found' });
|
||||
return res.status(404).json({ message: req.t('archivedEmail.notFound') });
|
||||
}
|
||||
return res.status(200).json(email);
|
||||
} catch (error) {
|
||||
console.error(`Get archived email by id ${req.params.id} error:`, error);
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
public deleteArchivedEmail = async (req: Request, res: Response): Promise<Response> => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
try {
|
||||
const { id } = req.params;
|
||||
@@ -59,11 +59,11 @@ export class ArchivedEmailController {
|
||||
console.error(`Delete archived email ${req.params.id} error:`, error);
|
||||
if (error instanceof Error) {
|
||||
if (error.message === 'Archived email not found') {
|
||||
return res.status(404).json({ message: error.message });
|
||||
return res.status(404).json({ message: req.t('archivedEmail.notFound') });
|
||||
}
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export class AuthController {
|
||||
const { email, password, first_name, last_name } = req.body;
|
||||
|
||||
if (!email || !password || !first_name || !last_name) {
|
||||
return res.status(400).json({ message: 'Email, password, and name are required' });
|
||||
return res.status(400).json({ message: req.t('auth.setup.allFieldsRequired') });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -37,7 +37,7 @@ export class AuthController {
|
||||
const userCount = Number(userCountResult[0].count);
|
||||
|
||||
if (userCount > 0) {
|
||||
return res.status(403).json({ message: 'Setup has already been completed.' });
|
||||
return res.status(403).json({ message: req.t('auth.setup.alreadyCompleted') });
|
||||
}
|
||||
|
||||
const newUser = await this.#userService.createAdminUser(
|
||||
@@ -48,7 +48,7 @@ export class AuthController {
|
||||
return res.status(201).json(result);
|
||||
} catch (error) {
|
||||
console.error('Setup error:', error);
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,20 +56,20 @@ export class AuthController {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({ message: 'Email and password are required' });
|
||||
return res.status(400).json({ message: req.t('auth.login.emailAndPasswordRequired') });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.#authService.login(email, password);
|
||||
|
||||
if (!result) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
return res.status(401).json({ message: req.t('auth.login.invalidCredentials') });
|
||||
}
|
||||
|
||||
return res.status(200).json(result);
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -124,7 +124,7 @@ export class AuthController {
|
||||
return res.status(200).json({ needsSetupUser });
|
||||
} catch (error) {
|
||||
console.error('Status check error:', error);
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export class IamController {
|
||||
}
|
||||
res.status(200).json(roles);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Failed to get roles.' });
|
||||
res.status(500).json({ message: req.t('iam.failedToGetRoles') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,21 +34,21 @@ export class IamController {
|
||||
if (role) {
|
||||
res.status(200).json(role);
|
||||
} else {
|
||||
res.status(404).json({ message: 'Role not found.' });
|
||||
res.status(404).json({ message: req.t('iam.roleNotFound') });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Failed to get role.' });
|
||||
res.status(500).json({ message: req.t('iam.failedToGetRole') });
|
||||
}
|
||||
};
|
||||
|
||||
public createRole = async (req: Request, res: Response) => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
const { name, policies } = req.body;
|
||||
|
||||
if (!name || !policies) {
|
||||
res.status(400).json({ message: 'Missing required fields: name and policy.' });
|
||||
res.status(400).json({ message: req.t('iam.missingRoleFields') });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class IamController {
|
||||
for (const statement of policies) {
|
||||
const { valid, reason } = PolicyValidator.isValid(statement as CaslPolicy);
|
||||
if (!valid) {
|
||||
res.status(400).json({ message: `Invalid policy statement: ${reason}` });
|
||||
res.status(400).json({ message: `${req.t('iam.invalidPolicy')} ${reason}` });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -64,13 +64,13 @@ export class IamController {
|
||||
res.status(201).json(role);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ message: 'Failed to create role.' });
|
||||
res.status(500).json({ message: req.t('iam.failedToCreateRole') });
|
||||
}
|
||||
};
|
||||
|
||||
public deleteRole = async (req: Request, res: Response) => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
const { id } = req.params;
|
||||
|
||||
@@ -78,19 +78,19 @@ export class IamController {
|
||||
await this.#iamService.deleteRole(id);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Failed to delete role.' });
|
||||
res.status(500).json({ message: req.t('iam.failedToDeleteRole') });
|
||||
}
|
||||
};
|
||||
|
||||
public updateRole = async (req: Request, res: Response) => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
const { id } = req.params;
|
||||
const { name, policies } = req.body;
|
||||
|
||||
if (!name && !policies) {
|
||||
res.status(400).json({ message: 'Missing fields to update: name or policies.' });
|
||||
res.status(400).json({ message: req.t('iam.missingUpdateFields') });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export class IamController {
|
||||
for (const statement of policies) {
|
||||
const { valid, reason } = PolicyValidator.isValid(statement as CaslPolicy);
|
||||
if (!valid) {
|
||||
res.status(400).json({ message: `Invalid policy statement: ${reason}` });
|
||||
res.status(400).json({ message: `${req.t('iam.invalidPolicy')} ${reason}` });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export class IamController {
|
||||
const role = await this.#iamService.updateRole(id, { name, policies });
|
||||
res.status(200).json(role);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Failed to update role.' });
|
||||
res.status(500).json({ message: req.t('iam.failedToUpdateRole') });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ export class IngestionController {
|
||||
|
||||
public create = async (req: Request, res: Response): Promise<Response> => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
try {
|
||||
const dto: CreateIngestionSourceDto = req.body;
|
||||
const userId = req.user?.sub;
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: 'Unauthorized' });
|
||||
return res.status(401).json({ message: req.t('errors.unauthorized') });
|
||||
}
|
||||
const newSource = await IngestionService.create(dto, userId);
|
||||
const safeSource = this.toSafeIngestionSource(newSource);
|
||||
@@ -38,8 +38,7 @@ export class IngestionController {
|
||||
logger.error({ err: error }, 'Create ingestion source error');
|
||||
// Return a 400 Bad Request for connection errors
|
||||
return res.status(400).json({
|
||||
message:
|
||||
error.message || 'Failed to create ingestion source due to a connection error.',
|
||||
message: error.message || req.t('ingestion.failedToCreate'),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -48,14 +47,14 @@ export class IngestionController {
|
||||
try {
|
||||
const userId = req.user?.sub;
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: 'Unauthorized' });
|
||||
return res.status(401).json({ message: req.t('errors.unauthorized') });
|
||||
}
|
||||
const sources = await IngestionService.findAll(userId);
|
||||
const safeSources = sources.map(this.toSafeIngestionSource);
|
||||
return res.status(200).json(safeSources);
|
||||
} catch (error) {
|
||||
console.error('Find all ingestion sources error:', error);
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,15 +67,15 @@ export class IngestionController {
|
||||
} catch (error) {
|
||||
console.error(`Find ingestion source by id ${req.params.id} error:`, error);
|
||||
if (error instanceof Error && error.message === 'Ingestion source not found') {
|
||||
return res.status(404).json({ message: error.message });
|
||||
return res.status(404).json({ message: req.t('ingestion.notFound') });
|
||||
}
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
public update = async (req: Request, res: Response): Promise<Response> => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
try {
|
||||
const { id } = req.params;
|
||||
@@ -87,15 +86,15 @@ export class IngestionController {
|
||||
} catch (error) {
|
||||
console.error(`Update ingestion source ${req.params.id} error:`, error);
|
||||
if (error instanceof Error && error.message === 'Ingestion source not found') {
|
||||
return res.status(404).json({ message: error.message });
|
||||
return res.status(404).json({ message: req.t('ingestion.notFound') });
|
||||
}
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
public delete = async (req: Request, res: Response): Promise<Response> => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
try {
|
||||
const { id } = req.params;
|
||||
@@ -104,32 +103,32 @@ export class IngestionController {
|
||||
} catch (error) {
|
||||
console.error(`Delete ingestion source ${req.params.id} error:`, error);
|
||||
if (error instanceof Error && error.message === 'Ingestion source not found') {
|
||||
return res.status(404).json({ message: error.message });
|
||||
return res.status(404).json({ message: req.t('ingestion.notFound') });
|
||||
}
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
public triggerInitialImport = async (req: Request, res: Response): Promise<Response> => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await IngestionService.triggerInitialImport(id);
|
||||
return res.status(202).json({ message: 'Initial import triggered successfully.' });
|
||||
return res.status(202).json({ message: req.t('ingestion.initialImportTriggered') });
|
||||
} catch (error) {
|
||||
console.error(`Trigger initial import for ${req.params.id} error:`, error);
|
||||
if (error instanceof Error && error.message === 'Ingestion source not found') {
|
||||
return res.status(404).json({ message: error.message });
|
||||
return res.status(404).json({ message: req.t('ingestion.notFound') });
|
||||
}
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
public pause = async (req: Request, res: Response): Promise<Response> => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
try {
|
||||
const { id } = req.params;
|
||||
@@ -139,26 +138,26 @@ export class IngestionController {
|
||||
} catch (error) {
|
||||
console.error(`Pause ingestion source ${req.params.id} error:`, error);
|
||||
if (error instanceof Error && error.message === 'Ingestion source not found') {
|
||||
return res.status(404).json({ message: error.message });
|
||||
return res.status(404).json({ message: req.t('ingestion.notFound') });
|
||||
}
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
|
||||
public triggerForceSync = async (req: Request, res: Response): Promise<Response> => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await IngestionService.triggerForceSync(id);
|
||||
return res.status(202).json({ message: 'Force sync triggered successfully.' });
|
||||
return res.status(202).json({ message: req.t('ingestion.forceSyncTriggered') });
|
||||
} catch (error) {
|
||||
console.error(`Trigger force sync for ${req.params.id} error:`, error);
|
||||
if (error instanceof Error && error.message === 'Ingestion source not found') {
|
||||
return res.status(404).json({ message: error.message });
|
||||
return res.status(404).json({ message: req.t('ingestion.notFound') });
|
||||
}
|
||||
return res.status(500).json({ message: 'An internal server error occurred' });
|
||||
return res.status(500).json({ message: req.t('errors.internalServerError') });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ export class SearchController {
|
||||
const userId = req.user?.sub;
|
||||
|
||||
if (!userId) {
|
||||
res.status(401).json({ message: 'Unauthorized' });
|
||||
res.status(401).json({ message: req.t('errors.unauthorized') });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keywords) {
|
||||
res.status(400).json({ message: 'Keywords are required' });
|
||||
res.status(400).json({ message: req.t('search.keywordsRequired') });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class SearchController {
|
||||
|
||||
res.status(200).json(results);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'An unknown error occurred';
|
||||
const message = error instanceof Error ? error.message : req.t('errors.unknown');
|
||||
res.status(500).json({ message });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,22 +4,22 @@ import { SettingsService } from '../../services/SettingsService';
|
||||
const settingsService = new SettingsService();
|
||||
|
||||
export const getSettings = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const settings = await settingsService.getSettings();
|
||||
res.status(200).json(settings);
|
||||
} catch (error) {
|
||||
// A more specific error could be logged here
|
||||
res.status(500).json({ message: 'Failed to retrieve settings' });
|
||||
}
|
||||
try {
|
||||
const settings = await settingsService.getSettings();
|
||||
res.status(200).json(settings);
|
||||
} catch (error) {
|
||||
// A more specific error could be logged here
|
||||
res.status(500).json({ message: req.t('settings.failedToRetrieve') });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateSettings = async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Basic validation can be performed here if necessary
|
||||
const updatedSettings = await settingsService.updateSettings(req.body);
|
||||
res.status(200).json(updatedSettings);
|
||||
} catch (error) {
|
||||
// A more specific error could be logged here
|
||||
res.status(500).json({ message: 'Failed to update settings' });
|
||||
}
|
||||
try {
|
||||
// Basic validation can be performed here if necessary
|
||||
const updatedSettings = await settingsService.updateSettings(req.body);
|
||||
res.status(200).json(updatedSettings);
|
||||
} catch (error) {
|
||||
// A more specific error could be logged here
|
||||
res.status(500).json({ message: req.t('settings.failedToUpdate') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export class StorageController {
|
||||
const unsafePath = req.query.path as string;
|
||||
|
||||
if (!unsafePath) {
|
||||
res.status(400).send('File path is required');
|
||||
res.status(400).send(req.t('storage.filePathRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class StorageController {
|
||||
const fullPath = path.join(basePath, normalizedPath);
|
||||
|
||||
if (!fullPath.startsWith(basePath)) {
|
||||
res.status(400).send('Invalid file path');
|
||||
res.status(400).send(req.t('storage.invalidFilePath'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class StorageController {
|
||||
try {
|
||||
const fileExists = await this.storageService.exists(safePath);
|
||||
if (!fileExists) {
|
||||
res.status(404).send('File not found');
|
||||
res.status(404).send(req.t('storage.fileNotFound'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export class StorageController {
|
||||
fileStream.pipe(res);
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
res.status(500).send('Error downloading file');
|
||||
res.status(500).send(req.t('storage.downloadError'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@ export const getUsers = async (req: Request, res: Response) => {
|
||||
export const getUser = async (req: Request, res: Response) => {
|
||||
const user = await userService.findById(req.params.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
return res.status(404).json({ message: req.t('user.notFound') });
|
||||
}
|
||||
res.json(user);
|
||||
};
|
||||
|
||||
export const createUser = async (req: Request, res: Response) => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
const { email, first_name, last_name, password, roleId } = req.body;
|
||||
|
||||
@@ -35,7 +35,7 @@ export const createUser = async (req: Request, res: Response) => {
|
||||
|
||||
export const updateUser = async (req: Request, res: Response) => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
const { email, first_name, last_name, roleId } = req.body;
|
||||
const updatedUser = await userService.updateUser(
|
||||
@@ -44,21 +44,21 @@ export const updateUser = async (req: Request, res: Response) => {
|
||||
roleId
|
||||
);
|
||||
if (!updatedUser) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
return res.status(404).json({ message: req.t('user.notFound') });
|
||||
}
|
||||
res.json(updatedUser);
|
||||
};
|
||||
|
||||
export const deleteUser = async (req: Request, res: Response) => {
|
||||
if (config.app.isDemo) {
|
||||
return res.status(403).json({ message: 'This operation is not allowed in demo mode.' });
|
||||
return res.status(403).json({ message: req.t('errors.demoMode') });
|
||||
}
|
||||
const userCountResult = await db.select({ count: sql<number>`count(*)` }).from(schema.users);
|
||||
console.log('iusercount,', userCountResult[0].count);
|
||||
|
||||
const isOnlyUser = Number(userCountResult[0].count) === 1;
|
||||
if (isOnlyUser) {
|
||||
return res.status(400).json({
|
||||
message: 'You are trying to delete the only user in the database, this is not allowed.',
|
||||
message: req.t('user.cannotDeleteOnlyUser'),
|
||||
});
|
||||
}
|
||||
await userService.deleteUser(req.params.id);
|
||||
|
||||
@@ -25,9 +25,11 @@ export const requirePermission = (
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
const message = rejectMessage
|
||||
? req.t(rejectMessage)
|
||||
: req.t('errors.noPermissionToAction');
|
||||
return res.status(403).json({
|
||||
message:
|
||||
rejectMessage || `You don't have the permission to perform the current action.`,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,47 +11,27 @@ export const createDashboardRouter = (authService: AuthService): Router => {
|
||||
|
||||
router.get(
|
||||
'/stats',
|
||||
requirePermission(
|
||||
'read',
|
||||
'dashboard',
|
||||
'You need the dashboard read permission to view dashboard stats.'
|
||||
),
|
||||
requirePermission('read', 'dashboard', 'dashboard.permissionRequired'),
|
||||
dashboardController.getStats
|
||||
);
|
||||
router.get(
|
||||
'/ingestion-history',
|
||||
requirePermission(
|
||||
'read',
|
||||
'dashboard',
|
||||
'You need the dashboard read permission to view dashboard data.'
|
||||
),
|
||||
requirePermission('read', 'dashboard', 'dashboard.permissionRequired'),
|
||||
dashboardController.getIngestionHistory
|
||||
);
|
||||
router.get(
|
||||
'/ingestion-sources',
|
||||
requirePermission(
|
||||
'read',
|
||||
'dashboard',
|
||||
'You need the dashboard read permission to view dashboard data.'
|
||||
),
|
||||
requirePermission('read', 'dashboard', 'dashboard.permissionRequired'),
|
||||
dashboardController.getIngestionSources
|
||||
);
|
||||
router.get(
|
||||
'/recent-syncs',
|
||||
requirePermission(
|
||||
'read',
|
||||
'dashboard',
|
||||
'You need the dashboard read permission to view dashboard data.'
|
||||
),
|
||||
requirePermission('read', 'dashboard', 'dashboard.permissionRequired'),
|
||||
dashboardController.getRecentSyncs
|
||||
);
|
||||
router.get(
|
||||
'/indexed-insights',
|
||||
requirePermission(
|
||||
'read',
|
||||
'dashboard',
|
||||
'You need the dashboard read permission to view dashboard data.'
|
||||
),
|
||||
requirePermission('read', 'dashboard', 'dashboard.permissionRequired'),
|
||||
dashboardController.getIndexedInsights
|
||||
);
|
||||
|
||||
|
||||
@@ -23,19 +23,19 @@ export const createIamRouter = (iamController: IamController, authService: AuthS
|
||||
*/
|
||||
router.post(
|
||||
'/roles',
|
||||
requirePermission('manage', 'all', 'Super Admin role is required to manage roles.'),
|
||||
requirePermission('manage', 'all', 'iam.requiresSuperAdminRole'),
|
||||
iamController.createRole
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/roles/:id',
|
||||
requirePermission('manage', 'all', 'Super Admin role is required to manage roles.'),
|
||||
requirePermission('manage', 'all', 'iam.requiresSuperAdminRole'),
|
||||
iamController.deleteRole
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/roles/:id',
|
||||
requirePermission('manage', 'all', 'Super Admin role is required to manage roles.'),
|
||||
requirePermission('manage', 'all', 'iam.requiresSuperAdminRole'),
|
||||
iamController.updateRole
|
||||
);
|
||||
return router;
|
||||
|
||||
@@ -5,18 +5,21 @@ import { requirePermission } from '../middleware/requirePermission';
|
||||
import { AuthService } from '../../services/AuthService';
|
||||
|
||||
export const createSettingsRouter = (authService: AuthService): Router => {
|
||||
const router = Router();
|
||||
const router = Router();
|
||||
|
||||
// Public route to get non-sensitive settings. settings read should not be scoped with a permission because all end users need the settings data in the frontend. However, for sensitive settings data, we need to add a new permission subject to limit access. So this route should only expose non-sensitive settings data.
|
||||
router.get('/', settingsController.getSettings);
|
||||
// Public route to get non-sensitive settings. settings read should not be scoped with a permission because all end users need the settings data in the frontend. However, for sensitive settings data, we need to add a new permission subject to limit access. So this route should only expose non-sensitive settings data.
|
||||
/**
|
||||
* @returns SystemSettings
|
||||
*/
|
||||
router.get('/', settingsController.getSettings);
|
||||
|
||||
// Protected route to update settings
|
||||
router.put(
|
||||
'/',
|
||||
requireAuth(authService),
|
||||
requirePermission('manage', 'settings', 'You do not have permission to update system settings.'),
|
||||
settingsController.updateSettings
|
||||
);
|
||||
// Protected route to update settings
|
||||
router.put(
|
||||
'/',
|
||||
requireAuth(authService),
|
||||
requirePermission('manage', 'settings', 'settings.noPermissionToUpdate'),
|
||||
settingsController.updateSettings
|
||||
);
|
||||
|
||||
return router;
|
||||
return router;
|
||||
};
|
||||
|
||||
@@ -18,19 +18,19 @@ export const createUserRouter = (authService: AuthService): Router => {
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
requirePermission('manage', 'all', 'Super Admin role is required to manage users.'),
|
||||
requirePermission('manage', 'all', 'user.requiresSuperAdminRole'),
|
||||
userController.createUser
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/:id',
|
||||
requirePermission('manage', 'all', 'Super Admin role is required to manage users.'),
|
||||
requirePermission('manage', 'all', 'user.requiresSuperAdminRole'),
|
||||
userController.updateUser
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:id',
|
||||
requirePermission('manage', 'all', 'Super Admin role is required to manage users.'),
|
||||
requirePermission('manage', 'all', 'user.requiresSuperAdminRole'),
|
||||
userController.deleteUser
|
||||
);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,132 +1,132 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1752225352591,
|
||||
"tag": "0000_amusing_namora",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1752326803882,
|
||||
"tag": "0001_odd_night_thrasher",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1752332648392,
|
||||
"tag": "0002_lethal_quentin_quire",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1752332967084,
|
||||
"tag": "0003_petite_wrecker",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1752606108876,
|
||||
"tag": "0004_sleepy_paper_doll",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1752606327253,
|
||||
"tag": "0005_chunky_sue_storm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1753112018514,
|
||||
"tag": "0006_majestic_caretaker",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1753190159356,
|
||||
"tag": "0007_handy_archangel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "7",
|
||||
"when": 1753370737317,
|
||||
"tag": "0008_eminent_the_spike",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1754337938241,
|
||||
"tag": "0009_late_lenny_balinger",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "7",
|
||||
"when": 1754420780849,
|
||||
"tag": "0010_perpetual_lightspeed",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "7",
|
||||
"when": 1754422064158,
|
||||
"tag": "0011_tan_blackheart",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "7",
|
||||
"when": 1754476962901,
|
||||
"tag": "0012_warm_the_stranger",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "7",
|
||||
"when": 1754659373517,
|
||||
"tag": "0013_classy_talkback",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "7",
|
||||
"when": 1754831765718,
|
||||
"tag": "0014_foamy_vapor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "7",
|
||||
"when": 1755443936046,
|
||||
"tag": "0015_wakeful_norman_osborn",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "7",
|
||||
"when": 1755780572342,
|
||||
"tag": "0016_lonely_mariko_yashida",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 17,
|
||||
"version": "7",
|
||||
"when": 1755961566627,
|
||||
"tag": "0017_tranquil_shooting_star",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1752225352591,
|
||||
"tag": "0000_amusing_namora",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1752326803882,
|
||||
"tag": "0001_odd_night_thrasher",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1752332648392,
|
||||
"tag": "0002_lethal_quentin_quire",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1752332967084,
|
||||
"tag": "0003_petite_wrecker",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1752606108876,
|
||||
"tag": "0004_sleepy_paper_doll",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1752606327253,
|
||||
"tag": "0005_chunky_sue_storm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1753112018514,
|
||||
"tag": "0006_majestic_caretaker",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1753190159356,
|
||||
"tag": "0007_handy_archangel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "7",
|
||||
"when": 1753370737317,
|
||||
"tag": "0008_eminent_the_spike",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1754337938241,
|
||||
"tag": "0009_late_lenny_balinger",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "7",
|
||||
"when": 1754420780849,
|
||||
"tag": "0010_perpetual_lightspeed",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "7",
|
||||
"when": 1754422064158,
|
||||
"tag": "0011_tan_blackheart",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "7",
|
||||
"when": 1754476962901,
|
||||
"tag": "0012_warm_the_stranger",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "7",
|
||||
"when": 1754659373517,
|
||||
"tag": "0013_classy_talkback",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "7",
|
||||
"when": 1754831765718,
|
||||
"tag": "0014_foamy_vapor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "7",
|
||||
"when": 1755443936046,
|
||||
"tag": "0015_wakeful_norman_osborn",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "7",
|
||||
"when": 1755780572342,
|
||||
"tag": "0016_lonely_mariko_yashida",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 17,
|
||||
"version": "7",
|
||||
"when": 1755961566627,
|
||||
"tag": "0017_tranquil_shooting_star",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@ export * from './schema/compliance';
|
||||
export * from './schema/custodians';
|
||||
export * from './schema/ingestion-sources';
|
||||
export * from './schema/users';
|
||||
export * from './schema/system-settings'
|
||||
export * from './schema/system-settings';
|
||||
|
||||
@@ -2,6 +2,6 @@ import { pgTable, serial, jsonb } from 'drizzle-orm/pg-core';
|
||||
import type { SystemSettings } from '@open-archiver/types';
|
||||
|
||||
export const systemSettings = pgTable('system_settings', {
|
||||
id: serial('id').primaryKey(),
|
||||
config: jsonb('config').$type<SystemSettings>().notNull(),
|
||||
id: serial('id').primaryKey(),
|
||||
config: jsonb('config').$type<SystemSettings>().notNull(),
|
||||
});
|
||||
|
||||
@@ -22,6 +22,12 @@ import { UserService } from './services/UserService';
|
||||
import { IamService } from './services/IamService';
|
||||
import { StorageService } from './services/StorageService';
|
||||
import { SearchService } from './services/SearchService';
|
||||
import { SettingsService } from './services/SettingsService';
|
||||
import i18next from 'i18next';
|
||||
import FsBackend from 'i18next-fs-backend';
|
||||
import i18nextMiddleware from 'i18next-http-middleware';
|
||||
import path from 'path';
|
||||
import { logger } from './config/logger';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
@@ -35,6 +41,22 @@ if (!PORT_BACKEND || !JWT_SECRET || !JWT_EXPIRES_IN) {
|
||||
);
|
||||
}
|
||||
|
||||
// --- i18next Initialization ---
|
||||
const initializeI18next = async () => {
|
||||
const systemSettings = await settingsService.getSettings();
|
||||
const defaultLanguage = systemSettings?.language || 'en';
|
||||
logger.info({ language: defaultLanguage }, 'Default language');
|
||||
await i18next.use(FsBackend).init({
|
||||
lng: defaultLanguage,
|
||||
fallbackLng: defaultLanguage,
|
||||
ns: ['translation'],
|
||||
defaultNS: 'translation',
|
||||
backend: {
|
||||
loadPath: path.resolve(__dirname, './locales/{{lng}}/{{ns}}.json'),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// --- Dependency Injection Setup ---
|
||||
|
||||
const userService = new UserService();
|
||||
@@ -48,6 +70,7 @@ const searchService = new SearchService();
|
||||
const searchController = new SearchController();
|
||||
const iamService = new IamService();
|
||||
const iamController = new IamController(iamService);
|
||||
const settingsService = new SettingsService();
|
||||
|
||||
// --- Express App Initialization ---
|
||||
const app = express();
|
||||
@@ -70,6 +93,9 @@ app.use('/v1/upload', uploadRouter);
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// i18n middleware
|
||||
app.use(i18nextMiddleware.handle(i18next));
|
||||
|
||||
app.use('/v1/auth', authRouter);
|
||||
app.use('/v1/iam', iamRouter);
|
||||
app.use('/v1/ingestion-sources', ingestionRouter);
|
||||
@@ -95,15 +121,19 @@ app.get('/', (req, res) => {
|
||||
// --- Server Start ---
|
||||
const startServer = async () => {
|
||||
try {
|
||||
// Initialize i18next
|
||||
await initializeI18next();
|
||||
logger.info({}, 'i18next initialized');
|
||||
|
||||
// Configure the Meilisearch index on startup
|
||||
console.log('Configuring email index...');
|
||||
logger.info({}, 'Configuring email index...');
|
||||
await searchService.configureEmailIndex();
|
||||
|
||||
app.listen(PORT_BACKEND, () => {
|
||||
console.log(`Backend listening at http://localhost:${PORT_BACKEND}`);
|
||||
logger.info({}, `Backend listening at http://localhost:${PORT_BACKEND}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to start the server:', error);
|
||||
logger.error({ error }, 'Failed to start the server:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
62
packages/backend/src/locales/de/translation.json
Normal file
62
packages/backend/src/locales/de/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "E-Mail, Passwort und Name sind erforderlich",
|
||||
"alreadyCompleted": "Die Einrichtung wurde bereits abgeschlossen."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "E-Mail und Passwort sind erforderlich",
|
||||
"invalidCredentials": "Ungültige Anmeldeinformationen"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Ein interner Serverfehler ist aufgetreten",
|
||||
"demoMode": "Dieser Vorgang ist im Demo-Modus nicht zulässig.",
|
||||
"unauthorized": "Unbefugt",
|
||||
"unknown": "Ein unbekannter Fehler ist aufgetreten",
|
||||
"noPermissionToAction": "Sie haben keine Berechtigung, die aktuelle Aktion auszuführen."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Benutzer nicht gefunden",
|
||||
"cannotDeleteOnlyUser": "Sie versuchen, den einzigen Benutzer in der Datenbank zu löschen, dies ist nicht gestattet.",
|
||||
"requiresSuperAdminRole": "Die Rolle des Super-Admins ist erforderlich, um Benutzer zu verwalten."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Rollen konnten nicht abgerufen werden.",
|
||||
"roleNotFound": "Rolle nicht gefunden.",
|
||||
"failedToGetRole": "Rolle konnte nicht abgerufen werden.",
|
||||
"missingRoleFields": "Fehlende erforderliche Felder: Name und Richtlinie.",
|
||||
"invalidPolicy": "Ungültige Richtlinienanweisung:",
|
||||
"failedToCreateRole": "Rolle konnte nicht erstellt werden.",
|
||||
"failedToDeleteRole": "Rolle konnte nicht gelöscht werden.",
|
||||
"missingUpdateFields": "Fehlende Felder zum Aktualisieren: Name oder Richtlinien.",
|
||||
"failedToUpdateRole": "Rolle konnte nicht aktualisiert werden.",
|
||||
"requiresSuperAdminRole": "Die Rolle des Super-Admins ist erforderlich, um Rollen zu verwalten."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Einstellungen konnten nicht abgerufen werden",
|
||||
"failedToUpdate": "Einstellungen konnten nicht aktualisiert werden",
|
||||
"noPermissionToUpdate": "Sie haben keine Berechtigung, die Systemeinstellungen zu aktualisieren."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "Sie benötigen die Leseberechtigung für das Dashboard, um Dashboard-Daten anzuzeigen."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Die Erfassungsquelle konnte aufgrund eines Verbindungsfehlers nicht erstellt werden.",
|
||||
"notFound": "Erfassungsquelle nicht gefunden",
|
||||
"initialImportTriggered": "Erstimport erfolgreich ausgelöst.",
|
||||
"forceSyncTriggered": "Erzwungene Synchronisierung erfolgreich ausgelöst."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "Archivierte E-Mail nicht gefunden"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Schlüsselwörter sind erforderlich"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Dateipfad ist erforderlich",
|
||||
"invalidFilePath": "Ungültiger Dateipfad",
|
||||
"fileNotFound": "Datei nicht gefunden",
|
||||
"downloadError": "Fehler beim Herunterladen der Datei"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/el/translation.json
Normal file
62
packages/backend/src/locales/el/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "Το email, ο κωδικός πρόσβασης και το όνομα είναι υποχρεωτικά",
|
||||
"alreadyCompleted": "Η εγκατάσταση έχει ήδη ολοκληρωθεί."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "Το email και ο κωδικός πρόσβασης είναι υποχρεωτικά",
|
||||
"invalidCredentials": "Μη έγκυρα διαπιστευτήρια"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Παρουσιάστηκε ένα εσωτερικό σφάλμα διακομιστή",
|
||||
"demoMode": "Αυτή η λειτουργία δεν επιτρέπεται σε λειτουργία επίδειξης.",
|
||||
"unauthorized": "Μη εξουσιοδοτημένο",
|
||||
"unknown": "Παρουσιάστηκε ένα άγνωστο σφάλμα",
|
||||
"noPermissionToAction": "Δεν έχετε την άδεια να εκτελέσετε την τρέχουσα ενέργεια."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Ο χρήστης δεν βρέθηκε",
|
||||
"cannotDeleteOnlyUser": "Προσπαθείτε να διαγράψετε τον μοναδικό χρήστη στη βάση δεδομένων, αυτό δεν επιτρέπεται.",
|
||||
"requiresSuperAdminRole": "Απαιτείται ο ρόλος του Super Admin για τη διαχείριση των χρηστών."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Η λήψη των ρόλων απέτυχε.",
|
||||
"roleNotFound": "Ο ρόλος δεν βρέθηκε.",
|
||||
"failedToGetRole": "Η λήψη του ρόλου απέτυχε.",
|
||||
"missingRoleFields": "Λείπουν τα απαιτούμενα πεδία: όνομα και πολιτική.",
|
||||
"invalidPolicy": "Μη έγκυρη δήλωση πολιτικής:",
|
||||
"failedToCreateRole": "Η δημιουργία του ρόλου απέτυχε.",
|
||||
"failedToDeleteRole": "Η διαγραφή του ρόλου απέτυχε.",
|
||||
"missingUpdateFields": "Λείπουν πεδία για ενημέρωση: όνομα ή πολιτικές.",
|
||||
"failedToUpdateRole": "Η ενημέρωση του ρόλου απέτυχε.",
|
||||
"requiresSuperAdminRole": "Απαιτείται ο ρόλος του Super Admin για τη διαχείριση των ρόλων."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Η ανάκτηση των ρυθμίσεων απέτυχε",
|
||||
"failedToUpdate": "Η ενημέρωση των ρυθμίσεων απέτυχε",
|
||||
"noPermissionToUpdate": "Δεν έχετε άδεια να ενημερώσετε τις ρυθμίσεις του συστήματος."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "Χρειάζεστε την άδεια ανάγνωσης του πίνακα ελέγχου για να δείτε τα δεδομένα του πίνακα ελέγχου."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Η δημιουργία της πηγής πρόσληψης απέτυχε λόγω σφάλματος σύνδεσης.",
|
||||
"notFound": "Η πηγή πρόσληψης δεν βρέθηκε",
|
||||
"initialImportTriggered": "Η αρχική εισαγωγή ενεργοποιήθηκε με επιτυχία.",
|
||||
"forceSyncTriggered": "Ο εξαναγκασμένος συγχρονισμός ενεργοποιήθηκε με επιτυχία."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "Το αρχειοθετημένο email δεν βρέθηκε"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Οι λέξεις-κλειδιά είναι υποχρεωτικές"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Η διαδρομή του αρχείου είναι υποχρεωτική",
|
||||
"invalidFilePath": "Μη έγκυρη διαδρομή αρχείου",
|
||||
"fileNotFound": "Το αρχείο δεν βρέθηκε",
|
||||
"downloadError": "Σφάλμα κατά τη λήψη του αρχείου"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/en/translation.json
Normal file
62
packages/backend/src/locales/en/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "Email, password, and name are required",
|
||||
"alreadyCompleted": "Setup has already been completed."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "Email and password are required",
|
||||
"invalidCredentials": "Invalid credentials"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "An internal server error occurred",
|
||||
"demoMode": "This operation is not allowed in demo mode.",
|
||||
"unauthorized": "Unauthorized",
|
||||
"unknown": "An unknown error occurred",
|
||||
"noPermissionToAction": "You don't have the permission to perform the current action."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "User not found",
|
||||
"cannotDeleteOnlyUser": "You are trying to delete the only user in the database, this is not allowed.",
|
||||
"requiresSuperAdminRole": "Super Admin role is required to manage users."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Failed to get roles.",
|
||||
"roleNotFound": "Role not found.",
|
||||
"failedToGetRole": "Failed to get role.",
|
||||
"missingRoleFields": "Missing required fields: name and policy.",
|
||||
"invalidPolicy": "Invalid policy statement:",
|
||||
"failedToCreateRole": "Failed to create role.",
|
||||
"failedToDeleteRole": "Failed to delete role.",
|
||||
"missingUpdateFields": "Missing fields to update: name or policies.",
|
||||
"failedToUpdateRole": "Failed to update role.",
|
||||
"requiresSuperAdminRole": "Super Admin role is required to manage roles."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Failed to retrieve settings",
|
||||
"failedToUpdate": "Failed to update settings",
|
||||
"noPermissionToUpdate": "You do not have permission to update system settings."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "You need the dashboard read permission to view dashboard data."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Failed to create ingestion source due to a connection error.",
|
||||
"notFound": "Ingestion source not found",
|
||||
"initialImportTriggered": "Initial import triggered successfully.",
|
||||
"forceSyncTriggered": "Force sync triggered successfully."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "Archived email not found"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Keywords are required"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "File path is required",
|
||||
"invalidFilePath": "Invalid file path",
|
||||
"fileNotFound": "File not found",
|
||||
"downloadError": "Error downloading file"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/es/translation.json
Normal file
62
packages/backend/src/locales/es/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "Se requieren correo electrónico, contraseña y nombre",
|
||||
"alreadyCompleted": "La configuración ya se ha completado."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "Se requieren correo electrónico y contraseña",
|
||||
"invalidCredentials": "Credenciales no válidas"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Ocurrió un error interno del servidor",
|
||||
"demoMode": "Esta operación no está permitida en modo de demostración.",
|
||||
"unauthorized": "No autorizado",
|
||||
"unknown": "Ocurrió un error desconocido",
|
||||
"noPermissionToAction": "No tienes permiso para realizar la acción actual."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Usuario no encontrado",
|
||||
"cannotDeleteOnlyUser": "Estás intentando eliminar al único usuario de la base de datos, esto no está permitido.",
|
||||
"requiresSuperAdminRole": "Se requiere el rol de Superadministrador para gestionar usuarios."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Error al obtener los roles.",
|
||||
"roleNotFound": "Rol no encontrado.",
|
||||
"failedToGetRole": "Error al obtener el rol.",
|
||||
"missingRoleFields": "Faltan campos obligatorios: nombre y política.",
|
||||
"invalidPolicy": "Declaración de política no válida:",
|
||||
"failedToCreateRole": "Error al crear el rol.",
|
||||
"failedToDeleteRole": "Error al eliminar el rol.",
|
||||
"missingUpdateFields": "Faltan campos para actualizar: nombre o políticas.",
|
||||
"failedToUpdateRole": "Error al actualizar el rol.",
|
||||
"requiresSuperAdminRole": "Se requiere el rol de Superadministrador para gestionar los roles."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Error al recuperar la configuración",
|
||||
"failedToUpdate": "Error al actualizar la configuración",
|
||||
"noPermissionToUpdate": "No tienes permiso para actualizar la configuración del sistema."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "Necesitas el permiso de lectura del panel de control para ver los datos del panel."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Error al crear la fuente de ingesta debido a un error de conexión.",
|
||||
"notFound": "Fuente de ingesta no encontrada",
|
||||
"initialImportTriggered": "Importación inicial activada correctamente.",
|
||||
"forceSyncTriggered": "Sincronización forzada activada correctamente."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "Correo electrónico archivado no encontrado"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Se requieren palabras clave"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Se requiere la ruta del archivo",
|
||||
"invalidFilePath": "Ruta de archivo no válida",
|
||||
"fileNotFound": "Archivo no encontrado",
|
||||
"downloadError": "Error al descargar el archivo"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/et/translation.json
Normal file
62
packages/backend/src/locales/et/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "E-post, parool ja nimi on kohustuslikud",
|
||||
"alreadyCompleted": "Seadistamine on juba lõpule viidud."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "E-post ja parool on kohustuslikud",
|
||||
"invalidCredentials": "Valed sisselogimisandmed"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Ilmnes sisemine serveriviga",
|
||||
"demoMode": "See toiming pole demorežiimis lubatud.",
|
||||
"unauthorized": "Volitamata",
|
||||
"unknown": "Ilmnes tundmatu viga",
|
||||
"noPermissionToAction": "Teil pole praeguse toimingu tegemiseks luba."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Kasutajat ei leitud",
|
||||
"cannotDeleteOnlyUser": "Püüate kustutada andmebaasi ainsat kasutajat, see pole lubatud.",
|
||||
"requiresSuperAdminRole": "Kasutajate haldamiseks on vajalik superadministraatori roll."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Rollide hankimine ebaõnnestus.",
|
||||
"roleNotFound": "Rolli ei leitud.",
|
||||
"failedToGetRole": "Rolli hankimine ebaõnnestus.",
|
||||
"missingRoleFields": "Puuduvad kohustuslikud väljad: nimi ja poliitika.",
|
||||
"invalidPolicy": "Kehtetu poliitika avaldus:",
|
||||
"failedToCreateRole": "Rolli loomine ebaõnnestus.",
|
||||
"failedToDeleteRole": "Rolli kustutamine ebaõnnestus.",
|
||||
"missingUpdateFields": "Uuendamiseks puuduvad väljad: nimi või poliitikad.",
|
||||
"failedToUpdateRole": "Rolli värskendamine ebaõnnestus.",
|
||||
"requiresSuperAdminRole": "Rollide haldamiseks on vajalik superadministraatori roll."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Seadete toomine ebaõnnestus",
|
||||
"failedToUpdate": "Seadete värskendamine ebaõnnestus",
|
||||
"noPermissionToUpdate": "Teil pole süsteemi seadete värskendamiseks luba."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "Armatuurlaua andmete vaatamiseks on teil vaja armatuurlaua lugemisluba."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Söötmeallika loomine ebaõnnestus ühenduse vea tõttu.",
|
||||
"notFound": "Söötmeallikat ei leitud",
|
||||
"initialImportTriggered": "Esialgne import käivitati edukalt.",
|
||||
"forceSyncTriggered": "Sundsünkroonimine käivitati edukalt."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "Arhiveeritud e-kirja ei leitud"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Märksõnad on kohustuslikud"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Faili tee on kohustuslik",
|
||||
"invalidFilePath": "Kehtetu faili tee",
|
||||
"fileNotFound": "Faili ei leitud",
|
||||
"downloadError": "Faili allalaadimisel ilmnes viga"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/fr/translation.json
Normal file
62
packages/backend/src/locales/fr/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "L'e-mail, le mot de passe et le nom sont requis",
|
||||
"alreadyCompleted": "La configuration est déjà terminée."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "L'e-mail et le mot de passe sont requis",
|
||||
"invalidCredentials": "Identifiants invalides"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Une erreur interne du serveur s'est produite",
|
||||
"demoMode": "Cette opération n'est pas autorisée en mode démo.",
|
||||
"unauthorized": "Non autorisé",
|
||||
"unknown": "Une erreur inconnue s'est produite",
|
||||
"noPermissionToAction": "Vous n'avez pas la permission d'effectuer l'action en cours."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Utilisateur non trouvé",
|
||||
"cannotDeleteOnlyUser": "Vous essayez de supprimer le seul utilisateur de la base de données, ce n'est pas autorisé.",
|
||||
"requiresSuperAdminRole": "Le rôle de Super Admin est requis pour gérer les utilisateurs."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Échec de la récupération des rôles.",
|
||||
"roleNotFound": "Rôle non trouvé.",
|
||||
"failedToGetRole": "Échec de la récupération du rôle.",
|
||||
"missingRoleFields": "Champs obligatoires manquants : nom et politique.",
|
||||
"invalidPolicy": "Déclaration de politique invalide :",
|
||||
"failedToCreateRole": "Échec de la création du rôle.",
|
||||
"failedToDeleteRole": "Échec de la suppression du rôle.",
|
||||
"missingUpdateFields": "Champs à mettre à jour manquants : nom ou politiques.",
|
||||
"failedToUpdateRole": "Échec de la mise à jour du rôle.",
|
||||
"requiresSuperAdminRole": "Le rôle de Super Admin est requis pour gérer les rôles."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Échec de la récupération des paramètres",
|
||||
"failedToUpdate": "Échec de la mise à jour des paramètres",
|
||||
"noPermissionToUpdate": "Vous n'avez pas la permission de mettre à jour les paramètres système."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "Vous avez besoin de la permission de lecture du tableau de bord pour afficher les données du tableau de bord."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Échec de la création de la source d'ingestion en raison d'une erreur de connexion.",
|
||||
"notFound": "Source d'ingestion non trouvée",
|
||||
"initialImportTriggered": "Importation initiale déclenchée avec succès.",
|
||||
"forceSyncTriggered": "Synchronisation forcée déclenchée avec succès."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "E-mail archivé non trouvé"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Des mots-clés sont requis"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Le chemin du fichier est requis",
|
||||
"invalidFilePath": "Chemin de fichier invalide",
|
||||
"fileNotFound": "Fichier non trouvé",
|
||||
"downloadError": "Erreur lors du téléchargement du fichier"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/it/translation.json
Normal file
62
packages/backend/src/locales/it/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "Email, password e nome sono obbligatori",
|
||||
"alreadyCompleted": "La configurazione è già stata completata."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "Email and password are required",
|
||||
"invalidCredentials": "Credenziali non valide"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Si è verificato un errore interno del server",
|
||||
"demoMode": "Questa operazione non è consentita in modalità demo.",
|
||||
"unauthorized": "Non autorizzato",
|
||||
"unknown": "Si è verificato un errore sconosciuto",
|
||||
"noPermissionToAction": "Non hai il permesso di eseguire l'azione corrente."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Utente non trovato",
|
||||
"cannotDeleteOnlyUser": "Stai tentando di eliminare l'unico utente nel database, ciò non è consentito.",
|
||||
"requiresSuperAdminRole": "È richiesto il ruolo di Super Admin per gestire gli utenti."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Impossibile ottenere i ruoli.",
|
||||
"roleNotFound": "Ruolo non trovato.",
|
||||
"failedToGetRole": "Impossibile ottenere il ruolo.",
|
||||
"missingRoleFields": "Campi obbligatori mancanti: nome e policy.",
|
||||
"invalidPolicy": "Dichiarazione di policy non valida:",
|
||||
"failedToCreateRole": "Impossibile creare il ruolo.",
|
||||
"failedToDeleteRole": "Impossibile eliminare il ruolo.",
|
||||
"missingUpdateFields": "Campi da aggiornare mancanti: nome o policy.",
|
||||
"failedToUpdateRole": "Impossibile aggiornare il ruolo.",
|
||||
"requiresSuperAdminRole": "È richiesto il ruolo di Super Admin per gestire i ruoli."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Impossibile recuperare le impostazioni",
|
||||
"failedToUpdate": "Impossibile aggiornare le impostazioni",
|
||||
"noPermissionToUpdate": "Non hai il permesso di aggiornare le impostazioni di sistema."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "È necessaria l'autorizzazione di lettura della dashboard per visualizzare i dati della dashboard."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Impossibile creare l'origine di inserimento a causa di un errore di connessione.",
|
||||
"notFound": "Origine di inserimento non trovata",
|
||||
"initialImportTriggered": "Importazione iniziale attivata con successo.",
|
||||
"forceSyncTriggered": "Sincronizzazione forzata attivata con successo."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "Email archiviata non trovata"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Le parole chiave sono obbligatorie"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Il percorso del file è obbligatorio",
|
||||
"invalidFilePath": "Percorso del file non valido",
|
||||
"fileNotFound": "File non trovato",
|
||||
"downloadError": "Errore durante il download del file"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/ja/translation.json
Normal file
62
packages/backend/src/locales/ja/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "メールアドレス、パスワード、名前は必須です",
|
||||
"alreadyCompleted": "セットアップはすでに完了しています。"
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "メールアドレスとパスワードは必須です",
|
||||
"invalidCredentials": "無効な認証情報"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "内部サーバーエラーが発生しました",
|
||||
"demoMode": "この操作はデモモードでは許可されていません。",
|
||||
"unauthorized": "不正なアクセス",
|
||||
"unknown": "不明なエラーが発生しました",
|
||||
"noPermissionToAction": "現在の操作を実行する権限がありません。"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "ユーザーが見つかりません",
|
||||
"cannotDeleteOnlyUser": "データベース内の唯一のユーザーを削除しようとしていますが、これは許可されていません。",
|
||||
"requiresSuperAdminRole": "ユーザーを管理するには、スーパー管理者ロールが必要です。"
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "役割の取得に失敗しました。",
|
||||
"roleNotFound": "役割が見つかりません。",
|
||||
"failedToGetRole": "役割の取得に失敗しました。",
|
||||
"missingRoleFields": "必須フィールドがありません:名前とポリシー。",
|
||||
"invalidPolicy": "無効なポリシーステートメント:",
|
||||
"failedToCreateRole": "役割の作成に失敗しました。",
|
||||
"failedToDeleteRole": "役割の削除に失敗しました。",
|
||||
"missingUpdateFields": "更新するフィールドがありません:名前またはポリシー。",
|
||||
"failedToUpdateRole": "役割の更新に失敗しました。",
|
||||
"requiresSuperAdminRole": "役割を管理するには、スーパー管理者ロールが必要です。"
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "設定の取得に失敗しました",
|
||||
"failedToUpdate": "設定の更新に失敗しました",
|
||||
"noPermissionToUpdate": "システム設定を更新する権限がありません。"
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "ダッシュボードのデータを表示するには、ダッシュボードの読み取り権限が必要です。"
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "接続エラーのため、取り込みソースの作成に失敗しました。",
|
||||
"notFound": "取り込みソースが見つかりません",
|
||||
"initialImportTriggered": "初期インポートが正常にトリガーされました。",
|
||||
"forceSyncTriggered": "強制同期が正常にトリガーされました。"
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "アーカイブされたメールが見つかりません"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "キーワードは必須です"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "ファイルパスは必須です",
|
||||
"invalidFilePath": "無効なファイルパス",
|
||||
"fileNotFound": "ファイルが見つかりません",
|
||||
"downloadError": "ファイルのダウンロード中にエラーが発生しました"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/nl/translation.json
Normal file
62
packages/backend/src/locales/nl/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "E-mail, wachtwoord en naam zijn verplicht",
|
||||
"alreadyCompleted": "De installatie is al voltooid."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "E-mail en wachtwoord zijn verplicht",
|
||||
"invalidCredentials": "Ongeldige inloggegevens"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Er is een interne serverfout opgetreden",
|
||||
"demoMode": "Deze bewerking is niet toegestaan in de demomodus.",
|
||||
"unauthorized": "Ongeautoriseerd",
|
||||
"unknown": "Er is een onbekende fout opgetreden",
|
||||
"noPermissionToAction": "U heeft geen toestemming om de huidige actie uit te voeren."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Gebruiker niet gevonden",
|
||||
"cannotDeleteOnlyUser": "U probeert de enige gebruiker in de database te verwijderen, dit is niet toegestaan.",
|
||||
"requiresSuperAdminRole": "De rol van Super Admin is vereist om gebruikers te beheren."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Kan rollen niet ophalen.",
|
||||
"roleNotFound": "Rol niet gevonden.",
|
||||
"failedToGetRole": "Kan rol niet ophalen.",
|
||||
"missingRoleFields": "Ontbrekende verplichte velden: naam en beleid.",
|
||||
"invalidPolicy": "Ongeldige beleidsverklaring:",
|
||||
"failedToCreateRole": "Kan rol niet aanmaken.",
|
||||
"failedToDeleteRole": "Kan rol niet verwijderen.",
|
||||
"missingUpdateFields": "Ontbrekende velden om bij te werken: naam of beleid.",
|
||||
"failedToUpdateRole": "Kan rol niet bijwerken.",
|
||||
"requiresSuperAdminRole": "De rol van Super Admin is vereist om rollen te beheren."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Kan instellingen niet ophalen",
|
||||
"failedToUpdate": "Kan instellingen niet bijwerken",
|
||||
"noPermissionToUpdate": "U heeft geen toestemming om de systeeminstellingen bij te werken."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "U heeft de leesrechten voor het dashboard nodig om dashboardgegevens te bekijken."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Kan de opnamebron niet aanmaken vanwege een verbindingsfout.",
|
||||
"notFound": "Opnamebron niet gevonden",
|
||||
"initialImportTriggered": "Initiële import succesvol geactiveerd.",
|
||||
"forceSyncTriggered": "Geforceerde synchronisatie succesvol geactiveerd."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "Gearchiveerde e-mail niet gevonden"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Trefwoorden zijn verplicht"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Bestandspad is verplicht",
|
||||
"invalidFilePath": "Ongeldig bestandspad",
|
||||
"fileNotFound": "Bestand niet gevonden",
|
||||
"downloadError": "Fout bij het downloaden van het bestand"
|
||||
}
|
||||
}
|
||||
62
packages/backend/src/locales/pt/translation.json
Normal file
62
packages/backend/src/locales/pt/translation.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "E-mail, senha e nome são obrigatórios",
|
||||
"alreadyCompleted": "A configuração já foi concluída."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "E-mail e senha são obrigatórios",
|
||||
"invalidCredentials": "Credenciais inválidas"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Ocorreu um erro interno do servidor",
|
||||
"demoMode": "Esta operação não é permitida no modo de demonstração.",
|
||||
"unauthorized": "Não autorizado",
|
||||
"unknown": "Ocorreu um erro desconhecido",
|
||||
"noPermissionToAction": "Você não tem permissão para executar a ação atual."
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Usuário não encontrado",
|
||||
"cannotDeleteOnlyUser": "Você está tentando excluir o único usuário no banco de dados, isso não é permitido.",
|
||||
"requiresSuperAdminRole": "A função de Super Admin é necessária para gerenciar usuários."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Falha ao obter as funções.",
|
||||
"roleNotFound": "Função não encontrada.",
|
||||
"failedToGetRole": "Falha ao obter a função.",
|
||||
"missingRoleFields": "Campos obrigatórios ausentes: nome e política.",
|
||||
"invalidPolicy": "Declaração de política inválida:",
|
||||
"failedToCreateRole": "Falha ao criar a função.",
|
||||
"failedToDeleteRole": "Falha ao excluir a função.",
|
||||
"missingUpdateFields": "Campos ausentes para atualização: nome ou políticas.",
|
||||
"failedToUpdateRole": "Falha ao atualizar a função.",
|
||||
"requiresSuperAdminRole": "A função de Super Admin é necessária para gerenciar as funções."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Falha ao recuperar as configurações",
|
||||
"failedToUpdate": "Falha ao atualizar as configurações",
|
||||
"noPermissionToUpdate": "Você não tem permissão para atualizar as configurações do sistema."
|
||||
},
|
||||
"dashboard": {
|
||||
"permissionRequired": "Você precisa da permissão de leitura do painel para visualizar os dados do painel."
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Falha ao criar a fonte de ingestão devido a um erro de conexão.",
|
||||
"notFound": "Fonte de ingestão não encontrada",
|
||||
"initialImportTriggered": "Importação inicial acionada com sucesso.",
|
||||
"forceSyncTriggered": "Sincronização forçada acionada com sucesso."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "E-mail arquivado não encontrado"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Palavras-chave são obrigatórias"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "O caminho do arquivo é obrigatório",
|
||||
"invalidFilePath": "Caminho de arquivo inválido",
|
||||
"fileNotFound": "Arquivo não encontrado",
|
||||
"downloadError": "Erro ao baixar o arquivo"
|
||||
}
|
||||
}
|
||||
@@ -4,57 +4,52 @@ import type { SystemSettings } from '@open-archiver/types';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
const DEFAULT_SETTINGS: SystemSettings = {
|
||||
language: 'en',
|
||||
theme: 'system',
|
||||
supportEmail: null,
|
||||
language: 'en',
|
||||
theme: 'system',
|
||||
supportEmail: null,
|
||||
};
|
||||
|
||||
export class SettingsService {
|
||||
/**
|
||||
* Retrieves the current system settings.
|
||||
* If no settings exist, it initializes and returns the default settings.
|
||||
* @returns The system settings.
|
||||
*/
|
||||
public async getSettings(): Promise<SystemSettings> {
|
||||
const settings = await db.select().from(systemSettings).limit(1);
|
||||
/**
|
||||
* Retrieves the current system settings.
|
||||
* If no settings exist, it initializes and returns the default settings.
|
||||
* @returns The system settings.
|
||||
*/
|
||||
public async getSettings(): Promise<SystemSettings> {
|
||||
const settings = await db.select().from(systemSettings).limit(1);
|
||||
|
||||
if (settings.length === 0) {
|
||||
return this.createDefaultSettings();
|
||||
}
|
||||
if (settings.length === 0) {
|
||||
return this.createDefaultSettings();
|
||||
}
|
||||
|
||||
return settings[0].config;
|
||||
}
|
||||
return settings[0].config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the system settings by merging the new configuration with the existing one.
|
||||
* @param newConfig - A partial object of the new settings configuration.
|
||||
* @returns The updated system settings.
|
||||
*/
|
||||
public async updateSettings(
|
||||
newConfig: Partial<SystemSettings>
|
||||
): Promise<SystemSettings> {
|
||||
const currentConfig = await this.getSettings();
|
||||
const mergedConfig = { ...currentConfig, ...newConfig };
|
||||
/**
|
||||
* Updates the system settings by merging the new configuration with the existing one.
|
||||
* @param newConfig - A partial object of the new settings configuration.
|
||||
* @returns The updated system settings.
|
||||
*/
|
||||
public async updateSettings(newConfig: Partial<SystemSettings>): Promise<SystemSettings> {
|
||||
const currentConfig = await this.getSettings();
|
||||
const mergedConfig = { ...currentConfig, ...newConfig };
|
||||
|
||||
// Since getSettings ensures a record always exists, we can directly update.
|
||||
const [result] = await db
|
||||
.update(systemSettings)
|
||||
.set({ config: mergedConfig })
|
||||
.returning();
|
||||
// Since getSettings ensures a record always exists, we can directly update.
|
||||
const [result] = await db.update(systemSettings).set({ config: mergedConfig }).returning();
|
||||
|
||||
return result.config;
|
||||
}
|
||||
return result.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and saves the default system settings.
|
||||
* This is called internally when no settings are found.
|
||||
* @returns The newly created default settings.
|
||||
*/
|
||||
private async createDefaultSettings(): Promise<SystemSettings> {
|
||||
const [result] = await db
|
||||
.insert(systemSettings)
|
||||
.values({ config: DEFAULT_SETTINGS })
|
||||
.returning();
|
||||
return result.config;
|
||||
}
|
||||
/**
|
||||
* Creates and saves the default system settings.
|
||||
* This is called internally when no settings are found.
|
||||
* @returns The newly created default settings.
|
||||
*/
|
||||
private async createDefaultSettings(): Promise<SystemSettings> {
|
||||
const [result] = await db
|
||||
.insert(systemSettings)
|
||||
.values({ config: DEFAULT_SETTINGS })
|
||||
.returning();
|
||||
return result.config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"lucide-svelte": "^0.525.0",
|
||||
"postal-mime": "^2.4.4",
|
||||
"svelte-persisted-store": "^0.12.0",
|
||||
"sveltekit-i18n": "^2.4.2",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^1.0.0"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import PostalMime, { type Email } from 'postal-mime';
|
||||
import type { Buffer } from 'buffer';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let {
|
||||
raw,
|
||||
@@ -51,13 +52,16 @@
|
||||
|
||||
<div class="mt-2 rounded-md border bg-white p-4">
|
||||
{#if isLoading}
|
||||
<p>Loading email preview...</p>
|
||||
<p>{$t('components.email_preview.loading')}</p>
|
||||
{:else if emailHtml}
|
||||
<iframe title="Email Preview" srcdoc={emailHtml()} class="h-[600px] w-full border-none"
|
||||
<iframe
|
||||
title={$t('archive.email_preview')}
|
||||
srcdoc={emailHtml()}
|
||||
class="h-[600px] w-full border-none"
|
||||
></iframe>
|
||||
{:else if raw}
|
||||
<p>Could not render email preview.</p>
|
||||
<p>{$t('components.email_preview.render_error')}</p>
|
||||
{:else}
|
||||
<p class="text-gray-500">Raw .eml file not available for this email.</p>
|
||||
<p class="text-gray-500">{$t('components.email_preview.not_available')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import type { ArchivedEmail } from '@open-archiver/types';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let {
|
||||
thread,
|
||||
@@ -47,16 +48,16 @@
|
||||
goto(`/dashboard/archived-emails/${item.id}`, {
|
||||
invalidateAll: true,
|
||||
});
|
||||
}}>{item.subject || 'No Subject'}</a
|
||||
}}>{item.subject || $t('app.archive.no_subject')}</a
|
||||
>
|
||||
{:else}
|
||||
{item.subject || 'No Subject'}
|
||||
{item.subject || $t('app.archive.no_subject')}
|
||||
{/if}
|
||||
</h4>
|
||||
<div
|
||||
class="flex flex-col space-y-2 text-sm font-normal leading-none text-gray-400"
|
||||
>
|
||||
<span>From: {item.senderEmail}</span>
|
||||
<span>{$t('app.archive.from')}: {item.senderEmail}</span>
|
||||
<time class="">{new Date(item.sentAt).toLocaleString()}</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<footer class=" bg-muted py-6 md:py-0">
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/translations';
|
||||
</script>
|
||||
|
||||
<footer class="bg-muted py-6 md:py-0">
|
||||
<div
|
||||
class="container mx-auto flex flex-col items-center justify-center gap-4 md:h-24 md:flex-row"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<p class=" text-balance text-center text-xs font-medium leading-loose">
|
||||
<p class="text-balance text-center text-xs font-medium leading-loose">
|
||||
© {new Date().getFullYear()}
|
||||
<a href="https://openarchiver.com/" target="_blank">Open Archiver</a>. All rights
|
||||
reserved.
|
||||
<a href="https://openarchiver.com/" target="_blank">Open Archiver</a>. {$t(
|
||||
'app.components.footer.all_rights_reserved'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { setAlert } from '$lib/components/custom/alert/alert-state.svelte';
|
||||
import { api } from '$lib/api.client';
|
||||
import { Loader2 } from 'lucide-svelte';
|
||||
import { t } from '$lib/translations';
|
||||
let {
|
||||
source = null,
|
||||
onSubmit,
|
||||
@@ -20,11 +21,26 @@
|
||||
} = $props();
|
||||
|
||||
const providerOptions = [
|
||||
{ value: 'generic_imap', label: 'Generic IMAP' },
|
||||
{ value: 'google_workspace', label: 'Google Workspace' },
|
||||
{ value: 'microsoft_365', label: 'Microsoft 365' },
|
||||
{ value: 'pst_import', label: 'PST Import' },
|
||||
{ value: 'eml_import', label: 'EML Import' },
|
||||
{
|
||||
value: 'generic_imap',
|
||||
label: $t('app.components.ingestion_source_form.provider_generic_imap'),
|
||||
},
|
||||
{
|
||||
value: 'google_workspace',
|
||||
label: $t('app.components.ingestion_source_form.provider_google_workspace'),
|
||||
},
|
||||
{
|
||||
value: 'microsoft_365',
|
||||
label: $t('app.components.ingestion_source_form.provider_microsoft_365'),
|
||||
},
|
||||
{
|
||||
value: 'pst_import',
|
||||
label: $t('app.components.ingestion_source_form.provider_pst_import'),
|
||||
},
|
||||
{
|
||||
value: 'eml_import',
|
||||
label: $t('app.components.ingestion_source_form.provider_eml_import'),
|
||||
},
|
||||
];
|
||||
|
||||
let formData: CreateIngestionSourceDto = $state({
|
||||
@@ -42,7 +58,8 @@
|
||||
});
|
||||
|
||||
const triggerContent = $derived(
|
||||
providerOptions.find((p) => p.value === formData.provider)?.label ?? 'Select a provider'
|
||||
providerOptions.find((p) => p.value === formData.provider)?.label ??
|
||||
$t('app.components.ingestion_source_form.select_provider')
|
||||
);
|
||||
|
||||
let isSubmitting = $state(false);
|
||||
@@ -89,7 +106,7 @@
|
||||
fileUploading = false;
|
||||
setAlert({
|
||||
type: 'error',
|
||||
title: 'Upload Failed, please try again',
|
||||
title: $t('app.components.ingestion_source_form.upload_failed'),
|
||||
message: JSON.stringify(error),
|
||||
duration: 5000,
|
||||
show: true,
|
||||
@@ -100,11 +117,11 @@
|
||||
|
||||
<form onsubmit={handleSubmit} class="grid gap-4 py-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="name" class="text-left">Name</Label>
|
||||
<Label for="name" class="text-left">{$t('app.ingestions.name')}</Label>
|
||||
<Input id="name" bind:value={formData.name} class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="provider" class="text-left">Provider</Label>
|
||||
<Label for="provider" class="text-left">{$t('app.ingestions.provider')}</Label>
|
||||
<Select.Root name="provider" bind:value={formData.provider} type="single">
|
||||
<Select.Trigger class="col-span-3">
|
||||
{triggerContent}
|
||||
@@ -119,16 +136,22 @@
|
||||
|
||||
{#if formData.provider === 'google_workspace'}
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="serviceAccountKeyJson" class="text-left">Service Account Key (JSON)</Label>
|
||||
<Label for="serviceAccountKeyJson" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.service_account_key')}</Label
|
||||
>
|
||||
<Textarea
|
||||
placeholder="Paste your service account key JSON content"
|
||||
placeholder={$t(
|
||||
'app.components.ingestion_source_form.service_account_key_placeholder'
|
||||
)}
|
||||
id="serviceAccountKeyJson"
|
||||
bind:value={formData.providerConfig.serviceAccountKeyJson}
|
||||
class="col-span-3 max-h-32"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="impersonatedAdminEmail" class="text-left">Impersonated Admin Email</Label>
|
||||
<Label for="impersonatedAdminEmail" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.impersonated_admin_email')}</Label
|
||||
>
|
||||
<Input
|
||||
id="impersonatedAdminEmail"
|
||||
bind:value={formData.providerConfig.impersonatedAdminEmail}
|
||||
@@ -137,30 +160,40 @@
|
||||
</div>
|
||||
{:else if formData.provider === 'microsoft_365'}
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="clientId" class="text-left">Application (Client) ID</Label>
|
||||
<Label for="clientId" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.client_id')}</Label
|
||||
>
|
||||
<Input id="clientId" bind:value={formData.providerConfig.clientId} class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="clientSecret" class="text-left">Client Secret Value</Label>
|
||||
<Label for="clientSecret" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.client_secret')}</Label
|
||||
>
|
||||
<Input
|
||||
id="clientSecret"
|
||||
type="password"
|
||||
placeholder="Enter the secret Value, not the Secret ID"
|
||||
placeholder={$t('app.components.ingestion_source_form.client_secret_placeholder')}
|
||||
bind:value={formData.providerConfig.clientSecret}
|
||||
class="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="tenantId" class="text-left">Directory (Tenant) ID</Label>
|
||||
<Label for="tenantId" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.tenant_id')}</Label
|
||||
>
|
||||
<Input id="tenantId" bind:value={formData.providerConfig.tenantId} class="col-span-3" />
|
||||
</div>
|
||||
{:else if formData.provider === 'generic_imap'}
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="host" class="text-left">Host</Label>
|
||||
<Label for="host" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.host')}</Label
|
||||
>
|
||||
<Input id="host" bind:value={formData.providerConfig.host} class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="port" class="text-left">Port</Label>
|
||||
<Label for="port" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.port')}</Label
|
||||
>
|
||||
<Input
|
||||
id="port"
|
||||
type="number"
|
||||
@@ -169,11 +202,13 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="username" class="text-left">Username</Label>
|
||||
<Label for="username" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.username')}</Label
|
||||
>
|
||||
<Input id="username" bind:value={formData.providerConfig.username} class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="password" class="text-left">Password</Label>
|
||||
<Label for="password" class="text-left">{$t('auth.password')}</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
@@ -182,12 +217,16 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="secure" class="text-left">Use TLS</Label>
|
||||
<Label for="secure" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.use_tls')}</Label
|
||||
>
|
||||
<Checkbox id="secure" bind:checked={formData.providerConfig.secure} />
|
||||
</div>
|
||||
{:else if formData.provider === 'pst_import'}
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="pst-file" class="text-left">PST File</Label>
|
||||
<Label for="pst-file" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.pst_file')}</Label
|
||||
>
|
||||
<div class="col-span-3 flex flex-row items-center space-x-2">
|
||||
<Input
|
||||
id="pst-file"
|
||||
@@ -203,7 +242,9 @@
|
||||
</div>
|
||||
{:else if formData.provider === 'eml_import'}
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="eml-file" class="text-left">EML File</Label>
|
||||
<Label for="eml-file" class="text-left"
|
||||
>{$t('app.components.ingestion_source_form.eml_file')}</Label
|
||||
>
|
||||
<div class="col-span-3 flex flex-row items-center space-x-2">
|
||||
<Input
|
||||
id="eml-file"
|
||||
@@ -220,12 +261,10 @@
|
||||
{/if}
|
||||
{#if formData.provider === 'google_workspace' || formData.provider === 'microsoft_365'}
|
||||
<Alert.Root>
|
||||
<Alert.Title>Heads up!</Alert.Title>
|
||||
<Alert.Title>{$t('app.components.ingestion_source_form.heads_up')}</Alert.Title>
|
||||
<Alert.Description>
|
||||
<div class="my-1">
|
||||
Please note that this is an organization-wide operation. This kind of ingestions
|
||||
will import and index <b>all</b> email inboxes in your organization. If you want
|
||||
to import only specific email inboxes, use the IMAP connector.
|
||||
{@html $t('app.components.ingestion_source_form.org_wide_warning')}
|
||||
</div>
|
||||
</Alert.Description>
|
||||
</Alert.Root>
|
||||
@@ -233,9 +272,9 @@
|
||||
<Dialog.Footer>
|
||||
<Button type="submit" disabled={isSubmitting || fileUploading}>
|
||||
{#if isSubmitting}
|
||||
Submitting...
|
||||
{$t('app.components.common.submitting')}
|
||||
{:else}
|
||||
Submit
|
||||
{$t('app.components.common.submit')}
|
||||
{/if}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { role, onSubmit }: { role: Role | null; onSubmit: (formData: Partial<Role>) => void } =
|
||||
$props();
|
||||
@@ -16,7 +17,7 @@
|
||||
const parsedPolicies: CaslPolicy[] = JSON.parse(policies);
|
||||
onSubmit({ name, policies: parsedPolicies });
|
||||
} catch (error) {
|
||||
alert('Invalid JSON format for policies.');
|
||||
alert($t('app.components.role_form.invalid_json'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -29,11 +30,13 @@
|
||||
class="grid gap-4 py-4"
|
||||
>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="name" class="text-right">Name</Label>
|
||||
<Label for="name" class="text-right">{$t('app.roles.name')}</Label>
|
||||
<Input id="name" bind:value={name} class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="policies" class="text-right">Policies (JSON)</Label>
|
||||
<Label for="policies" class="text-right"
|
||||
>{$t('app.components.role_form.policies_json')}</Label
|
||||
>
|
||||
<Textarea
|
||||
id="policies"
|
||||
bind:value={policies}
|
||||
@@ -42,6 +45,6 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button type="submit">Save</Button>
|
||||
<Button type="submit">{$t('app.components.common.save')}</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { Sun, Moon, Laptop } from 'lucide-svelte';
|
||||
import { t } from '$lib/translations';
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -14,21 +15,21 @@
|
||||
<Moon
|
||||
class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
|
||||
/>
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
<span class="sr-only">{$t('app.components.theme_switcher.toggle_theme')}</span>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Item onclick={() => ($theme = 'light')}>
|
||||
<Sun class="mr-2 h-4 w-4" />
|
||||
<span>Light</span>
|
||||
<span>{$t('app.system_settings.light')}</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => ($theme = 'dark')}>
|
||||
<Moon class="mr-2 h-4 w-4" />
|
||||
<span>Dark</span>
|
||||
<span>{$t('app.system_settings.dark')}</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => ($theme = 'system')}>
|
||||
<Laptop class="mr-2 h-4 w-4" />
|
||||
<span>System</span>
|
||||
<span>{$t('app.system_settings.system')}</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let {
|
||||
user = null,
|
||||
@@ -25,7 +26,8 @@
|
||||
});
|
||||
|
||||
const triggerContent = $derived(
|
||||
roles.find((r) => r.id === formData.roleId)?.name ?? 'Select a role'
|
||||
roles.find((r) => r.id === formData.roleId)?.name ??
|
||||
$t('app.components.user_form.select_role')
|
||||
);
|
||||
|
||||
let isSubmitting = $state(false);
|
||||
@@ -53,20 +55,20 @@
|
||||
|
||||
<form onsubmit={handleSubmit} class="grid gap-4 py-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="first_name" class="text-left">First Name</Label>
|
||||
<Label for="first_name" class="text-left">{$t('app.setup.first_name')}</Label>
|
||||
<Input id="first_name" bind:value={formData.first_name} class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="last_name" class="text-left">Last Name</Label>
|
||||
<Label for="last_name" class="text-left">{$t('app.setup.last_name')}</Label>
|
||||
<Input id="last_name" bind:value={formData.last_name} class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="email" class="text-left">Email</Label>
|
||||
<Label for="email" class="text-left">{$t('app.users.email')}</Label>
|
||||
<Input id="email" type="email" bind:value={formData.email} class="col-span-3" />
|
||||
</div>
|
||||
{#if !user}
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="password" class="text-left">Password</Label>
|
||||
<Label for="password" class="text-left">{$t('app.auth.password')}</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
@@ -76,7 +78,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="role" class="text-left">Role</Label>
|
||||
<Label for="role" class="text-left">{$t('app.users.role')}</Label>
|
||||
<Select.Root name="role" bind:value={formData.roleId} type="single">
|
||||
<Select.Trigger class="col-span-3">
|
||||
{triggerContent}
|
||||
@@ -92,9 +94,9 @@
|
||||
<Dialog.Footer>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{#if isSubmitting}
|
||||
Submitting...
|
||||
{$t('app.components.common.submitting')}
|
||||
{:else}
|
||||
Submit
|
||||
{$t('app.components.common.submit')}
|
||||
{/if}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { AreaChart } from 'layerchart';
|
||||
import { curveMonotoneX } from 'd3-shape';
|
||||
import type { ChartConfig } from '$lib/components/ui/chart';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
export let data: { date: Date; count: number }[];
|
||||
|
||||
const chartConfig = {
|
||||
count: {
|
||||
label: 'Emails Ingested',
|
||||
label: $t('app.components.charts.emails_ingested'),
|
||||
color: 'var(--chart-1)',
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
import type { IngestionSourceStats } from '@open-archiver/types';
|
||||
import type { ChartConfig } from '$lib/components/ui/chart';
|
||||
import { formatBytes } from '$lib/utils';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
export let data: IngestionSourceStats[];
|
||||
|
||||
const chartConfig = {
|
||||
storageUsed: {
|
||||
label: 'Storage Used',
|
||||
label: $t('app.components.charts.storage_used'),
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
</script>
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { BarChart } from 'layerchart';
|
||||
import type { TopSender } from '@open-archiver/types';
|
||||
import type { ChartConfig } from '$lib/components/ui/chart';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
export let data: TopSender[];
|
||||
|
||||
const chartConfig = {
|
||||
count: {
|
||||
label: 'Emails',
|
||||
label: $t('app.components.charts.emails'),
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
</script>
|
||||
|
||||
260
packages/frontend/src/lib/translations/de.json
Normal file
260
packages/frontend/src/lib/translations/de.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Anmelden",
|
||||
"login_tip": "Geben Sie unten Ihre E-Mail-Adresse ein, um sich bei Ihrem Konto anzumelden.",
|
||||
"email": "Email",
|
||||
"password": "Passwort"
|
||||
},
|
||||
"common": {
|
||||
"working": "Arbeiten"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archiv",
|
||||
"no_subject": "Kein Betreff",
|
||||
"from": "Von",
|
||||
"sent": "Gesendet",
|
||||
"recipients": "Empfänger",
|
||||
"to": "An",
|
||||
"meta_data": "Metadaten",
|
||||
"folder": "Ordner",
|
||||
"tags": "Tags",
|
||||
"size": "Größe",
|
||||
"email_preview": "E-Mail-Vorschau",
|
||||
"attachments": "Anhänge",
|
||||
"download": "Herunterladen",
|
||||
"actions": "Aktionen",
|
||||
"download_eml": "E-Mail herunterladen (.eml)",
|
||||
"delete_email": "E-Mail löschen",
|
||||
"email_thread": "E-Mail-Thread",
|
||||
"delete_confirmation_title": "Möchten Sie diese E-Mail wirklich löschen?",
|
||||
"delete_confirmation_description": "Diese Aktion kann nicht rückgängig gemacht werden und entfernt die E-Mail und ihre Anhänge dauerhaft.",
|
||||
"deleting": "Löschen",
|
||||
"confirm": "Bestätigen",
|
||||
"cancel": "Abbrechen",
|
||||
"not_found": "E-Mail nicht gefunden."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Erfassungsquellen",
|
||||
"ingestion_sources": "Erfassungsquellen",
|
||||
"bulk_actions": "Massenaktionen",
|
||||
"force_sync": "Synchronisierung erzwingen",
|
||||
"delete": "Löschen",
|
||||
"create_new": "Neu erstellen",
|
||||
"name": "Name",
|
||||
"provider": "Anbieter",
|
||||
"status": "Status",
|
||||
"active": "Aktiv",
|
||||
"created_at": "Erstellt am",
|
||||
"actions": "Aktionen",
|
||||
"last_sync_message": "Letzte Synchronisierungsnachricht",
|
||||
"empty": "Leer",
|
||||
"open_menu": "Menü öffnen",
|
||||
"edit": "Bearbeiten",
|
||||
"create": "Erstellen",
|
||||
"ingestion_source": "Erfassungsquelle",
|
||||
"edit_description": "Nehmen Sie hier Änderungen an Ihrer Erfassungsquelle vor.",
|
||||
"create_description": "Fügen Sie eine neue Erfassungsquelle hinzu, um mit der Archivierung von E-Mails zu beginnen.",
|
||||
"read": "Lesen",
|
||||
"docs_here": "Dokumente hier",
|
||||
"delete_confirmation_title": "Möchten Sie diese Erfassung wirklich löschen?",
|
||||
"delete_confirmation_description": "Dadurch werden alle archivierten E-Mails, Anhänge, Indizierungen und Dateien, die mit dieser Erfassung verknüpft sind, gelöscht. Wenn Sie nur die Synchronisierung neuer E-Mails beenden möchten, können Sie stattdessen die Erfassung anhalten.",
|
||||
"deleting": "Löschen",
|
||||
"confirm": "Bestätigen",
|
||||
"cancel": "Abbrechen",
|
||||
"bulk_delete_confirmation_title": "Möchten Sie wirklich {{count}} ausgewählte Erfassungen löschen?",
|
||||
"bulk_delete_confirmation_description": "Dadurch werden alle archivierten E-Mails, Anhänge, Indizierungen und Dateien, die mit diesen Erfassungen verknüpft sind, gelöscht. Wenn Sie nur die Synchronisierung neuer E-Mails beenden möchten, können Sie stattdessen die Erfassungen anhalten."
|
||||
},
|
||||
"search": {
|
||||
"title": "Suche",
|
||||
"description": "Suchen Sie nach archivierten E-Mails.",
|
||||
"email_search": "E-Mail-Suche",
|
||||
"placeholder": "Suche nach Stichwort, Absender, Empfänger...",
|
||||
"search_button": "Suche",
|
||||
"search_options": "Suchoptionen",
|
||||
"strategy_fuzzy": "Fuzzy",
|
||||
"strategy_verbatim": "Wörtlich",
|
||||
"strategy_frequency": "Frequenz",
|
||||
"select_strategy": "Wählen Sie eine Strategie",
|
||||
"error": "Fehler",
|
||||
"found_results_in": "{{total}} Ergebnisse in {{seconds}}s gefunden",
|
||||
"found_results": "{{total}} Ergebnisse gefunden",
|
||||
"from": "Von",
|
||||
"to": "An",
|
||||
"in_email_body": "Im E-Mail-Text",
|
||||
"in_attachment": "Im Anhang: {{filename}}",
|
||||
"prev": "Zurück",
|
||||
"next": "Weiter"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Rollenverwaltung",
|
||||
"role_management": "Rollenverwaltung",
|
||||
"create_new": "Neu erstellen",
|
||||
"name": "Name",
|
||||
"created_at": "Erstellt am",
|
||||
"actions": "Aktionen",
|
||||
"open_menu": "Menü öffnen",
|
||||
"view_policy": "Richtlinie anzeigen",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"no_roles_found": "Keine Rollen gefunden.",
|
||||
"role_policy": "Rollenrichtlinie",
|
||||
"viewing_policy_for_role": "Richtlinie für Rolle anzeigen: {{name}}",
|
||||
"create": "Erstellen",
|
||||
"role": "Rolle",
|
||||
"edit_description": "Nehmen Sie hier Änderungen an der Rolle vor.",
|
||||
"create_description": "Fügen Sie dem System eine neue Rolle hinzu.",
|
||||
"delete_confirmation_title": "Möchten Sie diese Rolle wirklich löschen?",
|
||||
"delete_confirmation_description": "Diese Aktion kann nicht rückgängig gemacht werden. Dadurch wird die Rolle dauerhaft gelöscht.",
|
||||
"deleting": "Löschen",
|
||||
"confirm": "Bestätigen",
|
||||
"cancel": "Abbrechen"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "Systemeinstellungen",
|
||||
"system_settings": "Systemeinstellungen",
|
||||
"description": "Globale Anwendungseinstellungen verwalten.",
|
||||
"language": "Sprache",
|
||||
"default_theme": "Standardthema",
|
||||
"light": "Hell",
|
||||
"dark": "Dunkel",
|
||||
"system": "System",
|
||||
"support_email": "Support-E-Mail",
|
||||
"saving": "Speichern",
|
||||
"save_changes": "Änderungen speichern"
|
||||
},
|
||||
"users": {
|
||||
"title": "Benutzerverwaltung",
|
||||
"user_management": "Benutzerverwaltung",
|
||||
"create_new": "Neu erstellen",
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"role": "Rolle",
|
||||
"created_at": "Erstellt am",
|
||||
"actions": "Aktionen",
|
||||
"open_menu": "Menü öffnen",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"no_users_found": "Keine Benutzer gefunden.",
|
||||
"create": "Erstellen",
|
||||
"user": "Benutzer",
|
||||
"edit_description": "Nehmen Sie hier Änderungen am Benutzer vor.",
|
||||
"create_description": "Fügen Sie dem System einen neuen Benutzer hinzu.",
|
||||
"delete_confirmation_title": "Möchten Sie diesen Benutzer wirklich löschen?",
|
||||
"delete_confirmation_description": "Diese Aktion kann nicht rückgängig gemacht werden. Dadurch wird der Benutzer dauerhaft gelöscht und seine Daten von unseren Servern entfernt.",
|
||||
"deleting": "Löschen",
|
||||
"confirm": "Bestätigen",
|
||||
"cancel": "Abbrechen"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Einrichtung",
|
||||
"description": "Richten Sie das anfängliche Administratorkonto für Open Archiver ein.",
|
||||
"welcome": "Willkommen",
|
||||
"create_admin_account": "Erstellen Sie das erste Administratorkonto, um loszulegen.",
|
||||
"first_name": "Vorname",
|
||||
"last_name": "Nachname",
|
||||
"email": "Email",
|
||||
"password": "Passwort",
|
||||
"creating_account": "Konto wird erstellt",
|
||||
"create_account": "Konto erstellen"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Dashboard",
|
||||
"ingestions": "Erfassungen",
|
||||
"archived_emails": "Archivierte E-Mails",
|
||||
"search": "Suche",
|
||||
"settings": "Einstellungen",
|
||||
"system": "System",
|
||||
"users": "Benutzer",
|
||||
"roles": "Rollen",
|
||||
"logout": "Abmelden"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "E-Mails aufgenommen",
|
||||
"storage_used": "Speicher verwendet",
|
||||
"emails": "E-Mails"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Übermittlung...",
|
||||
"submit": "Übermitteln",
|
||||
"save": "Speichern"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "E-Mail-Vorschau wird geladen...",
|
||||
"render_error": "E-Mail-Vorschau konnte nicht gerendert werden.",
|
||||
"not_available": "Rohe .eml-Datei für diese E-Mail nicht verfügbar."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "Alle Rechte vorbehalten."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "Generisches IMAP",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "PST-Import",
|
||||
"provider_eml_import": "EML-Import",
|
||||
"select_provider": "Wählen Sie einen Anbieter",
|
||||
"service_account_key": "Dienstkontoschlüssel (JSON)",
|
||||
"service_account_key_placeholder": "Fügen Sie den JSON-Inhalt Ihres Dienstkontoschlüssels ein",
|
||||
"impersonated_admin_email": "Impersonierte Admin-E-Mail",
|
||||
"client_id": "Anwendungs-(Client-)ID",
|
||||
"client_secret": "Client-Geheimniswert",
|
||||
"client_secret_placeholder": "Geben Sie den Geheimniswert ein, nicht die Geheimnis-ID",
|
||||
"tenant_id": "Verzeichnis-(Mandanten-)ID",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"username": "Benutzername",
|
||||
"use_tls": "TLS verwenden",
|
||||
"pst_file": "PST-Datei",
|
||||
"eml_file": "EML-Datei",
|
||||
"heads_up": "Achtung!",
|
||||
"org_wide_warning": "Bitte beachten Sie, dass dies ein organisationsweiter Vorgang ist. Diese Art von Erfassungen importiert und indiziert <b>alle</b> E-Mail-Postfächer in Ihrer Organisation. Wenn Sie nur bestimmte E-Mail-Postfächer importieren möchten, verwenden Sie den IMAP-Connector.",
|
||||
"upload_failed": "Hochladen fehlgeschlagen, bitte versuchen Sie es erneut"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Richtlinien (JSON)",
|
||||
"invalid_json": "Ungültiges JSON-Format für Richtlinien."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Thema umschalten"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Wählen Sie eine Rolle aus"
|
||||
}
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Dashboard",
|
||||
"meta_description": "Übersicht über Ihr E-Mail-Archiv.",
|
||||
"header": "Dashboard",
|
||||
"create_ingestion": "Erfassung erstellen",
|
||||
"no_ingestion_header": "Sie haben keine Erfassungsquelle eingerichtet.",
|
||||
"no_ingestion_text": "Fügen Sie eine Erfassungsquelle hinzu, um mit der Archivierung Ihrer Posteingänge zu beginnen.",
|
||||
"total_emails_archived": "Insgesamt archivierte E-Mails",
|
||||
"total_storage_used": "Insgesamt genutzter Speicherplatz",
|
||||
"failed_ingestions": "Fehlgeschlagene Erfassungen (letzte 7 Tage)",
|
||||
"ingestion_history": "Erfassungsverlauf",
|
||||
"no_ingestion_history": "Kein Erfassungsverlauf verfügbar.",
|
||||
"storage_by_source": "Speicher nach Erfassungsquelle",
|
||||
"no_ingestion_sources": "Keine Erfassungsquellen verfügbar.",
|
||||
"indexed_insights": "Indizierte Einblicke",
|
||||
"top_10_senders": "Top 10 Absender",
|
||||
"no_indexed_insights": "Keine indizierten Einblicke verfügbar."
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "Archivierte E-Mails",
|
||||
"header": "Archivierte E-Mails",
|
||||
"select_ingestion_source": "Wählen Sie eine Erfassungsquelle aus",
|
||||
"date": "Datum",
|
||||
"subject": "Betreff",
|
||||
"sender": "Absender",
|
||||
"inbox": "Posteingang",
|
||||
"path": "Pfad",
|
||||
"actions": "Aktionen",
|
||||
"view": "Ansehen",
|
||||
"no_emails_found": "Keine archivierten E-Mails gefunden.",
|
||||
"prev": "Zurück",
|
||||
"next": "Weiter"
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/frontend/src/lib/translations/el.json
Normal file
260
packages/frontend/src/lib/translations/el.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Σύνδεση",
|
||||
"login_tip": "Εισαγάγετε το email σας παρακάτω για να συνδεθείτε στον λογαριασμό σας.",
|
||||
"email": "Email",
|
||||
"password": "Κωδικός πρόσβασης"
|
||||
},
|
||||
"common": {
|
||||
"working": "Επεξεργασία"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Αρχείο",
|
||||
"no_subject": "Χωρίς θέμα",
|
||||
"from": "Από",
|
||||
"sent": "Απεσταλμένα",
|
||||
"recipients": "Παραλήπτες",
|
||||
"to": "Προς",
|
||||
"meta_data": "Μεταδεδομένα",
|
||||
"folder": "Φάκελος",
|
||||
"tags": "Ετικέτες",
|
||||
"size": "Μέγεθος",
|
||||
"email_preview": "Προεπισκόπηση email",
|
||||
"attachments": "Συνημμένα",
|
||||
"download": "Λήψη",
|
||||
"actions": "Ενέργειες",
|
||||
"download_eml": "Λήψη email (.eml)",
|
||||
"delete_email": "Διαγραφή email",
|
||||
"email_thread": "Συνομιλία email",
|
||||
"delete_confirmation_title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το email;",
|
||||
"delete_confirmation_description": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί και θα διαγράψει οριστικά το email και τα συνημμένα του.",
|
||||
"deleting": "Διαγραφή",
|
||||
"confirm": "Επιβεβαίωση",
|
||||
"cancel": "Άκυρο",
|
||||
"not_found": "Το email δεν βρέθηκε."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Πηγές εισαγωγής",
|
||||
"ingestion_sources": "Πηγές εισαγωγής",
|
||||
"bulk_actions": "Μαζικές ενέργειες",
|
||||
"force_sync": "Εξαναγκασμένος συγχρονισμός",
|
||||
"delete": "Διαγραφή",
|
||||
"create_new": "Δημιουργία νέου",
|
||||
"name": "Όνομα",
|
||||
"provider": "Πάροχος",
|
||||
"status": "Κατάσταση",
|
||||
"active": "Ενεργό",
|
||||
"created_at": "Δημιουργήθηκε στις",
|
||||
"actions": "Ενέργειες",
|
||||
"last_sync_message": "Τελευταίο μήνυμα συγχρονισμού",
|
||||
"empty": "Κενό",
|
||||
"open_menu": "Άνοιγμα μενού",
|
||||
"edit": "Επεξεργασία",
|
||||
"create": "Δημιουργία",
|
||||
"ingestion_source": "Πηγή εισαγωγής",
|
||||
"edit_description": "Κάντε αλλαγές στην πηγή εισαγωγής σας εδώ.",
|
||||
"create_description": "Προσθέστε μια νέα πηγή εισαγωγής για να ξεκινήσετε την αρχειοθέτηση των email.",
|
||||
"read": "Διαβάστε",
|
||||
"docs_here": "την τεκμηρίωση εδώ",
|
||||
"delete_confirmation_title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την εισαγωγή;",
|
||||
"delete_confirmation_description": "Αυτό θα διαγράψει όλα τα αρχειοθετημένα email, τα συνημμένα, την ευρετηρίαση και τα αρχεία που σχετίζονται με αυτήν την εισαγωγή. Εάν θέλετε μόνο να σταματήσετε τον συγχρονισμό νέων email, μπορείτε να θέσετε σε παύση την εισαγωγή.",
|
||||
"deleting": "Διαγραφή",
|
||||
"confirm": "Επιβεβαίωση",
|
||||
"cancel": "Άκυρο",
|
||||
"bulk_delete_confirmation_title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τις {{count}} επιλεγμένες εισαγωγές;",
|
||||
"bulk_delete_confirmation_description": "Αυτό θα διαγράψει όλα τα αρχειοθετημένα email, τα συνημμένα, την ευρετηρίαση και τα αρχεία που σχετίζονται με αυτές τις εισαγωγές. Εάν θέλετε μόνο να σταματήσετε τον συγχρονισμό νέων email, μπορείτε να θέσετε σε παύση τις εισαγωγές."
|
||||
},
|
||||
"search": {
|
||||
"title": "Αναζήτηση",
|
||||
"description": "Αναζήτηση για αρχειοθετημένα email.",
|
||||
"email_search": "Αναζήτηση email",
|
||||
"placeholder": "Αναζήτηση με λέξη-κλειδί, αποστολέα, παραλήπτη...",
|
||||
"search_button": "Αναζήτηση",
|
||||
"search_options": "Επιλογές αναζήτησης",
|
||||
"strategy_fuzzy": "Ασαφής",
|
||||
"strategy_verbatim": "Κατά λέξη",
|
||||
"strategy_frequency": "Συχνότητα",
|
||||
"select_strategy": "Επιλέξτε μια στρατηγική",
|
||||
"error": "Σφάλμα",
|
||||
"found_results_in": "Βρέθηκαν {{total}} αποτελέσματα σε {{seconds}}s",
|
||||
"found_results": "Βρέθηκαν {{total}} αποτελέσματα",
|
||||
"from": "Από",
|
||||
"to": "Προς",
|
||||
"in_email_body": "Στο σώμα του email",
|
||||
"in_attachment": "Στο συνημμένο: {{filename}}",
|
||||
"prev": "Προηγούμενο",
|
||||
"next": "Επόμενο"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Διαχείριση ρόλων",
|
||||
"role_management": "Διαχείριση ρόλων",
|
||||
"create_new": "Δημιουργία νέου",
|
||||
"name": "Όνομα",
|
||||
"created_at": "Δημιουργήθηκε στις",
|
||||
"actions": "Ενέργειες",
|
||||
"open_menu": "Άνοιγμα μενού",
|
||||
"view_policy": "Προβολή πολιτικής",
|
||||
"edit": "Επεξεργασία",
|
||||
"delete": "Διαγραφή",
|
||||
"no_roles_found": "Δεν βρέθηκαν ρόλοι.",
|
||||
"role_policy": "Πολιτική ρόλου",
|
||||
"viewing_policy_for_role": "Προβολή πολιτικής για τον ρόλο: {{name}}",
|
||||
"create": "Δημιουργία",
|
||||
"role": "Ρόλος",
|
||||
"edit_description": "Κάντε αλλαγές στον ρόλο εδώ.",
|
||||
"create_description": "Προσθέστε έναν νέο ρόλο στο σύστημα.",
|
||||
"delete_confirmation_title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον ρόλο;",
|
||||
"delete_confirmation_description": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. Αυτό θα διαγράψει οριστικά τον ρόλο.",
|
||||
"deleting": "Διαγραφή",
|
||||
"confirm": "Επιβεβαίωση",
|
||||
"cancel": "Άκυρο"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "Ρυθμίσεις συστήματος",
|
||||
"system_settings": "Ρυθμίσεις συστήματος",
|
||||
"description": "Διαχείριση καθολικών ρυθμίσεων εφαρμογής.",
|
||||
"language": "Γλώσσα",
|
||||
"default_theme": "Προεπιλεγμένο θέμα",
|
||||
"light": "Φωτεινό",
|
||||
"dark": "Σκοτεινό",
|
||||
"system": "Σύστημα",
|
||||
"support_email": "Email υποστήριξης",
|
||||
"saving": "Αποθήκευση",
|
||||
"save_changes": "Αποθήκευση αλλαγών"
|
||||
},
|
||||
"users": {
|
||||
"title": "Διαχείριση χρηστών",
|
||||
"user_management": "Διαχείριση χρηστών",
|
||||
"create_new": "Δημιουργία νέου",
|
||||
"name": "Όνομα",
|
||||
"email": "Email",
|
||||
"role": "Ρόλος",
|
||||
"created_at": "Δημιουργήθηκε στις",
|
||||
"actions": "Ενέργειες",
|
||||
"open_menu": "Άνοιγμα μενού",
|
||||
"edit": "Επεξεργασία",
|
||||
"delete": "Διαγραφή",
|
||||
"no_users_found": "Δεν βρέθηκαν χρήστες.",
|
||||
"create": "Δημιουργία",
|
||||
"user": "Χρήστης",
|
||||
"edit_description": "Κάντε αλλαγές στον χρήστη εδώ.",
|
||||
"create_description": "Προσθέστε έναν νέο χρήστη στο σύστημα.",
|
||||
"delete_confirmation_title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον χρήστη;",
|
||||
"delete_confirmation_description": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. Αυτό θα διαγράψει οριστικά τον χρήστη και θα αφαιρέσει τα δεδομένα του από τους διακομιστές μας.",
|
||||
"deleting": "Διαγραφή",
|
||||
"confirm": "Επιβεβαίωση",
|
||||
"cancel": "Άκυρο"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "Εισερχόμενα email",
|
||||
"storage_used": "Χρησιμοποιημένος χώρος αποθήκευσης",
|
||||
"emails": "Email"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Υποβολή...",
|
||||
"submit": "Υποβολή",
|
||||
"save": "Αποθήκευση"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "Φόρτωση προεπισκόπησης email...",
|
||||
"render_error": "Δεν ήταν δυνατή η απόδοση της προεπισκόπησης email.",
|
||||
"not_available": "Το ακατέργαστο αρχείο .eml δεν είναι διαθέσιμο για αυτό το email."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "Με επιφύλαξη παντός δικαιώματος."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "Γενικό IMAP",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "Εισαγωγή PST",
|
||||
"provider_eml_import": "Εισαγωγή EML",
|
||||
"select_provider": "Επιλέξτε έναν πάροχο",
|
||||
"service_account_key": "Κλειδί λογαριασμού υπηρεσίας (JSON)",
|
||||
"service_account_key_placeholder": "Επικολλήστε το περιεχόμενο JSON του κλειδιού του λογαριασμού υπηρεσίας σας",
|
||||
"impersonated_admin_email": "Email διαχειριστή που έχει πλαστοπροσωπηθεί",
|
||||
"client_id": "Αναγνωριστικό εφαρμογής (πελάτη)",
|
||||
"client_secret": "Τιμή μυστικού πελάτη",
|
||||
"client_secret_placeholder": "Εισαγάγετε την τιμή του μυστικού, όχι το αναγνωριστικό του μυστικού",
|
||||
"tenant_id": "Αναγνωριστικό καταλόγου (μισθωτή)",
|
||||
"host": "Κεντρικός υπολογιστής",
|
||||
"port": "Θύρα",
|
||||
"username": "Όνομα χρήστη",
|
||||
"use_tls": "Χρήση TLS",
|
||||
"pst_file": "Αρχείο PST",
|
||||
"eml_file": "Αρχείο EML",
|
||||
"heads_up": "Προσοχή!",
|
||||
"org_wide_warning": "Λάβετε υπόψη ότι αυτή είναι μια λειτουργία σε επίπεδο οργανισμού. Αυτό το είδος εισαγωγής θα εισαγάγει και θα ευρετηριάσει <b>όλα</b> τα εισερχόμενα email στον οργανισμό σας. Εάν θέλετε να εισαγάγετε μόνο συγκεκριμένα εισερχόμενα email, χρησιμοποιήστε τη σύνδεση IMAP.",
|
||||
"upload_failed": "Η μεταφόρτωση απέτυχε, δοκιμάστε ξανά"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Πολιτικές (JSON)",
|
||||
"invalid_json": "Μη έγκυρη μορφή JSON για τις πολιτικές."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Εναλλαγή θέματος"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Επιλέξτε έναν ρόλο"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Εγκατάσταση",
|
||||
"description": "Ρυθμίστε τον αρχικό λογαριασμό διαχειριστή για το Open Archiver.",
|
||||
"welcome": "Καλώς ορίσατε",
|
||||
"create_admin_account": "Δημιουργήστε τον πρώτο λογαριασμό διαχειριστή για να ξεκινήσετε.",
|
||||
"first_name": "Όνομα",
|
||||
"last_name": "Επώνυμο",
|
||||
"email": "Email",
|
||||
"password": "Κωδικός πρόσβασης",
|
||||
"creating_account": "Δημιουργία λογαριασμού",
|
||||
"create_account": "Δημιουργία λογαριασμού"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Πίνακας ελέγχου",
|
||||
"ingestions": "Εισαγωγές",
|
||||
"archived_emails": "Αρχειοθετημένα email",
|
||||
"search": "Αναζήτηση",
|
||||
"settings": "Ρυθμίσεις",
|
||||
"system": "Σύστημα",
|
||||
"users": "Χρήστες",
|
||||
"roles": "Ρόλοι",
|
||||
"logout": "Αποσύνδεση"
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "Αρχειοθετημένα email",
|
||||
"header": "Αρχειοθετημένα email",
|
||||
"select_ingestion_source": "Επιλέξτε μια πηγή εισαγωγής",
|
||||
"date": "Ημερομηνία",
|
||||
"subject": "Θέμα",
|
||||
"sender": "Αποστολέας",
|
||||
"inbox": "Εισερχόμενα",
|
||||
"path": "Διαδρομή",
|
||||
"actions": "Ενέργειες",
|
||||
"view": "Προβολή",
|
||||
"no_emails_found": "Δεν βρέθηκαν αρχειοθετημένα email.",
|
||||
"prev": "Προηγούμενο",
|
||||
"next": "Επόμενο"
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Πίνακας ελέγχου",
|
||||
"meta_description": "Επισκόπηση του αρχείου email σας.",
|
||||
"header": "Πίνακας ελέγχου",
|
||||
"create_ingestion": "Δημιουργία εισαγωγής",
|
||||
"no_ingestion_header": "Δεν έχετε ρυθμίσει καμία πηγή εισαγωγής.",
|
||||
"no_ingestion_text": "Προσθέστε μια πηγή εισαγωγής για να ξεκινήσετε την αρχειοθέτηση των εισερχομένων σας.",
|
||||
"total_emails_archived": "Συνολικά αρχειοθετημένα email",
|
||||
"total_storage_used": "Συνολικός χρησιμοποιημένος χώρος αποθήκευσης",
|
||||
"failed_ingestions": "Αποτυχημένες εισαγωγές (Τελευταίες 7 ημέρες)",
|
||||
"ingestion_history": "Ιστορικό εισαγωγής",
|
||||
"no_ingestion_history": "Δεν υπάρχει διαθέσιμο ιστορικό εισαγωγής.",
|
||||
"storage_by_source": "Αποθήκευση ανά πηγή εισαγωγής",
|
||||
"no_ingestion_sources": "Δεν υπάρχουν διαθέσιμες πηγές εισαγωγής.",
|
||||
"indexed_insights": "Ευρετηριασμένες πληροφορίες",
|
||||
"top_10_senders": "Οι 10 κορυφαίοι αποστολείς",
|
||||
"no_indexed_insights": "Δεν υπάρχουν διαθέσιμες ευρετηριασμένες πληροφορίες."
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/frontend/src/lib/translations/en.json
Normal file
260
packages/frontend/src/lib/translations/en.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"login_tip": "Enter your email below to login to your account.",
|
||||
"email": "Email",
|
||||
"password": "Password"
|
||||
},
|
||||
"common": {
|
||||
"working": "Working"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archive",
|
||||
"no_subject": "No Subject",
|
||||
"from": "From",
|
||||
"sent": "Sent",
|
||||
"recipients": "Recipients",
|
||||
"to": "To",
|
||||
"meta_data": "Meta Data",
|
||||
"folder": "Folder",
|
||||
"tags": "Tags",
|
||||
"size": "Size",
|
||||
"email_preview": "Email Preview",
|
||||
"attachments": "Attachments",
|
||||
"download": "Download",
|
||||
"actions": "Actions",
|
||||
"download_eml": "Download Email (.eml)",
|
||||
"delete_email": "Delete Email",
|
||||
"email_thread": "Email Thread",
|
||||
"delete_confirmation_title": "Are you sure you want to delete this email?",
|
||||
"delete_confirmation_description": "This action cannot be undone and will permanently remove the email and its attachments.",
|
||||
"deleting": "Deleting",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"not_found": "Email not found."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Ingestion Sources",
|
||||
"ingestion_sources": "Ingestion Sources",
|
||||
"bulk_actions": "Bulk Actions",
|
||||
"force_sync": "Force Sync",
|
||||
"delete": "Delete",
|
||||
"create_new": "Create New",
|
||||
"name": "Name",
|
||||
"provider": "Provider",
|
||||
"status": "Status",
|
||||
"active": "Active",
|
||||
"created_at": "Created At",
|
||||
"actions": "Actions",
|
||||
"last_sync_message": "Last sync message",
|
||||
"empty": "Empty",
|
||||
"open_menu": "Open menu",
|
||||
"edit": "Edit",
|
||||
"create": "Create",
|
||||
"ingestion_source": "Ingestion Source",
|
||||
"edit_description": "Make changes to your ingestion source here.",
|
||||
"create_description": "Add a new ingestion source to start archiving emails.",
|
||||
"read": "Read",
|
||||
"docs_here": "docs here",
|
||||
"delete_confirmation_title": "Are you sure you want to delete this ingestion?",
|
||||
"delete_confirmation_description": "This will delete all archived emails, attachments, indexing, and files associated with this ingestion. If you only want to stop syncing new emails, you can pause the ingestion instead.",
|
||||
"deleting": "Deleting",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"bulk_delete_confirmation_title": "Are you sure you want to delete {{count}} selected ingestions?",
|
||||
"bulk_delete_confirmation_description": "This will delete all archived emails, attachments, indexing, and files associated with these ingestions. If you only want to stop syncing new emails, you can pause the ingestions instead."
|
||||
},
|
||||
"search": {
|
||||
"title": "Search",
|
||||
"description": "Search for archived emails.",
|
||||
"email_search": "Email Search",
|
||||
"placeholder": "Search by keyword, sender, recipient...",
|
||||
"search_button": "Search",
|
||||
"search_options": "Search options",
|
||||
"strategy_fuzzy": "Fuzzy",
|
||||
"strategy_verbatim": "Verbatim",
|
||||
"strategy_frequency": "Frequency",
|
||||
"select_strategy": "Select a strategy",
|
||||
"error": "Error",
|
||||
"found_results_in": "Found {{total}} results in {{seconds}}s",
|
||||
"found_results": "Found {{total}} results",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"in_email_body": "In email body",
|
||||
"in_attachment": "In attachment: {{filename}}",
|
||||
"prev": "Prev",
|
||||
"next": "Next"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Role Management",
|
||||
"role_management": "Role Management",
|
||||
"create_new": "Create New",
|
||||
"name": "Name",
|
||||
"created_at": "Created At",
|
||||
"actions": "Actions",
|
||||
"open_menu": "Open menu",
|
||||
"view_policy": "View Policy",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"no_roles_found": "No roles found.",
|
||||
"role_policy": "Role Policy",
|
||||
"viewing_policy_for_role": "Viewing policy for role: {{name}}",
|
||||
"create": "Create",
|
||||
"role": "Role",
|
||||
"edit_description": "Make changes to the role here.",
|
||||
"create_description": "Add a new role to the system.",
|
||||
"delete_confirmation_title": "Are you sure you want to delete this role?",
|
||||
"delete_confirmation_description": "This action cannot be undone. This will permanently delete the role.",
|
||||
"deleting": "Deleting",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "System Settings",
|
||||
"system_settings": "System Settings",
|
||||
"description": "Manage global application settings.",
|
||||
"language": "Language",
|
||||
"default_theme": "Default theme",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System",
|
||||
"support_email": "Support Email",
|
||||
"saving": "Saving",
|
||||
"save_changes": "Save Changes"
|
||||
},
|
||||
"users": {
|
||||
"title": "User Management",
|
||||
"user_management": "User Management",
|
||||
"create_new": "Create New",
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"role": "Role",
|
||||
"created_at": "Created At",
|
||||
"actions": "Actions",
|
||||
"open_menu": "Open menu",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"no_users_found": "No users found.",
|
||||
"create": "Create",
|
||||
"user": "User",
|
||||
"edit_description": "Make changes to the user here.",
|
||||
"create_description": "Add a new user to the system.",
|
||||
"delete_confirmation_title": "Are you sure you want to delete this user?",
|
||||
"delete_confirmation_description": "This action cannot be undone. This will permanently delete the user and remove their data from our servers.",
|
||||
"deleting": "Deleting",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "Emails Ingested",
|
||||
"storage_used": "Storage Used",
|
||||
"emails": "Emails"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Submitting...",
|
||||
"submit": "Submit",
|
||||
"save": "Save"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "Loading email preview...",
|
||||
"render_error": "Could not render email preview.",
|
||||
"not_available": "Raw .eml file not available for this email."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "All rights reserved."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "Generic IMAP",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "PST Import",
|
||||
"provider_eml_import": "EML Import",
|
||||
"select_provider": "Select a provider",
|
||||
"service_account_key": "Service Account Key (JSON)",
|
||||
"service_account_key_placeholder": "Paste your service account key JSON content",
|
||||
"impersonated_admin_email": "Impersonated Admin Email",
|
||||
"client_id": "Application (Client) ID",
|
||||
"client_secret": "Client Secret Value",
|
||||
"client_secret_placeholder": "Enter the secret Value, not the Secret ID",
|
||||
"tenant_id": "Directory (Tenant) ID",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"username": "Username",
|
||||
"use_tls": "Use TLS",
|
||||
"pst_file": "PST File",
|
||||
"eml_file": "EML File",
|
||||
"heads_up": "Heads up!",
|
||||
"org_wide_warning": "Please note that this is an organization-wide operation. This kind of ingestions will import and index <b>all</b> email inboxes in your organization. If you want to import only specific email inboxes, use the IMAP connector.",
|
||||
"upload_failed": "Upload Failed, please try again"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Policies (JSON)",
|
||||
"invalid_json": "Invalid JSON format for policies."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Toggle theme"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Select a role"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Setup",
|
||||
"description": "Set up the initial administrator account for Open Archiver.",
|
||||
"welcome": "Welcome",
|
||||
"create_admin_account": "Create the first administrator account to get started.",
|
||||
"first_name": "First name",
|
||||
"last_name": "Last name",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"creating_account": "Creating Account",
|
||||
"create_account": "Create Account"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Dashboard",
|
||||
"ingestions": "Ingestions",
|
||||
"archived_emails": "Archived emails",
|
||||
"search": "Search",
|
||||
"settings": "Settings",
|
||||
"system": "System",
|
||||
"users": "Users",
|
||||
"roles": "Roles",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "Archived emails",
|
||||
"header": "Archived Emails",
|
||||
"select_ingestion_source": "Select an ingestion source",
|
||||
"date": "Date",
|
||||
"subject": "Subject",
|
||||
"sender": "Sender",
|
||||
"inbox": "Inbox",
|
||||
"path": "Path",
|
||||
"actions": "Actions",
|
||||
"view": "View",
|
||||
"no_emails_found": "No archived emails found.",
|
||||
"prev": "Prev",
|
||||
"next": "Next"
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Dashboard",
|
||||
"meta_description": "Overview of your email archive.",
|
||||
"header": "Dashboard",
|
||||
"create_ingestion": "Create an ingestion",
|
||||
"no_ingestion_header": "You don't have any ingestion source set up.",
|
||||
"no_ingestion_text": "Add an ingestion source to start archiving your inboxes.",
|
||||
"total_emails_archived": "Total Emails Archived",
|
||||
"total_storage_used": "Total Storage Used",
|
||||
"failed_ingestions": "Failed Ingestions (Last 7 Days)",
|
||||
"ingestion_history": "Ingestion History",
|
||||
"no_ingestion_history": "No ingestion history available.",
|
||||
"storage_by_source": "Storage by Ingestion Source",
|
||||
"no_ingestion_sources": "No ingestion sources available.",
|
||||
"indexed_insights": "Indexed insights",
|
||||
"top_10_senders": "Top 10 Senders",
|
||||
"no_indexed_insights": "No indexed insights available."
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/frontend/src/lib/translations/es.json
Normal file
260
packages/frontend/src/lib/translations/es.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Iniciar sesión",
|
||||
"login_tip": "Ingrese su correo electrónico a continuación para iniciar sesión en su cuenta.",
|
||||
"email": "Correo electrónico",
|
||||
"password": "Contraseña"
|
||||
},
|
||||
"common": {
|
||||
"working": "Trabajando"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archivo",
|
||||
"no_subject": "Sin asunto",
|
||||
"from": "De",
|
||||
"sent": "Enviado",
|
||||
"recipients": "Destinatarios",
|
||||
"to": "Para",
|
||||
"meta_data": "Metadatos",
|
||||
"folder": "Carpeta",
|
||||
"tags": "Etiquetas",
|
||||
"size": "Tamaño",
|
||||
"email_preview": "Vista previa del correo electrónico",
|
||||
"attachments": "Archivos adjuntos",
|
||||
"download": "Descargar",
|
||||
"actions": "Acciones",
|
||||
"download_eml": "Descargar correo electrónico (.eml)",
|
||||
"delete_email": "Eliminar correo electrónico",
|
||||
"email_thread": "Hilo de correo electrónico",
|
||||
"delete_confirmation_title": "¿Está seguro de que desea eliminar este correo electrónico?",
|
||||
"delete_confirmation_description": "Esta acción no se puede deshacer y eliminará permanentemente el correo electrónico y sus archivos adjuntos.",
|
||||
"deleting": "Eliminando",
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar",
|
||||
"not_found": "Correo electrónico no encontrado."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Fuentes de ingesta",
|
||||
"ingestion_sources": "Fuentes de ingesta",
|
||||
"bulk_actions": "Acciones masivas",
|
||||
"force_sync": "Forzar sincronización",
|
||||
"delete": "Eliminar",
|
||||
"create_new": "Crear nuevo",
|
||||
"name": "Nombre",
|
||||
"provider": "Proveedor",
|
||||
"status": "Estado",
|
||||
"active": "Activo",
|
||||
"created_at": "Creado el",
|
||||
"actions": "Acciones",
|
||||
"last_sync_message": "Último mensaje de sincronización",
|
||||
"empty": "Vacío",
|
||||
"open_menu": "Abrir menú",
|
||||
"edit": "Editar",
|
||||
"create": "Crear",
|
||||
"ingestion_source": "Fuente de ingesta",
|
||||
"edit_description": "Realice cambios en su fuente de ingesta aquí.",
|
||||
"create_description": "Agregue una nueva fuente de ingesta para comenzar a archivar correos electrónicos.",
|
||||
"read": "Leer",
|
||||
"docs_here": "documentos aquí",
|
||||
"delete_confirmation_title": "¿Está seguro de que desea eliminar esta ingesta?",
|
||||
"delete_confirmation_description": "Esto eliminará todos los correos electrónicos archivados, archivos adjuntos, indexación y archivos asociados con esta ingesta. Si solo desea dejar de sincronizar nuevos correos electrónicos, puede pausar la ingesta en su lugar.",
|
||||
"deleting": "Eliminando",
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar",
|
||||
"bulk_delete_confirmation_title": "¿Está seguro de que desea eliminar {{count}} ingestas seleccionadas?",
|
||||
"bulk_delete_confirmation_description": "Esto eliminará todos los correos electrónicos archivados, archivos adjuntos, indexación y archivos asociados con estas ingestas. Si solo desea dejar de sincronizar nuevos correos electrónicos, puede pausar las ingestas en su lugar."
|
||||
},
|
||||
"search": {
|
||||
"title": "Buscar",
|
||||
"description": "Buscar correos electrónicos archivados.",
|
||||
"email_search": "Búsqueda de correo electrónico",
|
||||
"placeholder": "Buscar por palabra clave, remitente, destinatario...",
|
||||
"search_button": "Buscar",
|
||||
"search_options": "Opciones de búsqueda",
|
||||
"strategy_fuzzy": "Difuso",
|
||||
"strategy_verbatim": "Literal",
|
||||
"strategy_frequency": "Frecuencia",
|
||||
"select_strategy": "Seleccione una estrategia",
|
||||
"error": "Error",
|
||||
"found_results_in": "Se encontraron {{total}} resultados en {{seconds}}s",
|
||||
"found_results": "Se encontraron {{total}} resultados",
|
||||
"from": "De",
|
||||
"to": "Para",
|
||||
"in_email_body": "En el cuerpo del correo electrónico",
|
||||
"in_attachment": "En el archivo adjunto: {{filename}}",
|
||||
"prev": "Anterior",
|
||||
"next": "Siguiente"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Gestión de roles",
|
||||
"role_management": "Gestión de roles",
|
||||
"create_new": "Crear nuevo",
|
||||
"name": "Nombre",
|
||||
"created_at": "Creado el",
|
||||
"actions": "Acciones",
|
||||
"open_menu": "Abrir menú",
|
||||
"view_policy": "Ver política",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"no_roles_found": "No se encontraron roles.",
|
||||
"role_policy": "Política de roles",
|
||||
"viewing_policy_for_role": "Viendo la política para el rol: {{name}}",
|
||||
"create": "Crear",
|
||||
"role": "Rol",
|
||||
"edit_description": "Realice cambios en el rol aquí.",
|
||||
"create_description": "Agregue un nuevo rol al sistema.",
|
||||
"delete_confirmation_title": "¿Está seguro de que desea eliminar este rol?",
|
||||
"delete_confirmation_description": "Esta acción no se puede deshacer. Esto eliminará permanentemente el rol.",
|
||||
"deleting": "Eliminando",
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "Configuración del sistema",
|
||||
"system_settings": "Configuración del sistema",
|
||||
"description": "Administrar la configuración global de la aplicación.",
|
||||
"language": "Idioma",
|
||||
"default_theme": "Tema predeterminado",
|
||||
"light": "Claro",
|
||||
"dark": "Oscuro",
|
||||
"system": "Sistema",
|
||||
"support_email": "Correo electrónico de soporte",
|
||||
"saving": "Guardando",
|
||||
"save_changes": "Guardar cambios"
|
||||
},
|
||||
"users": {
|
||||
"title": "Gestión de usuarios",
|
||||
"user_management": "Gestión de usuarios",
|
||||
"create_new": "Crear nuevo",
|
||||
"name": "Nombre",
|
||||
"email": "Correo electrónico",
|
||||
"role": "Rol",
|
||||
"created_at": "Creado el",
|
||||
"actions": "Acciones",
|
||||
"open_menu": "Abrir menú",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"no_users_found": "No se encontraron usuarios.",
|
||||
"create": "Crear",
|
||||
"user": "Usuario",
|
||||
"edit_description": "Realice cambios en el usuario aquí.",
|
||||
"create_description": "Agregue un nuevo usuario al sistema.",
|
||||
"delete_confirmation_title": "¿Está seguro de que desea eliminar este usuario?",
|
||||
"delete_confirmation_description": "Esta acción no se puede deshacer. Esto eliminará permanentemente al usuario y eliminará sus datos de nuestros servidores.",
|
||||
"deleting": "Eliminando",
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configuración",
|
||||
"description": "Configure la cuenta de administrador inicial para Open Archiver.",
|
||||
"welcome": "Bienvenido",
|
||||
"create_admin_account": "Cree la primera cuenta de administrador para comenzar.",
|
||||
"first_name": "Nombre",
|
||||
"last_name": "Apellido",
|
||||
"email": "Correo electrónico",
|
||||
"password": "Contraseña",
|
||||
"creating_account": "Creando cuenta",
|
||||
"create_account": "Crear cuenta"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Tablero",
|
||||
"ingestions": "Ingestas",
|
||||
"archived_emails": "Correos electrónicos archivados",
|
||||
"search": "Buscar",
|
||||
"settings": "Configuración",
|
||||
"system": "Sistema",
|
||||
"users": "Usuarios",
|
||||
"roles": "Roles",
|
||||
"logout": "Cerrar sesión"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "Correos electrónicos ingeridos",
|
||||
"storage_used": "Almacenamiento utilizado",
|
||||
"emails": "Correos electrónicos"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Enviando...",
|
||||
"submit": "Enviar",
|
||||
"save": "Guardar"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "Cargando vista previa del correo electrónico...",
|
||||
"render_error": "No se pudo renderizar la vista previa del correo electrónico.",
|
||||
"not_available": "El archivo .eml sin procesar no está disponible para este correo electrónico."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "Todos los derechos reservados."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "IMAP genérico",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "Importación de PST",
|
||||
"provider_eml_import": "Importación de EML",
|
||||
"select_provider": "Seleccione un proveedor",
|
||||
"service_account_key": "Clave de cuenta de servicio (JSON)",
|
||||
"service_account_key_placeholder": "Pegue el contenido JSON de su clave de cuenta de servicio",
|
||||
"impersonated_admin_email": "Correo electrónico de administrador suplantado",
|
||||
"client_id": "ID de aplicación (cliente)",
|
||||
"client_secret": "Valor secreto del cliente",
|
||||
"client_secret_placeholder": "Ingrese el valor secreto, no el ID secreto",
|
||||
"tenant_id": "ID de directorio (inquilino)",
|
||||
"host": "Host",
|
||||
"port": "Puerto",
|
||||
"username": "Nombre de usuario",
|
||||
"use_tls": "Usar TLS",
|
||||
"pst_file": "Archivo PST",
|
||||
"eml_file": "Archivo EML",
|
||||
"heads_up": "¡Atención!",
|
||||
"org_wide_warning": "Tenga en cuenta que esta es una operación para toda la organización. Este tipo de ingestas importará e indexará <b>todos</b> los buzones de correo electrónico de su organización. Si desea importar solo buzones de correo electrónico específicos, utilice el conector IMAP.",
|
||||
"upload_failed": "Error al cargar, por favor intente de nuevo"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Políticas (JSON)",
|
||||
"invalid_json": "Formato JSON no válido para las políticas."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Cambiar tema"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Seleccione un rol"
|
||||
}
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Tablero",
|
||||
"meta_description": "Resumen de su archivo de correo electrónico.",
|
||||
"header": "Tablero",
|
||||
"create_ingestion": "Crear una ingesta",
|
||||
"no_ingestion_header": "No tiene ninguna fuente de ingesta configurada.",
|
||||
"no_ingestion_text": "Agregue una fuente de ingesta para comenzar a archivar sus bandejas de entrada.",
|
||||
"total_emails_archived": "Total de correos electrónicos archivados",
|
||||
"total_storage_used": "Almacenamiento total utilizado",
|
||||
"failed_ingestions": "Ingestas fallidas (últimos 7 días)",
|
||||
"ingestion_history": "Historial de ingesta",
|
||||
"no_ingestion_history": "No hay historial de ingesta disponible.",
|
||||
"storage_by_source": "Almacenamiento por fuente de ingesta",
|
||||
"no_ingestion_sources": "No hay fuentes de ingesta disponibles.",
|
||||
"indexed_insights": "Información indexada",
|
||||
"top_10_senders": "Los 10 principales remitentes",
|
||||
"no_indexed_insights": "No hay información indexada disponible."
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "Correos electrónicos archivados",
|
||||
"header": "Correos electrónicos archivados",
|
||||
"select_ingestion_source": "Seleccione una fuente de ingesta",
|
||||
"date": "Fecha",
|
||||
"subject": "Asunto",
|
||||
"sender": "Remitente",
|
||||
"inbox": "Bandeja de entrada",
|
||||
"path": "Ruta",
|
||||
"actions": "Acciones",
|
||||
"view": "Ver",
|
||||
"no_emails_found": "No se encontraron correos electrónicos archivados.",
|
||||
"prev": "Anterior",
|
||||
"next": "Siguiente"
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/frontend/src/lib/translations/et.json
Normal file
260
packages/frontend/src/lib/translations/et.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Logi sisse",
|
||||
"login_tip": "Oma kontole sisselogimiseks sisestage allpool oma e-posti aadress.",
|
||||
"email": "E-post",
|
||||
"password": "Parool"
|
||||
},
|
||||
"common": {
|
||||
"working": "Töötan"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Arhiiv",
|
||||
"no_subject": "Teema puudub",
|
||||
"from": "Kellelt",
|
||||
"sent": "Saadetud",
|
||||
"recipients": "Saajad",
|
||||
"to": "Kellele",
|
||||
"meta_data": "Metaandmed",
|
||||
"folder": "Kaust",
|
||||
"tags": "Sildid",
|
||||
"size": "Suurus",
|
||||
"email_preview": "E-kirja eelvaade",
|
||||
"attachments": "Manused",
|
||||
"download": "Laadi alla",
|
||||
"actions": "Toimingud",
|
||||
"download_eml": "Laadi alla e-kiri (.eml)",
|
||||
"delete_email": "Kustuta e-kiri",
|
||||
"email_thread": "E-kirja lõim",
|
||||
"delete_confirmation_title": "Kas olete kindel, et soovite selle e-kirja kustutada?",
|
||||
"delete_confirmation_description": "Seda toimingut ei saa tagasi võtta ja see eemaldab e-kirja ja selle manused jäädavalt.",
|
||||
"deleting": "Kustutamine",
|
||||
"confirm": "Kinnita",
|
||||
"cancel": "Tühista",
|
||||
"not_found": "E-kirja ei leitud."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Sissevõtuallikad",
|
||||
"ingestion_sources": "Sissevõtuallikad",
|
||||
"bulk_actions": "Hulgitoimingud",
|
||||
"force_sync": "Sunni sünkroonimine",
|
||||
"delete": "Kustuta",
|
||||
"create_new": "Loo uus",
|
||||
"name": "Nimi",
|
||||
"provider": "Pakkuja",
|
||||
"status": "Olek",
|
||||
"active": "Aktiivne",
|
||||
"created_at": "Loodud",
|
||||
"actions": "Toimingud",
|
||||
"last_sync_message": "Viimane sünkroonimissõnum",
|
||||
"empty": "Tühi",
|
||||
"open_menu": "Ava menüü",
|
||||
"edit": "Muuda",
|
||||
"create": "Loo",
|
||||
"ingestion_source": "Sissevõtuallikas",
|
||||
"edit_description": "Tehke siin oma sissevõtuallikas muudatusi.",
|
||||
"create_description": "E-kirjade arhiveerimise alustamiseks lisage uus sissevõtuallikas.",
|
||||
"read": "Loe",
|
||||
"docs_here": "dokumendid siin",
|
||||
"delete_confirmation_title": "Kas olete kindel, et soovite selle sissevõtu kustutada?",
|
||||
"delete_confirmation_description": "See kustutab kõik selle sissevõtuga seotud arhiveeritud e-kirjad, manused, indekseerimise ja failid. Kui soovite ainult uute e-kirjade sünkroonimise peatada, saate sissevõtu peatada.",
|
||||
"deleting": "Kustutamine",
|
||||
"confirm": "Kinnita",
|
||||
"cancel": "Tühista",
|
||||
"bulk_delete_confirmation_title": "Kas olete kindel, et soovite kustutada {{count}} valitud sissevõttu?",
|
||||
"bulk_delete_confirmation_description": "See kustutab kõik nende sissevõttudega seotud arhiveeritud e-kirjad, manused, indekseerimise ja failid. Kui soovite ainult uute e-kirjade sünkroonimise peatada, saate sissevõtud peatada."
|
||||
},
|
||||
"search": {
|
||||
"title": "Otsing",
|
||||
"description": "Otsige arhiveeritud e-kirju.",
|
||||
"email_search": "E-kirja otsing",
|
||||
"placeholder": "Otsige märksõna, saatja, saaja järgi...",
|
||||
"search_button": "Otsi",
|
||||
"search_options": "Otsinguvalikud",
|
||||
"strategy_fuzzy": "Hägune",
|
||||
"strategy_verbatim": "Sõnasõnaline",
|
||||
"strategy_frequency": "Sagedus",
|
||||
"select_strategy": "Valige strateegia",
|
||||
"error": "Viga",
|
||||
"found_results_in": "Leiti {{total}} tulemust {{seconds}} sekundiga",
|
||||
"found_results": "Leiti {{total}} tulemust",
|
||||
"from": "Kellelt",
|
||||
"to": "Kellele",
|
||||
"in_email_body": "E-kirja sisus",
|
||||
"in_attachment": "Manuses: {{filename}}",
|
||||
"prev": "Eelmine",
|
||||
"next": "Järgmine"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Rollide haldamine",
|
||||
"role_management": "Rollide haldamine",
|
||||
"create_new": "Loo uus",
|
||||
"name": "Nimi",
|
||||
"created_at": "Loodud",
|
||||
"actions": "Toimingud",
|
||||
"open_menu": "Ava menüü",
|
||||
"view_policy": "Vaata poliitikat",
|
||||
"edit": "Muuda",
|
||||
"delete": "Kustuta",
|
||||
"no_roles_found": "Rolle ei leitud.",
|
||||
"role_policy": "Rollipoliitika",
|
||||
"viewing_policy_for_role": "Rolli poliitika vaatamine: {{name}}",
|
||||
"create": "Loo",
|
||||
"role": "Roll",
|
||||
"edit_description": "Tehke siin rollis muudatusi.",
|
||||
"create_description": "Lisage süsteemi uus roll.",
|
||||
"delete_confirmation_title": "Kas olete kindel, et soovite selle rolli kustutada?",
|
||||
"delete_confirmation_description": "Seda toimingut ei saa tagasi võtta. See kustutab rolli jäädavalt.",
|
||||
"deleting": "Kustutamine",
|
||||
"confirm": "Kinnita",
|
||||
"cancel": "Tühista"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "Süsteemi seaded",
|
||||
"system_settings": "Süsteemi seaded",
|
||||
"description": "Hallake globaalseid rakenduse seadeid.",
|
||||
"language": "Keel",
|
||||
"default_theme": "Vaiketeema",
|
||||
"light": "Hele",
|
||||
"dark": "Tume",
|
||||
"system": "Süsteem",
|
||||
"support_email": "Tugi e-post",
|
||||
"saving": "Salvestamine",
|
||||
"save_changes": "Salvesta muudatused"
|
||||
},
|
||||
"users": {
|
||||
"title": "Kasutajate haldamine",
|
||||
"user_management": "Kasutajate haldamine",
|
||||
"create_new": "Loo uus",
|
||||
"name": "Nimi",
|
||||
"email": "E-post",
|
||||
"role": "Roll",
|
||||
"created_at": "Loodud",
|
||||
"actions": "Toimingud",
|
||||
"open_menu": "Ava menüü",
|
||||
"edit": "Muuda",
|
||||
"delete": "Kustuta",
|
||||
"no_users_found": "Kasutajaid ei leitud.",
|
||||
"create": "Loo",
|
||||
"user": "Kasutaja",
|
||||
"edit_description": "Tehke siin kasutajas muudatusi.",
|
||||
"create_description": "Lisage süsteemi uus kasutaja.",
|
||||
"delete_confirmation_title": "Kas olete kindel, et soovite selle kasutaja kustutada?",
|
||||
"delete_confirmation_description": "Seda toimingut ei saa tagasi võtta. See kustutab kasutaja jäädavalt ja eemaldab tema andmed meie serveritest.",
|
||||
"deleting": "Kustutamine",
|
||||
"confirm": "Kinnita",
|
||||
"cancel": "Tühista"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Seadistamine",
|
||||
"description": "Seadistage Open Archiveri esialgne administraatorikonto.",
|
||||
"welcome": "Tere tulemast",
|
||||
"create_admin_account": "Alustamiseks looge esimene administraatorikonto.",
|
||||
"first_name": "Eesnimi",
|
||||
"last_name": "Perekonnanimi",
|
||||
"email": "E-post",
|
||||
"password": "Parool",
|
||||
"creating_account": "Konto loomine",
|
||||
"create_account": "Loo konto"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Armatuurlaud",
|
||||
"ingestions": "Sissevõtud",
|
||||
"archived_emails": "Arhiveeritud e-kirjad",
|
||||
"search": "Otsing",
|
||||
"settings": "Seaded",
|
||||
"system": "Süsteem",
|
||||
"users": "Kasutajad",
|
||||
"roles": "Rollid",
|
||||
"logout": "Logi välja"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "Sissevõetud e-kirjad",
|
||||
"storage_used": "Kasutatud salvestusruum",
|
||||
"emails": "E-kirjad"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Esitamine...",
|
||||
"submit": "Esita",
|
||||
"save": "Salvesta"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "E-kirja eelvaate laadimine...",
|
||||
"render_error": "E-kirja eelvaadet ei saanud renderdada.",
|
||||
"not_available": "Selle e-kirja jaoks pole toores .eml-faili saadaval."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "Kõik õigused kaitstud."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "Üldine IMAP",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "PST import",
|
||||
"provider_eml_import": "EML import",
|
||||
"select_provider": "Valige pakkuja",
|
||||
"service_account_key": "Teenusekonto võti (JSON)",
|
||||
"service_account_key_placeholder": "Kleepige oma teenusekonto võtme JSON-sisu",
|
||||
"impersonated_admin_email": "Impersoniseeritud administraatori e-post",
|
||||
"client_id": "Rakenduse (kliendi) ID",
|
||||
"client_secret": "Kliendi salajane väärtus",
|
||||
"client_secret_placeholder": "Sisestage salajane väärtus, mitte salajane ID",
|
||||
"tenant_id": "Kataloogi (üürniku) ID",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"username": "Kasutajanimi",
|
||||
"use_tls": "Kasuta TLS-i",
|
||||
"pst_file": "PST-fail",
|
||||
"eml_file": "EML-fail",
|
||||
"heads_up": "Tähelepanu!",
|
||||
"org_wide_warning": "Pange tähele, et see on kogu organisatsiooni hõlmav toiming. Seda tüüpi sissevõtud impordivad ja indekseerivad <b>kõik</b> teie organisatsiooni e-posti postkastid. Kui soovite importida ainult konkreetseid e-posti postkaste, kasutage IMAP-konnektorit.",
|
||||
"upload_failed": "Üleslaadimine ebaõnnestus, proovige uuesti"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Poliitikad (JSON)",
|
||||
"invalid_json": "Poliitikate jaoks kehtetu JSON-vorming."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Vaheta teemat"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Valige roll"
|
||||
}
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Armatuurlaud",
|
||||
"meta_description": "Ülevaade teie e-posti arhiivist.",
|
||||
"header": "Armatuurlaud",
|
||||
"create_ingestion": "Loo sissevõtt",
|
||||
"no_ingestion_header": "Teil pole ühtegi sissevõtuallikat seadistatud.",
|
||||
"no_ingestion_text": "Postkastide arhiveerimise alustamiseks lisage sissevõtuallikas.",
|
||||
"total_emails_archived": "Arhiveeritud e-kirjade koguarv",
|
||||
"total_storage_used": "Kasutatud salvestusruum kokku",
|
||||
"failed_ingestions": "Ebaõnnestunud sissevõtud (viimased 7 päeva)",
|
||||
"ingestion_history": "Sissevõtuajalugu",
|
||||
"no_ingestion_history": "Sissevõtuajalugu pole saadaval.",
|
||||
"storage_by_source": "Salvestusruum sissevõtuallika järgi",
|
||||
"no_ingestion_sources": "Sissevõtuallikaid pole saadaval.",
|
||||
"indexed_insights": "Indekseeritud ülevaated",
|
||||
"top_10_senders": "Top 10 saatjat",
|
||||
"no_indexed_insights": "Indekseeritud ülevaateid pole saadaval."
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "Arhiveeritud e-kirjad",
|
||||
"header": "Arhiveeritud e-kirjad",
|
||||
"select_ingestion_source": "Valige sissevõtuallikas",
|
||||
"date": "Kuupäev",
|
||||
"subject": "Teema",
|
||||
"sender": "Saatja",
|
||||
"inbox": "Postkast",
|
||||
"path": "Tee",
|
||||
"actions": "Toimingud",
|
||||
"view": "Vaade",
|
||||
"no_emails_found": "Arhiveeritud e-kirju ei leitud.",
|
||||
"prev": "Eelmine",
|
||||
"next": "Järgmine"
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/frontend/src/lib/translations/fr.json
Normal file
260
packages/frontend/src/lib/translations/fr.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Connexion",
|
||||
"login_tip": "Entrez votre email ci-dessous pour vous connecter à votre compte.",
|
||||
"email": "Email",
|
||||
"password": "Mot de passe"
|
||||
},
|
||||
"common": {
|
||||
"working": "Travail en cours"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archive",
|
||||
"no_subject": "Pas de sujet",
|
||||
"from": "De",
|
||||
"sent": "Envoyé",
|
||||
"recipients": "Destinataires",
|
||||
"to": "À",
|
||||
"meta_data": "Métadonnées",
|
||||
"folder": "Dossier",
|
||||
"tags": "Tags",
|
||||
"size": "Taille",
|
||||
"email_preview": "Aperçu de l'email",
|
||||
"attachments": "Pièces jointes",
|
||||
"download": "Télécharger",
|
||||
"actions": "Actions",
|
||||
"download_eml": "Télécharger l'email (.eml)",
|
||||
"delete_email": "Supprimer l'email",
|
||||
"email_thread": "Fil de discussion",
|
||||
"delete_confirmation_title": "Êtes-vous sûr de vouloir supprimer cet email ?",
|
||||
"delete_confirmation_description": "Cette action est irréversible et supprimera définitivement l'email et ses pièces jointes.",
|
||||
"deleting": "Suppression en cours",
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler",
|
||||
"not_found": "Email non trouvé."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Sources d'ingestion",
|
||||
"ingestion_sources": "Sources d'ingestion",
|
||||
"bulk_actions": "Actions en masse",
|
||||
"force_sync": "Forcer la synchronisation",
|
||||
"delete": "Supprimer",
|
||||
"create_new": "Créer",
|
||||
"name": "Nom",
|
||||
"provider": "Fournisseur",
|
||||
"status": "Statut",
|
||||
"active": "Actif",
|
||||
"created_at": "Créé le",
|
||||
"actions": "Actions",
|
||||
"last_sync_message": "Dernier message de synchronisation",
|
||||
"empty": "Vide",
|
||||
"open_menu": "Ouvrir le menu",
|
||||
"edit": "Modifier",
|
||||
"create": "Créer",
|
||||
"ingestion_source": "Source d'ingestion",
|
||||
"edit_description": "Modifiez votre source d'ingestion ici.",
|
||||
"create_description": "Ajoutez une nouvelle source d'ingestion pour commencer à archiver les emails.",
|
||||
"read": "Lire",
|
||||
"docs_here": "docs ici",
|
||||
"delete_confirmation_title": "Êtes-vous sûr de vouloir supprimer cette ingestion ?",
|
||||
"delete_confirmation_description": "Cela supprimera tous les emails archivés, les pièces jointes, l'indexation et les fichiers associés à cette ingestion. Si vous souhaitez uniquement arrêter la synchronisation des nouveaux emails, vous pouvez suspendre l'ingestion à la place.",
|
||||
"deleting": "Suppression en cours",
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler",
|
||||
"bulk_delete_confirmation_title": "Êtes-vous sûr de vouloir supprimer {{count}} ingestions sélectionnées ?",
|
||||
"bulk_delete_confirmation_description": "Cela supprimera tous les emails archivés, les pièces jointes, l'indexation et les fichiers associés à ces ingestions. Si vous souhaitez uniquement arrêter la synchronisation des nouveaux emails, vous pouvez suspendre les ingestions à la place."
|
||||
},
|
||||
"search": {
|
||||
"title": "Recherche",
|
||||
"description": "Rechercher des emails archivés.",
|
||||
"email_search": "Recherche d'emails",
|
||||
"placeholder": "Rechercher par mot-clé, expéditeur, destinataire...",
|
||||
"search_button": "Rechercher",
|
||||
"search_options": "Options de recherche",
|
||||
"strategy_fuzzy": "Floue",
|
||||
"strategy_verbatim": "Textuelle",
|
||||
"strategy_frequency": "Fréquence",
|
||||
"select_strategy": "Sélectionnez une stratégie",
|
||||
"error": "Erreur",
|
||||
"found_results_in": "{{total}} résultats trouvés en {{seconds}}s",
|
||||
"found_results": "{{total}} résultats trouvés",
|
||||
"from": "De",
|
||||
"to": "À",
|
||||
"in_email_body": "Dans le corps de l'email",
|
||||
"in_attachment": "Dans la pièce jointe : {{filename}}",
|
||||
"prev": "Préc",
|
||||
"next": "Suiv"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Gestion des rôles",
|
||||
"role_management": "Gestion des rôles",
|
||||
"create_new": "Créer",
|
||||
"name": "Nom",
|
||||
"created_at": "Créé le",
|
||||
"actions": "Actions",
|
||||
"open_menu": "Ouvrir le menu",
|
||||
"view_policy": "Voir la politique",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"no_roles_found": "Aucun rôle trouvé.",
|
||||
"role_policy": "Politique de rôle",
|
||||
"viewing_policy_for_role": "Affichage de la politique pour le rôle : {{name}}",
|
||||
"create": "Créer",
|
||||
"role": "Rôle",
|
||||
"edit_description": "Modifiez le rôle ici.",
|
||||
"create_description": "Ajoutez un nouveau rôle au système.",
|
||||
"delete_confirmation_title": "Êtes-vous sûr de vouloir supprimer ce rôle ?",
|
||||
"delete_confirmation_description": "Cette action est irréversible. Cela supprimera définitivement le rôle.",
|
||||
"deleting": "Suppression en cours",
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "Paramètres système",
|
||||
"system_settings": "Paramètres système",
|
||||
"description": "Gérer les paramètres globaux de l'application.",
|
||||
"language": "Langue",
|
||||
"default_theme": "Thème par défaut",
|
||||
"light": "Clair",
|
||||
"dark": "Sombre",
|
||||
"system": "Système",
|
||||
"support_email": "Email de support",
|
||||
"saving": "Enregistrement",
|
||||
"save_changes": "Enregistrer les modifications"
|
||||
},
|
||||
"users": {
|
||||
"title": "Gestion des utilisateurs",
|
||||
"user_management": "Gestion des utilisateurs",
|
||||
"create_new": "Créer",
|
||||
"name": "Nom",
|
||||
"email": "Email",
|
||||
"role": "Rôle",
|
||||
"created_at": "Créé le",
|
||||
"actions": "Actions",
|
||||
"open_menu": "Ouvrir le menu",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"no_users_found": "Aucun utilisateur trouvé.",
|
||||
"create": "Créer",
|
||||
"user": "Utilisateur",
|
||||
"edit_description": "Modifiez l'utilisateur ici.",
|
||||
"create_description": "Ajoutez un nouvel utilisateur au système.",
|
||||
"delete_confirmation_title": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?",
|
||||
"delete_confirmation_description": "Cette action est irréversible. Cela supprimera définitivement l'utilisateur et ses données de nos serveurs.",
|
||||
"deleting": "Suppression en cours",
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configuration",
|
||||
"description": "Configurez le compte administrateur initial pour Open Archiver.",
|
||||
"welcome": "Bienvenue",
|
||||
"create_admin_account": "Créez le premier compte administrateur pour commencer.",
|
||||
"first_name": "Prénom",
|
||||
"last_name": "Nom de famille",
|
||||
"email": "Email",
|
||||
"password": "Mot de passe",
|
||||
"creating_account": "Création du compte",
|
||||
"create_account": "Créer un compte"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Tableau de bord",
|
||||
"ingestions": "Ingestions",
|
||||
"archived_emails": "E-mails archivés",
|
||||
"search": "Recherche",
|
||||
"settings": "Paramètres",
|
||||
"system": "Système",
|
||||
"users": "Utilisateurs",
|
||||
"roles": "Rôles",
|
||||
"logout": "Déconnexion"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "E-mails ingérés",
|
||||
"storage_used": "Stockage utilisé",
|
||||
"emails": "E-mails"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Soumission...",
|
||||
"submit": "Soumettre",
|
||||
"save": "Enregistrer"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "Chargement de l'aperçu de l'email...",
|
||||
"render_error": "Impossible de rendre l'aperçu de l'email.",
|
||||
"not_available": "Le fichier .eml brut n'est pas disponible pour cet email."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "Tous droits réservés."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "IMAP générique",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "Importation PST",
|
||||
"provider_eml_import": "Importation EML",
|
||||
"select_provider": "Sélectionnez un fournisseur",
|
||||
"service_account_key": "Clé de compte de service (JSON)",
|
||||
"service_account_key_placeholder": "Collez le contenu JSON de votre clé de compte de service",
|
||||
"impersonated_admin_email": "Email de l'administrateur impersonné",
|
||||
"client_id": "ID de l'application (client)",
|
||||
"client_secret": "Valeur secrète du client",
|
||||
"client_secret_placeholder": "Entrez la valeur secrète, pas l'ID secret",
|
||||
"tenant_id": "ID du répertoire (locataire)",
|
||||
"host": "Hôte",
|
||||
"port": "Port",
|
||||
"username": "Nom d'utilisateur",
|
||||
"use_tls": "Utiliser TLS",
|
||||
"pst_file": "Fichier PST",
|
||||
"eml_file": "Fichier EML",
|
||||
"heads_up": "Attention !",
|
||||
"org_wide_warning": "Veuillez noter qu'il s'agit d'une opération à l'échelle de l'organisation. Ce type d'ingestion importera et indexera <b>toutes</b> les boîtes de réception de votre organisation. Si vous souhaitez importer uniquement des boîtes de réception spécifiques, utilisez le connecteur IMAP.",
|
||||
"upload_failed": "Échec du téléchargement, veuillez réessayer"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Politiques (JSON)",
|
||||
"invalid_json": "Format JSON invalide pour les politiques."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Changer de thème"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Sélectionnez un rôle"
|
||||
}
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Tableau de bord",
|
||||
"meta_description": "Aperçu de vos archives d'e-mails.",
|
||||
"header": "Tableau de bord",
|
||||
"create_ingestion": "Créer une ingestion",
|
||||
"no_ingestion_header": "Vous n'avez aucune source d'ingestion configurée.",
|
||||
"no_ingestion_text": "Ajoutez une source d'ingestion pour commencer à archiver vos boîtes de réception.",
|
||||
"total_emails_archived": "Total des e-mails archivés",
|
||||
"total_storage_used": "Stockage total utilisé",
|
||||
"failed_ingestions": "Ingestions échouées (7 derniers jours)",
|
||||
"ingestion_history": "Historique d'ingestion",
|
||||
"no_ingestion_history": "Aucun historique d'ingestion disponible.",
|
||||
"storage_by_source": "Stockage par source d'ingestion",
|
||||
"no_ingestion_sources": "Aucune source d'ingestion disponible.",
|
||||
"indexed_insights": "Informations indexées",
|
||||
"top_10_senders": "Top 10 des expéditeurs",
|
||||
"no_indexed_insights": "Aucune information indexée disponible."
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "E-mails archivés",
|
||||
"header": "E-mails archivés",
|
||||
"select_ingestion_source": "Sélectionnez une source d'ingestion",
|
||||
"date": "Date",
|
||||
"subject": "Sujet",
|
||||
"sender": "Expéditeur",
|
||||
"inbox": "Boîte de réception",
|
||||
"path": "Chemin",
|
||||
"actions": "Actions",
|
||||
"view": "Voir",
|
||||
"no_emails_found": "Aucun e-mail archivé trouvé.",
|
||||
"prev": "Préc",
|
||||
"next": "Suiv"
|
||||
}
|
||||
}
|
||||
}
|
||||
129
packages/frontend/src/lib/translations/index.ts
Normal file
129
packages/frontend/src/lib/translations/index.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import i18n from 'sveltekit-i18n';
|
||||
import type { Config } from 'sveltekit-i18n';
|
||||
|
||||
// Import your locales
|
||||
import en from './en.json';
|
||||
import de from './de.json';
|
||||
import es from './es.json';
|
||||
import fr from './fr.json';
|
||||
import it from './it.json';
|
||||
import pt from './pt.json';
|
||||
import nl from './nl.json';
|
||||
import ja from './ja.json';
|
||||
import et from './et.json';
|
||||
import el from './el.json';
|
||||
// This is your config object.
|
||||
// It defines the languages and how to load them.
|
||||
const config: Config = {
|
||||
// Define the loaders for each language
|
||||
loaders: [
|
||||
// English 🇬🇧
|
||||
{
|
||||
locale: 'en',
|
||||
key: 'app', // This key matches the top-level key in your en.json
|
||||
loader: async () => en.app, // We return the nested 'app' object
|
||||
},
|
||||
// German 🇩🇪
|
||||
{
|
||||
locale: 'de',
|
||||
key: 'app', // This key matches the top-level key in your en.json
|
||||
loader: async () => de.app, // We return the nested 'app' object
|
||||
},
|
||||
// Spanish 🇪🇸
|
||||
{
|
||||
locale: 'es',
|
||||
key: 'app',
|
||||
loader: async () => es.app,
|
||||
},
|
||||
// French 🇫🇷
|
||||
{
|
||||
locale: 'fr',
|
||||
key: 'app',
|
||||
loader: async () => fr.app,
|
||||
},
|
||||
// Italian 🇮🇹
|
||||
{
|
||||
locale: 'it',
|
||||
key: 'app',
|
||||
loader: async () => it.app,
|
||||
},
|
||||
// Portuguese 🇵🇹
|
||||
{
|
||||
locale: 'pt',
|
||||
key: 'app',
|
||||
loader: async () => pt.app,
|
||||
},
|
||||
// Dutch 🇳🇱
|
||||
{
|
||||
locale: 'nl',
|
||||
key: 'app',
|
||||
loader: async () => nl.app,
|
||||
},
|
||||
// Japanese 🇯🇵
|
||||
{
|
||||
locale: 'ja',
|
||||
key: 'app',
|
||||
loader: async () => ja.app,
|
||||
},
|
||||
// Estonian 🇪🇪
|
||||
{
|
||||
locale: 'et',
|
||||
key: 'app',
|
||||
loader: async () => et.app,
|
||||
},
|
||||
// Greek 🇬🇷
|
||||
{
|
||||
locale: 'el',
|
||||
key: 'app',
|
||||
loader: async () => el.app,
|
||||
},
|
||||
],
|
||||
fallbackLocale: 'en',
|
||||
};
|
||||
|
||||
// Create the i18n instance.
|
||||
// export const i18nInstance = new i18n(config);
|
||||
|
||||
export const { t, locale, locales, loading, loadTranslations } = new i18n(config);
|
||||
|
||||
// Export the t store for use in components
|
||||
// export const t = i18n.t;
|
||||
|
||||
// import i18n from 'sveltekit-i18n';
|
||||
// import type { Config } from 'sveltekit-i18n';
|
||||
|
||||
// const config: Config = ({
|
||||
// loaders: [
|
||||
// {
|
||||
// locale: 'en',
|
||||
// key: 'app',
|
||||
// loader: async () => (
|
||||
// await import('./en/app.json')
|
||||
// ).default,
|
||||
// },
|
||||
// {
|
||||
// locale: 'en',
|
||||
// key: 'marketing',
|
||||
// loader: async () => (
|
||||
// await import('./en/marketing.json')
|
||||
// ).default,
|
||||
// },
|
||||
// {
|
||||
// locale: 'fr',
|
||||
// key: 'app',
|
||||
// loader: async () => (
|
||||
// await import('./fr/app.json')
|
||||
// ).default,
|
||||
// },
|
||||
// {
|
||||
// locale: 'fr',
|
||||
// key: 'marketing',
|
||||
// loader: async () => (
|
||||
// await import('./fr/marketing.json')
|
||||
// ).default,
|
||||
// },
|
||||
// ],
|
||||
// fallbackLocale: 'en'
|
||||
// });
|
||||
|
||||
// export const { t, locale, locales, loading, loadTranslations } = new i18n(config);
|
||||
260
packages/frontend/src/lib/translations/it.json
Normal file
260
packages/frontend/src/lib/translations/it.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Accesso",
|
||||
"login_tip": "Inserisci la tua email qui sotto per accedere al tuo account.",
|
||||
"email": "Email",
|
||||
"password": "Password"
|
||||
},
|
||||
"common": {
|
||||
"working": "In lavorazione"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archivio",
|
||||
"no_subject": "Nessun oggetto",
|
||||
"from": "Da",
|
||||
"sent": "Inviato",
|
||||
"recipients": "Destinatari",
|
||||
"to": "A",
|
||||
"meta_data": "Metadati",
|
||||
"folder": "Cartella",
|
||||
"tags": "Tag",
|
||||
"size": "Dimensione",
|
||||
"email_preview": "Anteprima email",
|
||||
"attachments": "Allegati",
|
||||
"download": "Scarica",
|
||||
"actions": "Azioni",
|
||||
"download_eml": "Scarica email (.eml)",
|
||||
"delete_email": "Elimina email",
|
||||
"email_thread": "Thread email",
|
||||
"delete_confirmation_title": "Sei sicuro di voler eliminare questa email?",
|
||||
"delete_confirmation_description": "Questa azione non può essere annullata ed eliminerà permanentemente l'email e i suoi allegati.",
|
||||
"deleting": "Eliminazione in corso",
|
||||
"confirm": "Conferma",
|
||||
"cancel": "Annulla",
|
||||
"not_found": "Email non trovata."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Fonti di ingestione",
|
||||
"ingestion_sources": "Fonti di ingestione",
|
||||
"bulk_actions": "Azioni di massa",
|
||||
"force_sync": "Forza sincronizzazione",
|
||||
"delete": "Elimina",
|
||||
"create_new": "Crea nuovo",
|
||||
"name": "Nome",
|
||||
"provider": "Provider",
|
||||
"status": "Stato",
|
||||
"active": "Attivo",
|
||||
"created_at": "Creato il",
|
||||
"actions": "Azioni",
|
||||
"last_sync_message": "Ultimo messaggio di sincronizzazione",
|
||||
"empty": "Vuoto",
|
||||
"open_menu": "Apri menu",
|
||||
"edit": "Modifica",
|
||||
"create": "Crea",
|
||||
"ingestion_source": "Fonte di ingestione",
|
||||
"edit_description": "Apporta modifiche alla tua fonte di ingestione qui.",
|
||||
"create_description": "Aggiungi una nuova fonte di ingestione per iniziare ad archiviare le email.",
|
||||
"read": "Leggi",
|
||||
"docs_here": "documenti qui",
|
||||
"delete_confirmation_title": "Sei sicuro di voler eliminare questa ingestione?",
|
||||
"delete_confirmation_description": "Questo eliminerà tutte le email archiviate, gli allegati, l'indicizzazione e i file associati a questa ingestione. Se desideri solo interrompere la sincronizzazione di nuove email, puoi invece mettere in pausa l'ingestione.",
|
||||
"deleting": "Eliminazione in corso",
|
||||
"confirm": "Conferma",
|
||||
"cancel": "Annulla",
|
||||
"bulk_delete_confirmation_title": "Sei sicuro di voler eliminare {{count}} ingestioni selezionate?",
|
||||
"bulk_delete_confirmation_description": "Questo eliminerà tutte le email archiviate, gli allegati, l'indicizzazione e i file associati a queste ingestioni. Se desideri solo interrompere la sincronizzazione di nuove email, puoi invece mettere in pausa le ingestioni."
|
||||
},
|
||||
"search": {
|
||||
"title": "Cerca",
|
||||
"description": "Cerca email archiviate.",
|
||||
"email_search": "Ricerca email",
|
||||
"placeholder": "Cerca per parola chiave, mittente, destinatario...",
|
||||
"search_button": "Cerca",
|
||||
"search_options": "Opzioni di ricerca",
|
||||
"strategy_fuzzy": "Fuzzy",
|
||||
"strategy_verbatim": "Verbatim",
|
||||
"strategy_frequency": "Frequenza",
|
||||
"select_strategy": "Seleziona una strategia",
|
||||
"error": "Errore",
|
||||
"found_results_in": "Trovati {{total}} risultati in {{seconds}}s",
|
||||
"found_results": "Trovati {{total}} risultati",
|
||||
"from": "Da",
|
||||
"to": "A",
|
||||
"in_email_body": "Nel corpo dell'email",
|
||||
"in_attachment": "Nell'allegato: {{filename}}",
|
||||
"prev": "Prec",
|
||||
"next": "Succ"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Gestione ruoli",
|
||||
"role_management": "Gestione ruoli",
|
||||
"create_new": "Crea nuovo",
|
||||
"name": "Nome",
|
||||
"created_at": "Creato il",
|
||||
"actions": "Azioni",
|
||||
"open_menu": "Apri menu",
|
||||
"view_policy": "Visualizza policy",
|
||||
"edit": "Modifica",
|
||||
"delete": "Elimina",
|
||||
"no_roles_found": "Nessun ruolo trovato.",
|
||||
"role_policy": "Policy ruolo",
|
||||
"viewing_policy_for_role": "Visualizzazione policy per il ruolo: {{name}}",
|
||||
"create": "Crea",
|
||||
"role": "Ruolo",
|
||||
"edit_description": "Apporta modifiche al ruolo qui.",
|
||||
"create_description": "Aggiungi un nuovo ruolo al sistema.",
|
||||
"delete_confirmation_title": "Sei sicuro di voler eliminare questo ruolo?",
|
||||
"delete_confirmation_description": "Questa azione non può essere annullata. Questo eliminerà permanentemente il ruolo.",
|
||||
"deleting": "Eliminazione in corso",
|
||||
"confirm": "Conferma",
|
||||
"cancel": "Annulla"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "Impostazioni di sistema",
|
||||
"system_settings": "Impostazioni di sistema",
|
||||
"description": "Gestisci le impostazioni globali dell'applicazione.",
|
||||
"language": "Lingua",
|
||||
"default_theme": "Tema predefinito",
|
||||
"light": "Chiaro",
|
||||
"dark": "Scuro",
|
||||
"system": "Sistema",
|
||||
"support_email": "Email di supporto",
|
||||
"saving": "Salvataggio",
|
||||
"save_changes": "Salva modifiche"
|
||||
},
|
||||
"users": {
|
||||
"title": "Gestione utenti",
|
||||
"user_management": "Gestione utenti",
|
||||
"create_new": "Crea nuovo",
|
||||
"name": "Nome",
|
||||
"email": "Email",
|
||||
"role": "Ruolo",
|
||||
"created_at": "Creato il",
|
||||
"actions": "Azioni",
|
||||
"open_menu": "Apri menu",
|
||||
"edit": "Modifica",
|
||||
"delete": "Elimina",
|
||||
"no_users_found": "Nessun utente trovato.",
|
||||
"create": "Crea",
|
||||
"user": "Utente",
|
||||
"edit_description": "Apporta modifiche all'utente qui.",
|
||||
"create_description": "Aggiungi un nuovo utente al sistema.",
|
||||
"delete_confirmation_title": "Sei sicuro di voler eliminare questo utente?",
|
||||
"delete_confirmation_description": "Questa azione non può essere annullata. Questo eliminerà permanentemente l'utente e rimuoverà i suoi dati dai nostri server.",
|
||||
"deleting": "Eliminazione in corso",
|
||||
"confirm": "Conferma",
|
||||
"cancel": "Annulla"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configurazione",
|
||||
"description": "Configura l'account amministratore iniziale per Open Archiver.",
|
||||
"welcome": "Benvenuto",
|
||||
"create_admin_account": "Crea il primo account amministratore per iniziare.",
|
||||
"first_name": "Nome",
|
||||
"last_name": "Cognome",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"creating_account": "Creazione account",
|
||||
"create_account": "Crea account"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Dashboard",
|
||||
"ingestions": "Ingestioni",
|
||||
"archived_emails": "Email archiviate",
|
||||
"search": "Cerca",
|
||||
"settings": "Impostazioni",
|
||||
"system": "Sistema",
|
||||
"users": "Utenti",
|
||||
"roles": "Ruoli",
|
||||
"logout": "Esci"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "Email ingerite",
|
||||
"storage_used": "Spazio di archiviazione utilizzato",
|
||||
"emails": "Email"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Invio in corso...",
|
||||
"submit": "Invia",
|
||||
"save": "Salva"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "Caricamento anteprima email...",
|
||||
"render_error": "Impossibile visualizzare l'anteprima dell'email.",
|
||||
"not_available": "File .eml non disponibile per questa email."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "Tutti i diritti riservati."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "IMAP generico",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "Importazione PST",
|
||||
"provider_eml_import": "Importazione EML",
|
||||
"select_provider": "Seleziona un provider",
|
||||
"service_account_key": "Chiave account di servizio (JSON)",
|
||||
"service_account_key_placeholder": "Incolla il contenuto JSON della tua chiave account di servizio",
|
||||
"impersonated_admin_email": "Email amministratore impersonata",
|
||||
"client_id": "ID applicazione (client)",
|
||||
"client_secret": "Valore segreto client",
|
||||
"client_secret_placeholder": "Inserisci il valore segreto, non l'ID segreto",
|
||||
"tenant_id": "ID directory (tenant)",
|
||||
"host": "Host",
|
||||
"port": "Porta",
|
||||
"username": "Nome utente",
|
||||
"use_tls": "Usa TLS",
|
||||
"pst_file": "File PST",
|
||||
"eml_file": "File EML",
|
||||
"heads_up": "Attenzione!",
|
||||
"org_wide_warning": "Si prega di notare che questa è un'operazione a livello di organizzazione. Questo tipo di ingestione importerà e indicizzerà <b>tutte</b> le caselle di posta elettronica della tua organizzazione. Se desideri importare solo caselle di posta elettronica specifiche, utilizza il connettore IMAP.",
|
||||
"upload_failed": "Caricamento non riuscito, riprova"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Policy (JSON)",
|
||||
"invalid_json": "Formato JSON non valido per le policy."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Cambia tema"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Seleziona un ruolo"
|
||||
}
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Dashboard",
|
||||
"meta_description": "Panoramica del tuo archivio email.",
|
||||
"header": "Dashboard",
|
||||
"create_ingestion": "Crea un'ingestione",
|
||||
"no_ingestion_header": "Non hai alcuna fonte di ingestione configurata.",
|
||||
"no_ingestion_text": "Aggiungi una fonte di ingestione per iniziare ad archiviare le tue caselle di posta.",
|
||||
"total_emails_archived": "Email totali archiviate",
|
||||
"total_storage_used": "Spazio di archiviazione totale utilizzato",
|
||||
"failed_ingestions": "Ingestioni non riuscite (ultimi 7 giorni)",
|
||||
"ingestion_history": "Cronologia ingestioni",
|
||||
"no_ingestion_history": "Nessuna cronologia di ingestione disponibile.",
|
||||
"storage_by_source": "Archiviazione per fonte di ingestione",
|
||||
"no_ingestion_sources": "Nessuna fonte di ingestione disponibile.",
|
||||
"indexed_insights": "Approfondimenti indicizzati",
|
||||
"top_10_senders": "Top 10 mittenti",
|
||||
"no_indexed_insights": "Nessun approfondimento indicizzato disponibile."
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "Email archiviate",
|
||||
"header": "Email archiviate",
|
||||
"select_ingestion_source": "Seleziona una fonte di ingestione",
|
||||
"date": "Data",
|
||||
"subject": "Oggetto",
|
||||
"sender": "Mittente",
|
||||
"inbox": "Posta in arrivo",
|
||||
"path": "Percorso",
|
||||
"actions": "Azioni",
|
||||
"view": "Visualizza",
|
||||
"no_emails_found": "Nessuna email archiviata trovata.",
|
||||
"prev": "Prec",
|
||||
"next": "Succ"
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/frontend/src/lib/translations/ja.json
Normal file
260
packages/frontend/src/lib/translations/ja.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "ログイン",
|
||||
"login_tip": "アカウントにログインするには、以下にメールアドレスを入力してください。",
|
||||
"email": "メール",
|
||||
"password": "パスワード"
|
||||
},
|
||||
"common": {
|
||||
"working": "作業中"
|
||||
},
|
||||
"archive": {
|
||||
"title": "アーカイブ",
|
||||
"no_subject": "件名なし",
|
||||
"from": "差出人",
|
||||
"sent": "送信日時",
|
||||
"recipients": "受信者",
|
||||
"to": "宛先",
|
||||
"meta_data": "メタデータ",
|
||||
"folder": "フォルダー",
|
||||
"tags": "タグ",
|
||||
"size": "サイズ",
|
||||
"email_preview": "メールプレビュー",
|
||||
"attachments": "添付ファイル",
|
||||
"download": "ダウンロード",
|
||||
"actions": "アクション",
|
||||
"download_eml": "メールをダウンロード (.eml)",
|
||||
"delete_email": "メールを削除",
|
||||
"email_thread": "メールのスレッド",
|
||||
"delete_confirmation_title": "このメールを削除してもよろしいですか?",
|
||||
"delete_confirmation_description": "この操作は元に戻せません。メールと添付ファイルが完全に削除されます。",
|
||||
"deleting": "削除中",
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル",
|
||||
"not_found": "メールが見つかりません。"
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "取り込み元",
|
||||
"ingestion_sources": "取り込み元",
|
||||
"bulk_actions": "一括操作",
|
||||
"force_sync": "強制同期",
|
||||
"delete": "削除",
|
||||
"create_new": "新規作成",
|
||||
"name": "名前",
|
||||
"provider": "プロバイダー",
|
||||
"status": "ステータス",
|
||||
"active": "アクティブ",
|
||||
"created_at": "作成日",
|
||||
"actions": "アクション",
|
||||
"last_sync_message": "最終同期メッセージ",
|
||||
"empty": "空",
|
||||
"open_menu": "メニューを開く",
|
||||
"edit": "編集",
|
||||
"create": "作成",
|
||||
"ingestion_source": "取り込み元",
|
||||
"edit_description": "ここで取り込み元を変更します。",
|
||||
"create_description": "メールのアーカイブを開始するために、新しい取り込み元を追加します。",
|
||||
"read": "読む",
|
||||
"docs_here": "ドキュメントはこちら",
|
||||
"delete_confirmation_title": "この取り込みを削除してもよろしいですか?",
|
||||
"delete_confirmation_description": "これにより、この取り込みに関連するすべてのアーカイブ済みメール、添付ファイル、インデックス、およびファイルが削除されます。新しいメールの同期を停止したいだけの場合は、代わりに取り込みを一時停止できます。",
|
||||
"deleting": "削除中",
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル",
|
||||
"bulk_delete_confirmation_title": "選択した{{count}}件の取り込みを削除してもよろしいですか?",
|
||||
"bulk_delete_confirmation_description": "これにより、これらの取り込みに関連するすべてのアーカイブ済みメール、添付ファイル、インデックス、およびファイルが削除されます。新しいメールの同期を停止したいだけの場合は、代わりに取り込みを一時停止できます。"
|
||||
},
|
||||
"search": {
|
||||
"title": "検索",
|
||||
"description": "アーカイブされたメールを検索します。",
|
||||
"email_search": "メール検索",
|
||||
"placeholder": "キーワード、送信者、受信者で検索...",
|
||||
"search_button": "検索",
|
||||
"search_options": "検索オプション",
|
||||
"strategy_fuzzy": "あいまい",
|
||||
"strategy_verbatim": "逐語的",
|
||||
"strategy_frequency": "頻度",
|
||||
"select_strategy": "戦略を選択",
|
||||
"error": "エラー",
|
||||
"found_results_in": "{{seconds}}秒で{{total}}件の結果が見つかりました",
|
||||
"found_results": "{{total}}件の結果が見つかりました",
|
||||
"from": "差出人",
|
||||
"to": "宛先",
|
||||
"in_email_body": "メール本文内",
|
||||
"in_attachment": "添付ファイル内: {{filename}}",
|
||||
"prev": "前へ",
|
||||
"next": "次へ"
|
||||
},
|
||||
"roles": {
|
||||
"title": "ロール管理",
|
||||
"role_management": "ロール管理",
|
||||
"create_new": "新規作成",
|
||||
"name": "名前",
|
||||
"created_at": "作成日",
|
||||
"actions": "アクション",
|
||||
"open_menu": "メニューを開く",
|
||||
"view_policy": "ポリシーを表示",
|
||||
"edit": "編集",
|
||||
"delete": "削除",
|
||||
"no_roles_found": "ロールが見つかりません。",
|
||||
"role_policy": "ロールポリシー",
|
||||
"viewing_policy_for_role": "ロールのポリシーを表示中: {{name}}",
|
||||
"create": "作成",
|
||||
"role": "ロール",
|
||||
"edit_description": "ここでロールを変更します。",
|
||||
"create_description": "システムに新しいロールを追加します。",
|
||||
"delete_confirmation_title": "このロールを削除してもよろしいですか?",
|
||||
"delete_confirmation_description": "この操作は元に戻せません。これにより、ロールが完全に削除されます。",
|
||||
"deleting": "削除中",
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "システム設定",
|
||||
"system_settings": "システム設定",
|
||||
"description": "グローバルなアプリケーション設定を管理します。",
|
||||
"language": "言語",
|
||||
"default_theme": "デフォルトのテーマ",
|
||||
"light": "ライト",
|
||||
"dark": "ダーク",
|
||||
"system": "システム",
|
||||
"support_email": "サポートメール",
|
||||
"saving": "保存中",
|
||||
"save_changes": "変更を保存"
|
||||
},
|
||||
"users": {
|
||||
"title": "ユーザー管理",
|
||||
"user_management": "ユーザー管理",
|
||||
"create_new": "新規作成",
|
||||
"name": "名前",
|
||||
"email": "メール",
|
||||
"role": "ロール",
|
||||
"created_at": "作成日",
|
||||
"actions": "アクション",
|
||||
"open_menu": "メニューを開く",
|
||||
"edit": "編集",
|
||||
"delete": "削除",
|
||||
"no_users_found": "ユーザーが見つかりません。",
|
||||
"create": "作成",
|
||||
"user": "ユーザー",
|
||||
"edit_description": "ここでユーザーを変更します。",
|
||||
"create_description": "システムに新しいユーザーを追加します。",
|
||||
"delete_confirmation_title": "このユーザーを削除してもよろしいですか?",
|
||||
"delete_confirmation_description": "この操作は元に戻せません。これにより、ユーザーが完全に削除され、データがサーバーから削除されます。",
|
||||
"deleting": "削除中",
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル"
|
||||
},
|
||||
"setup": {
|
||||
"title": "セットアップ",
|
||||
"description": "Open Archiverの初期管理者アカウントをセットアップします。",
|
||||
"welcome": "ようこそ",
|
||||
"create_admin_account": "開始するには、最初の管理者アカウントを作成してください。",
|
||||
"first_name": "名",
|
||||
"last_name": "姓",
|
||||
"email": "メール",
|
||||
"password": "パスワード",
|
||||
"creating_account": "アカウント作成中",
|
||||
"create_account": "アカウントを作成"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "ダッシュボード",
|
||||
"ingestions": "取り込み",
|
||||
"archived_emails": "アーカイブされたメール",
|
||||
"search": "検索",
|
||||
"settings": "設定",
|
||||
"system": "システム",
|
||||
"users": "ユーザー",
|
||||
"roles": "ロール",
|
||||
"logout": "ログアウト"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "取り込まれたメール",
|
||||
"storage_used": "使用済みストレージ",
|
||||
"emails": "メール"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "送信中...",
|
||||
"submit": "送信",
|
||||
"save": "保存"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "メールプレビューを読み込んでいます...",
|
||||
"render_error": "メールプレビューをレンダリングできませんでした。",
|
||||
"not_available": "このメールの生の.emlファイルはありません。"
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "無断複写・転載を禁じます。"
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "汎用IMAP",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "PSTインポート",
|
||||
"provider_eml_import": "EMLインポート",
|
||||
"select_provider": "プロバイダーを選択",
|
||||
"service_account_key": "サービスアカウントキー (JSON)",
|
||||
"service_account_key_placeholder": "サービスアカウントキーのJSONコンテンツを貼り付けます",
|
||||
"impersonated_admin_email": "偽装された管理者メール",
|
||||
"client_id": "アプリケーション (クライアント) ID",
|
||||
"client_secret": "クライアントシークレット値",
|
||||
"client_secret_placeholder": "シークレットIDではなく、シークレット値を入力してください",
|
||||
"tenant_id": "ディレクトリ (テナント) ID",
|
||||
"host": "ホスト",
|
||||
"port": "ポート",
|
||||
"username": "ユーザー名",
|
||||
"use_tls": "TLSを使用",
|
||||
"pst_file": "PSTファイル",
|
||||
"eml_file": "EMLファイル",
|
||||
"heads_up": "ご注意ください!",
|
||||
"org_wide_warning": "これは組織全体の操作であることに注意してください。この種の取り込みは、組織内の<b>すべて</b>のメールボックスをインポートしてインデックスを作成します。特定のメールボックスのみをインポートする場合は、IMAPコネクタを使用してください。",
|
||||
"upload_failed": "アップロードに失敗しました。もう一度お試しください"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "ポリシー (JSON)",
|
||||
"invalid_json": "ポリシーのJSON形式が無効です。"
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "テーマを切り替える"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "役割を選択"
|
||||
}
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "ダッシュボード",
|
||||
"meta_description": "メールアーカイブの概要。",
|
||||
"header": "ダッシュボード",
|
||||
"create_ingestion": "取り込みを作成",
|
||||
"no_ingestion_header": "取り込みソースが設定されていません。",
|
||||
"no_ingestion_text": "受信トレイのアーカイブを開始するには、取り込みソースを追加してください。",
|
||||
"total_emails_archived": "アーカイブされたメールの総数",
|
||||
"total_storage_used": "総ストレージ使用量",
|
||||
"failed_ingestions": "失敗した取り込み(過去7日間)",
|
||||
"ingestion_history": "取り込み履歴",
|
||||
"no_ingestion_history": "取り込み履歴はありません。",
|
||||
"storage_by_source": "取り込みソース別のストレージ",
|
||||
"no_ingestion_sources": "利用可能な取り込みソースはありません。",
|
||||
"indexed_insights": "インデックス付きインサイト",
|
||||
"top_10_senders": "トップ10送信者",
|
||||
"no_indexed_insights": "インデックス付きインサイトはありません。"
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "アーカイブされたメール",
|
||||
"header": "アーカイブされたメール",
|
||||
"select_ingestion_source": "取り込み元を選択",
|
||||
"date": "日付",
|
||||
"subject": "件名",
|
||||
"sender": "差出人",
|
||||
"inbox": "受信トレイ",
|
||||
"path": "パス",
|
||||
"actions": "アクション",
|
||||
"view": "表示",
|
||||
"no_emails_found": "アーカイブされたメールは見つかりませんでした。",
|
||||
"prev": "前へ",
|
||||
"next": "次へ"
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/frontend/src/lib/translations/nl.json
Normal file
260
packages/frontend/src/lib/translations/nl.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Inloggen",
|
||||
"login_tip": "Voer hieronder uw e-mailadres in om in te loggen op uw account.",
|
||||
"email": "E-mail",
|
||||
"password": "Wachtwoord"
|
||||
},
|
||||
"common": {
|
||||
"working": "Bezig"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archief",
|
||||
"no_subject": "Geen onderwerp",
|
||||
"from": "Van",
|
||||
"sent": "Verzonden",
|
||||
"recipients": "Ontvangers",
|
||||
"to": "Aan",
|
||||
"meta_data": "Metadata",
|
||||
"folder": "Map",
|
||||
"tags": "Tags",
|
||||
"size": "Grootte",
|
||||
"email_preview": "E-mailvoorbeeld",
|
||||
"attachments": "Bijlagen",
|
||||
"download": "Downloaden",
|
||||
"actions": "Acties",
|
||||
"download_eml": "E-mail downloaden (.eml)",
|
||||
"delete_email": "E-mail verwijderen",
|
||||
"email_thread": "E-mailthread",
|
||||
"delete_confirmation_title": "Weet u zeker dat u deze e-mail wilt verwijderen?",
|
||||
"delete_confirmation_description": "Deze actie kan niet ongedaan worden gemaakt en zal de e-mail en de bijlagen permanent verwijderen.",
|
||||
"deleting": "Verwijderen",
|
||||
"confirm": "Bevestigen",
|
||||
"cancel": "Annuleren",
|
||||
"not_found": "E-mail niet gevonden."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Innamebronnen",
|
||||
"ingestion_sources": "Innamebronnen",
|
||||
"bulk_actions": "Bulkacties",
|
||||
"force_sync": "Synchronisatie forceren",
|
||||
"delete": "Verwijderen",
|
||||
"create_new": "Nieuw aanmaken",
|
||||
"name": "Naam",
|
||||
"provider": "Provider",
|
||||
"status": "Status",
|
||||
"active": "Actief",
|
||||
"created_at": "Aangemaakt op",
|
||||
"actions": "Acties",
|
||||
"last_sync_message": "Laatste synchronisatiebericht",
|
||||
"empty": "Leeg",
|
||||
"open_menu": "Menu openen",
|
||||
"edit": "Bewerken",
|
||||
"create": "Aanmaken",
|
||||
"ingestion_source": "Innamebron",
|
||||
"edit_description": "Breng hier wijzigingen aan in uw innamebron.",
|
||||
"create_description": "Voeg een nieuwe innamebron toe om e-mails te archiveren.",
|
||||
"read": "Lezen",
|
||||
"docs_here": "documenten hier",
|
||||
"delete_confirmation_title": "Weet u zeker dat u deze inname wilt verwijderen?",
|
||||
"delete_confirmation_description": "Dit verwijdert alle gearchiveerde e-mails, bijlagen, indexering en bestanden die aan deze inname zijn gekoppeld. Als u alleen wilt stoppen met het synchroniseren van nieuwe e-mails, kunt u de inname in plaats daarvan pauzeren.",
|
||||
"deleting": "Verwijderen",
|
||||
"confirm": "Bevestigen",
|
||||
"cancel": "Annuleren",
|
||||
"bulk_delete_confirmation_title": "Weet u zeker dat u {{count}} geselecteerde innames wilt verwijderen?",
|
||||
"bulk_delete_confirmation_description": "Dit verwijdert alle gearchiveerde e-mails, bijlagen, indexering en bestanden die aan deze innames zijn gekoppeld. Als u alleen wilt stoppen met het synchroniseren van nieuwe e-mails, kunt u de innames in plaats daarvan pauzeren."
|
||||
},
|
||||
"search": {
|
||||
"title": "Zoeken",
|
||||
"description": "Zoeken naar gearchiveerde e-mails.",
|
||||
"email_search": "E-mail zoeken",
|
||||
"placeholder": "Zoeken op trefwoord, afzender, ontvanger...",
|
||||
"search_button": "Zoeken",
|
||||
"search_options": "Zoekopties",
|
||||
"strategy_fuzzy": "Fuzzy",
|
||||
"strategy_verbatim": "Letterlijk",
|
||||
"strategy_frequency": "Frequentie",
|
||||
"select_strategy": "Selecteer een strategie",
|
||||
"error": "Fout",
|
||||
"found_results_in": "{{total}} resultaten gevonden in {{seconds}}s",
|
||||
"found_results": "{{total}} resultaten gevonden",
|
||||
"from": "Van",
|
||||
"to": "Aan",
|
||||
"in_email_body": "In e-mailtekst",
|
||||
"in_attachment": "In bijlage: {{filename}}",
|
||||
"prev": "Vorige",
|
||||
"next": "Volgende"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Rollenbeheer",
|
||||
"role_management": "Rollenbeheer",
|
||||
"create_new": "Nieuw aanmaken",
|
||||
"name": "Naam",
|
||||
"created_at": "Aangemaakt op",
|
||||
"actions": "Acties",
|
||||
"open_menu": "Menu openen",
|
||||
"view_policy": "Beleid bekijken",
|
||||
"edit": "Bewerken",
|
||||
"delete": "Verwijderen",
|
||||
"no_roles_found": "Geen rollen gevonden.",
|
||||
"role_policy": "Rollenbeleid",
|
||||
"viewing_policy_for_role": "Beleid bekijken voor rol: {{name}}",
|
||||
"create": "Aanmaken",
|
||||
"role": "Rol",
|
||||
"edit_description": "Breng hier wijzigingen aan in de rol.",
|
||||
"create_description": "Voeg een nieuwe rol toe aan het systeem.",
|
||||
"delete_confirmation_title": "Weet u zeker dat u deze rol wilt verwijderen?",
|
||||
"delete_confirmation_description": "Deze actie kan niet ongedaan worden gemaakt. Dit zal de rol permanent verwijderen.",
|
||||
"deleting": "Verwijderen",
|
||||
"confirm": "Bevestigen",
|
||||
"cancel": "Annuleren"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "Systeeminstellingen",
|
||||
"system_settings": "Systeeminstellingen",
|
||||
"description": "Beheer de algemene applicatie-instellingen.",
|
||||
"language": "Taal",
|
||||
"default_theme": "Standaardthema",
|
||||
"light": "Licht",
|
||||
"dark": "Donker",
|
||||
"system": "Systeem",
|
||||
"support_email": "Ondersteunings-e-mail",
|
||||
"saving": "Opslaan",
|
||||
"save_changes": "Wijzigingen opslaan"
|
||||
},
|
||||
"users": {
|
||||
"title": "Gebruikersbeheer",
|
||||
"user_management": "Gebruikersbeheer",
|
||||
"create_new": "Nieuw aanmaken",
|
||||
"name": "Naam",
|
||||
"email": "E-mail",
|
||||
"role": "Rol",
|
||||
"created_at": "Aangemaakt op",
|
||||
"actions": "Acties",
|
||||
"open_menu": "Menu openen",
|
||||
"edit": "Bewerken",
|
||||
"delete": "Verwijderen",
|
||||
"no_users_found": "Geen gebruikers gevonden.",
|
||||
"create": "Aanmaken",
|
||||
"user": "Gebruiker",
|
||||
"edit_description": "Breng hier wijzigingen aan in de gebruiker.",
|
||||
"create_description": "Voeg een nieuwe gebruiker toe aan het systeem.",
|
||||
"delete_confirmation_title": "Weet u zeker dat u deze gebruiker wilt verwijderen?",
|
||||
"delete_confirmation_description": "Deze actie kan niet ongedaan worden gemaakt. Dit zal de gebruiker permanent verwijderen en hun gegevens van onze servers verwijderen.",
|
||||
"deleting": "Verwijderen",
|
||||
"confirm": "Bevestigen",
|
||||
"cancel": "Annuleren"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Installatie",
|
||||
"description": "Stel het initiële beheerdersaccount in voor Open Archiver.",
|
||||
"welcome": "Welkom",
|
||||
"create_admin_account": "Maak het eerste beheerdersaccount aan om te beginnen.",
|
||||
"first_name": "Voornaam",
|
||||
"last_name": "Achternaam",
|
||||
"email": "E-mail",
|
||||
"password": "Wachtwoord",
|
||||
"creating_account": "Account aanmaken",
|
||||
"create_account": "Account aanmaken"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Dashboard",
|
||||
"ingestions": "Innames",
|
||||
"archived_emails": "Gearchiveerde e-mails",
|
||||
"search": "Zoeken",
|
||||
"settings": "Instellingen",
|
||||
"system": "Systeem",
|
||||
"users": "Gebruikers",
|
||||
"roles": "Rollen",
|
||||
"logout": "Uitloggen"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "E-mails opgenomen",
|
||||
"storage_used": "Opslag gebruikt",
|
||||
"emails": "E-mails"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Verzenden...",
|
||||
"submit": "Verzenden",
|
||||
"save": "Opslaan"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "E-mailvoorbeeld laden...",
|
||||
"render_error": "Kan e-mailvoorbeeld niet weergeven.",
|
||||
"not_available": "Ruwe .eml-bestand niet beschikbaar voor deze e-mail."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "Alle rechten voorbehouden."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "Generieke IMAP",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "PST importeren",
|
||||
"provider_eml_import": "EML importeren",
|
||||
"select_provider": "Selecteer een provider",
|
||||
"service_account_key": "Servicesleutel (JSON)",
|
||||
"service_account_key_placeholder": "Plak de JSON-inhoud van uw servicesleutel",
|
||||
"impersonated_admin_email": "Geïmpersoneerde beheerders-e-mail",
|
||||
"client_id": "Applicatie (client) ID",
|
||||
"client_secret": "Clientgeheimwaarde",
|
||||
"client_secret_placeholder": "Voer de geheime waarde in, niet de geheime ID",
|
||||
"tenant_id": "Directory (tenant) ID",
|
||||
"host": "Host",
|
||||
"port": "Poort",
|
||||
"username": "Gebruikersnaam",
|
||||
"use_tls": "TLS gebruiken",
|
||||
"pst_file": "PST-bestand",
|
||||
"eml_file": "EML-bestand",
|
||||
"heads_up": "Let op!",
|
||||
"org_wide_warning": "Houd er rekening mee dat dit een organisatiebrede bewerking is. Dit type inname importeert en indexeert <b>alle</b> e-mailboxen in uw organisatie. Als u alleen specifieke e-mailboxen wilt importeren, gebruik dan de IMAP-connector.",
|
||||
"upload_failed": "Uploaden mislukt, probeer het opnieuw"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Beleid (JSON)",
|
||||
"invalid_json": "Ongeldig JSON-formaat voor beleid."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Thema wisselen"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Selecteer een rol"
|
||||
}
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Dashboard",
|
||||
"meta_description": "Overzicht van uw e-mailarchief.",
|
||||
"header": "Dashboard",
|
||||
"create_ingestion": "Maak een inname",
|
||||
"no_ingestion_header": "U heeft geen innamebron ingesteld.",
|
||||
"no_ingestion_text": "Voeg een innamebron toe om uw inboxen te archiveren.",
|
||||
"total_emails_archived": "Totaal aantal gearchiveerde e-mails",
|
||||
"total_storage_used": "Totaal gebruikte opslag",
|
||||
"failed_ingestions": "Mislukte innames (laatste 7 dagen)",
|
||||
"ingestion_history": "Innamegeschiedenis",
|
||||
"no_ingestion_history": "Geen innamegeschiedenis beschikbaar.",
|
||||
"storage_by_source": "Opslag per innamebron",
|
||||
"no_ingestion_sources": "Geen innamebronnen beschikbaar.",
|
||||
"indexed_insights": "Geïndexeerde inzichten",
|
||||
"top_10_senders": "Top 10 afzenders",
|
||||
"no_indexed_insights": "Geen geïndexeerde inzichten beschikbaar."
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "Gearchiveerde e-mails",
|
||||
"header": "Gearchiveerde e-mails",
|
||||
"select_ingestion_source": "Selecteer een innamebron",
|
||||
"date": "Datum",
|
||||
"subject": "Onderwerp",
|
||||
"sender": "Afzender",
|
||||
"inbox": "Inbox",
|
||||
"path": "Pad",
|
||||
"actions": "Acties",
|
||||
"view": "Bekijken",
|
||||
"no_emails_found": "Geen gearchiveerde e-mails gevonden.",
|
||||
"prev": "Vorige",
|
||||
"next": "Volgende"
|
||||
}
|
||||
}
|
||||
}
|
||||
260
packages/frontend/src/lib/translations/pt.json
Normal file
260
packages/frontend/src/lib/translations/pt.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"app": {
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"login_tip": "Digite seu e-mail abaixo para fazer login em sua conta.",
|
||||
"email": "E-mail",
|
||||
"password": "Senha"
|
||||
},
|
||||
"common": {
|
||||
"working": "Trabalhando"
|
||||
},
|
||||
"archive": {
|
||||
"title": "Arquivo",
|
||||
"no_subject": "Sem assunto",
|
||||
"from": "De",
|
||||
"sent": "Enviado",
|
||||
"recipients": "Destinatários",
|
||||
"to": "Para",
|
||||
"meta_data": "Metadados",
|
||||
"folder": "Pasta",
|
||||
"tags": "Tags",
|
||||
"size": "Tamanho",
|
||||
"email_preview": "Visualização de e-mail",
|
||||
"attachments": "Anexos",
|
||||
"download": "Baixar",
|
||||
"actions": "Ações",
|
||||
"download_eml": "Baixar e-mail (.eml)",
|
||||
"delete_email": "Excluir e-mail",
|
||||
"email_thread": "Thread de e-mail",
|
||||
"delete_confirmation_title": "Tem certeza de que deseja excluir este e-mail?",
|
||||
"delete_confirmation_description": "Esta ação não pode ser desfeita e removerá permanentemente o e-mail e seus anexos.",
|
||||
"deleting": "Excluindo",
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar",
|
||||
"not_found": "E-mail não encontrado."
|
||||
},
|
||||
"ingestions": {
|
||||
"title": "Fontes de ingestão",
|
||||
"ingestion_sources": "Fontes de ingestão",
|
||||
"bulk_actions": "Ações em massa",
|
||||
"force_sync": "Forçar sincronização",
|
||||
"delete": "Excluir",
|
||||
"create_new": "Criar novo",
|
||||
"name": "Nome",
|
||||
"provider": "Provedor",
|
||||
"status": "Status",
|
||||
"active": "Ativo",
|
||||
"created_at": "Criado em",
|
||||
"actions": "Ações",
|
||||
"last_sync_message": "Última mensagem de sincronização",
|
||||
"empty": "Vazio",
|
||||
"open_menu": "Abrir menu",
|
||||
"edit": "Editar",
|
||||
"create": "Criar",
|
||||
"ingestion_source": "Fonte de ingestão",
|
||||
"edit_description": "Faça alterações em sua fonte de ingestão aqui.",
|
||||
"create_description": "Adicione uma nova fonte de ingestão para começar a arquivar e-mails.",
|
||||
"read": "Ler",
|
||||
"docs_here": "documentos aqui",
|
||||
"delete_confirmation_title": "Tem certeza de que deseja excluir esta ingestão?",
|
||||
"delete_confirmation_description": "Isso excluirá todos os e-mails arquivados, anexos, indexação e arquivos associados a esta ingestão. Se você deseja apenas parar de sincronizar novos e-mails, pode pausar a ingestão.",
|
||||
"deleting": "Excluindo",
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar",
|
||||
"bulk_delete_confirmation_title": "Tem certeza de que deseja excluir {{count}} ingestões selecionadas?",
|
||||
"bulk_delete_confirmation_description": "Isso excluirá todos os e-mails arquivados, anexos, indexação e arquivos associados a essas ingestões. Se você deseja apenas parar de sincronizar novos e-mails, pode pausar as ingestões."
|
||||
},
|
||||
"search": {
|
||||
"title": "Pesquisar",
|
||||
"description": "Pesquisar e-mails arquivados.",
|
||||
"email_search": "Pesquisa de e-mail",
|
||||
"placeholder": "Pesquisar por palavra-chave, remetente, destinatário...",
|
||||
"search_button": "Pesquisar",
|
||||
"search_options": "Opções de pesquisa",
|
||||
"strategy_fuzzy": "Fuzzy",
|
||||
"strategy_verbatim": "Verbatim",
|
||||
"strategy_frequency": "Frequência",
|
||||
"select_strategy": "Selecione uma estratégia",
|
||||
"error": "Erro",
|
||||
"found_results_in": "Encontrados {{total}} resultados em {{seconds}}s",
|
||||
"found_results": "Encontrados {{total}} resultados",
|
||||
"from": "De",
|
||||
"to": "Para",
|
||||
"in_email_body": "No corpo do e-mail",
|
||||
"in_attachment": "No anexo: {{filename}}",
|
||||
"prev": "Anterior",
|
||||
"next": "Próximo"
|
||||
},
|
||||
"roles": {
|
||||
"title": "Gerenciamento de funções",
|
||||
"role_management": "Gerenciamento de funções",
|
||||
"create_new": "Criar novo",
|
||||
"name": "Nome",
|
||||
"created_at": "Criado em",
|
||||
"actions": "Ações",
|
||||
"open_menu": "Abrir menu",
|
||||
"view_policy": "Visualizar política",
|
||||
"edit": "Editar",
|
||||
"delete": "Excluir",
|
||||
"no_roles_found": "Nenhuma função encontrada.",
|
||||
"role_policy": "Política de função",
|
||||
"viewing_policy_for_role": "Visualizando política para a função: {{name}}",
|
||||
"create": "Criar",
|
||||
"role": "Função",
|
||||
"edit_description": "Faça alterações na função aqui.",
|
||||
"create_description": "Adicione uma nova função ao sistema.",
|
||||
"delete_confirmation_title": "Tem certeza de que deseja excluir esta função?",
|
||||
"delete_confirmation_description": "Esta ação não pode ser desfeita. Isso excluirá permanentemente a função.",
|
||||
"deleting": "Excluindo",
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"system_settings": {
|
||||
"title": "Configurações do sistema",
|
||||
"system_settings": "Configurações do sistema",
|
||||
"description": "Gerenciar configurações globais do aplicativo.",
|
||||
"language": "Idioma",
|
||||
"default_theme": "Tema padrão",
|
||||
"light": "Claro",
|
||||
"dark": "Escuro",
|
||||
"system": "Sistema",
|
||||
"support_email": "E-mail de suporte",
|
||||
"saving": "Salvando",
|
||||
"save_changes": "Salvar alterações"
|
||||
},
|
||||
"users": {
|
||||
"title": "Gerenciamento de usuários",
|
||||
"user_management": "Gerenciamento de usuários",
|
||||
"create_new": "Criar novo",
|
||||
"name": "Nome",
|
||||
"email": "E-mail",
|
||||
"role": "Função",
|
||||
"created_at": "Criado em",
|
||||
"actions": "Ações",
|
||||
"open_menu": "Abrir menu",
|
||||
"edit": "Editar",
|
||||
"delete": "Excluir",
|
||||
"no_users_found": "Nenhum usuário encontrado.",
|
||||
"create": "Criar",
|
||||
"user": "Usuário",
|
||||
"edit_description": "Faça alterações no usuário aqui.",
|
||||
"create_description": "Adicione um novo usuário ao sistema.",
|
||||
"delete_confirmation_title": "Tem certeza de que deseja excluir este usuário?",
|
||||
"delete_confirmation_description": "Esta ação não pode ser desfeita. Isso excluirá permanentemente o usuário e removerá seus dados de nossos servidores.",
|
||||
"deleting": "Excluindo",
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configuração",
|
||||
"description": "Configure a conta de administrador inicial para o Open Archiver.",
|
||||
"welcome": "Bem-vindo",
|
||||
"create_admin_account": "Crie a primeira conta de administrador para começar.",
|
||||
"first_name": "Primeiro nome",
|
||||
"last_name": "Último nome",
|
||||
"email": "E-mail",
|
||||
"password": "Senha",
|
||||
"creating_account": "Criando conta",
|
||||
"create_account": "Criar conta"
|
||||
},
|
||||
"layout": {
|
||||
"dashboard": "Painel",
|
||||
"ingestions": "Ingestões",
|
||||
"archived_emails": "E-mails arquivados",
|
||||
"search": "Pesquisar",
|
||||
"settings": "Configurações",
|
||||
"system": "Sistema",
|
||||
"users": "Usuários",
|
||||
"roles": "Funções",
|
||||
"logout": "Sair"
|
||||
},
|
||||
"components": {
|
||||
"charts": {
|
||||
"emails_ingested": "E-mails ingeridos",
|
||||
"storage_used": "Armazenamento usado",
|
||||
"emails": "E-mails"
|
||||
},
|
||||
"common": {
|
||||
"submitting": "Enviando...",
|
||||
"submit": "Enviar",
|
||||
"save": "Salvar"
|
||||
},
|
||||
"email_preview": {
|
||||
"loading": "Carregando visualização de e-mail...",
|
||||
"render_error": "Não foi possível renderizar a visualização do e-mail.",
|
||||
"not_available": "Arquivo .eml bruto não disponível para este e-mail."
|
||||
},
|
||||
"footer": {
|
||||
"all_rights_reserved": "Todos os direitos reservados."
|
||||
},
|
||||
"ingestion_source_form": {
|
||||
"provider_generic_imap": "IMAP genérico",
|
||||
"provider_google_workspace": "Google Workspace",
|
||||
"provider_microsoft_365": "Microsoft 365",
|
||||
"provider_pst_import": "Importação de PST",
|
||||
"provider_eml_import": "Importação de EML",
|
||||
"select_provider": "Selecione um provedor",
|
||||
"service_account_key": "Chave da conta de serviço (JSON)",
|
||||
"service_account_key_placeholder": "Cole o conteúdo JSON da sua chave de conta de serviço",
|
||||
"impersonated_admin_email": "E-mail de administrador representado",
|
||||
"client_id": "ID do aplicativo (cliente)",
|
||||
"client_secret": "Valor secreto do cliente",
|
||||
"client_secret_placeholder": "Digite o valor secreto, não o ID secreto",
|
||||
"tenant_id": "ID do diretório (locatário)",
|
||||
"host": "Host",
|
||||
"port": "Porta",
|
||||
"username": "Nome de usuário",
|
||||
"use_tls": "Usar TLS",
|
||||
"pst_file": "Arquivo PST",
|
||||
"eml_file": "Arquivo EML",
|
||||
"heads_up": "Atenção!",
|
||||
"org_wide_warning": "Observe que esta é uma operação em toda a organização. Esse tipo de ingestão importará e indexará <b>todas</b> as caixas de correio de e-mail em sua organização. Se você deseja importar apenas caixas de correio de e-mail específicas, use o conector IMAP.",
|
||||
"upload_failed": "Falha no upload, tente novamente"
|
||||
},
|
||||
"role_form": {
|
||||
"policies_json": "Políticas (JSON)",
|
||||
"invalid_json": "Formato JSON inválido para políticas."
|
||||
},
|
||||
"theme_switcher": {
|
||||
"toggle_theme": "Alternar tema"
|
||||
},
|
||||
"user_form": {
|
||||
"select_role": "Selecione uma função"
|
||||
}
|
||||
},
|
||||
"dashboard_page": {
|
||||
"title": "Painel",
|
||||
"meta_description": "Visão geral do seu arquivo de e-mail.",
|
||||
"header": "Painel",
|
||||
"create_ingestion": "Criar uma ingestão",
|
||||
"no_ingestion_header": "Você não tem nenhuma fonte de ingestão configurada.",
|
||||
"no_ingestion_text": "Adicione uma fonte de ingestão para começar a arquivar suas caixas de entrada.",
|
||||
"total_emails_archived": "Total de e-mails arquivados",
|
||||
"total_storage_used": "Armazenamento total usado",
|
||||
"failed_ingestions": "Ingestões com falha (últimos 7 dias)",
|
||||
"ingestion_history": "Histórico de ingestão",
|
||||
"no_ingestion_history": "Nenhum histórico de ingestão disponível.",
|
||||
"storage_by_source": "Armazenamento por fonte de ingestão",
|
||||
"no_ingestion_sources": "Nenhuma fonte de ingestão disponível.",
|
||||
"indexed_insights": "Informações indexadas",
|
||||
"top_10_senders": "10 principais remetentes",
|
||||
"no_indexed_insights": "Nenhuma informação indexada disponível."
|
||||
},
|
||||
"archived_emails_page": {
|
||||
"title": "E-mails arquivados",
|
||||
"header": "E-mails arquivados",
|
||||
"select_ingestion_source": "Selecione uma fonte de ingestão",
|
||||
"date": "Data",
|
||||
"subject": "Assunto",
|
||||
"sender": "Remetente",
|
||||
"inbox": "Caixa de entrada",
|
||||
"path": "Caminho",
|
||||
"actions": "Ações",
|
||||
"view": "Visualizar",
|
||||
"no_emails_found": "Nenhum e-mail arquivado encontrado.",
|
||||
"prev": "Anterior",
|
||||
"next": "Próximo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { redirect } from '@sveltejs/kit';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import 'dotenv/config';
|
||||
import { api } from '$lib/server/api';
|
||||
import type { SystemSettings } from '@open-archiver/types';
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
const { locals, url } = event;
|
||||
@@ -21,7 +22,9 @@ export const load: LayoutServerLoad = async (event) => {
|
||||
}
|
||||
|
||||
const settingsResponse = await api('/settings', event);
|
||||
const settings = settingsResponse.ok ? await settingsResponse.json() : null;
|
||||
const settings: SystemSettings | null = settingsResponse.ok
|
||||
? await settingsResponse.json()
|
||||
: null;
|
||||
|
||||
return {
|
||||
user: locals.user,
|
||||
|
||||
21
packages/frontend/src/routes/+layout.ts
Normal file
21
packages/frontend/src/routes/+layout.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { loadTranslations } from '$lib/translations';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import type { SupportedLanguage } from '@open-archiver/types';
|
||||
|
||||
export const load: LayoutLoad = async ({ url, data }) => {
|
||||
const { pathname } = url;
|
||||
|
||||
let initLocale: SupportedLanguage = 'en'; // Default fallback
|
||||
|
||||
if (data.settings?.language) {
|
||||
initLocale = data.settings.language;
|
||||
}
|
||||
|
||||
console.log(initLocale);
|
||||
await loadTranslations(initLocale, pathname);
|
||||
|
||||
return {
|
||||
...data,
|
||||
};
|
||||
};
|
||||
@@ -5,6 +5,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import ThemeSwitcher from '$lib/components/custom/ThemeSwitcher.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
const navItems: {
|
||||
href?: string;
|
||||
label: string;
|
||||
@@ -13,24 +14,24 @@
|
||||
label: string;
|
||||
}[];
|
||||
}[] = [
|
||||
{ href: '/dashboard', label: 'Dashboard' },
|
||||
{ href: '/dashboard/ingestions', label: 'Ingestions' },
|
||||
{ href: '/dashboard/archived-emails', label: 'Archived emails' },
|
||||
{ href: '/dashboard/search', label: 'Search' },
|
||||
{ href: '/dashboard', label: $t('app.layout.dashboard') },
|
||||
{ href: '/dashboard/ingestions', label: $t('app.layout.ingestions') },
|
||||
{ href: '/dashboard/archived-emails', label: $t('app.layout.archived_emails') },
|
||||
{ href: '/dashboard/search', label: $t('app.layout.search') },
|
||||
{
|
||||
label: 'Settings',
|
||||
label: $t('app.layout.settings'),
|
||||
subMenu: [
|
||||
{
|
||||
href: '/dashboard/settings/system',
|
||||
label: 'System',
|
||||
label: $t('app.layout.system'),
|
||||
},
|
||||
{
|
||||
href: '/dashboard/settings/users',
|
||||
label: 'Users',
|
||||
label: $t('app.layout.users'),
|
||||
},
|
||||
{
|
||||
href: '/dashboard/settings/roles',
|
||||
label: 'Roles',
|
||||
label: $t('app.layout.roles'),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -90,7 +91,7 @@
|
||||
</NavigationMenu.Root>
|
||||
<div class="flex items-center gap-4">
|
||||
<ThemeSwitcher />
|
||||
<Button onclick={handleLogout} variant="outline">Logout</Button>
|
||||
<Button onclick={handleLogout} variant="outline">{$t('app.layout.logout')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import TopSendersChart from '$lib/components/custom/charts/TopSendersChart.svelte';
|
||||
import IngestionHistoryChart from '$lib/components/custom/charts/IngestionHistoryChart.svelte';
|
||||
import StorageBySourceChart from '$lib/components/custom/charts/StorageBySourceChart.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const transformedHistory = $derived(
|
||||
@@ -19,20 +20,20 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Dashboard - OpenArchiver</title>
|
||||
<meta name="description" content="Overview of your email archive." />
|
||||
<title>{$t('app.dashboard_page.title')} - OpenArchiver</title>
|
||||
<meta name="description" content={$t('app.dashboard_page.meta_description')} />
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex-1 space-y-4">
|
||||
<div class="flex items-center justify-between space-y-2">
|
||||
<h2 class="text-3xl font-bold tracking-tight">Dashboard</h2>
|
||||
<h2 class="text-3xl font-bold tracking-tight">{$t('app.dashboard_page.header')}</h2>
|
||||
</div>
|
||||
{#if !data.ingestionSources || data.ingestionSources?.length === 0}
|
||||
<div>
|
||||
<EmptyState
|
||||
buttonText="Create an ingestion"
|
||||
header="You don't have any ingestion source set up."
|
||||
text="Add an ingestion source to start archiving your inboxes."
|
||||
buttonText={$t('app.dashboard_page.create_ingestion')}
|
||||
header={$t('app.dashboard_page.no_ingestion_header')}
|
||||
text={$t('app.dashboard_page.no_ingestion_text')}
|
||||
click={() => {
|
||||
goto('/dashboard/ingestions');
|
||||
}}
|
||||
@@ -47,9 +48,9 @@
|
||||
<Card.Header
|
||||
class="flex flex-row items-center justify-between space-y-0 pb-2"
|
||||
>
|
||||
<Card.Title class="text-sm font-medium"
|
||||
>Total Emails Archived</Card.Title
|
||||
>
|
||||
<Card.Title class="text-sm font-medium">
|
||||
{$t('app.dashboard_page.total_emails_archived')}
|
||||
</Card.Title>
|
||||
<Archive class="text-muted-foreground h-4 w-4" />
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
@@ -62,7 +63,9 @@
|
||||
<Card.Header
|
||||
class="flex flex-row items-center justify-between space-y-0 pb-2"
|
||||
>
|
||||
<Card.Title class="text-sm font-medium">Total Storage Used</Card.Title>
|
||||
<Card.Title class="text-sm font-medium"
|
||||
>{$t('app.dashboard_page.total_storage_used')}</Card.Title
|
||||
>
|
||||
<HardDrive class="text-muted-foreground h-4 w-4" />
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
@@ -75,9 +78,9 @@
|
||||
<Card.Header
|
||||
class="flex flex-row items-center justify-between space-y-0 pb-2"
|
||||
>
|
||||
<Card.Title class="text-sm font-medium"
|
||||
>Failed Ingestions (Last 7 Days)</Card.Title
|
||||
>
|
||||
<Card.Title class="text-sm font-medium">
|
||||
{$t('app.dashboard_page.failed_ingestions')}
|
||||
</Card.Title>
|
||||
<CircleAlert class=" text-muted-foreground h-4 w-4" />
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
@@ -97,13 +100,13 @@
|
||||
<div class=" lg:col-span-2">
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Ingestion History</Card.Title>
|
||||
<Card.Title>{$t('app.dashboard_page.ingestion_history')}</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class=" pl-4">
|
||||
{#if transformedHistory.length > 0}
|
||||
<IngestionHistoryChart data={transformedHistory} />
|
||||
{:else}
|
||||
<p>No ingestion history available.</p>
|
||||
<p>{$t('app.dashboard_page.no_ingestion_history')}</p>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -111,31 +114,33 @@
|
||||
<div class=" lg:col-span-1">
|
||||
<Card.Root class="h-full">
|
||||
<Card.Header>
|
||||
<Card.Title>Storage by Ingestion Source</Card.Title>
|
||||
<Card.Title>{$t('app.dashboard_page.storage_by_source')}</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="h-full">
|
||||
{#if data.ingestionSources && data.ingestionSources.length > 0}
|
||||
<StorageBySourceChart data={data.ingestionSources} />
|
||||
{:else}
|
||||
<p>No ingestion sources available.</p>
|
||||
<p>{$t('app.dashboard_page.no_ingestion_sources')}</p>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold leading-6">Indexed insights</h1>
|
||||
<h1 class="text-xl font-semibold leading-6">
|
||||
{$t('app.dashboard_page.indexed_insights')}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="grid grid-cols-1">
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Top 10 Senders</Card.Title>
|
||||
<Card.Title>{$t('app.dashboard_page.top_10_senders')}</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
{#if data.indexedInsights && data.indexedInsights.topSenders.length > 0}
|
||||
<TopSendersChart data={data.indexedInsights.topSenders} />
|
||||
{:else}
|
||||
<p>No indexed insights available.</p>
|
||||
<p>{$t('app.dashboard_page.no_indexed_insights')}</p>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { goto } from '$app/navigation';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
@@ -68,11 +69,11 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Archived emails - OpenArchiver</title>
|
||||
<title>{$t('app.archived_emails_page.title')} - OpenArchiver</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold">Archived Emails</h1>
|
||||
<h1 class="text-2xl font-bold">{$t('app.archived_emails_page.header')}</h1>
|
||||
{#if ingestionSources.length > 0}
|
||||
<div class="w-[250px]">
|
||||
<Select.Root
|
||||
@@ -84,7 +85,7 @@
|
||||
<span
|
||||
>{selectedIngestionSourceId
|
||||
? ingestionSources.find((s) => s.id === selectedIngestionSourceId)?.name
|
||||
: 'Select an ingestion source'}</span
|
||||
: $t('app.archived_emails_page.select_ingestion_source')}</span
|
||||
>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
@@ -101,12 +102,12 @@
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head>Date</Table.Head>
|
||||
<Table.Head>Subject</Table.Head>
|
||||
<Table.Head>Sender</Table.Head>
|
||||
<Table.Head>Inbox</Table.Head>
|
||||
<Table.Head>Path</Table.Head>
|
||||
<Table.Head class="text-right">Actions</Table.Head>
|
||||
<Table.Head>{$t('app.archived_emails_page.date')}</Table.Head>
|
||||
<Table.Head>{$t('app.archived_emails_page.subject')}</Table.Head>
|
||||
<Table.Head>{$t('app.archived_emails_page.sender')}</Table.Head>
|
||||
<Table.Head>{$t('app.archived_emails_page.inbox')}</Table.Head>
|
||||
<Table.Head>{$t('app.archived_emails_page.path')}</Table.Head>
|
||||
<Table.Head class="text-right">{$t('app.archived_emails_page.actions')}</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body class="text-sm">
|
||||
@@ -135,7 +136,9 @@
|
||||
</Table.Cell>
|
||||
<Table.Cell class="text-right">
|
||||
<a href={`/dashboard/archived-emails/${email.id}`}>
|
||||
<Button variant="outline">View</Button>
|
||||
<Button variant="outline"
|
||||
>{$t('app.archived_emails_page.view')}</Button
|
||||
>
|
||||
</a>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
@@ -143,7 +146,7 @@
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={5} class="text-center"
|
||||
>No archived emails found.</Table.Cell
|
||||
>{$t('app.archived_emails_page.no_emails_found')}</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
@@ -159,7 +162,9 @@
|
||||
}&limit=${archivedEmails.limit}`}
|
||||
class={archivedEmails.page === 1 ? 'pointer-events-none' : ''}
|
||||
>
|
||||
<Button variant="outline" disabled={archivedEmails.page === 1}>Prev</Button>
|
||||
<Button variant="outline" disabled={archivedEmails.page === 1}
|
||||
>{$t('app.archived_emails_page.prev')}</Button
|
||||
>
|
||||
</a>
|
||||
|
||||
{#each paginationItems as item}
|
||||
@@ -187,7 +192,8 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={archivedEmails.page ===
|
||||
Math.ceil(archivedEmails.total / archivedEmails.limit)}>Next</Button
|
||||
Math.ceil(archivedEmails.total / archivedEmails.limit)}
|
||||
>{$t('app.archived_emails_page.next')}</Button
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { setAlert } from '$lib/components/custom/alert/alert-state.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let email = $derived(data.email);
|
||||
@@ -72,7 +73,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{email?.subject} | Archived emails - OpenArchiver</title>
|
||||
<title>{email?.subject} | {$t('app.archive.title')} - OpenArchiver</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if email}
|
||||
@@ -80,29 +81,31 @@
|
||||
<div class="col-span-3 md:col-span-2">
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>{email.subject || 'No Subject'}</Card.Title>
|
||||
<Card.Title>{email.subject || $t('app.archive.no_subject')}</Card.Title>
|
||||
<Card.Description>
|
||||
From: {email.senderEmail || email.senderName} | Sent: {new Date(
|
||||
email.sentAt
|
||||
).toLocaleString()}
|
||||
{$t('app.archive.from')}: {email.senderEmail || email.senderName} | {$t(
|
||||
'app.archive.sent'
|
||||
)}: {new Date(email.sentAt).toLocaleString()}
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-1">
|
||||
<h3 class="font-semibold">Recipients</h3>
|
||||
<h3 class="font-semibold">{$t('app.archive.recipients')}</h3>
|
||||
<Card.Description>
|
||||
<p>
|
||||
To: {email.recipients.map((r) => r.email || r.name).join(', ')}
|
||||
{$t('app.archive.to')}: {email.recipients
|
||||
.map((r) => r.email || r.name)
|
||||
.join(', ')}
|
||||
</p>
|
||||
</Card.Description>
|
||||
</div>
|
||||
<div class=" space-y-1">
|
||||
<h3 class="font-semibold">Meta data</h3>
|
||||
<h3 class="font-semibold">{$t('app.archive.meta_data')}</h3>
|
||||
<Card.Description class="space-y-2">
|
||||
{#if email.path}
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span>Folder:</span>
|
||||
<span>{$t('app.archive.folder')}:</span>
|
||||
<span class=" bg-muted truncate rounded p-1.5 text-xs"
|
||||
>{email.path || '/'}</span
|
||||
>
|
||||
@@ -110,7 +113,7 @@
|
||||
{/if}
|
||||
{#if email.tags && email.tags.length > 0}
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span> Tags: </span>
|
||||
<span> {$t('app.archive.tags')}: </span>
|
||||
{#each email.tags as tag}
|
||||
<span class=" bg-muted truncate rounded p-1.5 text-xs"
|
||||
>{tag}</span
|
||||
@@ -119,7 +122,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span>size:</span>
|
||||
<span>{$t('app.archive.size')}:</span>
|
||||
<span class=" bg-muted truncate rounded p-1.5 text-xs"
|
||||
>{formatBytes(email.sizeBytes)}</span
|
||||
>
|
||||
@@ -127,12 +130,14 @@
|
||||
</Card.Description>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold">Email Preview</h3>
|
||||
<h3 class="font-semibold">{$t('app.archive.email_preview')}</h3>
|
||||
<EmailPreview raw={email.raw} />
|
||||
</div>
|
||||
{#if email.attachments && email.attachments.length > 0}
|
||||
<div>
|
||||
<h3 class="font-semibold">Attachments</h3>
|
||||
<h3 class="font-semibold">
|
||||
{$t('app.archive.attachments')}
|
||||
</h3>
|
||||
<ul class="mt-2 space-y-2">
|
||||
{#each email.attachments as attachment}
|
||||
<li
|
||||
@@ -152,7 +157,7 @@
|
||||
attachment.filename
|
||||
)}
|
||||
>
|
||||
Download
|
||||
{$t('app.archive.download')}
|
||||
</Button>
|
||||
</li>
|
||||
{/each}
|
||||
@@ -166,16 +171,16 @@
|
||||
<div class="col-span-3 space-y-6 md:col-span-1">
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Actions</Card.Title>
|
||||
<Card.Title>{$t('app.archive.actions')}</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-2">
|
||||
<Button
|
||||
onclick={() =>
|
||||
download(email.storagePath, `${email.subject || 'email'}.eml`)}
|
||||
>Download Email (.eml)</Button
|
||||
>{$t('app.archive.download_eml')}</Button
|
||||
>
|
||||
<Button variant="destructive" onclick={() => (isDeleteDialogOpen = true)}>
|
||||
Delete Email
|
||||
{$t('app.archive.delete_email')}
|
||||
</Button>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -183,7 +188,7 @@
|
||||
{#if email.thread && email.thread.length > 1}
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Email thread</Card.Title>
|
||||
<Card.Title>{$t('app.archive.email_thread')}</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<EmailThread thread={email.thread} currentEmailId={email.id} />
|
||||
@@ -196,10 +201,9 @@
|
||||
<Dialog.Root bind:open={isDeleteDialogOpen}>
|
||||
<Dialog.Content class="sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Are you sure you want to delete this email?</Dialog.Title>
|
||||
<Dialog.Title>{$t('app.archive.delete_confirmation_title')}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
This action cannot be undone and will permanently remove the email and its
|
||||
attachments.
|
||||
{$t('app.archive.delete_confirmation_description')}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Footer class="sm:justify-start">
|
||||
@@ -209,14 +213,18 @@
|
||||
onclick={confirmDelete}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{#if isDeleting}Deleting...{:else}Confirm{/if}
|
||||
{#if isDeleting}
|
||||
{$t('app.archive.deleting')}...
|
||||
{:else}
|
||||
{$t('app.archive.confirm')}
|
||||
{/if}
|
||||
</Button>
|
||||
<Dialog.Close>
|
||||
<Button type="button" variant="secondary">Cancel</Button>
|
||||
<Button type="button" variant="secondary">{$t('app.archive.cancel')}</Button>
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
{:else}
|
||||
<p>Email not found.</p>
|
||||
<p>{$t('app.archive.not_found')}</p>
|
||||
{/if}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import Badge from '$lib/components/ui/badge/badge.svelte';
|
||||
import { setAlert } from '$lib/components/custom/alert/alert-state.svelte';
|
||||
import * as HoverCard from '$lib/components/ui/hover-card/index.js';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let ingestionSources = $state(data.ingestionSources);
|
||||
@@ -266,38 +267,40 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Ingestion sources - OpenArchiver</title>
|
||||
<title>{$t('app.ingestions.title')} - OpenArchiver</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<h1 class="text-2xl font-bold">Ingestion Sources</h1>
|
||||
<h1 class="text-2xl font-bold">{$t('app.ingestions.ingestion_sources')}</h1>
|
||||
{#if selectedIds.length > 0}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Button variant="outline">
|
||||
Bulk Actions ({selectedIds.length})
|
||||
{$t('app.ingestions.bulk_actions')} ({selectedIds.length})
|
||||
<MoreHorizontal class="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Item onclick={handleBulkForceSync}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Force Sync
|
||||
{$t('app.ingestions.force_sync')}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
class="text-red-600"
|
||||
onclick={() => (isBulkDeleteDialogOpen = true)}
|
||||
>
|
||||
<Trash class="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
{$t('app.ingestions.delete')}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
{/if}
|
||||
</div>
|
||||
<Button onclick={openCreateDialog} disabled={data.isDemo}>Create New</Button>
|
||||
<Button onclick={openCreateDialog} disabled={data.isDemo}
|
||||
>{$t('app.ingestions.create_new')}</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
@@ -319,12 +322,12 @@
|
||||
: ((selectedIds.length > 0 ? 'indeterminate' : false) as any)}
|
||||
/>
|
||||
</Table.Head>
|
||||
<Table.Head>Name</Table.Head>
|
||||
<Table.Head>Provider</Table.Head>
|
||||
<Table.Head>Status</Table.Head>
|
||||
<Table.Head>Active</Table.Head>
|
||||
<Table.Head>Created At</Table.Head>
|
||||
<Table.Head class="text-right">Actions</Table.Head>
|
||||
<Table.Head>{$t('app.ingestions.name')}</Table.Head>
|
||||
<Table.Head>{$t('app.ingestions.provider')}</Table.Head>
|
||||
<Table.Head>{$t('app.ingestions.status')}</Table.Head>
|
||||
<Table.Head>{$t('app.ingestions.active')}</Table.Head>
|
||||
<Table.Head>{$t('app.ingestions.created_at')}</Table.Head>
|
||||
<Table.Head class="text-right">{$t('app.ingestions.actions')}</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
@@ -369,8 +372,9 @@
|
||||
<HoverCard.Content class="{getStatusClasses(source.status)} ">
|
||||
<div class="flex flex-col space-y-4 text-sm">
|
||||
<p class=" font-mono">
|
||||
<b>Last sync message:</b>
|
||||
{source.lastSyncStatusMessage || 'Empty'}
|
||||
<b>{$t('app.ingestions.last_sync_message')}:</b>
|
||||
{source.lastSyncStatusMessage ||
|
||||
$t('app.ingestions.empty')}
|
||||
</p>
|
||||
</div>
|
||||
</HoverCard.Content>
|
||||
@@ -393,23 +397,27 @@
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Button variant="ghost" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Open menu</span>
|
||||
<span class="sr-only"
|
||||
>{$t('app.ingestions.open_menu')}</span
|
||||
>
|
||||
<MoreHorizontal class="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Label>Actions</DropdownMenu.Label>
|
||||
<DropdownMenu.Label
|
||||
>{$t('app.ingestions.actions')}</DropdownMenu.Label
|
||||
>
|
||||
<DropdownMenu.Item onclick={() => openEditDialog(source)}
|
||||
>Edit</DropdownMenu.Item
|
||||
>{$t('app.ingestions.edit')}</DropdownMenu.Item
|
||||
>
|
||||
<DropdownMenu.Item onclick={() => handleSync(source.id)}
|
||||
>Force sync</DropdownMenu.Item
|
||||
>{$t('app.ingestions.force_sync')}</DropdownMenu.Item
|
||||
>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item
|
||||
class="text-red-600"
|
||||
onclick={() => openDeleteDialog(source)}
|
||||
>Delete</DropdownMenu.Item
|
||||
>{$t('app.ingestions.delete')}</DropdownMenu.Item
|
||||
>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
@@ -429,17 +437,21 @@
|
||||
<Dialog.Root bind:open={isDialogOpen}>
|
||||
<Dialog.Content class="sm:max-w-120 md:max-w-180">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{selectedSource ? 'Edit' : 'Create'} Ingestion Source</Dialog.Title>
|
||||
<Dialog.Title
|
||||
>{selectedSource ? $t('app.ingestions.edit') : $t('app.ingestions.create')}{' '}
|
||||
{$t('app.ingestions.ingestion_source')}</Dialog.Title
|
||||
>
|
||||
<Dialog.Description>
|
||||
{selectedSource
|
||||
? 'Make changes to your ingestion source here.'
|
||||
: 'Add a new ingestion source to start archiving emails.'}
|
||||
? $t('app.ingestions.edit_description')
|
||||
: $t('app.ingestions.create_description')}
|
||||
<span
|
||||
>Read <a
|
||||
>{$t('app.ingestions.read')}{' '}
|
||||
<a
|
||||
class="text-primary underline underline-offset-2"
|
||||
target="_blank"
|
||||
href="https://docs.openarchiver.com/user-guides/email-providers/"
|
||||
>docs here</a
|
||||
>{$t('app.ingestions.docs_here')}</a
|
||||
>.</span
|
||||
>
|
||||
</Dialog.Description>
|
||||
@@ -451,11 +463,9 @@
|
||||
<Dialog.Root bind:open={isDeleteDialogOpen}>
|
||||
<Dialog.Content class="sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Are you sure you want to delete this ingestion?</Dialog.Title>
|
||||
<Dialog.Title>{$t('app.ingestions.delete_confirmation_title')}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
This will delete all archived emails, attachments, indexing, and files associated
|
||||
with this ingestion. If you only want to stop syncing new emails, you can pause the
|
||||
ingestion instead.
|
||||
{$t('app.ingestions.delete_confirmation_description')}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Footer class="sm:justify-start">
|
||||
@@ -464,10 +474,14 @@
|
||||
variant="destructive"
|
||||
onclick={confirmDelete}
|
||||
disabled={isDeleting}
|
||||
>{#if isDeleting}Deleting...{:else}Confirm{/if}</Button
|
||||
>{#if isDeleting}
|
||||
{$t('app.ingestions.deleting')}...
|
||||
{:else}
|
||||
{$t('app.ingestions.confirm')}
|
||||
{/if}</Button
|
||||
>
|
||||
<Dialog.Close>
|
||||
<Button type="button" variant="secondary">Cancel</Button>
|
||||
<Button type="button" variant="secondary">{$t('app.ingestions.cancel')}</Button>
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
@@ -477,12 +491,12 @@
|
||||
<Dialog.Content class="sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title
|
||||
>Are you sure you want to delete {selectedIds.length} selected ingestions?</Dialog.Title
|
||||
>{$t('app.ingestions.bulk_delete_confirmation_title', {
|
||||
count: selectedIds.length,
|
||||
} as any)}</Dialog.Title
|
||||
>
|
||||
<Dialog.Description>
|
||||
This will delete all archived emails, attachments, indexing, and files associated
|
||||
with these ingestions. If you only want to stop syncing new emails, you can pause
|
||||
the ingestions instead.
|
||||
{$t('app.ingestions.bulk_delete_confirmation_description')}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Footer class="sm:justify-start">
|
||||
@@ -491,10 +505,14 @@
|
||||
variant="destructive"
|
||||
onclick={handleBulkDelete}
|
||||
disabled={isDeleting}
|
||||
>{#if isDeleting}Deleting...{:else}Confirm{/if}</Button
|
||||
>{#if isDeleting}
|
||||
{$t('app.ingestions.deleting')}...
|
||||
{:else}
|
||||
{$t('app.ingestions.confirm')}
|
||||
{/if}</Button
|
||||
>
|
||||
<Dialog.Close>
|
||||
<Button type="button" variant="secondary">Cancel</Button>
|
||||
<Button type="button" variant="secondary">{$t('app.ingestions.cancel')}</Button>
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import type { MatchingStrategy } from '@open-archiver/types';
|
||||
import CircleAlertIcon from '@lucide/svelte/icons/circle-alert';
|
||||
import * as Alert from '$lib/components/ui/alert/index.js';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let searchResult = $derived(data.searchResult);
|
||||
@@ -27,13 +28,14 @@
|
||||
);
|
||||
|
||||
const strategies = [
|
||||
{ value: 'last', label: 'Fuzzy' },
|
||||
{ value: 'all', label: 'Verbatim' },
|
||||
{ value: 'frequency', label: 'Frequency' },
|
||||
{ value: 'last', label: $t('app.search.strategy_fuzzy') },
|
||||
{ value: 'all', label: $t('app.search.strategy_verbatim') },
|
||||
{ value: 'frequency', label: $t('app.search.strategy_frequency') },
|
||||
];
|
||||
|
||||
const triggerContent = $derived(
|
||||
strategies.find((s) => s.value === matchingStrategy)?.label ?? 'Select a strategy'
|
||||
strategies.find((s) => s.value === matchingStrategy)?.label ??
|
||||
$t('app.search.select_strategy')
|
||||
);
|
||||
|
||||
let isMounted = $state(false);
|
||||
@@ -170,25 +172,27 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Search | Open Archiver</title>
|
||||
<meta name="description" content="Search for archived emails." />
|
||||
<title>{$t('app.search.title')} | Open Archiver</title>
|
||||
<meta name="description" content={$t('app.search.description')} />
|
||||
</svelte:head>
|
||||
|
||||
<div class="container mx-auto p-4 md:p-8">
|
||||
<h1 class="mb-4 text-2xl font-bold">Email Search</h1>
|
||||
<h1 class="mb-4 text-2xl font-bold">{$t('app.search.email_search')}</h1>
|
||||
|
||||
<form onsubmit={handleSearch} class="mb-8 flex flex-col space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Input
|
||||
type="search"
|
||||
name="keywords"
|
||||
placeholder="Search by keyword, sender, recipient..."
|
||||
placeholder={$t('app.search.placeholder')}
|
||||
class=" h-12 flex-grow"
|
||||
bind:value={keywords}
|
||||
/>
|
||||
<Button type="submit" class="h-12 cursor-pointer">Search</Button>
|
||||
<Button type="submit" class="h-12 cursor-pointer"
|
||||
>{$t('app.search.search_button')}</Button
|
||||
>
|
||||
</div>
|
||||
<div class="mt-1 text-xs font-medium">Search options</div>
|
||||
<div class="mt-1 text-xs font-medium">{$t('app.search.search_options')}</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select.Root type="single" name="matchingStrategy" bind:value={matchingStrategy}>
|
||||
<Select.Trigger class=" w-[180px] cursor-pointer">
|
||||
@@ -212,7 +216,7 @@
|
||||
{#if error}
|
||||
<Alert.Root variant="destructive">
|
||||
<CircleAlertIcon class="size-4" />
|
||||
<Alert.Title>Error</Alert.Title>
|
||||
<Alert.Title>{$t('app.search.error')}</Alert.Title>
|
||||
<Alert.Description>{error}</Alert.Description>
|
||||
</Alert.Root>
|
||||
{/if}
|
||||
@@ -220,9 +224,12 @@
|
||||
{#if searchResult}
|
||||
<p class="text-muted-foreground mb-4">
|
||||
{#if searchResult.total > 0}
|
||||
Found {searchResult.total} results in {searchResult.processingTimeMs / 1000}s
|
||||
{$t('app.search.found_results_in', {
|
||||
total: searchResult.total,
|
||||
seconds: searchResult.processingTimeMs / 1000,
|
||||
} as any)}
|
||||
{:else}
|
||||
Found {searchResult.total} results
|
||||
{$t('app.search.found_results', { total: searchResult.total } as any)}
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
@@ -240,7 +247,7 @@
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<CardDescription class="flex items-center space-x-1">
|
||||
<span>From:</span>
|
||||
<span>{$t('app.search.from')}:</span>
|
||||
{#if !isMounted}
|
||||
<span class="bg-accent h-4 w-40 animate-pulse rounded-md"
|
||||
></span>
|
||||
@@ -251,7 +258,7 @@
|
||||
></span>
|
||||
{/if}
|
||||
<span class="mx-2">|</span>
|
||||
<span>To:</span>
|
||||
<span>{$t('app.search.to')}:</span>
|
||||
{#if !isMounted}
|
||||
<span class="bg-accent h-4 w-40 animate-pulse rounded-md"
|
||||
></span>
|
||||
@@ -280,7 +287,9 @@
|
||||
<div
|
||||
class="space-y-2 rounded-md bg-slate-100 p-2 dark:bg-slate-800"
|
||||
>
|
||||
<p class="text-sm text-gray-500">In email body:</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
{$t('app.search.in_email_body')}:
|
||||
</p>
|
||||
{#if !isMounted}
|
||||
<Skeleton class="my-2 h-5 w-full bg-gray-200" />
|
||||
{:else}
|
||||
@@ -302,7 +311,9 @@
|
||||
class="space-y-2 rounded-md bg-slate-100 p-2 dark:bg-slate-800"
|
||||
>
|
||||
<p class="text-sm text-gray-500">
|
||||
In attachment: {attachment.filename}
|
||||
{$t('app.search.in_attachment', {
|
||||
filename: attachment.filename,
|
||||
} as any)}
|
||||
</p>
|
||||
{#if !isMounted}
|
||||
<Skeleton class="my-2 h-5 w-full bg-gray-200" />
|
||||
@@ -331,7 +342,7 @@
|
||||
}&matchingStrategy=${matchingStrategy}`}
|
||||
class={page === 1 ? 'pointer-events-none' : ''}
|
||||
>
|
||||
<Button variant="outline" disabled={page === 1}>Prev</Button>
|
||||
<Button variant="outline" disabled={page === 1}>{$t('app.search.prev')}</Button>
|
||||
</a>
|
||||
|
||||
{#each paginationItems as item}
|
||||
@@ -357,7 +368,7 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={page === Math.ceil(searchResult.total / searchResult.limit)}
|
||||
>Next</Button
|
||||
>{$t('app.search.next')}</Button
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import RoleForm from '$lib/components/custom/RoleForm.svelte';
|
||||
import { api } from '$lib/api.client';
|
||||
import type { Role } from '@open-archiver/types';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let roles = $state(data.roles);
|
||||
@@ -108,22 +109,22 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Role Management - OpenArchiver</title>
|
||||
<title>{$t('app.roles.title')} - OpenArchiver</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold">Role Management</h1>
|
||||
<Button onclick={openCreateDialog}>Create New</Button>
|
||||
<h1 class="text-2xl font-bold">{$t('app.roles.role_management')}</h1>
|
||||
<Button onclick={openCreateDialog}>{$t('app.roles.create_new')}</Button>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head>Name</Table.Head>
|
||||
<Table.Head>Created At</Table.Head>
|
||||
<Table.Head class="text-right">Actions</Table.Head>
|
||||
<Table.Head>{$t('app.roles.name')}</Table.Head>
|
||||
<Table.Head>{$t('app.roles.created_at')}</Table.Head>
|
||||
<Table.Head class="text-right">{$t('app.roles.actions')}</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
@@ -136,25 +137,27 @@
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Button variant="ghost" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Open menu</span>
|
||||
<span class="sr-only">{$t('app.roles.open_menu')}</span>
|
||||
<MoreHorizontal class="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Label>Actions</DropdownMenu.Label>
|
||||
<DropdownMenu.Label
|
||||
>{$t('app.roles.actions')}</DropdownMenu.Label
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
onclick={() => openViewPolicyDialog(role)}
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<Eye class="mr-2 h-4 w-4" />
|
||||
View Policy
|
||||
{$t('app.roles.view_policy')}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
onclick={() => openEditDialog(role)}
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<Edit class="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
{$t('app.roles.edit')}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item
|
||||
@@ -162,7 +165,7 @@
|
||||
onclick={() => openDeleteDialog(role)}
|
||||
>
|
||||
<Trash class="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
{$t('app.roles.delete')}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
@@ -171,7 +174,8 @@
|
||||
{/each}
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={3} class="h-24 text-center">No roles found.</Table.Cell
|
||||
<Table.Cell colspan={3} class="h-24 text-center"
|
||||
>{$t('app.roles.no_roles_found')}</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
@@ -183,9 +187,9 @@
|
||||
<Dialog.Root bind:open={isViewPolicyDialogOpen}>
|
||||
<Dialog.Content class="sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Role Policy</Dialog.Title>
|
||||
<Dialog.Title>{$t('app.roles.role_policy')}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Viewing policy for role: {selectedRole?.name}
|
||||
{$t('app.roles.viewing_policy_for_role', { name: selectedRole?.name } as any)}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<div
|
||||
@@ -199,9 +203,14 @@
|
||||
<Dialog.Root bind:open={isFormDialogOpen}>
|
||||
<Dialog.Content class="sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{selectedRole ? 'Edit' : 'Create'} Role</Dialog.Title>
|
||||
<Dialog.Title
|
||||
>{selectedRole ? $t('app.roles.edit') : $t('app.roles.create')}
|
||||
{$t('app.roles.role')}</Dialog.Title
|
||||
>
|
||||
<Dialog.Description>
|
||||
{selectedRole ? 'Make changes to the role here.' : 'Add a new role to the system.'}
|
||||
{selectedRole
|
||||
? $t('app.roles.edit_description')
|
||||
: $t('app.roles.create_description')}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<RoleForm role={selectedRole} onSubmit={handleFormSubmit} />
|
||||
@@ -211,9 +220,9 @@
|
||||
<Dialog.Root bind:open={isDeleteDialogOpen}>
|
||||
<Dialog.Content class="sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Are you sure you want to delete this role?</Dialog.Title>
|
||||
<Dialog.Title>{$t('app.roles.delete_confirmation_title')}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
This action cannot be undone. This will permanently delete the role.
|
||||
{$t('app.roles.delete_confirmation_description')}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Footer class="sm:justify-start">
|
||||
@@ -223,10 +232,14 @@
|
||||
onclick={confirmDelete}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{#if isDeleting}Deleting...{:else}Confirm{/if}
|
||||
{#if isDeleting}
|
||||
{$t('app.roles.deleting')}...
|
||||
{:else}
|
||||
{$t('app.roles.confirm')}
|
||||
{/if}
|
||||
</Button>
|
||||
<Dialog.Close>
|
||||
<Button type="button" variant="secondary">Cancel</Button>
|
||||
<Button type="button" variant="secondary">{$t('app.roles.cancel')}</Button>
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -4,47 +4,47 @@ import { error, fail } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const response = await api('/settings', event);
|
||||
const response = await api('/settings', event);
|
||||
|
||||
if (!response.ok) {
|
||||
const { message } = await response.json();
|
||||
throw error(response.status, message || 'Failed to fetch system settings');
|
||||
}
|
||||
if (!response.ok) {
|
||||
const { message } = await response.json();
|
||||
throw error(response.status, message || 'Failed to fetch system settings');
|
||||
}
|
||||
|
||||
const settings: SystemSettings = await response.json();
|
||||
return {
|
||||
settings,
|
||||
};
|
||||
const settings: SystemSettings = await response.json();
|
||||
return {
|
||||
settings,
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const formData = await event.request.formData();
|
||||
const language = formData.get('language');
|
||||
const theme = formData.get('theme');
|
||||
const supportEmail = formData.get('supportEmail');
|
||||
default: async (event) => {
|
||||
const formData = await event.request.formData();
|
||||
const language = formData.get('language');
|
||||
const theme = formData.get('theme');
|
||||
const supportEmail = formData.get('supportEmail');
|
||||
|
||||
const body: Partial<SystemSettings> = {
|
||||
language: language as SystemSettings['language'],
|
||||
theme: theme as SystemSettings['theme'],
|
||||
supportEmail: supportEmail ? String(supportEmail) : null,
|
||||
};
|
||||
const body: Partial<SystemSettings> = {
|
||||
language: language as SystemSettings['language'],
|
||||
theme: theme as SystemSettings['theme'],
|
||||
supportEmail: supportEmail ? String(supportEmail) : null,
|
||||
};
|
||||
|
||||
const response = await api('/settings', event, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const response = await api('/settings', event, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const { message } = await response.json();
|
||||
return fail(response.status, { message: message || 'Failed to update settings' });
|
||||
}
|
||||
if (!response.ok) {
|
||||
const { message } = await response.json();
|
||||
return fail(response.status, { message: message || 'Failed to update settings' });
|
||||
}
|
||||
|
||||
const updatedSettings: SystemSettings = await response.json();
|
||||
const updatedSettings: SystemSettings = await response.json();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
settings: updatedSettings,
|
||||
};
|
||||
},
|
||||
return {
|
||||
success: true,
|
||||
settings: updatedSettings,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,20 +8,23 @@
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { setAlert } from '$lib/components/custom/alert/alert-state.svelte';
|
||||
import type { SupportedLanguage } from '@open-archiver/types';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { data, form }: { data: PageData; form: any } = $props();
|
||||
let settings = $state(data.settings);
|
||||
let isSaving = $state(false);
|
||||
|
||||
const languageOptions: { value: SupportedLanguage; label: string }[] = [
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'es', label: 'Spanish' },
|
||||
{ value: 'fr', label: 'French' },
|
||||
{ value: 'de', label: 'German' },
|
||||
{ value: 'it', label: 'Italian' },
|
||||
{ value: 'pt', label: 'Portuguese' },
|
||||
{ value: 'nl', label: 'Dutch' },
|
||||
{ value: 'ja', label: 'Japanese' },
|
||||
{ value: 'en', label: '🇬🇧 English' },
|
||||
{ value: 'de', label: '🇩🇪 Deutsch' },
|
||||
{ value: 'fr', label: '🇫🇷 Français' },
|
||||
{ value: 'et', label: '🇪🇪 Eesti' },
|
||||
{ value: 'es', label: '🇪🇸 Español' },
|
||||
{ value: 'it', label: '🇮🇹 Italiano' },
|
||||
{ value: 'pt', label: '🇵🇹 Português' },
|
||||
{ value: 'nl', label: '🇳🇱 Nederlands' },
|
||||
{ value: 'el', label: '🇬🇷 Ελληνικά' },
|
||||
{ value: 'ja', label: '🇯🇵 日本語' },
|
||||
];
|
||||
|
||||
const languageTriggerContent = $derived(
|
||||
@@ -52,35 +55,37 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>System Settings - OpenArchiver</title>
|
||||
<title>{$t('app.system_settings.title')} - OpenArchiver</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">System Settings</h1>
|
||||
<p class="text-muted-foreground">Manage global application settings.</p>
|
||||
<h1 class="text-2xl font-bold">{$t('app.system_settings.system_settings')}</h1>
|
||||
<p class="text-muted-foreground">{$t('app.system_settings.description')}</p>
|
||||
</div>
|
||||
|
||||
<form method="POST" class="space-y-8" onsubmit={() => (isSaving = true)}>
|
||||
<Card.Root>
|
||||
<Card.Content class="space-y-4">
|
||||
<!-- Hide language setting for now -->
|
||||
<!-- <div class="grid gap-2">
|
||||
<Label.Root class="mb-1" for="language">Language</Label.Root>
|
||||
<div class="grid gap-2">
|
||||
<Label.Root class="mb-1" for="language"
|
||||
>{$t('app.system_settings.language')}</Label.Root
|
||||
>
|
||||
<Select.Root name="language" bind:value={settings.language} type="single">
|
||||
<Select.Trigger class="w-[280px]">
|
||||
{languageTriggerContent}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Content>
|
||||
{#each languageOptions as lang}
|
||||
<Select.Item value={lang.value}>{lang.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label.Root class="mb-1">Default theme</Label.Root>
|
||||
<Label.Root class="mb-1">{$t('app.system_settings.default_theme')}</Label.Root>
|
||||
<RadioGroup.Root
|
||||
bind:value={settings.theme}
|
||||
name="theme"
|
||||
@@ -88,21 +93,23 @@
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<RadioGroup.Item value="light" id="light" />
|
||||
<Label.Root for="light">Light</Label.Root>
|
||||
<Label.Root for="light">{$t('app.system_settings.light')}</Label.Root>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<RadioGroup.Item value="dark" id="dark" />
|
||||
<Label.Root for="dark">Dark</Label.Root>
|
||||
<Label.Root for="dark">{$t('app.system_settings.dark')}</Label.Root>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<RadioGroup.Item value="system" id="system" />
|
||||
<Label.Root for="system">System</Label.Root>
|
||||
<Label.Root for="system">{$t('app.system_settings.system')}</Label.Root>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label.Root class="mb-1" for="supportEmail">Support Email</Label.Root>
|
||||
<Label.Root class="mb-1" for="supportEmail"
|
||||
>{$t('app.system_settings.support_email')}</Label.Root
|
||||
>
|
||||
<Input
|
||||
id="supportEmail"
|
||||
name="supportEmail"
|
||||
@@ -115,7 +122,11 @@
|
||||
</Card.Content>
|
||||
<Card.Footer class="border-t px-6 py-4">
|
||||
<Button type="submit" disabled={isSaving}>
|
||||
{#if isSaving}Saving...{:else}Save Changes{/if}
|
||||
{#if isSaving}
|
||||
{$t('app.system_settings.saving')}...
|
||||
{:else}
|
||||
{$t('app.system_settings.save_changes')}
|
||||
{/if}
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import UserForm from '$lib/components/custom/UserForm.svelte';
|
||||
import { api } from '$lib/api.client';
|
||||
import type { User } from '@open-archiver/types';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let users = $state(data.users);
|
||||
@@ -103,24 +104,24 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>User Management - OpenArchiver</title>
|
||||
<title>{$t('app.users.title')} - OpenArchiver</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold">User Management</h1>
|
||||
<Button onclick={openCreateDialog}>Create New</Button>
|
||||
<h1 class="text-2xl font-bold">{$t('app.users.user_management')}</h1>
|
||||
<Button onclick={openCreateDialog}>{$t('app.users.create_new')}</Button>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head>Name</Table.Head>
|
||||
<Table.Head>Email</Table.Head>
|
||||
<Table.Head>Role</Table.Head>
|
||||
<Table.Head>Created At</Table.Head>
|
||||
<Table.Head class="text-right">Actions</Table.Head>
|
||||
<Table.Head>{$t('app.users.name')}</Table.Head>
|
||||
<Table.Head>{$t('app.users.email')}</Table.Head>
|
||||
<Table.Head>{$t('app.users.role')}</Table.Head>
|
||||
<Table.Head>{$t('app.users.created_at')}</Table.Head>
|
||||
<Table.Head class="text-right">{$t('app.users.actions')}</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
@@ -135,18 +136,20 @@
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<Button variant="ghost" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Open menu</span>
|
||||
<span class="sr-only">{$t('app.users.open_menu')}</span>
|
||||
<MoreHorizontal class="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Label>Actions</DropdownMenu.Label>
|
||||
<DropdownMenu.Label
|
||||
>{$t('app.users.actions')}</DropdownMenu.Label
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
onclick={() => openEditDialog(user)}
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<Edit class="mr-2 h-4 w-4" />
|
||||
Edit</DropdownMenu.Item
|
||||
{$t('app.users.edit')}</DropdownMenu.Item
|
||||
>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item
|
||||
@@ -154,7 +157,7 @@
|
||||
onclick={() => openDeleteDialog(user)}
|
||||
>
|
||||
<Trash class="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
{$t('app.users.delete')}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
@@ -163,7 +166,8 @@
|
||||
{/each}
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={5} class="h-24 text-center">No users found.</Table.Cell
|
||||
<Table.Cell colspan={5} class="h-24 text-center"
|
||||
>{$t('app.users.no_users_found')}</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
@@ -175,9 +179,14 @@
|
||||
<Dialog.Root bind:open={isDialogOpen}>
|
||||
<Dialog.Content class="sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{selectedUser ? 'Edit' : 'Create'} User</Dialog.Title>
|
||||
<Dialog.Title
|
||||
>{selectedUser ? $t('app.users.edit') : $t('app.users.create')}
|
||||
{$t('app.users.user')}</Dialog.Title
|
||||
>
|
||||
<Dialog.Description>
|
||||
{selectedUser ? 'Make changes to the user here.' : 'Add a new user to the system.'}
|
||||
{selectedUser
|
||||
? $t('app.users.edit_description')
|
||||
: $t('app.users.create_description')}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<UserForm {roles} user={selectedUser} onSubmit={handleFormSubmit} />
|
||||
@@ -187,10 +196,9 @@
|
||||
<Dialog.Root bind:open={isDeleteDialogOpen}>
|
||||
<Dialog.Content class="sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Are you sure you want to delete this user?</Dialog.Title>
|
||||
<Dialog.Title>{$t('app.users.delete_confirmation_title')}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
This action cannot be undone. This will permanently delete the user and remove their
|
||||
data from our servers.
|
||||
{$t('app.users.delete_confirmation_description')}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Footer class="sm:justify-start">
|
||||
@@ -200,10 +208,14 @@
|
||||
onclick={confirmDelete}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{#if isDeleting}Deleting...{:else}Confirm{/if}
|
||||
{#if isDeleting}
|
||||
{$t('app.users.deleting')}...
|
||||
{:else}
|
||||
{$t('app.users.confirm')}
|
||||
{/if}
|
||||
</Button>
|
||||
<Dialog.Close>
|
||||
<Button type="button" variant="secondary">Cancel</Button>
|
||||
<Button type="button" variant="secondary">{$t('app.users.cancel')}</Button>
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { api } from '$lib/api.client';
|
||||
import { authStore } from '$lib/stores/auth.store';
|
||||
import { setAlert } from '$lib/components/custom/alert/alert-state.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let first_name = '';
|
||||
let last_name = '';
|
||||
@@ -45,11 +46,8 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Setup - Open Archiver</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Set up the initial administrator account for Open Archiver."
|
||||
/>
|
||||
<title>{$t('app.setup.title')} - Open Archiver</title>
|
||||
<meta name="description" content={$t('app.setup.description')} />
|
||||
</svelte:head>
|
||||
|
||||
<div
|
||||
@@ -67,35 +65,33 @@
|
||||
</div>
|
||||
<Card.Root class="w-full max-w-md">
|
||||
<Card.Header class="space-y-1">
|
||||
<Card.Title class="text-2xl">Welcome</Card.Title>
|
||||
<Card.Description
|
||||
>Create the first administrator account to get started.</Card.Description
|
||||
>
|
||||
<Card.Title class="text-2xl">{$t('app.setup.welcome')}</Card.Title>
|
||||
<Card.Description>{$t('app.setup.create_admin_account')}</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content class="grid gap-4">
|
||||
<form on:submit|preventDefault={handleSubmit} class="grid gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="first_name">First name</Label>
|
||||
<Label for="first_name">{$t('app.setup.first_name')}</Label>
|
||||
<Input
|
||||
id="first_name"
|
||||
type="text"
|
||||
placeholder="First name"
|
||||
placeholder={$t('app.setup.first_name')}
|
||||
bind:value={first_name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="last_name">Last name</Label>
|
||||
<Label for="last_name">{$t('app.setup.last_name')}</Label>
|
||||
<Input
|
||||
id="last_name"
|
||||
type="text"
|
||||
placeholder="Last name"
|
||||
placeholder={$t('app.setup.last_name')}
|
||||
bind:value={last_name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="email">Email</Label>
|
||||
<Label for="email">{$t('app.setup.email')}</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
@@ -105,15 +101,15 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="password">Password</Label>
|
||||
<Label for="password">{$t('app.setup.password')}</Label>
|
||||
<Input id="password" type="password" bind:value={password} required />
|
||||
</div>
|
||||
|
||||
<Button type="submit" class="w-full" disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
<span>Creating Account...</span>
|
||||
<span>{$t('app.setup.creating_account')}...</span>
|
||||
{:else}
|
||||
<span>Create Account</span>
|
||||
<span>{$t('app.setup.create_account')}</span>
|
||||
{/if}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { authStore } from '$lib/stores/auth.store';
|
||||
import type { LoginResponse } from '@open-archiver/types';
|
||||
import { setAlert } from '$lib/components/custom/alert/alert-state.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let email = '';
|
||||
let password = '';
|
||||
@@ -50,7 +51,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Login - Open Archiver</title>
|
||||
<title>{$t('app.auth.login')} - Open Archiver</title>
|
||||
<meta name="description" content="Login to your Open Archiver account." />
|
||||
</svelte:head>
|
||||
|
||||
@@ -69,13 +70,13 @@
|
||||
</div>
|
||||
<Card.Root class="w-full max-w-md">
|
||||
<Card.Header class="space-y-1">
|
||||
<Card.Title class="text-2xl">Login</Card.Title>
|
||||
<Card.Description>Enter your email below to login to your account.</Card.Description>
|
||||
<Card.Title class="text-2xl">{$t('app.auth.login')}</Card.Title>
|
||||
<Card.Description>{$t('app.auth.login_tip')}</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content class="grid gap-4">
|
||||
<form onsubmit={handleSubmit} class="grid gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="email">Email</Label>
|
||||
<Label for="email">{$t('app.auth.email')}</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
@@ -85,12 +86,12 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="password">Password</Label>
|
||||
<Label for="password">{$t('app.auth.password')}</Label>
|
||||
<Input id="password" type="password" bind:value={password} required />
|
||||
</div>
|
||||
|
||||
<Button type="submit" class=" w-full" disabled={isLoading}>
|
||||
{isLoading ? 'Logging in...' : 'Login'}
|
||||
{isLoading ? $t('app.common.working') : $t('app.auth.login')}
|
||||
</Button>
|
||||
</form>
|
||||
</Card.Content>
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
export type SupportedLanguage =
|
||||
| 'en' // English
|
||||
| 'es' // Spanish
|
||||
| 'fr' // French
|
||||
| 'de' // German
|
||||
| 'it' // Italian
|
||||
| 'pt' // Portuguese
|
||||
| 'nl' // Dutch
|
||||
| 'ja'; // Japanese
|
||||
| 'en' // English
|
||||
| 'es' // Spanish
|
||||
| 'fr' // French
|
||||
| 'de' // German
|
||||
| 'it' // Italian
|
||||
| 'pt' // Portuguese
|
||||
| 'nl' // Dutch
|
||||
| 'ja' // Japanese
|
||||
| 'et' // Estonian
|
||||
| 'el'; // Greek
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'system';
|
||||
|
||||
export interface SystemSettings {
|
||||
/** The default display language for the application UI. */
|
||||
language: SupportedLanguage;
|
||||
/** The default display language for the application UI. */
|
||||
language: SupportedLanguage;
|
||||
|
||||
/** The default color theme for the application. */
|
||||
theme: Theme;
|
||||
/** The default color theme for the application. */
|
||||
theme: Theme;
|
||||
|
||||
/** A public-facing email address for user support inquiries. */
|
||||
supportEmail: string | null;
|
||||
/** A public-facing email address for user support inquiries. */
|
||||
supportEmail: string | null;
|
||||
}
|
||||
|
||||
61
pnpm-lock.yaml
generated
61
pnpm-lock.yaml
generated
@@ -96,6 +96,15 @@ importers:
|
||||
googleapis:
|
||||
specifier: ^152.0.0
|
||||
version: 152.0.0
|
||||
i18next:
|
||||
specifier: ^25.4.2
|
||||
version: 25.4.2(typescript@5.8.3)
|
||||
i18next-fs-backend:
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0
|
||||
i18next-http-middleware:
|
||||
specifier: ^3.8.0
|
||||
version: 3.8.0
|
||||
imapflow:
|
||||
specifier: ^1.0.191
|
||||
version: 1.0.191
|
||||
@@ -220,6 +229,9 @@ importers:
|
||||
svelte-persisted-store:
|
||||
specifier: ^0.12.0
|
||||
version: 0.12.0(svelte@5.35.5)
|
||||
sveltekit-i18n:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2(svelte@5.35.5)
|
||||
tailwind-merge:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
@@ -1589,6 +1601,14 @@ packages:
|
||||
svelte: ^5.0.0
|
||||
vite: ^6.0.0
|
||||
|
||||
'@sveltekit-i18n/base@1.3.7':
|
||||
resolution: {integrity: sha512-kg1kql1/ro/lIudwFiWrv949Q07gmweln87tflUZR51MNdXXzK4fiJQv5Mw50K/CdQ5BOk/dJ0WOH2vOtBI6yw==}
|
||||
peerDependencies:
|
||||
svelte: '>=3.49.0'
|
||||
|
||||
'@sveltekit-i18n/parser-default@1.1.1':
|
||||
resolution: {integrity: sha512-/gtzLlqm/sox7EoPKD56BxGZktK/syGc79EbJAPWY5KVitQD9SM0TP8yJCqDxTVPk7Lk0WJhrBGUE2Nn0f5M1w==}
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
|
||||
|
||||
@@ -3041,6 +3061,20 @@ packages:
|
||||
humanize-ms@1.2.1:
|
||||
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
||||
|
||||
i18next-fs-backend@2.6.0:
|
||||
resolution: {integrity: sha512-3ZlhNoF9yxnM8pa8bWp5120/Ob6t4lVl1l/tbLmkml/ei3ud8IWySCHt2lrY5xWRlSU5D9IV2sm5bEbGuTqwTw==}
|
||||
|
||||
i18next-http-middleware@3.8.0:
|
||||
resolution: {integrity: sha512-G3DpT/ibwOx6BCI5A2xUmZ2ybUDUrI6emCdEE7AX9TKvz+WzA6peuyv0BxC8kIrJHtrdnmUWwk4rDN9nxWvsFg==}
|
||||
|
||||
i18next@25.4.2:
|
||||
resolution: {integrity: sha512-gD4T25a6ovNXsfXY1TwHXXXLnD/K2t99jyYMCSimSCBnBRJVQr5j+VAaU83RJCPzrTGhVQ6dqIga66xO2rtd5g==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -4370,6 +4404,11 @@ packages:
|
||||
resolution: {integrity: sha512-KuRvI82rhh0RMz1EKsUJD96gZyHJ+h2+8zrwO8iqE/p/CmcNKvIItDUAeUePhuCDgtegDJmF8IKThbHIfmTgTA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
sveltekit-i18n@2.4.2:
|
||||
resolution: {integrity: sha512-hjRWn4V4DBL8JQKJoJa3MRvn6d32Zo+rWkoSP5bsQ/XIAguPdQUZJ8LMe6Nc1rST8WEVdu9+vZI3aFdKYGR3+Q==}
|
||||
peerDependencies:
|
||||
svelte: '>=3.49.0'
|
||||
|
||||
tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
|
||||
@@ -6320,6 +6359,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@sveltekit-i18n/base@1.3.7(svelte@5.35.5)':
|
||||
dependencies:
|
||||
svelte: 5.35.5
|
||||
|
||||
'@sveltekit-i18n/parser-default@1.1.1': {}
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -7909,6 +7954,16 @@ snapshots:
|
||||
ms: 2.1.3
|
||||
optional: true
|
||||
|
||||
i18next-fs-backend@2.6.0: {}
|
||||
|
||||
i18next-http-middleware@3.8.0: {}
|
||||
|
||||
i18next@25.4.2(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
@@ -9308,6 +9363,12 @@ snapshots:
|
||||
magic-string: 0.30.17
|
||||
zimmerframe: 1.1.2
|
||||
|
||||
sveltekit-i18n@2.4.2(svelte@5.35.5):
|
||||
dependencies:
|
||||
'@sveltekit-i18n/base': 1.3.7(svelte@5.35.5)
|
||||
'@sveltekit-i18n/parser-default': 1.1.1
|
||||
svelte: 5.35.5
|
||||
|
||||
tabbable@6.2.0: {}
|
||||
|
||||
tailwind-merge@3.0.2: {}
|
||||
|
||||
Reference in New Issue
Block a user