mirror of
https://github.com/LogicLabs-OU/OpenArchiver.git
synced 2026-04-06 00:31:57 +02:00
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`
This commit is contained in:
@@ -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);
|
||||
@@ -39,7 +39,7 @@ export class IngestionController {
|
||||
// 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.',
|
||||
error.message || req.t('ingestion.failedToCreate'),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -48,14 +48,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 +68,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 +87,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 +104,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 +139,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 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export const getSettings = async (req: Request, res: Response) => {
|
||||
res.status(200).json(settings);
|
||||
} catch (error) {
|
||||
// A more specific error could be logged here
|
||||
res.status(500).json({ message: 'Failed to retrieve settings' });
|
||||
res.status(500).json({ message: req.t('settings.failedToRetrieve') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,6 +20,6 @@ export const updateSettings = async (req: Request, res: Response) => {
|
||||
res.status(200).json(updatedSettings);
|
||||
} catch (error) {
|
||||
// A more specific error could be logged here
|
||||
res.status(500).json({ message: 'Failed to update settings' });
|
||||
res.status(500).json({ message: req.t('settings.failedToUpdate') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,13 +4,13 @@ import * as path from 'path';
|
||||
import { storage as storageConfig } from '../../config/storage';
|
||||
|
||||
export class StorageController {
|
||||
constructor(private storageService: StorageService) {}
|
||||
constructor(private storageService: StorageService) { }
|
||||
|
||||
public downloadFile = async (req: Request, res: Response): Promise<void> => {
|
||||
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);
|
||||
|
||||
@@ -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();
|
||||
@@ -48,6 +54,25 @@ const searchService = new SearchService();
|
||||
const searchController = new SearchController();
|
||||
const iamService = new IamService();
|
||||
const iamController = new IamController(iamService);
|
||||
const settingsService = new SettingsService();
|
||||
|
||||
// --- 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'),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// --- Express App Initialization ---
|
||||
const app = express();
|
||||
@@ -70,6 +95,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 +123,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);
|
||||
}
|
||||
};
|
||||
|
||||
55
packages/backend/src/locales/de/translation.json
Normal file
55
packages/backend/src/locales/de/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Benutzer nicht gefunden",
|
||||
"cannotDeleteOnlyUser": "Sie versuchen, den einzigen Benutzer in der Datenbank zu löschen, dies ist nicht gestattet."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Einstellungen konnten nicht abgerufen werden",
|
||||
"failedToUpdate": "Einstellungen konnten nicht aktualisiert werden"
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Die Erfassungsquelle konnte aufgrund eines Verbindungsfehlers nicht erstellt werden.",
|
||||
"notFound": "Erfassungsquelle nicht gefunden",
|
||||
"initialImportTriggered": "Der erstmalige Import wurde erfolgreich ausgelöst.",
|
||||
"forceSyncTriggered": "Die Zwangssynchronisierung wurde 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"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/el/translation.json
Normal file
55
packages/backend/src/locales/el/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "Το email, ο κωδικός πρόσβασης και το όνομα είναι υποχρεωτικά",
|
||||
"alreadyCompleted": "Η εγκατάσταση έχει ήδη ολοκληρωθεί."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "Το email και ο κωδικός πρόσβασης είναι υποχρεωτικά",
|
||||
"invalidCredentials": "Μη έγκυρα διαπιστευτήρια"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Παρουσιάστηκε ένα εσωτερικό σφάλμα διακομιστή",
|
||||
"demoMode": "Αυτή η λειτουργία δεν επιτρέπεται σε λειτουργία επίδειξης.",
|
||||
"unauthorized": "Μη εξουσιοδοτημένο",
|
||||
"unknown": "Παρουσιάστηκε ένα άγνωστο σφάλμα"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Ο χρήστης δεν βρέθηκε",
|
||||
"cannotDeleteOnlyUser": "Προσπαθείτε να διαγράψετε τον μοναδικό χρήστη στη βάση δεδομένων, αυτό δεν επιτρέπεται."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Η λήψη των ρόλων απέτυχε.",
|
||||
"roleNotFound": "Ο ρόλος δεν βρέθηκε.",
|
||||
"failedToGetRole": "Η λήψη του ρόλου απέτυχε.",
|
||||
"missingRoleFields": "Λείπουν τα απαιτούμενα πεδία: όνομα και πολιτική.",
|
||||
"invalidPolicy": "Μη έγκυρη δήλωση πολιτικής:",
|
||||
"failedToCreateRole": "Η δημιουργία του ρόλου απέτυχε.",
|
||||
"failedToDeleteRole": "Η διαγραφή του ρόλου απέτυχε.",
|
||||
"missingUpdateFields": "Λείπουν πεδία για ενημέρωση: όνομα ή πολιτικές.",
|
||||
"failedToUpdateRole": "Η ενημέρωση του ρόλου απέτυχε."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Η ανάκτηση των ρυθμίσεων απέτυχε",
|
||||
"failedToUpdate": "Η ενημέρωση των ρυθμίσεων απέτυχε"
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Η δημιουργία της πηγής πρόσληψης απέτυχε λόγω σφάλματος σύνδεσης.",
|
||||
"notFound": "Η πηγή πρόσληψης δεν βρέθηκε",
|
||||
"initialImportTriggered": "Η αρχική εισαγωγή ενεργοποιήθηκε με επιτυχία.",
|
||||
"forceSyncTriggered": "Ο εξαναγκασμένος συγχρονισμός ενεργοποιήθηκε με επιτυχία."
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "Το αρχειοθετημένο email δεν βρέθηκε"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "Απαιτούνται λέξεις-κλειδιά"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Απαιτείται η διαδρομή του αρχείου",
|
||||
"invalidFilePath": "Μη έγκυρη διαδρομή αρχείου",
|
||||
"fileNotFound": "Το αρχείο δεν βρέθηκε",
|
||||
"downloadError": "Σφάλμα κατά τη λήψη του αρχείου"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/en/translation.json
Normal file
55
packages/backend/src/locales/en/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"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 this is a translation"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "An internal server error occurred",
|
||||
"demoMode": "This operation is not allowed in demo mode.",
|
||||
"unauthorized": "Unauthorized",
|
||||
"unknown": "An unknown error occurred"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "User not found",
|
||||
"cannotDeleteOnlyUser": "You are trying to delete the only user in the database, this is not allowed."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Failed to retrieve settings",
|
||||
"failedToUpdate": "Failed to update settings"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/es/translation.json
Normal file
55
packages/backend/src/locales/es/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Usuario no encontrado",
|
||||
"cannotDeleteOnlyUser": "Está intentando eliminar el único usuario de la base de datos, esto no está permitido."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Error al recuperar la configuración",
|
||||
"failedToUpdate": "Error al actualizar la configuración"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/et/translation.json
Normal file
55
packages/backend/src/locales/et/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"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 serveri viga",
|
||||
"demoMode": "See toiming pole demo-režiimis lubatud.",
|
||||
"unauthorized": "Volitamata",
|
||||
"unknown": "Ilmnes tundmatu viga"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Kasutajat ei leitud",
|
||||
"cannotDeleteOnlyUser": "Püüate kustutada andmebaasi ainsat kasutajat, see pole lubatud."
|
||||
},
|
||||
"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": "Uuendatavad väljad puuduvad: nimi või poliitikad.",
|
||||
"failedToUpdateRole": "Rolli värskendamine ebaõnnestus."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Seadete toomine ebaõnnestus",
|
||||
"failedToUpdate": "Seadete värskendamine ebaõnnestus"
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Söötmeallika loomine ebaõnnestus ühenduse vea tõttu.",
|
||||
"notFound": "Söötmeallikat ei leitud",
|
||||
"initialImportTriggered": "Esialgne import käivitati edukalt.",
|
||||
"forceSyncTriggered": "Sunnitud sü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"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/fr/translation.json
Normal file
55
packages/backend/src/locales/fr/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "E-mail, mot de passe et nom sont requis",
|
||||
"alreadyCompleted": "La configuration est déjà terminée."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "E-mail et mot de passe sont requis",
|
||||
"invalidCredentials": "Identifiants non valides"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "Une erreur interne du serveur est survenue",
|
||||
"demoMode": "Cette opération n'est pas autorisée en mode démo.",
|
||||
"unauthorized": "Non autorisé",
|
||||
"unknown": "Une erreur inconnue est survenue"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Utilisateur non trouvé",
|
||||
"cannotDeleteOnlyUser": "Vous essayez de supprimer le seul utilisateur de la base de données, ce n'est pas autorisé."
|
||||
},
|
||||
"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 non valide :",
|
||||
"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."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Échec de la récupération des paramètres",
|
||||
"failedToUpdate": "Échec de la mise à jour des paramètres"
|
||||
},
|
||||
"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": "Mots-clés requis"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "Chemin du fichier requis",
|
||||
"invalidFilePath": "Chemin de fichier non valide",
|
||||
"fileNotFound": "Fichier non trouvé",
|
||||
"downloadError": "Erreur lors du téléchargement du fichier"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/it/translation.json
Normal file
55
packages/backend/src/locales/it/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "Email, password e nome sono obbligatori",
|
||||
"alreadyCompleted": "La configurazione è già stata completata."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "Email e password sono obbligatori",
|
||||
"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"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Utente non trovato",
|
||||
"cannotDeleteOnlyUser": "Stai tentando di eliminare l'unico utente nel database, ciò non è consentito."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Impossibile ottenere i ruoli.",
|
||||
"roleNotFound": "Ruolo non trovato.",
|
||||
"failedToGetRole": "Impossibile ottenere il ruolo.",
|
||||
"missingRoleFields": "Campi obbligatori mancanti: nome e politica.",
|
||||
"invalidPolicy": "Dichiarazione di politica non valida:",
|
||||
"failedToCreateRole": "Impossibile creare il ruolo.",
|
||||
"failedToDeleteRole": "Impossibile eliminare il ruolo.",
|
||||
"missingUpdateFields": "Campi da aggiornare mancanti: nome o politiche.",
|
||||
"failedToUpdateRole": "Impossibile aggiornare il ruolo."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Impossibile recuperare le impostazioni",
|
||||
"failedToUpdate": "Impossibile aggiornare le impostazioni"
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Impossibile creare l'origine di ingestione a causa di un errore di connessione.",
|
||||
"notFound": "Origine di ingestione 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"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/ja/translation.json
Normal file
55
packages/backend/src/locales/ja/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "メールアドレス、パスワード、氏名が必要です",
|
||||
"alreadyCompleted": "セットアップは既に完了しています。"
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "メールアドレスとパスワードが必要です",
|
||||
"invalidCredentials": "認証情報が無効です"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"internalServerError": "内部サーバーエラーが発生しました",
|
||||
"demoMode": "この操作はデモモードでは許可されていません。",
|
||||
"unauthorized": "不正なアクセスです",
|
||||
"unknown": "不明なエラーが発生しました"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "ユーザーが見つかりません",
|
||||
"cannotDeleteOnlyUser": "データベース内の唯一のユーザーを削除しようとしていますが、これは許可されていません。"
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "ロールの取得に失敗しました。",
|
||||
"roleNotFound": "ロールが見つかりません。",
|
||||
"failedToGetRole": "ロールの取得に失敗しました。",
|
||||
"missingRoleFields": "必須フィールドがありません:名前とポリシー。",
|
||||
"invalidPolicy": "無効なポリシーステートメント:",
|
||||
"failedToCreateRole": "ロールの作成に失敗しました。",
|
||||
"failedToDeleteRole": "ロールの削除に失敗しました。",
|
||||
"missingUpdateFields": "更新するフィールドがありません:名前またはポリシー。",
|
||||
"failedToUpdateRole": "ロールの更新に失敗しました。"
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "設定の取得に失敗しました",
|
||||
"failedToUpdate": "設定の更新に失敗しました"
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "接続エラーのため、取り込みソースの作成に失敗しました。",
|
||||
"notFound": "取り込みソースが見つかりません",
|
||||
"initialImportTriggered": "初期インポートが正常にトリガーされました。",
|
||||
"forceSyncTriggered": "強制同期が正常にトリガーされました。"
|
||||
},
|
||||
"archivedEmail": {
|
||||
"notFound": "アーカイブされたメールが見つかりません"
|
||||
},
|
||||
"search": {
|
||||
"keywordsRequired": "キーワードが必要です"
|
||||
},
|
||||
"storage": {
|
||||
"filePathRequired": "ファイルパスが必要です",
|
||||
"invalidFilePath": "無効なファイルパスです",
|
||||
"fileNotFound": "ファイルが見つかりません",
|
||||
"downloadError": "ファイルのダウンロード中にエラーが発生しました"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/nl/translation.json
Normal file
55
packages/backend/src/locales/nl/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Gebruiker niet gevonden",
|
||||
"cannotDeleteOnlyUser": "U probeert de enige gebruiker in de database te verwijderen, dit is niet toegestaan."
|
||||
},
|
||||
"iam": {
|
||||
"failedToGetRoles": "Rollen ophalen mislukt.",
|
||||
"roleNotFound": "Rol niet gevonden.",
|
||||
"failedToGetRole": "Rol ophalen mislukt.",
|
||||
"missingRoleFields": "Verplichte velden ontbreken: naam en beleid.",
|
||||
"invalidPolicy": "Ongeldige beleidsverklaring:",
|
||||
"failedToCreateRole": "Rol aanmaken mislukt.",
|
||||
"failedToDeleteRole": "Rol verwijderen mislukt.",
|
||||
"missingUpdateFields": "Te updaten velden ontbreken: naam of beleid.",
|
||||
"failedToUpdateRole": "Rol bijwerken mislukt."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Instellingen ophalen mislukt",
|
||||
"failedToUpdate": "Instellingen bijwerken mislukt"
|
||||
},
|
||||
"ingestion": {
|
||||
"failedToCreate": "Aanmaken van de opnamebron mislukt vanwege een verbindingsfout.",
|
||||
"notFound": "Opnamebron niet gevonden",
|
||||
"initialImportTriggered": "Initiële import succesvol gestart.",
|
||||
"forceSyncTriggered": "Geforceerde synchronisatie succesvol gestart."
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/locales/pt/translation.json
Normal file
55
packages/backend/src/locales/pt/translation.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"auth": {
|
||||
"setup": {
|
||||
"allFieldsRequired": "E-mail, senha e nome são obrigatórios",
|
||||
"alreadyCompleted": "A configuração já foi concluída."
|
||||
},
|
||||
"login": {
|
||||
"emailAndPasswordRequired": "E-mail and 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"
|
||||
},
|
||||
"user": {
|
||||
"notFound": "Usuário não encontrado",
|
||||
"cannotDeleteOnlyUser": "Você está tentando excluir o único usuário no banco de dados, isso não é permitido."
|
||||
},
|
||||
"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 atualizar: nome ou políticas.",
|
||||
"failedToUpdateRole": "Falha ao atualizar a função."
|
||||
},
|
||||
"settings": {
|
||||
"failedToRetrieve": "Falha ao recuperar as configurações",
|
||||
"failedToUpdate": "Falha ao atualizar as configurações"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
33
pnpm-lock.yaml
generated
33
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
|
||||
@@ -3052,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'}
|
||||
@@ -7931,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
|
||||
|
||||
Reference in New Issue
Block a user