From 5f7512577929fc4d607964ae178c06476967aecf Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 4 Jun 2026 09:08:52 +0100 Subject: [PATCH 01/18] refactor: decouple error classes from i18n and logging Errors now carry a raw translation key and context; translation happens at the API/UI boundary (unwrapError, handleErrors) and logging is the caller's responsibility. Removes i18n and logger options from BaseErrorOptions and all construction sites. --- demos/provider-mock/src/db.ts | 1 - .../api-express-router/src/errorHandler.ts | 1 + .../src/middlewares/authMiddleware.ts | 2 +- packages/common/src/error.ts | 89 ++++++------------- packages/database/src/base/mongo.ts | 1 - packages/database/src/databases/captcha.ts | 2 - packages/database/src/databases/client.ts | 1 - packages/database/src/databases/provider.ts | 10 --- packages/env/src/env.ts | 1 - .../getFrictionlessCaptchaChallenge.ts | 10 --- .../api/captcha/getImageCaptchaChallenge.ts | 10 --- .../src/api/captcha/getPoWCaptchaChallenge.ts | 10 --- .../api/captcha/submitImageCaptchaSolution.ts | 6 -- .../api/captcha/submitPoWCaptchaSolution.ts | 6 -- packages/provider/src/api/verify.ts | 12 --- .../provider/src/tasks/client/clientTasks.ts | 4 - 16 files changed, 28 insertions(+), 138 deletions(-) diff --git a/demos/provider-mock/src/db.ts b/demos/provider-mock/src/db.ts index beb93c5431..1cb6944ed1 100644 --- a/demos/provider-mock/src/db.ts +++ b/demos/provider-mock/src/db.ts @@ -100,7 +100,6 @@ export class JA4Database extends MongoDatabase { if (!this.tables) { throw new ProsopoDBError("DATABASE.TABLES_UNDEFINED", { context: { failedFuncName: this.getTables.name }, - logger: this.logger, }); } return this.tables; diff --git a/packages/api-express-router/src/errorHandler.ts b/packages/api-express-router/src/errorHandler.ts index 9a43084032..d949e519bf 100644 --- a/packages/api-express-router/src/errorHandler.ts +++ b/packages/api-express-router/src/errorHandler.ts @@ -24,6 +24,7 @@ export const handleErrors = ( response: Response, next: NextFunction, ) => { + request.logger.error(() => ({ err })); const { code, statusMessage, jsonError } = unwrapError(err, request.i18n); response.statusMessage = statusMessage; response.set("content-type", "application/json"); diff --git a/packages/api-express-router/src/middlewares/authMiddleware.ts b/packages/api-express-router/src/middlewares/authMiddleware.ts index 9a44fd50e0..7116902177 100644 --- a/packages/api-express-router/src/middlewares/authMiddleware.ts +++ b/packages/api-express-router/src/middlewares/authMiddleware.ts @@ -40,7 +40,7 @@ export const authMiddleware = ( res.status(401).json({ error: new ProsopoEnvError(error || "API.UNAUTHORIZED", { - context: { i18n: req.i18n, code: 401 }, + context: { code: 401 }, }), }); return; diff --git a/packages/common/src/error.ts b/packages/common/src/error.ts index e77b15eea3..8237bf65a3 100644 --- a/packages/common/src/error.ts +++ b/packages/common/src/error.ts @@ -16,16 +16,11 @@ import type { TranslationKey } from "@prosopo/locale"; import type { ApiJsonError } from "@prosopo/types"; import type { TFunction } from "i18next"; import { ZodError } from "zod"; -import { type LogLevel, type Logger, getLogger } from "./logger.js"; type BaseErrorOptions = { name?: string; translationKey?: TranslationKey; - logger?: Logger; - logLevel?: LogLevel; context?: ContextType; - silent?: boolean; - i18n?: { t: TFunction }; }; interface BaseContextParams { @@ -44,47 +39,27 @@ type ApiContextParams = BaseContextParams & { code?: number; }; -// if i18n is not loaded then we use this -const backupTranslationObj = { t: (key: string) => key }; - export abstract class ProsopoBaseError< ContextType extends BaseContextParams = BaseContextParams, > extends Error { - translationKey: string | undefined; + translationKey: TranslationKey | undefined; context: ContextType | undefined; + cause: Error | undefined; constructor( error: Error | TranslationKey, options?: BaseErrorOptions, ) { - const logger = options?.logger || getLogger("info", import.meta.url); - const logLevel = options?.logLevel || "error"; - const i18n = options?.i18n || backupTranslationObj; if (error instanceof Error) { - super(i18n.t(error.message)); + super(error.message); + this.cause = error; this.translationKey = options?.translationKey; - this.context = { - ...(options?.context as ContextType), - ...(options?.translationKey - ? { translationMessage: i18n.t(options.translationKey) } - : {}), - }; } else { - super(i18n.t(error)); + super(error); this.translationKey = error; - this.context = options?.context; } - if (!options?.silent) this.logError(logger, logLevel, options?.name); - } - - private logError(logger: Logger, logLevel: LogLevel, errorName?: string) { - const errorParams = { error: this.message, context: this.context }; - const errorMessage = { errorType: errorName || this.name, errorParams }; - if (logLevel === "debug") { - logger.debug(() => ({ data: { ...errorMessage, stack: this.stack } })); - return; - } - logger.error(() => ({ data: { ...errorMessage } })); + this.context = options?.context; + this.name = options?.name || this.constructor.name; } } @@ -94,9 +69,7 @@ export class ProsopoError extends ProsopoBaseError { error: Error | TranslationKey, options?: BaseErrorOptions, ) { - const errorName = options?.name || "ProsopoError"; - const optionsAll = { ...options, name: errorName }; - super(error, optionsAll); + super(error, options); } } @@ -105,9 +78,7 @@ export class ProsopoEnvError extends ProsopoBaseError { error: Error | TranslationKey, options?: BaseErrorOptions, ) { - const errorName = options?.name || "ProsopoEnvError"; - const optionsAll = { ...options, name: errorName }; - super(error, optionsAll); + super(error, options); } } @@ -116,9 +87,7 @@ export class ProsopoContractError extends ProsopoBaseError, ) { - const errorName = options?.name || "ProsopoContractError"; - const optionsAll = { ...options, name: errorName }; - super(error, optionsAll); + super(error, options); } } @@ -127,9 +96,7 @@ export class ProsopoTxQueueError extends ProsopoBaseError error: Error | TranslationKey, options?: BaseErrorOptions, ) { - const errorName = options?.name || "ProsopoTxQueueError"; - const optionsAll = { ...options, name: errorName }; - super(error, optionsAll); + super(error, options); } } @@ -138,9 +105,7 @@ export class ProsopoDBError extends ProsopoBaseError { error: Error | TranslationKey, options?: BaseErrorOptions, ) { - const errorName = options?.name || "ProsopoDBError"; - const optionsAll = { ...options, name: errorName }; - super(error, optionsAll); + super(error, options); } } @@ -149,9 +114,7 @@ export class ProsopoCliError extends ProsopoBaseError { error: Error | TranslationKey, options?: BaseErrorOptions, ) { - const errorName = options?.name || "ProsopoCliError"; - const optionsAll = { ...options, name: errorName }; - super(error, optionsAll); + super(error, options); } } @@ -160,9 +123,7 @@ export class ProsopoDatasetError extends ProsopoBaseError error: Error | TranslationKey, options?: BaseErrorOptions, ) { - const errorName = options?.name || "ProsopoDatasetError"; - const optionsAll = { ...options, name: errorName }; - super(error, optionsAll); + super(error, options); } } @@ -173,11 +134,9 @@ export class ProsopoApiError extends ProsopoBaseError { error: Error | TranslationKey, options?: BaseErrorOptions, ) { - const errorName = options?.name || "ProsopoApiError"; const code = options?.context?.code || 500; const optionsAll = { ...options, - name: errorName, context: { ...options?.context, code, @@ -191,35 +150,39 @@ export class ProsopoApiError extends ProsopoBaseError { } } +const fallbackTranslate = { t: (key: string) => key }; + export const unwrapError = ( err: ProsopoBaseError | SyntaxError | ZodError, i18nInstance?: { t: TFunction }, ) => { - const i18n = i18nInstance || backupTranslationObj; + const i18n = i18nInstance || fallbackTranslate; let code = "code" in err ? (err.code as number) : 400; - const message = i18n.t(err.message); // should be translated already - let jsonError: ApiJsonError = { code, message }; + let jsonError: ApiJsonError = { + code, + message: i18n.t( + (err as ProsopoBaseError).translationKey || err.message, + ), + }; const statusMessage = "Bad Request"; - jsonError.message = message; - jsonError.key = "translationKey" in err ? err.translationKey : "API.UNKNOWN"; + jsonError.key = + (err as ProsopoBaseError).translationKey ?? "API.UNKNOWN"; // unwrap the errors to get the actual error message while (err instanceof ProsopoBaseError && err.context) { - // base error will not have a translation key const contextTranslationKey = typeof err.context.translationKey === "string" ? err.context.translationKey : undefined; jsonError.key = contextTranslationKey || err.translationKey || "API.UNKNOWN"; - jsonError.message = i18n.t(err.message); + jsonError.message = i18n.t(err.translationKey || err.message); jsonError.data = err.context.data as Record | undefined; const contextCode = typeof err.context.code === "number" ? err.context.code : undefined; code = contextCode ?? jsonError.code; - // Only move to the next error if ProsopoBaseError or ZodError if ( err.context.error && (err.context.error instanceof ProsopoBaseError || diff --git a/packages/database/src/base/mongo.ts b/packages/database/src/base/mongo.ts index 45fc1f4092..25afa36b01 100644 --- a/packages/database/src/base/mongo.ts +++ b/packages/database/src/base/mongo.ts @@ -64,7 +64,6 @@ export class MongoDatabase implements IDatabase { if (!this.connection) { throw new ProsopoDBError("DATABASE.CONNECTION_UNDEFINED", { context: { failedFuncName: this.getConnection.name }, - logger: this.logger, }); } return this.connection; diff --git a/packages/database/src/databases/captcha.ts b/packages/database/src/databases/captcha.ts index cc287280f9..4f8c3b58f5 100644 --- a/packages/database/src/databases/captcha.ts +++ b/packages/database/src/databases/captcha.ts @@ -82,7 +82,6 @@ export class CaptchaDatabase extends MongoDatabase implements ICaptchaDatabase { if (!this.tables) { throw new ProsopoDBError("DATABASE.TABLES_UNDEFINED", { context: { failedFuncName: this.getTables.name }, - logger: this.logger, }); } return this.tables; @@ -233,7 +232,6 @@ export class CaptchaDatabase extends MongoDatabase implements ICaptchaDatabase { limit, failedFuncName: this.getCaptchas.name, }, - logger: this.logger, }); } finally { await this.close(); diff --git a/packages/database/src/databases/client.ts b/packages/database/src/databases/client.ts index 3e0ff6fa14..48fbf2b373 100644 --- a/packages/database/src/databases/client.ts +++ b/packages/database/src/databases/client.ts @@ -57,7 +57,6 @@ export class ClientDatabase extends MongoDatabase implements IClientDatabase { if (!this.tables) { throw new ProsopoDBError("DATABASE.TABLES_UNDEFINED", { context: { failedFuncName: this.getTables.name }, - logger: this.logger, }); } return this.tables; diff --git a/packages/database/src/databases/provider.ts b/packages/database/src/databases/provider.ts index 9a4e2c9dd8..3fc1696e19 100644 --- a/packages/database/src/databases/provider.ts +++ b/packages/database/src/databases/provider.ts @@ -251,7 +251,6 @@ export class ProviderDatabase if (!this.tables) { throw new ProsopoDBError("DATABASE.TABLES_UNDEFINED", { context: { failedFuncName: this.getTables.name }, - logger: this.logger, }); } return this.tables; @@ -417,7 +416,6 @@ export class ProviderDatabase } catch (err) { throw new ProsopoDBError("DATABASE.DATASET_LOAD_FAILED", { context: { failedFuncName: this.storeDataset.name, error: err }, - logger: this.logger, }); } } @@ -744,7 +742,6 @@ export class ProviderDatabase serverChecked, storedStatus, }, - logger: this.logger, }); this.logger.error(() => ({ err: error, @@ -765,7 +762,6 @@ export class ProviderDatabase if (!this.tables) { throw new ProsopoDBError("DATABASE.DATABASE_UNDEFINED", { context: { failedFuncName: this.getPowCaptchaRecordByChallenge.name }, - logger: this.logger, }); } @@ -790,7 +786,6 @@ export class ProviderDatabase } catch (error) { const err = new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", { context: { error, challenge }, - logger: this.logger, }); this.logger.error(() => ({ err: err, @@ -849,7 +844,6 @@ export class ProviderDatabase challenge, ...update, }, - logger: this.logger, }); this.logger.info(() => ({ err: err, @@ -871,7 +865,6 @@ export class ProviderDatabase challenge, ...update, }, - logger: this.logger, }); this.logger.error(() => ({ err: err, @@ -1083,7 +1076,6 @@ export class ProviderDatabase } catch (err) { throw new ProsopoDBError("DATABASE.SESSION_STORE_FAILED", { context: { error: err, sessionId: sessionRecord.sessionId }, - logger: this.logger, }); } } @@ -1138,7 +1130,6 @@ export class ProviderDatabase } catch (err) { throw new ProsopoDBError("DATABASE.SESSION_CHECK_REMOVE_FAILED", { context: { error: err, sessionId }, - logger: this.logger, }); } } @@ -1170,7 +1161,6 @@ export class ProviderDatabase } catch (err) { throw new ProsopoDBError("DATABASE.SESSION_GET_FAILED", { context: { error: err, userSitekeyIpHash }, - logger: this.logger, }); } } diff --git a/packages/env/src/env.ts b/packages/env/src/env.ts index 1ee59ca287..0dac6c7bfc 100644 --- a/packages/env/src/env.ts +++ b/packages/env/src/env.ts @@ -136,7 +136,6 @@ export class Environment implements ProsopoEnvironment { } catch (err) { throw new ProsopoEnvError("GENERAL.ENVIRONMENT_NOT_READY", { context: { error: err }, - logger: this.logger, }); } } diff --git a/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge.ts b/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge.ts index 2b16ee1993..7183a5f123 100644 --- a/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge.ts +++ b/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge.ts @@ -106,8 +106,6 @@ export default ( siteKey: dapp, user, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -170,8 +168,6 @@ export default ( return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -190,8 +186,6 @@ export default ( siteKey: dapp, user, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -344,8 +338,6 @@ export default ( siteKey: dapp, user, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -468,8 +460,6 @@ export default ( return next( new ProsopoApiError("API.BAD_REQUEST", { context: { code: 400, error: err }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/captcha/getImageCaptchaChallenge.ts b/packages/provider/src/api/captcha/getImageCaptchaChallenge.ts index 7db709dc5c..9f919aa4bf 100644 --- a/packages/provider/src/api/captcha/getImageCaptchaChallenge.ts +++ b/packages/provider/src/api/captcha/getImageCaptchaChallenge.ts @@ -47,8 +47,6 @@ export default ( return next( new ProsopoApiError("API.BAD_REQUEST", { context: { code: 400, error: "IP address not found" }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -61,8 +59,6 @@ export default ( return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -79,8 +75,6 @@ export default ( return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -121,8 +115,6 @@ export default ( siteKey: dapp, user, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -193,8 +185,6 @@ export default ( code: 500, params: req.params, }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/captcha/getPoWCaptchaChallenge.ts b/packages/provider/src/api/captcha/getPoWCaptchaChallenge.ts index 8fd29605f9..4a8cd33f05 100644 --- a/packages/provider/src/api/captcha/getPoWCaptchaChallenge.ts +++ b/packages/provider/src/api/captcha/getPoWCaptchaChallenge.ts @@ -48,8 +48,6 @@ export default ( return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -66,8 +64,6 @@ export default ( return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -108,8 +104,6 @@ export default ( siteKey: dapp, user, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -125,8 +119,6 @@ export default ( siteKey: dapp, user, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -195,8 +187,6 @@ export default ( user: req.body.user, error: err, }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/captcha/submitImageCaptchaSolution.ts b/packages/provider/src/api/captcha/submitImageCaptchaSolution.ts index 4f3d72fea7..94c537a1b0 100644 --- a/packages/provider/src/api/captcha/submitImageCaptchaSolution.ts +++ b/packages/provider/src/api/captcha/submitImageCaptchaSolution.ts @@ -59,8 +59,6 @@ export default ( return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err, body: req.body }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -77,8 +75,6 @@ export default ( return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -117,8 +113,6 @@ export default ( siteKey: req.body.dapp, error: err, }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/captcha/submitPoWCaptchaSolution.ts b/packages/provider/src/api/captcha/submitPoWCaptchaSolution.ts index fe9452bb83..de5eca96aa 100644 --- a/packages/provider/src/api/captcha/submitPoWCaptchaSolution.ts +++ b/packages/provider/src/api/captcha/submitPoWCaptchaSolution.ts @@ -52,8 +52,6 @@ export default (env: ProviderEnvironment) => return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err, body: req.body }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -79,8 +77,6 @@ export default (env: ProviderEnvironment) => return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -111,8 +107,6 @@ export default (env: ProviderEnvironment) => siteKey: req.body.dapp, error: err, }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/verify.ts b/packages/provider/src/api/verify.ts index 9b63daa599..f84b366e7b 100644 --- a/packages/provider/src/api/verify.ts +++ b/packages/provider/src/api/verify.ts @@ -79,8 +79,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err, body: req.body }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -102,8 +100,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp, user }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -141,8 +137,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("API.BAD_REQUEST", { context: { code: 500, siteKey: req.body.dapp, user: req.body.user }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -182,8 +176,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err, body: req.body }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -206,8 +198,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -251,8 +241,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("API.BAD_REQUEST", { context: { code: 500, error: err }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/tasks/client/clientTasks.ts b/packages/provider/src/tasks/client/clientTasks.ts index b259c79966..7b3d6b8ab2 100644 --- a/packages/provider/src/tasks/client/clientTasks.ts +++ b/packages/provider/src/tasks/client/clientTasks.ts @@ -263,7 +263,6 @@ export class ClientTaskManager { } catch (e: unknown) { const getClientListError = new ProsopoApiError("DATABASE.UNKNOWN", { context: { error: e }, - logger: this.logger, }); this.logger.error(() => ({ err: getClientListError, @@ -346,7 +345,6 @@ export class ClientTaskManager { "DATABASE.UNKNOWN", { context: { error: e }, - logger: this.logger, }, ); this.logger.error(() => ({ @@ -380,7 +378,6 @@ export class ClientTaskManager { if (!isValidPrivateKey(detectorKey)) { throw new ProsopoApiError("INVALID_DETECTOR_KEY", { context: { detectorKey }, - logger: this.logger, }); } await this.providerDB.storeDetectorKey(detectorKey); @@ -396,7 +393,6 @@ export class ClientTaskManager { if (!isValidPrivateKey(detectorKey)) { throw new ProsopoApiError("INVALID_DETECTOR_KEY", { context: { detectorKey }, - logger: this.logger, }); } await this.providerDB.removeDetectorKey(detectorKey, expirationInSeconds); From 8843210b54b82a2554ef1e679f4b41203bf98024 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 4 Jun 2026 22:12:11 +0100 Subject: [PATCH 02/18] refactor: complete error/i18n decoupling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove TranslationKey and TFunction imports from @prosopo/common; error constructors now accept plain string instead of the branded type - Drop @prosopo/locale and i18next from common's dependencies - Remove i18n parameter from unwrapError; message field is now the raw translation key so translation happens at the UI/response layer only - Fix i18SharedOptions: namespace → defaultNS, add explicit ns list - i18nBackend.initializeI18n now throws when called in browser context instead of silently returning an uninitialised instance - Remove request.i18n from handleErrors; update errorHandler tests to expect key strings rather than translated messages - ProcaptchaWidget translates error.key via useTranslation at render time --- .../api-express-router/src/errorHandler.ts | 2 +- .../src/tests/unit/errorHandler.unit.test.ts | 32 ++++++----------- packages/common/package.json | 2 -- packages/common/src/error.ts | 34 +++++++------------ packages/locale/src/i18SharedOptions.ts | 3 +- packages/locale/src/i18nBackend.ts | 8 +++-- .../src/components/ProcaptchaWidget.tsx | 8 ++++- 7 files changed, 39 insertions(+), 50 deletions(-) diff --git a/packages/api-express-router/src/errorHandler.ts b/packages/api-express-router/src/errorHandler.ts index d949e519bf..89c73ca9bd 100644 --- a/packages/api-express-router/src/errorHandler.ts +++ b/packages/api-express-router/src/errorHandler.ts @@ -25,7 +25,7 @@ export const handleErrors = ( next: NextFunction, ) => { request.logger.error(() => ({ err })); - const { code, statusMessage, jsonError } = unwrapError(err, request.i18n); + const { code, statusMessage, jsonError } = unwrapError(err); response.statusMessage = statusMessage; response.set("content-type", "application/json"); response.status(code); diff --git a/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts b/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts index 3f2bea15e9..4fad96095b 100644 --- a/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts +++ b/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts @@ -13,18 +13,15 @@ // limitations under the License. import { ProsopoApiError, ProsopoEnvError } from "@prosopo/common"; -import { loadI18next } from "@prosopo/locale"; import type { NextFunction, Request, Response } from "express"; import { describe, expect, it, vi } from "vitest"; import { ZodError } from "zod"; import { handleErrors } from "../../errorHandler.js"; -describe("handleErrors", async () => { - const i18n = await loadI18next(true); - await i18n.changeLanguage("en"); +describe("handleErrors", () => { + const mockRequest = {} as unknown as Request; - it("should handle ProsopoApiError", async () => { - const mockRequest = { i18n } as unknown as Request; + it("should handle ProsopoApiError", () => { const mockResponse = { writeHead: vi.fn().mockReturnThis(), set: vi.fn().mockReturnThis(), @@ -36,9 +33,7 @@ describe("handleErrors", async () => { const error = new ProsopoApiError("CONTRACT.INVALID_DATA_FORMAT", { context: { code: 400 }, - i18n, }); - console.log(error); handleErrors(error, mockRequest, mockResponse, mockNext); @@ -50,15 +45,14 @@ describe("handleErrors", async () => { error: { code: 400, key: "CONTRACT.INVALID_DATA_FORMAT", - message: "Invalid data format", + message: "CONTRACT.INVALID_DATA_FORMAT", }, }); expect(mockResponse.status).toHaveBeenCalledWith(400); expect(mockResponse.end).toHaveBeenCalled(); }); - it("should not return SyntaxError", async () => { - const mockRequest = { i18n } as unknown as Request; + it("should not return SyntaxError", () => { const mockResponse = { writeHead: vi.fn().mockReturnThis(), set: vi.fn().mockReturnThis(), @@ -89,7 +83,6 @@ describe("handleErrors", async () => { }); it("should handle ZodError", () => { - const mockRequest = { i18n } as unknown as Request; const mockResponse = { writeHead: vi.fn().mockReturnThis(), set: vi.fn().mockReturnThis(), @@ -119,8 +112,7 @@ describe("handleErrors", async () => { expect(mockResponse.end).toHaveBeenCalled(); }); - it("should unwrap nested ProsopoBaseError", async () => { - const mockRequest = { i18n } as unknown as Request; + it("should unwrap nested ProsopoBaseError", () => { const mockResponse = { writeHead: vi.fn().mockReturnThis(), set: vi.fn().mockReturnThis(), @@ -130,9 +122,7 @@ describe("handleErrors", async () => { } as unknown as Response; const mockNext = vi.fn() as unknown as NextFunction; - const envError = new ProsopoEnvError("GENERAL.ENVIRONMENT_NOT_READY", { - i18n, - }); + const envError = new ProsopoEnvError("GENERAL.ENVIRONMENT_NOT_READY"); const apiError = new ProsopoApiError(envError); handleErrors(apiError, mockRequest, mockResponse, mockNext); @@ -146,14 +136,13 @@ describe("handleErrors", async () => { error: { code: 500, key: "GENERAL.ENVIRONMENT_NOT_READY", - message: "Environment not ready", + message: "GENERAL.ENVIRONMENT_NOT_READY", }, }); expect(mockResponse.end).toHaveBeenCalled(); }); - it("should unwrap nested ProsopoBaseErrors but not an Error that is nested inside them", async () => { - const mockRequest = { i18n } as unknown as Request; + it("should unwrap nested ProsopoBaseErrors but not an Error that is nested inside them", () => { const mockResponse = { writeHead: vi.fn().mockReturnThis(), set: vi.fn().mockReturnThis(), @@ -168,7 +157,6 @@ describe("handleErrors", async () => { const error = new Error("Some error"); const apiError = new ProsopoApiError(key, { context: { code, error }, - i18n, }); handleErrors(apiError, mockRequest, mockResponse, mockNext); @@ -182,7 +170,7 @@ describe("handleErrors", async () => { error: { code, key, - message: "Unknown API error", + message: "API.UNKNOWN", }, }); expect(mockResponse.end).toHaveBeenCalled(); diff --git a/packages/common/package.json b/packages/common/package.json index c3fd594212..e214cc1db2 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -27,8 +27,6 @@ "author": "Prosopo Limited", "license": "Apache-2.0", "dependencies": { - "@prosopo/locale": "3.1.27", - "i18next": "24.1.0", "zod": "3.23.8" }, "devDependencies": { diff --git a/packages/common/src/error.ts b/packages/common/src/error.ts index 8237bf65a3..b36d086021 100644 --- a/packages/common/src/error.ts +++ b/packages/common/src/error.ts @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { TranslationKey } from "@prosopo/locale"; import type { ApiJsonError } from "@prosopo/types"; -import type { TFunction } from "i18next"; import { ZodError } from "zod"; type BaseErrorOptions = { name?: string; - translationKey?: TranslationKey; + translationKey?: string; context?: ContextType; }; @@ -42,12 +40,12 @@ type ApiContextParams = BaseContextParams & { export abstract class ProsopoBaseError< ContextType extends BaseContextParams = BaseContextParams, > extends Error { - translationKey: TranslationKey | undefined; + translationKey: string | undefined; context: ContextType | undefined; cause: Error | undefined; constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { if (error instanceof Error) { @@ -66,7 +64,7 @@ export abstract class ProsopoBaseError< // Generic error class export class ProsopoError extends ProsopoBaseError { constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { super(error, options); @@ -75,7 +73,7 @@ export class ProsopoError extends ProsopoBaseError { export class ProsopoEnvError extends ProsopoBaseError { constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { super(error, options); @@ -84,7 +82,7 @@ export class ProsopoEnvError extends ProsopoBaseError { export class ProsopoContractError extends ProsopoBaseError { constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { super(error, options); @@ -93,7 +91,7 @@ export class ProsopoContractError extends ProsopoBaseError { constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { super(error, options); @@ -102,7 +100,7 @@ export class ProsopoTxQueueError extends ProsopoBaseError export class ProsopoDBError extends ProsopoBaseError { constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { super(error, options); @@ -111,7 +109,7 @@ export class ProsopoDBError extends ProsopoBaseError { export class ProsopoCliError extends ProsopoBaseError { constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { super(error, options); @@ -120,7 +118,7 @@ export class ProsopoCliError extends ProsopoBaseError { export class ProsopoDatasetError extends ProsopoBaseError { constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { super(error, options); @@ -131,7 +129,7 @@ export class ProsopoApiError extends ProsopoBaseError { code: number; constructor( - error: Error | TranslationKey, + error: Error | string, options?: BaseErrorOptions, ) { const code = options?.context?.code || 500; @@ -150,20 +148,14 @@ export class ProsopoApiError extends ProsopoBaseError { } } -const fallbackTranslate = { t: (key: string) => key }; - export const unwrapError = ( err: ProsopoBaseError | SyntaxError | ZodError, - i18nInstance?: { t: TFunction }, ) => { - const i18n = i18nInstance || fallbackTranslate; let code = "code" in err ? (err.code as number) : 400; let jsonError: ApiJsonError = { code, - message: i18n.t( - (err as ProsopoBaseError).translationKey || err.message, - ), + message: (err as ProsopoBaseError).translationKey || err.message, }; const statusMessage = "Bad Request"; jsonError.key = @@ -177,7 +169,7 @@ export const unwrapError = ( : undefined; jsonError.key = contextTranslationKey || err.translationKey || "API.UNKNOWN"; - jsonError.message = i18n.t(err.translationKey || err.message); + jsonError.message = err.translationKey || err.message; jsonError.data = err.context.data as Record | undefined; const contextCode = diff --git a/packages/locale/src/i18SharedOptions.ts b/packages/locale/src/i18SharedOptions.ts index 63dd2702ff..5be0c131f6 100644 --- a/packages/locale/src/i18SharedOptions.ts +++ b/packages/locale/src/i18SharedOptions.ts @@ -16,7 +16,8 @@ import { LanguageSchema, Languages } from "./translations.js"; export const i18nSharedOptions = { debug: process.env.PROSOPO_LOG_LEVEL === "debug", fallbackLng: LanguageSchema.enum.en, - namespace: "translation", + defaultNS: "translation", + ns: ["translation"], supportedLngs: Object.values(Languages), nonExplicitSupportedLngs: false, }; diff --git a/packages/locale/src/i18nBackend.ts b/packages/locale/src/i18nBackend.ts index 5284ae83af..ecb5e8e52b 100644 --- a/packages/locale/src/i18nBackend.ts +++ b/packages/locale/src/i18nBackend.ts @@ -28,7 +28,12 @@ const loadPath = export function initializeI18n( i18nLoadedCallback?: (value: typeof i18n) => void, ) { - if (!i18n.isInitialized && isServerSide()) { + if (!isServerSide()) { + throw new Error( + "initializeI18n (backend) must not be called in browser context — use i18nFrontend instead", + ); + } + if (!i18n.isInitialized) { const lngDetector = new MiddlewareLanguageDetector(null, { order: ["header", "query", "cookie"], }); @@ -38,7 +43,6 @@ export function initializeI18n( .use(lngDetector) // this line should switch the language to the one the user reports in the Accept-Language header .init({ ...i18nSharedOptions, - ns: ["translation"], backend: { loadPath, }, diff --git a/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx b/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx index b4716c4dbc..4c6cc3370f 100644 --- a/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx +++ b/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx @@ -155,7 +155,13 @@ const ProcaptchaWidget = (props: ProcaptchaProps) => { }} checked={state.isHuman} labelText={isTranslationReady ? t("WIDGET.I_AM_HUMAN") : ""} - error={state.error?.message} + error={ + state.error + ? isTranslationReady + ? t(state.error.key as Parameters[0]) + : state.error.key + : undefined + } aria-label="human checkbox" loading={loading} /> From dec3ae4ad31e1c013e56d45dc3481a87f2147e7f Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 5 Jun 2026 21:20:18 +0100 Subject: [PATCH 03/18] fix(locale): repair TranslationKey type and getLeafFieldPath runtime bug getLeafFieldPath was returning [] for string leaf nodes, causing the runtime schema to have zero entries and TranslationKey to resolve to string rather than the specific union of all valid keys. Fix the runtime function by returning [""] as a sentinel for leaf nodes (parent joins with the key, omitting the separator for leaves). Add a Leaves recursive conditional type that TypeScript evaluates at compile time against the literal JSON shape, giving the exact union independently of the runtime function. Co-Authored-By: George Oastler --- packages/locale/src/translationKey.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/locale/src/translationKey.ts b/packages/locale/src/translationKey.ts index d749c4f16c..387ae9f036 100644 --- a/packages/locale/src/translationKey.ts +++ b/packages/locale/src/translationKey.ts @@ -21,9 +21,10 @@ type Node = } | string; +// Returns [""] for a string leaf so the parent can prepend its key without a separator. function getLeafFieldPath(obj: Node): string[] { if (typeof obj === "string") { - return []; + return [""]; } return Object.keys(obj).reduce((arr, key) => { @@ -35,14 +36,20 @@ function getLeafFieldPath(obj: Node): string[] { return arr.concat( children.map((child) => { - return `${key}.${child}`; + return child ? `${key}.${child}` : key; }), ); }, [] as string[]); } +// Recursive conditional type that derives the exact union of dot-notation leaf paths +// from the translation JSON shape at compile time, independently of the runtime function. +type Leaves = { + [K in keyof T & string]: T[K] extends string ? K : `${K}.${Leaves}`; +}[keyof T & string]; + export const TranslationKeysSchema = z.enum( getLeafFieldPath(translationEn) as [string, ...string[]], ); -export type TranslationKey = z.infer; +export type TranslationKey = Leaves; From 635e16ca47ac17fdea795bcb083eb7e3a7d512b4 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 5 Jun 2026 21:21:37 +0100 Subject: [PATCH 04/18] fix(locale): add missing translation keys to en/translation.json Adds 18 keys that were used in error constructors but absent from the translation file, causing i18next to return raw key strings to users: - BILLING.STRIPE_PORTAL/SESSION_NOT_FOUND/STRIPE_PAYMENT_FAILED (new section) - API.FEATURE_NOT_ENABLED/INVALID_AUTHORIZATION_HEADER/INVALID_DOMAIN/NO_PERMISSIONS - PORTAL.CANNOT_MODIFY_USER/API_KEYS_INVALID_NUMBER_OF_SITES/API_KEYS_NOT_FOUND - GENERAL.BILLING_ERROR/INVALID_JWT/MISSING_AUTH_HEADER/OBJECT_COUNT_ERROR/SECRET_MISSING - DEVELOPER.MISSING_SECRET_KEY - CAPTCHA.FAILED/PASSED Other locales fall back to English via i18next fallbackLng. Co-Authored-By: George Oastler --- .../locale/src/locales/en/translation.json | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/locale/src/locales/en/translation.json b/packages/locale/src/locales/en/translation.json index 26e24d6899..79e7a1a309 100644 --- a/packages/locale/src/locales/en/translation.json +++ b/packages/locale/src/locales/en/translation.json @@ -24,7 +24,12 @@ "ACCOUNT_NOT_FOUND": "Account not found", "SITE_KEY_NOT_FOUND": "Site key not found", "INVALID_TIMESTAMP": "Invalid timestamp", - "MISSING_SECRET_KEY": "Missing secret key" + "MISSING_SECRET_KEY": "Missing secret key", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "CONTRACT": { "INVALID_ADDRESS": "Failed to encode invalid address", @@ -72,6 +77,8 @@ "UNKNOWN": "Unknown database error" }, "CAPTCHA": { + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", "PARSE_ERROR": "Error parsing captcha", "INVALID_CAPTCHA_ID": "Invalid captcha id", "INVALID_ITEM_FORMAT": "Only image and text item types allowed", @@ -115,15 +122,27 @@ "NO_CONTENT_TYPE_HEADER": "No content-type header. Assumed JSON encoded body but failed to parse.", "TIMESTAMP_TOO_OLD": "Timestamp too old", "FORBIDDEN": "Forbidden", - "PROVIDER_VERIFY_FAILED": "Provider verification failed" + "PROVIDER_VERIFY_FAILED": "Provider verification failed", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Cannot modify owner user", + "CANNOT_MODIFY_USER": "Cannot modify this user", "CANNOT_DELETE_OWN_USER": "Cannot delete your own account", "CANNOT_DELETE_LAST_USER": "Cannot delete last user", "USER_ALREADY_EXISTS": "User already exists", "NO_SUGGESTIONS_FOUND": "No suggestions found", - "CANNOT_DEACTIVATE_LAST_SITE": "Cannot deactivate the last active site" + "CANNOT_DEACTIVATE_LAST_SITE": "Cannot deactivate the last active site", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" }, "CLI": { "PARAMETER_ERROR": "Invalid parameter" @@ -133,6 +152,7 @@ "PROVIDER_NO_CAPTCHA": "No captchas returned from provider", "MISSING_PROVIDER_PAIR": "Missing provider pair", "MISSING_ENV_VARIABLE": "Missing environment variable", + "MISSING_SECRET_KEY": "Missing secret key", "GENERAL": "General Dev Error, see context", "METHOD_NOT_IMPLEMENTED": "Method not implemented" }, From f5d8eaf351b36acd8908bacef43ed997b47e9164 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 5 Jun 2026 21:31:21 +0100 Subject: [PATCH 05/18] feat: enforce TranslationKey type in ProsopoBaseError constructors Add @prosopo/locale devDependency to common package and type all error constructor first arguments as Error | TranslationKey, replacing the previous untyped string. This ensures translation keys are validated at compile time against the known translation JSON. --- packages/common/package.json | 1 + packages/common/src/error.ts | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/common/package.json b/packages/common/package.json index e214cc1db2..3fedb1b9b8 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -31,6 +31,7 @@ }, "devDependencies": { "@prosopo/config": "3.2.1", + "@prosopo/locale": "3.1.27", "@types/node": "22.10.2", "@prosopo/types": "3.7.2", "@vitest/coverage-v8": "3.2.4", diff --git a/packages/common/src/error.ts b/packages/common/src/error.ts index b36d086021..9d0ac5b2b7 100644 --- a/packages/common/src/error.ts +++ b/packages/common/src/error.ts @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +import type { TranslationKey } from "@prosopo/locale"; import type { ApiJsonError } from "@prosopo/types"; import { ZodError } from "zod"; type BaseErrorOptions = { name?: string; - translationKey?: string; + translationKey?: TranslationKey; context?: ContextType; }; @@ -40,12 +41,12 @@ type ApiContextParams = BaseContextParams & { export abstract class ProsopoBaseError< ContextType extends BaseContextParams = BaseContextParams, > extends Error { - translationKey: string | undefined; + translationKey: TranslationKey | undefined; context: ContextType | undefined; cause: Error | undefined; constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { if (error instanceof Error) { @@ -64,7 +65,7 @@ export abstract class ProsopoBaseError< // Generic error class export class ProsopoError extends ProsopoBaseError { constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { super(error, options); @@ -73,7 +74,7 @@ export class ProsopoError extends ProsopoBaseError { export class ProsopoEnvError extends ProsopoBaseError { constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { super(error, options); @@ -82,7 +83,7 @@ export class ProsopoEnvError extends ProsopoBaseError { export class ProsopoContractError extends ProsopoBaseError { constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { super(error, options); @@ -91,7 +92,7 @@ export class ProsopoContractError extends ProsopoBaseError { constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { super(error, options); @@ -100,7 +101,7 @@ export class ProsopoTxQueueError extends ProsopoBaseError export class ProsopoDBError extends ProsopoBaseError { constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { super(error, options); @@ -109,7 +110,7 @@ export class ProsopoDBError extends ProsopoBaseError { export class ProsopoCliError extends ProsopoBaseError { constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { super(error, options); @@ -118,7 +119,7 @@ export class ProsopoCliError extends ProsopoBaseError { export class ProsopoDatasetError extends ProsopoBaseError { constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { super(error, options); @@ -129,7 +130,7 @@ export class ProsopoApiError extends ProsopoBaseError { code: number; constructor( - error: Error | string, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { const code = options?.context?.code || 500; From c67f74451e78d876712297d4c172e0b0db113dbf Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 5 Jun 2026 21:44:51 +0100 Subject: [PATCH 06/18] docs: document static-analysis assumption in VitePluginRemoveUnusedTranslations The plugin only retains translation keys that appear as string literals in the bundled source. Keys built at runtime (template literals, API responses, variable lookups) are silently stripped. Add a comment making this constraint explicit so callers know to list such keys statically. --- .../src/vite/vite-plugin-remove-unused-translations.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dev/config/src/vite/vite-plugin-remove-unused-translations.ts b/dev/config/src/vite/vite-plugin-remove-unused-translations.ts index 74228e9fc4..65c1cc8c40 100644 --- a/dev/config/src/vite/vite-plugin-remove-unused-translations.ts +++ b/dev/config/src/vite/vite-plugin-remove-unused-translations.ts @@ -63,6 +63,12 @@ export const unflatten = ( return result; }; +// This plugin uses static string scanning: it scans each source file for the +// presence of each known translation key as a literal substring. It only works +// for keys that appear verbatim in the bundled source — keys constructed at +// runtime (e.g. template literals, variable lookups, or keys from API responses) +// will be treated as unused and stripped. Add such keys to translationKeys +// statically, or they will be missing from the bundle. export default function VitePluginRemoveUnusedTranslations( translationKeys: string[], jsonPattern: string, From 661ecc3d8b9c474aa75b417df219b2072830c235 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 5 Jun 2026 21:46:33 +0100 Subject: [PATCH 07/18] refactor: split loadI18next into named frontend/backend functions Replace the overloaded loadI18next(boolean) with explicit loadI18nextFrontend() and loadI18nextBackend() exports. This removes the boolean flag footgun (callers could accidentally pass the wrong value) and also fixes a race condition where concurrent calls before the first promise resolved would invoke initializeI18n twice. The original loadI18next(boolean) is kept as a thin wrapper for compatibility. --- packages/cli/src/cli.ts | 4 +- packages/locale/src/i18nMiddleware.ts | 4 +- packages/locale/src/index.ts | 2 +- packages/locale/src/loadI18next.ts | 74 ++++++++++++------- .../src/util/widgetFactory.ts | 4 +- .../src/ProcaptchaFrictionless.tsx | 4 +- .../src/components/ProcaptchaWidget.tsx | 4 +- .../src/components/ProcaptchaWidget.tsx | 4 +- 8 files changed, 60 insertions(+), 40 deletions(-) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 75565c9016..bc2d822441 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -15,7 +15,7 @@ import process from "node:process"; import { LogLevel, getLogger } from "@prosopo/common"; import { loadEnv } from "@prosopo/dotenv"; import { getPair } from "@prosopo/keyring"; -import { loadI18next } from "@prosopo/locale"; +import { loadI18nextBackend } from "@prosopo/locale"; import type { ProsopoConfigOutput } from "@prosopo/types"; import { isMain } from "@prosopo/util"; import { processArgs } from "./argv.js"; @@ -59,7 +59,7 @@ async function main() { //if main process if (isMain(import.meta.url, "provider")) { - loadI18next(true).then(() => { + loadI18nextBackend().then(() => { main() .then(() => { log.info(() => ({ msg: "Running main process..." })); diff --git a/packages/locale/src/i18nMiddleware.ts b/packages/locale/src/i18nMiddleware.ts index ecdbe56d95..a4e39ebf91 100644 --- a/packages/locale/src/i18nMiddleware.ts +++ b/packages/locale/src/i18nMiddleware.ts @@ -13,12 +13,12 @@ // limitations under the License. import { type HandleOptions, handle } from "i18next-http-middleware"; -import loadI18next from "./loadI18next.js"; +import { loadI18nextBackend } from "./loadI18next.js"; async function i18nMiddleware( options: HandleOptions, ): Promise> { - const i18n = await loadI18next(true); + const i18n = await loadI18nextBackend(); return handle(i18n, { ...options }); } diff --git a/packages/locale/src/index.ts b/packages/locale/src/index.ts index 23c12e3387..7d2e3eda96 100644 --- a/packages/locale/src/index.ts +++ b/packages/locale/src/index.ts @@ -13,7 +13,7 @@ // limitations under the License. export { default as i18nMiddleware } from "./i18nMiddleware.js"; -export { default as loadI18next } from "./loadI18next.js"; +export { default as loadI18next, loadI18nextFrontend, loadI18nextBackend } from "./loadI18next.js"; export { Languages, LanguageSchema } from "./translations.js"; export { isClientSide } from "./util.js"; export { TranslationKeysSchema } from "./translationKey.js"; diff --git a/packages/locale/src/loadI18next.ts b/packages/locale/src/loadI18next.ts index a63f48caa9..fb605b18ba 100644 --- a/packages/locale/src/loadI18next.ts +++ b/packages/locale/src/loadI18next.ts @@ -13,35 +13,55 @@ // limitations under the License. import type { i18n } from "i18next"; -let i18nInstance: i18n; -async function loadI18next(backend: boolean): Promise { - return new Promise((resolve, reject) => { - try { - if (backend) { - import("./i18nBackend.js").then(({ default: initializeI18n }) => { - if (!i18nInstance) { - // pass the resolver into the i18n init fn which will resolve after i18n connected fires - i18nInstance = initializeI18n(resolve); - } else { - // we've already initialised i18n so just return it - resolve(i18nInstance); + +let frontendInstance: i18n | undefined; +let frontendPromise: Promise | undefined; + +let backendInstance: i18n | undefined; +let backendPromise: Promise | undefined; + +export async function loadI18nextFrontend(): Promise { + if (frontendInstance) return frontendInstance; + if (!frontendPromise) { + frontendPromise = import("./i18nFrontend.js").then( + ({ default: initializeI18n }) => + new Promise((resolve, reject) => { + try { + frontendInstance = initializeI18n((i18n) => { + frontendInstance = i18n; + resolve(i18n); + }); + } catch (e) { + reject(e); } - }); - } else { - import("./i18nFrontend.js").then(({ default: initializeI18n }) => { - if (!i18nInstance) { - // pass the resolver into the i18 init fn which will resolve after i18 connected fires - i18nInstance = initializeI18n(resolve); - } else { - // we've already initialised i18n so just return it - resolve(i18nInstance); + }), + ); + } + return frontendPromise; +} + +export async function loadI18nextBackend(): Promise { + if (backendInstance) return backendInstance; + if (!backendPromise) { + backendPromise = import("./i18nBackend.js").then( + ({ default: initializeI18n }) => + new Promise((resolve, reject) => { + try { + backendInstance = initializeI18n((i18n) => { + backendInstance = i18n; + resolve(i18n); + }); + } catch (e) { + reject(e); } - }); - } - } catch (e) { - reject(e); - } - }); + }), + ); + } + return backendPromise; +} + +export async function loadI18next(backend: boolean): Promise { + return backend ? loadI18nextBackend() : loadI18nextFrontend(); } export type { i18n as Ti18n }; diff --git a/packages/procaptcha-bundle/src/util/widgetFactory.ts b/packages/procaptcha-bundle/src/util/widgetFactory.ts index 4a6730fdba..bab3f0da43 100644 --- a/packages/procaptcha-bundle/src/util/widgetFactory.ts +++ b/packages/procaptcha-bundle/src/util/widgetFactory.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { loadI18next } from "@prosopo/locale"; +import { loadI18nextFrontend } from "@prosopo/locale"; import type { Ti18n } from "@prosopo/locale"; import { getDefaultCallbacks, @@ -128,7 +128,7 @@ class WidgetFactory { protected async getCaptchaRenderer(): Promise { if (this._i18n === null) { - this._i18n = await loadI18next(false); + this._i18n = await loadI18nextFrontend(); } if (this.captchaRenderer === null) { diff --git a/packages/procaptcha-frictionless/src/ProcaptchaFrictionless.tsx b/packages/procaptcha-frictionless/src/ProcaptchaFrictionless.tsx index 2e42fbf039..7136fde2d4 100644 --- a/packages/procaptcha-frictionless/src/ProcaptchaFrictionless.tsx +++ b/packages/procaptcha-frictionless/src/ProcaptchaFrictionless.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { loadI18next } from "@prosopo/locale"; +import { loadI18nextFrontend } from "@prosopo/locale"; import { Checkbox, getDefaultEvents, @@ -88,7 +88,7 @@ export const ProcaptchaFrictionless = ({ i18n.changeLanguage(config.language).then((r) => r); } } else { - loadI18next(false).then((i18n) => { + loadI18nextFrontend().then((i18n) => { if (i18n.language !== config.language) i18n.changeLanguage(config.language).then((r) => r); }); diff --git a/packages/procaptcha-pow/src/components/ProcaptchaWidget.tsx b/packages/procaptcha-pow/src/components/ProcaptchaWidget.tsx index a273659fad..59890c4f8a 100644 --- a/packages/procaptcha-pow/src/components/ProcaptchaWidget.tsx +++ b/packages/procaptcha-pow/src/components/ProcaptchaWidget.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { loadI18next, useTranslation } from "@prosopo/locale"; +import { loadI18nextFrontend, useTranslation } from "@prosopo/locale"; import { buildUpdateState, useProcaptcha } from "@prosopo/procaptcha-common"; import { Checkbox } from "@prosopo/procaptcha-common"; import { ModeEnum, type ProcaptchaProps } from "@prosopo/types"; @@ -45,7 +45,7 @@ const Procaptcha = (props: ProcaptchaProps) => { i18n.changeLanguage(config.language).then((r) => r); } } else { - loadI18next(false).then((i18n) => { + loadI18nextFrontend().then((i18n) => { if (i18n.language !== config.language) i18n.changeLanguage(config.language).then((r) => r); }); diff --git a/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx b/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx index 4c6cc3370f..0a5c1400a3 100644 --- a/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx +++ b/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx @@ -14,7 +14,7 @@ /** @jsxImportSource @emotion/react */ -import { loadI18next, useTranslation } from "@prosopo/locale"; +import { loadI18nextFrontend, useTranslation } from "@prosopo/locale"; import { Manager } from "@prosopo/procaptcha"; import { Checkbox, useProcaptcha } from "@prosopo/procaptcha-common"; import { ProcaptchaConfigSchema, type ProcaptchaProps } from "@prosopo/types"; @@ -50,7 +50,7 @@ const ProcaptchaWidget = (props: ProcaptchaProps) => { i18n.changeLanguage(config.language).then((r) => r); } } else { - loadI18next(false).then((i18n) => { + loadI18nextFrontend().then((i18n) => { if (i18n.language !== config.language) i18n.changeLanguage(config.language).then((r) => r); }); From bba2517588847c479f69cfd68b51118b7697fbaa Mon Sep 17 00:00:00 2001 From: George Oastler Date: Tue, 9 Jun 2026 12:46:38 +0100 Subject: [PATCH 08/18] feat: implement hybrid error pattern with typed keys and i18n race condition fixes Add message field to ProsopoBaseError to provide both translation keys and English fallback messages. Update Vite plugin to preserve backend error keys. Fix i18n initialization race conditions. Co-Authored-By: Claude Haiku 4.5 --- .../vite-plugin-remove-unused-translations.ts | 12 +++++- packages/common/src/error.ts | 14 +++++-- packages/locale/src/loadI18next.ts | 36 +++++++++++++++++ packages/procaptcha-bundle/vite.config.ts | 39 +++++++++++++++++++ 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/dev/config/src/vite/vite-plugin-remove-unused-translations.ts b/dev/config/src/vite/vite-plugin-remove-unused-translations.ts index 65c1cc8c40..3e12ec1858 100644 --- a/dev/config/src/vite/vite-plugin-remove-unused-translations.ts +++ b/dev/config/src/vite/vite-plugin-remove-unused-translations.ts @@ -69,10 +69,16 @@ export const unflatten = ( // runtime (e.g. template literals, variable lookups, or keys from API responses) // will be treated as unused and stripped. Add such keys to translationKeys // statically, or they will be missing from the bundle. +// +// Backend error keys are always preserved in the bundle since they're returned +// by the server and not found in frontend source code. export default function VitePluginRemoveUnusedTranslations( translationKeys: string[], jsonPattern: string, + backendErrorKeys?: string[], ): Plugin { + const backendKeys = new Set(backendErrorKeys || []); + return { name: "remove-unused-translations", transform(code: string) { @@ -100,9 +106,11 @@ export default function VitePluginRemoveUnusedTranslations( const jsonData = JSON.parse(content); const jsonDataFlattened = flatten(jsonData); - // Remove keys that are not in `used` + // Keep keys that are either used in frontend code or are backend error keys const filteredData = Object.fromEntries( - Object.entries(jsonDataFlattened).filter(([key]) => used.has(key)), + Object.entries(jsonDataFlattened).filter( + ([key]) => used.has(key) || backendKeys.has(key), + ), ); const unflattened = unflatten(filteredData); diff --git a/packages/common/src/error.ts b/packages/common/src/error.ts index 9d0ac5b2b7..458c971614 100644 --- a/packages/common/src/error.ts +++ b/packages/common/src/error.ts @@ -19,6 +19,7 @@ import { ZodError } from "zod"; type BaseErrorOptions = { name?: string; translationKey?: TranslationKey; + message?: string; context?: ContextType; }; @@ -42,6 +43,7 @@ export abstract class ProsopoBaseError< ContextType extends BaseContextParams = BaseContextParams, > extends Error { translationKey: TranslationKey | undefined; + message: string; context: ContextType | undefined; cause: Error | undefined; @@ -53,9 +55,12 @@ export abstract class ProsopoBaseError< super(error.message); this.cause = error; this.translationKey = options?.translationKey; + this.message = options?.message || error.message; } else { - super(error); + const fallback = options?.message || error; + super(fallback); this.translationKey = error; + this.message = fallback; } this.context = options?.context; this.name = options?.name || this.constructor.name; @@ -153,14 +158,15 @@ export const unwrapError = ( err: ProsopoBaseError | SyntaxError | ZodError, ) => { let code = "code" in err ? (err.code as number) : 400; + const baseError = err as ProsopoBaseError; let jsonError: ApiJsonError = { code, - message: (err as ProsopoBaseError).translationKey || err.message, + message: baseError.message || baseError.translationKey || err.message, }; const statusMessage = "Bad Request"; jsonError.key = - (err as ProsopoBaseError).translationKey ?? "API.UNKNOWN"; + baseError.translationKey ?? "API.UNKNOWN"; // unwrap the errors to get the actual error message while (err instanceof ProsopoBaseError && err.context) { @@ -170,7 +176,7 @@ export const unwrapError = ( : undefined; jsonError.key = contextTranslationKey || err.translationKey || "API.UNKNOWN"; - jsonError.message = err.translationKey || err.message; + jsonError.message = err.message || err.translationKey || "Unknown error"; jsonError.data = err.context.data as Record | undefined; const contextCode = diff --git a/packages/locale/src/loadI18next.ts b/packages/locale/src/loadI18next.ts index fb605b18ba..57ce040e61 100644 --- a/packages/locale/src/loadI18next.ts +++ b/packages/locale/src/loadI18next.ts @@ -16,9 +16,11 @@ import type { i18n } from "i18next"; let frontendInstance: i18n | undefined; let frontendPromise: Promise | undefined; +let frontendInitialized = false; let backendInstance: i18n | undefined; let backendPromise: Promise | undefined; +let backendInitialized = false; export async function loadI18nextFrontend(): Promise { if (frontendInstance) return frontendInstance; @@ -27,11 +29,26 @@ export async function loadI18nextFrontend(): Promise { ({ default: initializeI18n }) => new Promise((resolve, reject) => { try { + // Prevent race conditions: only initialize once + if (frontendInitialized) { + if (frontendInstance) return resolve(frontendInstance); + // Still initializing, wait for completion + return; + } + frontendInitialized = true; + frontendInstance = initializeI18n((i18n) => { frontendInstance = i18n; resolve(i18n); }); + // If init is synchronous and callback wasn't called, resolve with instance + if (frontendInstance && !frontendInstance.isInitialized) { + // Instance created but not yet initialized, let callback handle it + } else if (frontendInstance) { + resolve(frontendInstance); + } } catch (e) { + frontendInitialized = false; reject(e); } }), @@ -40,6 +57,10 @@ export async function loadI18nextFrontend(): Promise { return frontendPromise; } +export function getFrontendI18n(): i18n | undefined { + return frontendInstance; +} + export async function loadI18nextBackend(): Promise { if (backendInstance) return backendInstance; if (!backendPromise) { @@ -47,11 +68,26 @@ export async function loadI18nextBackend(): Promise { ({ default: initializeI18n }) => new Promise((resolve, reject) => { try { + // Prevent race conditions: only initialize once + if (backendInitialized) { + if (backendInstance) return resolve(backendInstance); + // Still initializing, wait for completion + return; + } + backendInitialized = true; + backendInstance = initializeI18n((i18n) => { backendInstance = i18n; resolve(i18n); }); + // If init is synchronous and callback wasn't called, resolve with instance + if (backendInstance && !backendInstance.isInitialized) { + // Instance created but not yet initialized, let callback handle it + } else if (backendInstance) { + resolve(backendInstance); + } } catch (e) { + backendInitialized = false; reject(e); } }), diff --git a/packages/procaptcha-bundle/vite.config.ts b/packages/procaptcha-bundle/vite.config.ts index 03333f8869..a7f80c2377 100644 --- a/packages/procaptcha-bundle/vite.config.ts +++ b/packages/procaptcha-bundle/vite.config.ts @@ -65,6 +65,44 @@ const translationKeys = Object.keys( flatten(JSON.parse(fs.readFileSync(at(localeFiles, 0), "utf-8"))), ); +// Collect backend error keys that should not be removed from translations +// These keys are returned by the API but don't appear as string literals in frontend code +const backendErrorKeys = [ + "API.FORBIDDEN", + "API.NO_CONTENT_TYPE_HEADER", + "API.MISSING_QUERY_PARAMETERS", + "API.MISSING_BODY", + "API.INVALID_BODY", + "API.PARSE_ERROR", + "API.MISSING_AUTHORIZATION_HEADER", + "API.INVALID_AUTHORIZATION_HEADER", + "API.INSUFFICIENT_PERMISSIONS", + "API.NO_PERMISSIONS", + "API.FEATURE_NOT_ENABLED", + "GENERAL.ACCOUNT_NOT_FOUND", + "API.SITE_KEY_NOT_REGISTERED", + "GENERAL.SITE_KEY_NOT_FOUND", + "API.INVALID_SITE_KEY", + "API.INVALID_DOMAIN", + "GENERAL.MISSING_SECRET_KEY", + "API.PROVIDER_VERIFY_FAILED", + "PORTAL.CANNOT_MODIFY_OWNER_USER", + "PORTAL.CANNOT_MODIFY_USER", + "PORTAL.CANNOT_DELETE_OWN_USER", + "PORTAL.CANNOT_DELETE_LAST_USER", + "PORTAL.USER_ALREADY_EXISTS", + "PORTAL.NO_SUGGESTIONS_FOUND", + "PORTAL.CANNOT_DEACTIVATE_LAST_SITE", + "API.TIMESTAMP_TOO_OLD", + "GENERAL.INVALID_SIGNATURE", + "BILLING.STRIPE_PORTAL", + "BILLING.STRIPE_SESSION_NOT_FOUND", + "BILLING.STRIPE_PAYMENT_FAILED", + "PORTAL.API_KEYS_INVALID_NUMBER_OF_SITES", + "PORTAL.API_KEYS_NOT_FOUND", + "API.UNKNOWN", +]; + // Merge with generic frontend config export default defineConfig(async ({ command, mode }) => { const frontendConfig = await ViteFrontendConfig( @@ -146,6 +184,7 @@ export default defineConfig(async ({ command, mode }) => { VitePluginRemoveUnusedTranslations( translationKeys, `${copyDir.destDir}/**/*.json`, + backendErrorKeys, ), ...(frontendConfig.plugins ? frontendConfig.plugins : []), From 8077f227ea7efb9b5d398bd623b65c2183be89d7 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Tue, 9 Jun 2026 13:58:21 +0100 Subject: [PATCH 09/18] refactor: add type-safe error key constants with compile-time validation Create ValidErrorKey type union derived from const assertions to enforce type safety on all error keys. Update error factories and Vite config to use constants instead of string literals. Single source of truth for all valid backend error keys prevents typos and drift. - Add errorKeys.ts with API_ERROR_KEYS, GENERAL_ERROR_KEYS, PORTAL_ERROR_KEYS, BILLING_ERROR_KEYS - Generate ValidErrorKey type for compile-time validation - Update ProsopoApiError to accept only ValidErrorKey - Update 17 error factories to use constants - Update Vite config to import BACKEND_ERROR_KEYS_ARRAY - Export new error key constants from common package --- packages/common/src/error.ts | 3 +- packages/common/src/errorKeys.ts | 77 +++++++++++++++++++++++ packages/common/src/index.ts | 1 + packages/procaptcha-bundle/vite.config.ts | 41 +----------- 4 files changed, 82 insertions(+), 40 deletions(-) create mode 100644 packages/common/src/errorKeys.ts diff --git a/packages/common/src/error.ts b/packages/common/src/error.ts index 458c971614..08f4866d2e 100644 --- a/packages/common/src/error.ts +++ b/packages/common/src/error.ts @@ -15,6 +15,7 @@ import type { TranslationKey } from "@prosopo/locale"; import type { ApiJsonError } from "@prosopo/types"; import { ZodError } from "zod"; +import type { ValidErrorKey } from "./errorKeys.js"; type BaseErrorOptions = { name?: string; @@ -135,7 +136,7 @@ export class ProsopoApiError extends ProsopoBaseError { code: number; constructor( - error: Error | TranslationKey, + error: Error | ValidErrorKey, options?: BaseErrorOptions, ) { const code = options?.context?.code || 500; diff --git a/packages/common/src/errorKeys.ts b/packages/common/src/errorKeys.ts new file mode 100644 index 0000000000..0fc919781a --- /dev/null +++ b/packages/common/src/errorKeys.ts @@ -0,0 +1,77 @@ +// Copyright 2021-2026 Prosopo (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Single source of truth for all valid API error keys +export const API_ERROR_KEYS = { + FORBIDDEN: "API.FORBIDDEN", + NO_CONTENT_TYPE_HEADER: "API.NO_CONTENT_TYPE_HEADER", + MISSING_QUERY_PARAMETERS: "API.MISSING_QUERY_PARAMETERS", + MISSING_BODY: "API.MISSING_BODY", + INVALID_BODY: "API.INVALID_BODY", + PARSE_ERROR: "API.PARSE_ERROR", + MISSING_AUTHORIZATION_HEADER: "API.MISSING_AUTHORIZATION_HEADER", + INVALID_AUTHORIZATION_HEADER: "API.INVALID_AUTHORIZATION_HEADER", + INSUFFICIENT_PERMISSIONS: "API.INSUFFICIENT_PERMISSIONS", + NO_PERMISSIONS: "API.NO_PERMISSIONS", + FEATURE_NOT_ENABLED: "API.FEATURE_NOT_ENABLED", + SITE_KEY_NOT_REGISTERED: "API.SITE_KEY_NOT_REGISTERED", + INVALID_SITE_KEY: "API.INVALID_SITE_KEY", + INVALID_DOMAIN: "API.INVALID_DOMAIN", + PROVIDER_VERIFY_FAILED: "API.PROVIDER_VERIFY_FAILED", + TIMESTAMP_TOO_OLD: "API.TIMESTAMP_TOO_OLD", + UNKNOWN: "API.UNKNOWN", +} as const; + +export const GENERAL_ERROR_KEYS = { + ACCOUNT_NOT_FOUND: "GENERAL.ACCOUNT_NOT_FOUND", + SITE_KEY_NOT_FOUND: "GENERAL.SITE_KEY_NOT_FOUND", + MISSING_SECRET_KEY: "GENERAL.MISSING_SECRET_KEY", + INVALID_SIGNATURE: "GENERAL.INVALID_SIGNATURE", +} as const; + +export const PORTAL_ERROR_KEYS = { + CANNOT_MODIFY_OWNER_USER: "PORTAL.CANNOT_MODIFY_OWNER_USER", + CANNOT_MODIFY_USER: "PORTAL.CANNOT_MODIFY_USER", + CANNOT_DELETE_OWN_USER: "PORTAL.CANNOT_DELETE_OWN_USER", + CANNOT_DELETE_LAST_USER: "PORTAL.CANNOT_DELETE_LAST_USER", + USER_ALREADY_EXISTS: "PORTAL.USER_ALREADY_EXISTS", + NO_SUGGESTIONS_FOUND: "PORTAL.NO_SUGGESTIONS_FOUND", + CANNOT_DEACTIVATE_LAST_SITE: "PORTAL.CANNOT_DEACTIVATE_LAST_SITE", + API_KEYS_INVALID_NUMBER_OF_SITES: "PORTAL.API_KEYS_INVALID_NUMBER_OF_SITES", + API_KEYS_NOT_FOUND: "PORTAL.API_KEYS_NOT_FOUND", +} as const; + +export const BILLING_ERROR_KEYS = { + STRIPE_PORTAL: "BILLING.STRIPE_PORTAL", + STRIPE_SESSION_NOT_FOUND: "BILLING.STRIPE_SESSION_NOT_FOUND", + STRIPE_PAYMENT_FAILED: "BILLING.STRIPE_PAYMENT_FAILED", +} as const; + +// Union of all error keys +export const ALL_ERROR_KEYS = { + ...API_ERROR_KEYS, + ...GENERAL_ERROR_KEYS, + ...PORTAL_ERROR_KEYS, + ...BILLING_ERROR_KEYS, +} as const; + +// Derive type from keys - this gives TypeScript enforcement +export type ValidErrorKey = + | (typeof API_ERROR_KEYS)[keyof typeof API_ERROR_KEYS] + | (typeof GENERAL_ERROR_KEYS)[keyof typeof GENERAL_ERROR_KEYS] + | (typeof PORTAL_ERROR_KEYS)[keyof typeof PORTAL_ERROR_KEYS] + | (typeof BILLING_ERROR_KEYS)[keyof typeof BILLING_ERROR_KEYS]; + +// Array of all keys for the Vite plugin +export const BACKEND_ERROR_KEYS_ARRAY = Object.values(ALL_ERROR_KEYS); diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 65bba2831f..53868adebc 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -13,6 +13,7 @@ // limitations under the License. export * from "./error.js"; +export * from "./errorKeys.js"; export * from "./logger.js"; export * from "./utils.js"; export * from "./batches.js"; diff --git a/packages/procaptcha-bundle/vite.config.ts b/packages/procaptcha-bundle/vite.config.ts index a7f80c2377..c1bbbeeec4 100644 --- a/packages/procaptcha-bundle/vite.config.ts +++ b/packages/procaptcha-bundle/vite.config.ts @@ -20,6 +20,7 @@ import { } from "@prosopo/config"; import { loadEnv } from "@prosopo/dotenv"; import { at, flatten } from "@prosopo/util"; +import { BACKEND_ERROR_KEYS_ARRAY } from "@prosopo/common"; import fg from "fast-glob"; import { defineConfig } from "vite"; @@ -65,44 +66,6 @@ const translationKeys = Object.keys( flatten(JSON.parse(fs.readFileSync(at(localeFiles, 0), "utf-8"))), ); -// Collect backend error keys that should not be removed from translations -// These keys are returned by the API but don't appear as string literals in frontend code -const backendErrorKeys = [ - "API.FORBIDDEN", - "API.NO_CONTENT_TYPE_HEADER", - "API.MISSING_QUERY_PARAMETERS", - "API.MISSING_BODY", - "API.INVALID_BODY", - "API.PARSE_ERROR", - "API.MISSING_AUTHORIZATION_HEADER", - "API.INVALID_AUTHORIZATION_HEADER", - "API.INSUFFICIENT_PERMISSIONS", - "API.NO_PERMISSIONS", - "API.FEATURE_NOT_ENABLED", - "GENERAL.ACCOUNT_NOT_FOUND", - "API.SITE_KEY_NOT_REGISTERED", - "GENERAL.SITE_KEY_NOT_FOUND", - "API.INVALID_SITE_KEY", - "API.INVALID_DOMAIN", - "GENERAL.MISSING_SECRET_KEY", - "API.PROVIDER_VERIFY_FAILED", - "PORTAL.CANNOT_MODIFY_OWNER_USER", - "PORTAL.CANNOT_MODIFY_USER", - "PORTAL.CANNOT_DELETE_OWN_USER", - "PORTAL.CANNOT_DELETE_LAST_USER", - "PORTAL.USER_ALREADY_EXISTS", - "PORTAL.NO_SUGGESTIONS_FOUND", - "PORTAL.CANNOT_DEACTIVATE_LAST_SITE", - "API.TIMESTAMP_TOO_OLD", - "GENERAL.INVALID_SIGNATURE", - "BILLING.STRIPE_PORTAL", - "BILLING.STRIPE_SESSION_NOT_FOUND", - "BILLING.STRIPE_PAYMENT_FAILED", - "PORTAL.API_KEYS_INVALID_NUMBER_OF_SITES", - "PORTAL.API_KEYS_NOT_FOUND", - "API.UNKNOWN", -]; - // Merge with generic frontend config export default defineConfig(async ({ command, mode }) => { const frontendConfig = await ViteFrontendConfig( @@ -184,7 +147,7 @@ export default defineConfig(async ({ command, mode }) => { VitePluginRemoveUnusedTranslations( translationKeys, `${copyDir.destDir}/**/*.json`, - backendErrorKeys, + BACKEND_ERROR_KEYS_ARRAY, ), ...(frontendConfig.plugins ? frontendConfig.plugins : []), From c903ae0139fe993325af270c3a24b2253c6ec1df Mon Sep 17 00:00:00 2001 From: George Oastler Date: Tue, 9 Jun 2026 14:07:59 +0100 Subject: [PATCH 10/18] refactor: add JSON-level type checking for error keys Validate that all ValidErrorKey values exist in translation.json at compile time. Imports the existing TranslationKey type derived from the English translation JSON and uses it to ensure no error key is missing from translations. This catches cases where you define an error key in errorKeys.ts but forget to add it to translation.json - TypeScript will error at build time instead of failing at runtime. --- packages/common/src/errorKeys.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/common/src/errorKeys.ts b/packages/common/src/errorKeys.ts index 0fc919781a..be7b0422bd 100644 --- a/packages/common/src/errorKeys.ts +++ b/packages/common/src/errorKeys.ts @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import type { TranslationKey } from "@prosopo/locale"; + // Single source of truth for all valid API error keys export const API_ERROR_KEYS = { FORBIDDEN: "API.FORBIDDEN", @@ -75,3 +77,11 @@ export type ValidErrorKey = // Array of all keys for the Vite plugin export const BACKEND_ERROR_KEYS_ARRAY = Object.values(ALL_ERROR_KEYS); + +// Type-level validation: ensure all error keys exist in translation.json +// Each ValidErrorKey must be assignable to TranslationKey (derived from translation JSON) +// If this errors, a key is defined here but missing from the translation file. +export const _validateErrorKeysExistInTranslations: Record = {} as Record< + ValidErrorKey, + TranslationKey +>; From 288b056fe569967ef904d58ae80e80ceff3634a0 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 11 Jun 2026 23:45:55 +0100 Subject: [PATCH 11/18] refactor: complete error/i18n decoupling and sync locale keys - ProsopoApiError accepts TranslationKey again (validation reasons are arbitrary translation keys); ValidErrorKey stays the curated bundle list - Remove obsolete i18n/logger constructor options from error call sites - Drop redundant forwarding constructors on Prosopo*Error subclasses - disapproveDappUserCommitment reason typed as ResultReason (matches storage) - Add missing backend translation keys (DATABASE.*, API.*, CAPTCHA.*) and propagate the full en key set to all 32 locales so key sets stay in sync - Rewrite common error tests for the decoupled API; fix api-express-router error handler/auth middleware tests for the new error shape --- .changeset/decouple-error-i18n.md | 21 ++ .../src/tests/unit/errorHandler.unit.test.ts | 4 +- .../middlewares/authMiddleware.unit.test.ts | 4 +- packages/common/src/error.ts | 75 +----- packages/common/src/tests/error.unit.test.ts | 241 ++++-------------- packages/database/src/databases/provider.ts | 8 - .../locale/src/locales/ar/translation.json | 44 +++- .../locale/src/locales/az/translation.json | 44 +++- .../locale/src/locales/cs/translation.json | 44 +++- .../locale/src/locales/de/translation.json | 44 +++- .../locale/src/locales/el/translation.json | 44 +++- .../locale/src/locales/en/translation.json | 12 + .../locale/src/locales/es/translation.json | 44 +++- .../locale/src/locales/fi/translation.json | 44 +++- .../locale/src/locales/fr/translation.json | 44 +++- .../locale/src/locales/hi/translation.json | 44 +++- .../locale/src/locales/hu/translation.json | 44 +++- .../locale/src/locales/id/translation.json | 44 +++- .../locale/src/locales/it/translation.json | 44 +++- .../locale/src/locales/ja/translation.json | 44 +++- .../locale/src/locales/jv/translation.json | 44 +++- .../locale/src/locales/ko/translation.json | 44 +++- .../locale/src/locales/ml/translation.json | 44 +++- .../locale/src/locales/ms/translation.json | 44 +++- .../locale/src/locales/nl/translation.json | 44 +++- .../locale/src/locales/no/translation.json | 44 +++- .../locale/src/locales/pl/translation.json | 44 +++- .../locale/src/locales/pt-BR/translation.json | 44 +++- .../locale/src/locales/pt/translation.json | 44 +++- .../locale/src/locales/ro/translation.json | 44 +++- .../locale/src/locales/ru/translation.json | 44 +++- .../locale/src/locales/sr/translation.json | 44 +++- .../locale/src/locales/sv/translation.json | 44 +++- .../locale/src/locales/th/translation.json | 44 +++- .../locale/src/locales/tr/translation.json | 44 +++- .../locale/src/locales/uk/translation.json | 44 +++- .../locale/src/locales/vi/translation.json | 44 +++- .../locale/src/locales/zh-CN/translation.json | 44 +++- .../src/api/captcha/checkSpamEmail.ts | 6 - .../decisionMachine.ts | 2 - .../handler.ts | 8 - .../api/captcha/getPuzzleCaptchaChallenge.ts | 10 - .../captcha/submitPuzzleCaptchaSolution.ts | 6 - packages/provider/src/api/domainMiddleware.ts | 8 - packages/provider/src/api/validateAddress.ts | 2 - packages/provider/src/api/verify.ts | 6 - .../provider/src/tasks/client/clientTasks.ts | 7 +- .../src/tasks/imgCaptcha/imgCaptchaTasks.ts | 4 +- .../unit/api/validateAddress.unit.test.ts | 2 +- packages/types-database/src/types/provider.ts | 5 +- 50 files changed, 1282 insertions(+), 513 deletions(-) create mode 100644 .changeset/decouple-error-i18n.md diff --git a/.changeset/decouple-error-i18n.md b/.changeset/decouple-error-i18n.md new file mode 100644 index 0000000000..a65ee0ce3b --- /dev/null +++ b/.changeset/decouple-error-i18n.md @@ -0,0 +1,21 @@ +--- +"@prosopo/api-express-router": patch +"@prosopo/procaptcha-frictionless": patch +"@prosopo/procaptcha-bundle": patch +"@prosopo/procaptcha-react": patch +"@prosopo/types-database": patch +"@prosopo/procaptcha-pow": patch +"@prosopo/database": patch +"@prosopo/provider": patch +"@prosopo/common": patch +"@prosopo/locale": patch +"@prosopo/env": patch +"@prosopo/cli": patch +--- + +Decouple error classes from i18n and logging, and move translations into a conventional i18n structure. + +- `ProsopoBaseError` and its subclasses no longer translate or log at construction time. They carry a `translationKey` and a fallback `message`; translation happens at the presentation layer (UI render or HTTP response via `unwrapError`). +- Removed the `i18n`, `logger` and `logLevel` constructor options from the error classes; callers log explicitly via their own logger. +- Error keys are validated against the translation files at compile time (`TranslationKey`), and the curated backend error-key registry (`BACKEND_ERROR_KEYS_ARRAY`) is preserved in the frontend bundle. +- Added the translation keys referenced by backend errors to every locale so the locale key sets stay in sync. diff --git a/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts b/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts index 4fad96095b..afca012e9c 100644 --- a/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts +++ b/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts @@ -19,7 +19,9 @@ import { ZodError } from "zod"; import { handleErrors } from "../../errorHandler.js"; describe("handleErrors", () => { - const mockRequest = {} as unknown as Request; + const mockRequest = { + logger: { error: vi.fn() }, + } as unknown as Request; it("should handle ProsopoApiError", () => { const mockResponse = { diff --git a/packages/api-express-router/src/tests/unit/middlewares/authMiddleware.unit.test.ts b/packages/api-express-router/src/tests/unit/middlewares/authMiddleware.unit.test.ts index 3cd809331d..a35df8e619 100644 --- a/packages/api-express-router/src/tests/unit/middlewares/authMiddleware.unit.test.ts +++ b/packages/api-express-router/src/tests/unit/middlewares/authMiddleware.unit.test.ts @@ -138,7 +138,7 @@ describe("authMiddleware", () => { expect(mockRes.status).toHaveBeenCalledWith(401); expect(mockRes.json).toHaveBeenCalledWith({ error: new ProsopoEnvError("API.UNAUTHORIZED", { - context: { i18n: mockReq.i18n, code: 401 }, + context: { code: 401 }, }), }); }); @@ -176,7 +176,7 @@ describe("authMiddleware", () => { expect(mockRes.status).toHaveBeenCalledWith(401); expect(mockRes.json).toHaveBeenCalledWith({ error: new ProsopoEnvError("API.UNAUTHORIZED", { - context: { i18n: mockReq.i18n, code: 401 }, + context: { code: 401 }, }), }); }); diff --git a/packages/common/src/error.ts b/packages/common/src/error.ts index 08f4866d2e..c60d7fee2b 100644 --- a/packages/common/src/error.ts +++ b/packages/common/src/error.ts @@ -15,7 +15,6 @@ import type { TranslationKey } from "@prosopo/locale"; import type { ApiJsonError } from "@prosopo/types"; import { ZodError } from "zod"; -import type { ValidErrorKey } from "./errorKeys.js"; type BaseErrorOptions = { name?: string; @@ -44,7 +43,7 @@ export abstract class ProsopoBaseError< ContextType extends BaseContextParams = BaseContextParams, > extends Error { translationKey: TranslationKey | undefined; - message: string; + override message: string; context: ContextType | undefined; cause: Error | undefined; @@ -69,74 +68,25 @@ export abstract class ProsopoBaseError< } // Generic error class -export class ProsopoError extends ProsopoBaseError { - constructor( - error: Error | TranslationKey, - options?: BaseErrorOptions, - ) { - super(error, options); - } -} +export class ProsopoError extends ProsopoBaseError {} -export class ProsopoEnvError extends ProsopoBaseError { - constructor( - error: Error | TranslationKey, - options?: BaseErrorOptions, - ) { - super(error, options); - } -} +export class ProsopoEnvError extends ProsopoBaseError {} -export class ProsopoContractError extends ProsopoBaseError { - constructor( - error: Error | TranslationKey, - options?: BaseErrorOptions, - ) { - super(error, options); - } -} +export class ProsopoContractError extends ProsopoBaseError {} -export class ProsopoTxQueueError extends ProsopoBaseError { - constructor( - error: Error | TranslationKey, - options?: BaseErrorOptions, - ) { - super(error, options); - } -} +export class ProsopoTxQueueError extends ProsopoBaseError {} -export class ProsopoDBError extends ProsopoBaseError { - constructor( - error: Error | TranslationKey, - options?: BaseErrorOptions, - ) { - super(error, options); - } -} +export class ProsopoDBError extends ProsopoBaseError {} -export class ProsopoCliError extends ProsopoBaseError { - constructor( - error: Error | TranslationKey, - options?: BaseErrorOptions, - ) { - super(error, options); - } -} +export class ProsopoCliError extends ProsopoBaseError {} -export class ProsopoDatasetError extends ProsopoBaseError { - constructor( - error: Error | TranslationKey, - options?: BaseErrorOptions, - ) { - super(error, options); - } -} +export class ProsopoDatasetError extends ProsopoBaseError {} export class ProsopoApiError extends ProsopoBaseError { code: number; constructor( - error: Error | ValidErrorKey, + error: Error | TranslationKey, options?: BaseErrorOptions, ) { const code = options?.context?.code || 500; @@ -155,9 +105,7 @@ export class ProsopoApiError extends ProsopoBaseError { } } -export const unwrapError = ( - err: ProsopoBaseError | SyntaxError | ZodError, -) => { +export const unwrapError = (err: ProsopoBaseError | SyntaxError | ZodError) => { let code = "code" in err ? (err.code as number) : 400; const baseError = err as ProsopoBaseError; @@ -166,8 +114,7 @@ export const unwrapError = ( message: baseError.message || baseError.translationKey || err.message, }; const statusMessage = "Bad Request"; - jsonError.key = - baseError.translationKey ?? "API.UNKNOWN"; + jsonError.key = baseError.translationKey ?? "API.UNKNOWN"; // unwrap the errors to get the actual error message while (err instanceof ProsopoBaseError && err.context) { diff --git a/packages/common/src/tests/error.unit.test.ts b/packages/common/src/tests/error.unit.test.ts index 87d099a711..8aab430f49 100644 --- a/packages/common/src/tests/error.unit.test.ts +++ b/packages/common/src/tests/error.unit.test.ts @@ -12,188 +12,60 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { - type LogObject, - type LogRecord, - type LogRecordFn, - type Logger, - NativeLogger, -} from "@prosopo/logger"; -import type { TFunction } from "i18next"; +import { NativeLogger } from "@prosopo/logger"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ProsopoApiError, ProsopoEnvError, unwrapError } from "../error.js"; -// Tiny stand-in for the i18next translator. Keys present in the map are -// translated, anything else falls through unchanged (mirrors i18next behavior -// for unknown keys). -const makeI18n = (translations: Record): { t: TFunction } => { - const t = ((key: string) => translations[key] ?? key) as unknown as TFunction; - return { t }; -}; - -const englishI18n = makeI18n({ - "CAPTCHA.NO_SESSION_FOUND": "No session found", - "API.INVALID_SITE_KEY": "Invalid site key", - "API.UNKNOWN": "Unknown error", -}); - -const frenchI18n = makeI18n({ - "CAPTCHA.NO_SESSION_FOUND": "Aucune session trouvée", - "API.INVALID_SITE_KEY": "Clé de site invalide", -}); - -// Mock logger that captures the LogRecord produced by each invocation so we -// can assert on the emitted shape. -type CapturedLog = { level: string; record: LogRecord }; - -const makeCapturingLogger = (): { - logger: Logger; - logs: CapturedLog[]; -} => { - const logs: CapturedLog[] = []; - const capture = (level: string) => (fn: LogRecordFn) => { - logs.push({ level, record: fn() }); - }; - const logger: Logger = { - setLogLevel: () => {}, - getLogLevel: () => "info", - getScope: () => "test", - info: capture("info"), - debug: capture("debug"), - trace: capture("trace"), - warn: capture("warn"), - error: capture("error"), - fatal: capture("fatal"), - log: (level, fn) => capture(level)(fn), - with: () => logger, - getPretty: () => false, - setPretty: () => {}, - getPrintStack: () => false, - setPrintStack: () => {}, - getFormat: () => "json", - setFormat: () => {}, - }; - return { logger, logs }; -}; - -describe("ProsopoBaseError.logError", () => { - it("logs the translation key as the top-level `err` field, not the translated message", () => { - const { logger, logs } = makeCapturingLogger(); - - new ProsopoApiError("CAPTCHA.NO_SESSION_FOUND", { +describe("ProsopoBaseError construction is decoupled from i18n and logging", () => { + it("stores the translation key without translating it", () => { + const err = new ProsopoApiError("API.INVALID_SITE_KEY", { context: { code: 400 }, - i18n: englishI18n, - logger, }); - expect(logs).toHaveLength(1); - expect(logs[0]?.level).toBe("error"); - expect(logs[0]?.record.err).toBe("CAPTCHA.NO_SESSION_FOUND"); - // The translated string must not be the top-level err. - expect(logs[0]?.record.err).not.toBe("No session found"); + // The error carries the key verbatim; translation is deferred to the + // presentation layer (UI render / HTTP response), not construction. + expect(err.translationKey).toBe("API.INVALID_SITE_KEY"); + expect(err.message).toBe("API.INVALID_SITE_KEY"); + expect(err.code).toBe(400); }); - it("logs the same translation key regardless of locale", () => { - const { logger: enLogger, logs: enLogs } = makeCapturingLogger(); - const { logger: frLogger, logs: frLogs } = makeCapturingLogger(); - - new ProsopoApiError("CAPTCHA.NO_SESSION_FOUND", { + it("uses an explicit message option as the fallback message while keeping the key", () => { + const err = new ProsopoApiError("API.INVALID_SITE_KEY", { + message: "Invalid site key", context: { code: 400 }, - i18n: englishI18n, - logger: enLogger, }); - new ProsopoApiError("CAPTCHA.NO_SESSION_FOUND", { - context: { code: 400 }, - i18n: frenchI18n, - logger: frLogger, - }); - - expect(enLogs[0]?.record.err).toBe("CAPTCHA.NO_SESSION_FOUND"); - expect(frLogs[0]?.record.err).toBe("CAPTCHA.NO_SESSION_FOUND"); - }); - it("emits errorType and context under `data`, with no `errorParams` nesting", () => { - const { logger, logs } = makeCapturingLogger(); - - new ProsopoApiError("API.INVALID_SITE_KEY", { - context: { code: 400, siteKey: "abc" }, - i18n: englishI18n, - logger, - }); - - const data = logs[0]?.record.data as LogObject & { - errorType?: string; - context?: Record; - errorParams?: unknown; - }; - expect(data?.errorType).toBe("ProsopoApiError"); - expect(data?.context?.code).toBe(400); - expect(data?.context?.siteKey).toBe("abc"); - expect(data?.errorParams).toBeUndefined(); + expect(err.translationKey).toBe("API.INVALID_SITE_KEY"); + expect(err.message).toBe("Invalid site key"); }); - it("does not inject a translated `translationMessage` into context when wrapping an Error", () => { - const { logger, logs } = makeCapturingLogger(); - + it("wraps an underlying Error, preserving its message as the cause", () => { const inner = new Error("kaboom"); - new ProsopoEnvError(inner, { + const err = new ProsopoEnvError(inner, { translationKey: "API.UNKNOWN", context: { code: 500 }, - i18n: englishI18n, - logger, }); - const data = logs[0]?.record.data as LogObject & { - context?: Record; - }; - expect(data?.context?.translationMessage).toBeUndefined(); - // The translation key still appears at the top level via `err`. - expect(logs[0]?.record.err).toBe("API.UNKNOWN"); + expect(err.cause).toBe(inner); + expect(err.message).toBe("kaboom"); + expect(err.translationKey).toBe("API.UNKNOWN"); }); - it("falls back to `message` when no translation key is available", () => { - const { logger, logs } = makeCapturingLogger(); + it("falls back to the wrapped Error message when no translation key is given", () => { + const err = new ProsopoEnvError(new Error("raw failure")); - // Constructed from a plain Error with no translationKey option. - new ProsopoEnvError(new Error("raw failure"), { - i18n: englishI18n, - logger, - }); - - expect(logs[0]?.record.err).toBe("raw failure"); - }); - - it("respects `silent: true` and does not log", () => { - const { logger, logs } = makeCapturingLogger(); - - new ProsopoApiError("API.INVALID_SITE_KEY", { - context: { code: 400 }, - i18n: englishI18n, - logger, - silent: true, - }); - - expect(logs).toHaveLength(0); + expect(err.translationKey).toBeUndefined(); + expect(err.message).toBe("raw failure"); }); - it("logs at debug level and includes the stack when logLevel is 'debug'", () => { - const { logger, logs } = makeCapturingLogger(); + it("defaults the API error code to 500 when none is supplied", () => { + const err = new ProsopoApiError("API.UNKNOWN"); - new ProsopoApiError("API.INVALID_SITE_KEY", { - context: { code: 400 }, - i18n: englishI18n, - logger, - logLevel: "debug", - }); - - expect(logs[0]?.level).toBe("debug"); - expect(logs[0]?.record.err).toBe("API.INVALID_SITE_KEY"); - const data = logs[0]?.record.data as LogObject & { stack?: string }; - expect(typeof data?.stack).toBe("string"); + expect(err.code).toBe(500); }); }); -describe("Logger.unpackError prefers translationKey over translated message", () => { +describe("Logger.unpackError prefers translationKey over the translated message", () => { let consoleErrorSpy: ReturnType; beforeEach(() => { @@ -204,27 +76,21 @@ describe("Logger.unpackError prefers translationKey over translated message", () consoleErrorSpy.mockRestore(); }); - it("emits the translation key as the top-level `err` when a ProsopoApiError is passed via { err }", () => { - // Build the error first with `silent: true` so its own auto-log doesn't - // interfere with the spy. - const apiError = new ProsopoApiError("CAPTCHA.NO_SESSION_FOUND", { + it("emits the translation key as the top-level `err` when a ProsopoApiError is logged", () => { + const apiError = new ProsopoApiError("API.INVALID_SITE_KEY", { + message: "Invalid site key", context: { code: 400 }, - i18n: frenchI18n, // .message is "Aucune session trouvée" - silent: true, }); - // `apiError.message` is the (French) translation; we want the logger - // to emit the *key* at top level instead. - expect(apiError.message).toBe("Aucune session trouvée"); - const logger = new NativeLogger("test"); logger.error(() => ({ err: apiError })); expect(consoleErrorSpy).toHaveBeenCalledTimes(1); const output = consoleErrorSpy.mock.calls[0]?.[0] as string; const parsed = JSON.parse(output) as { err?: string }; - expect(parsed.err).toBe("CAPTCHA.NO_SESSION_FOUND"); - expect(parsed.err).not.toBe("Aucune session trouvée"); + // The locale-stable key is logged, not the human-readable message. + expect(parsed.err).toBe("API.INVALID_SITE_KEY"); + expect(parsed.err).not.toBe("Invalid site key"); }); it("falls back to `message` when the error has no translationKey", () => { @@ -238,40 +104,27 @@ describe("Logger.unpackError prefers translationKey over translated message", () }); }); -describe("unwrapError still produces a translated HTTP response", () => { - it("translates the message via i18n for the response body even though the log emits the key", () => { - // Build silently so we don't pollute test output. - const err = new ProsopoApiError("CAPTCHA.NO_SESSION_FOUND", { +describe("unwrapError produces a JSON response carrying the translation key", () => { + it("exposes the translation key and code on the JSON response", () => { + const err = new ProsopoApiError("API.INVALID_SITE_KEY", { + message: "Invalid site key", context: { code: 400 }, - i18n: englishI18n, - silent: true, }); - const { jsonError } = unwrapError(err, englishI18n); - expect(jsonError.message).toBe("No session found"); - expect(jsonError.key).toBe("CAPTCHA.NO_SESSION_FOUND"); + const { code, jsonError } = unwrapError(err); + expect(jsonError.key).toBe("API.INVALID_SITE_KEY"); + expect(jsonError.message).toBe("Invalid site key"); expect(jsonError.code).toBe(400); + expect(code).toBe(400); }); - it("exposes the translation key on the JSON response regardless of construction locale", () => { - // Same error constructed under two different locales; both responses - // must carry the same `key` so clients can branch on it consistently. - const enErr = new ProsopoApiError("CAPTCHA.NO_SESSION_FOUND", { - context: { code: 400 }, - i18n: englishI18n, - silent: true, - }); - const frErr = new ProsopoApiError("CAPTCHA.NO_SESSION_FOUND", { - context: { code: 400 }, - i18n: frenchI18n, - silent: true, + it("defaults the key to API.UNKNOWN when the error carries no translation key", () => { + const err = new ProsopoEnvError(new Error("raw failure"), { + context: { code: 500 }, }); - expect(unwrapError(enErr, englishI18n).jsonError.key).toBe( - "CAPTCHA.NO_SESSION_FOUND", - ); - expect(unwrapError(frErr, frenchI18n).jsonError.key).toBe( - "CAPTCHA.NO_SESSION_FOUND", - ); + const { jsonError } = unwrapError(err); + expect(jsonError.key).toBe("API.UNKNOWN"); + expect(jsonError.message).toBe("raw failure"); }); }); diff --git a/packages/database/src/databases/provider.ts b/packages/database/src/databases/provider.ts index c09d8602c7..d0b9c1149f 100644 --- a/packages/database/src/databases/provider.ts +++ b/packages/database/src/databases/provider.ts @@ -1050,7 +1050,6 @@ export class ProviderDatabase challenge, ipInfo, }, - logger: this.logger, }); this.logger.error(() => ({ err: error, @@ -1073,7 +1072,6 @@ export class ProviderDatabase context: { failedFuncName: this.getPuzzleCaptchaRecordByChallenge.name, }, - logger: this.logger, }); } @@ -1122,7 +1120,6 @@ export class ProviderDatabase } catch (error) { const err = new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", { context: { error, challenge }, - logger: this.logger, }); this.logger.error(() => ({ err: err, @@ -1186,7 +1183,6 @@ export class ProviderDatabase challenge, ...update, }, - logger: this.logger, }); this.logger.info(() => ({ err: err, @@ -1208,7 +1204,6 @@ export class ProviderDatabase challenge, ...update, }, - logger: this.logger, }); this.logger.error(() => ({ err: err, @@ -1542,7 +1537,6 @@ export class ProviderDatabase } catch (err) { throw new ProsopoDBError("DATABASE.SESSION_GET_FAILED", { context: { error: err, sessionId }, - logger: this.logger, }); } } @@ -1573,7 +1567,6 @@ export class ProviderDatabase } catch (err) { throw new ProsopoDBError("DATABASE.SESSION_GET_FAILED", { context: { error: err, sessionId, stage }, - logger: this.logger, }); } } @@ -1624,7 +1617,6 @@ export class ProviderDatabase } catch (err) { throw new ProsopoDBError("DATABASE.SESSION_GET_FAILED", { context: { error: err, sessionId }, - logger: this.logger, }); } } diff --git a/packages/locale/src/locales/ar/translation.json b/packages/locale/src/locales/ar/translation.json index 9cc415a235..86b082eff8 100644 --- a/packages/locale/src/locales/ar/translation.json +++ b/packages/locale/src/locales/ar/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "توقيع غير صالح", "SITE_KEY_MISSING": "مفتاح الموقع مفقود", "ACCOUNT_NOT_FOUND": "الحساب غير موجود", - "INVALID_TIMESTAMP": "الطابع الزمني غير صالح" + "INVALID_TIMESTAMP": "الطابع الزمني غير صالح", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "صلاحيات غير كافية لأداء هذا الإجراء", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "لا يُسمح بالطلبات من شبكات الهاتف المحمول لهذا الموقع", "SATELLITE_BLOCKED": "لا يُسمح بالطلبات من الاتصالات الفضائية لهذا الموقع", "CRAWLER_BLOCKED": "لا يُسمح بالطلبات من برامج الزحف المعروفة لهذا الموقع", - "DISALLOWED_WEBVIEW": "الطلبات من المتصفحات داخل التطبيقات (WebView) غير مسموح بها لهذا الموقع" + "DISALLOWED_WEBVIEW": "الطلبات من المتصفحات داخل التطبيقات (WebView) غير مسموح بها لهذا الموقع", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "لا يمكن تعديل مالك المستخدم", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "لم يتم العثور على اقتراحات", "CANNOT_DEACTIVATE_LAST_SITE": "لا يمكن تعطيل آخر موقع نشط", "DUPLICATE_SITE_NAME": "يوجد موقع بهذا الاسم بالفعل", - "INVALID_SITE_NAME": "يجب أن يحتوي اسم الموقع على أحرف وأرقام وشرطات وشرطات سفلية فقط" + "INVALID_SITE_NAME": "يجب أن يحتوي اسم الموقع على أحرف وأرقام وشرطات وشرطات سفلية فقط", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "طائرات", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "جداول غير معرفة", "CONNECTION_UNDEFINED": "الاتصال غير معرف", "COMMITMENT_FLAG_FAILED": "فشل في تحديد التعهد كمعالج", - "UNKNOWN": "خطأ غير معروف في قاعدة البيانات" + "UNKNOWN": "خطأ غير معروف في قاعدة البيانات", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "خطأ في تحليل كلمة التحقق", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "عناوين IP غير متطابقة", "INVALID_TOKEN": "الرمز غير صالح", "INVALID_SOLUTION": "الحل غير صالح", - "DECRYPTION_ERROR": "خطأ في فك التشفير" + "DECRYPTION_ERROR": "خطأ في فك التشفير", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "معلمة غير صالحة" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "زوج مزود مفقود", "MISSING_ENV_VARIABLE": "متغير البيئة مفقود", "GENERAL": "خطأ تطوير عام، انظر السياق", - "METHOD_NOT_IMPLEMENTED": "الطريقة غير مُنفّذة" + "METHOD_NOT_IMPLEMENTED": "الطريقة غير مُنفّذة", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "الملف غير موجود", "FILE_ALREADY_EXISTS": "الملف موجود بالفعل", "INVALID_DIR_FORMAT": "تنسيق دليل غير صالح" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/az/translation.json b/packages/locale/src/locales/az/translation.json index a32c8a8434..736aca714c 100644 --- a/packages/locale/src/locales/az/translation.json +++ b/packages/locale/src/locales/az/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Yanlış imza", "SITE_KEY_MISSING": "SITE KEY çatışmır", "ACCOUNT_NOT_FOUND": "Hesab tapılmadı", - "INVALID_TIMESTAMP": "Yanlış vaxt damğı" + "INVALID_TIMESTAMP": "Yanlış vaxt damğı", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Bu əməliyyatı yerinə yetirmək üçün kifayət qədər icazə yoxdur", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Bu sayt üçün mobil şəbəkə bağlantılarından sorğulara icazə verilmir", "SATELLITE_BLOCKED": "Bu sayt üçün peyk bağlantılarından sorğulara icazə verilmir", "CRAWLER_BLOCKED": "Bu sayt üçün tanınmış tarayıcılardan sorğulara icazə verilmir", - "DISALLOWED_WEBVIEW": "Bu sayt üçün tətbiqdaxili brauzerlərdən (WebView) sorğulara icazə verilmir" + "DISALLOWED_WEBVIEW": "Bu sayt üçün tətbiqdaxili brauzerlərdən (WebView) sorğulara icazə verilmir", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Sahib istifadəçini dəyişmək olmaz", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Təklif tapılmadı", "CANNOT_DEACTIVATE_LAST_SITE": "Sonaktiv saytı deaktiv etmək olmaz", "DUPLICATE_SITE_NAME": "Bu adda sayt artıq mövcuddur", - "INVALID_SITE_NAME": "Sayt adı yalnız hərflərdən, rəqəmlərdən, defislərdən və alt xətlərdən ibarət olmalıdır" + "INVALID_SITE_NAME": "Sayt adı yalnız hərflərdən, rəqəmlərdən, defislərdən və alt xətlərdən ibarət olmalıdır", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "təyyarələr", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Cədvəllər müəyyən edilməyib", "CONNECTION_UNDEFINED": "Qoşulma müəyyənləndirilməyib", "COMMITMENT_FLAG_FAILED": "Müvafiq olaraq işarələmək alınmadı", - "UNKNOWN": "Naməlum verilənlər bazası səhvi" + "UNKNOWN": "Naməlum verilənlər bazası səhvi", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Captcha analiz edərkən səhv", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP ünvanları uyğun gəlmir", "INVALID_TOKEN": "Yanlış token", "INVALID_SOLUTION": "Yanlış həll", - "DECRYPTION_ERROR": "Şifrəni çözərkən səhv" + "DECRYPTION_ERROR": "Şifrəni çözərkən səhv", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Yanlış parametr" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Təminatçı cütü yoxdur", "MISSING_ENV_VARIABLE": "Mühit dəyişkəni yoxdur", "GENERAL": "Ümumi Dev Xəta, məzmunu gör", - "METHOD_NOT_IMPLEMENTED": "Metod icra olunmadı" + "METHOD_NOT_IMPLEMENTED": "Metod icra olunmadı", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Fayl tapılmadı", "FILE_ALREADY_EXISTS": "Fayl artıq mövcuddur", "INVALID_DIR_FORMAT": "Yanlış qovluq formatı" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/cs/translation.json b/packages/locale/src/locales/cs/translation.json index fa69d50406..06da7e7aaa 100644 --- a/packages/locale/src/locales/cs/translation.json +++ b/packages/locale/src/locales/cs/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Neplatný podpis", "SITE_KEY_MISSING": "Chybějící SITE KEY", "ACCOUNT_NOT_FOUND": "Účet nenalezen", - "INVALID_TIMESTAMP": "Neplatný časový razítko" + "INVALID_TIMESTAMP": "Neplatný časový razítko", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Nedostatečná oprávnění k provedení této akce", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Požadavky z mobilních sítí nejsou pro tento web povoleny", "SATELLITE_BLOCKED": "Požadavky ze satelitních připojení nejsou pro tento web povoleny", "CRAWLER_BLOCKED": "Požadavky ze známých crawlerů nejsou pro tento web povoleny", - "DISALLOWED_WEBVIEW": "Požadavky z prohlížečů v aplikacích (WebView) nejsou pro tento web povoleny" + "DISALLOWED_WEBVIEW": "Požadavky z prohlížečů v aplikacích (WebView) nejsou pro tento web povoleny", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Nelze upravit vlastníka uživatele", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Nebyly nalezeny žádné návrhy", "CANNOT_DEACTIVATE_LAST_SITE": "Nelze deaktivovat poslední aktivní web", "DUPLICATE_SITE_NAME": "Web s tímto názvem již existuje", - "INVALID_SITE_NAME": "Název webu smí obsahovat pouze písmena, čísla, pomlčky a podtržítka" + "INVALID_SITE_NAME": "Název webu smí obsahovat pouze písmena, čísla, pomlčky a podtržítka", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "letadla", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tabulky nedefinovány", "CONNECTION_UNDEFINED": "Připojení není definováno", "COMMITMENT_FLAG_FAILED": "Nepodařilo se označit závazek jako zpracovaný", - "UNKNOWN": "Neznámá chyba databáze" + "UNKNOWN": "Neznámá chyba databáze", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Chyba při analýze captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP adresy se neshodují", "INVALID_TOKEN": "Neplatný token", "INVALID_SOLUTION": "Neplatné řešení", - "DECRYPTION_ERROR": "Chyba dešifrování" + "DECRYPTION_ERROR": "Chyba dešifrování", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Neplatný parametr" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Chybějící dvojice poskytovatelů", "MISSING_ENV_VARIABLE": "Chybějící proměnná prostředí", "GENERAL": "Obecná chyba vývoje, viz kontext", - "METHOD_NOT_IMPLEMENTED": "Metoda není implementována" + "METHOD_NOT_IMPLEMENTED": "Metoda není implementována", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Soubor nenalezen", "FILE_ALREADY_EXISTS": "Soubor již existuje", "INVALID_DIR_FORMAT": "Neplatný formát adresáře" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/de/translation.json b/packages/locale/src/locales/de/translation.json index 0d03575339..c85fc6af11 100644 --- a/packages/locale/src/locales/de/translation.json +++ b/packages/locale/src/locales/de/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Ungültige Signatur", "SITE_KEY_MISSING": "SITE-KEY fehlt", "ACCOUNT_NOT_FOUND": "Konto nicht gefunden", - "INVALID_TIMESTAMP": "Ungültiger Zeitstempel" + "INVALID_TIMESTAMP": "Ungültiger Zeitstempel", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Unzureichende Berechtigungen zur Ausführung dieser Aktion", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Anfragen von Mobilfunkverbindungen sind für diese Website nicht erlaubt", "SATELLITE_BLOCKED": "Anfragen von Satellitenverbindungen sind für diese Website nicht erlaubt", "CRAWLER_BLOCKED": "Anfragen von bekannten Crawlern sind für diese Website nicht erlaubt", - "DISALLOWED_WEBVIEW": "Anfragen aus In-App-Browsern (WebView) sind für diese Website nicht erlaubt" + "DISALLOWED_WEBVIEW": "Anfragen aus In-App-Browsern (WebView) sind für diese Website nicht erlaubt", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Kann den Besitzerbenutzer nicht ändern", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Keine Vorschläge gefunden", "CANNOT_DEACTIVATE_LAST_SITE": "Kann den letzten aktiven Standort nicht deaktivieren", "DUPLICATE_SITE_NAME": "Eine Website mit diesem Namen existiert bereits", - "INVALID_SITE_NAME": "Der Websitename darf nur Buchstaben, Zahlen, Bindestriche und Unterstriche enthalten" + "INVALID_SITE_NAME": "Der Websitename darf nur Buchstaben, Zahlen, Bindestriche und Unterstriche enthalten", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Polkadot-Erweiterung nicht gefunden" @@ -121,7 +135,15 @@ "TABLES_UNDEFINED": "Tabellen undefiniert", "CONNECTION_UNDEFINED": "Verbindung undefiniert", "COMMITMENT_FLAG_FAILED": "Verpflichtung konnte nicht als verarbeitet markiert werden", - "UNKNOWN": "Unbekannter Datenbankfehler" + "UNKNOWN": "Unbekannter Datenbankfehler", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Fehler beim Parsen des Captchas", @@ -139,7 +161,11 @@ "IP_ADDRESS_MISMATCH": "IP-Adresse stimmt nicht überein", "INVALID_TOKEN": "Ungültiges Token", "INVALID_SOLUTION": "Ungültige Lösung", - "DECRYPTION_ERROR": "Fehler beim Entschlüsseln" + "DECRYPTION_ERROR": "Fehler beim Entschlüsseln", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Ungültiger Parameter" @@ -150,7 +176,8 @@ "MISSING_PROVIDER_PAIR": "Fehlendes Anbieterpaar", "MISSING_ENV_VARIABLE": "Fehlende Umgebungsvariable", "GENERAL": "Allgemeiner Entwicklerfehler, siehe Kontext", - "METHOD_NOT_IMPLEMENTED": "Methode nicht implementiert" + "METHOD_NOT_IMPLEMENTED": "Methode nicht implementiert", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Datei nicht gefunden", @@ -455,5 +482,10 @@ "sword": "Schwert", "syringe": "Spritze", "tambourine": "Tamburin" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/el/translation.json b/packages/locale/src/locales/el/translation.json index 06a284e328..98ae811215 100644 --- a/packages/locale/src/locales/el/translation.json +++ b/packages/locale/src/locales/el/translation.json @@ -38,7 +38,13 @@ "MOBILE_BLOCKED": "Αιτήματα από συνδέσεις κινητών δικτύων δεν επιτρέπονται για αυτόν τον ιστότοπο", "SATELLITE_BLOCKED": "Αιτήματα από δορυφορικές συνδέσεις δεν επιτρέπονται για αυτόν τον ιστότοπο", "CRAWLER_BLOCKED": "Αιτήματα από γνωστά προγράμματα ανίχνευσης δεν επιτρέπονται για αυτόν τον ιστότοπο", - "DISALLOWED_WEBVIEW": "Δεν επιτρέπονται αιτήματα από ενσωματωμένους περιηγητές (WebView) για αυτόν τον ιστότοπο" + "DISALLOWED_WEBVIEW": "Δεν επιτρέπονται αιτήματα από ενσωματωμένους περιηγητές (WebView) για αυτόν τον ιστότοπο", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "TARGET": { "animals": "ζώα", @@ -350,7 +356,12 @@ "INVALID_SIGNATURE": "Μη έγκυρη υπογραφή", "SITE_KEY_MISSING": "Λείπει το SITE KEY", "ACCOUNT_NOT_FOUND": "Δεν βρέθηκε λογαριασμός", - "INVALID_TIMESTAMP": "Μη έγκυρη σφραγίδα χρόνου" + "INVALID_TIMESTAMP": "Μη έγκυρη σφραγίδα χρόνου", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Δεν είναι δυνατή η τροποποίηση του χρήστη-κατόχου", @@ -360,7 +371,10 @@ "NO_SUGGESTIONS_FOUND": "Δεν βρέθηκαν προτάσεις", "CANNOT_DEACTIVATE_LAST_SITE": "Δεν είναι δυνατή η απενεργοποίηση του τελευταίου ιστότοπου", "DUPLICATE_SITE_NAME": "Υπάρχει ήδη ιστότοπος με αυτό το όνομα", - "INVALID_SITE_NAME": "Το όνομα ιστότοπου πρέπει να περιέχει μόνο γράμματα, αριθμούς, παύλες και κάτω παύλες" + "INVALID_SITE_NAME": "Το όνομα ιστότοπου πρέπει να περιέχει μόνο γράμματα, αριθμούς, παύλες και κάτω παύλες", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Δεν βρέθηκε επέκταση Polkadot" @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Οι πίνακες δεν έχουν οριστεί", "CONNECTION_UNDEFINED": "Η σύνδεση δεν έχει οριστεί", "COMMITMENT_FLAG_FAILED": "Αποτυχία σημαίας δέσμευσης ως επεξεργασμένη", - "UNKNOWN": "Άγνωστο σφάλμα βάσης δεδομένων" + "UNKNOWN": "Άγνωστο σφάλμα βάσης δεδομένων", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Σφάλμα ανάλυσης captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "Οι διευθύνσεις IP δεν ταιριάζουν", "INVALID_TOKEN": "Μη έγκυρο τεκμήριο", "INVALID_SOLUTION": "Μη έγκυρη λύση", - "DECRYPTION_ERROR": "Σφάλμα αποκρυπτογράφησης" + "DECRYPTION_ERROR": "Σφάλμα αποκρυπτογράφησης", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Μη έγκυρη παράμετρος" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Λείπει ζεύγος παρόχου", "MISSING_ENV_VARIABLE": "Λείπει μεταβλητή περιβάλλοντος", "GENERAL": "Γενικό σφάλμα ανάπτυξης, δες τα συμφραζόμενα", - "METHOD_NOT_IMPLEMENTED": "Η μέθοδος δεν έχει υλοποιηθεί" + "METHOD_NOT_IMPLEMENTED": "Η μέθοδος δεν έχει υλοποιηθεί", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Αρχείο δεν βρέθηκε", "FILE_ALREADY_EXISTS": "Το αρχείο υπάρχει ήδη", "INVALID_DIR_FORMAT": "Μη έγκυρος τύπος φακέλου" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/en/translation.json b/packages/locale/src/locales/en/translation.json index e45e1bd91d..4c7deadd45 100644 --- a/packages/locale/src/locales/en/translation.json +++ b/packages/locale/src/locales/en/translation.json @@ -75,6 +75,14 @@ "TABLES_UNDEFINED": "Tables undefined", "CONNECTION_UNDEFINED": "Connection undefined", "COMMITMENT_FLAG_FAILED": "Failed to flag commitment as processed", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined", "UNKNOWN": "Unknown database error" }, "CAPTCHA": { @@ -90,11 +98,13 @@ "MISSING_ITEM_HASH": "missing item hash", "INVALID_CAPTCHA_CHALLENGE": "Invalid captcha challenge", "DAPP_USER_SOLUTION_NOT_FOUND": "Dapp user solution not found", + "SOLUTION_NOT_FOUND": "Solution not found", "NO_CAPTCHA": "No captcha found", "NO_SESSION_FOUND": "No session found", "IP_ADDRESS_MISMATCH": "IP address mismatch detected", "INVALID_TOKEN": "Invalid token", "INVALID_SOLUTION": "Invalid solution", + "INVALID_TIMESTAMP": "Invalid timestamp", "DECRYPTION_ERROR": "Error decrypting" }, "API": { @@ -107,6 +117,8 @@ "USER_NOT_VERIFIED_NO_SOLUTION": "User not verified. No captcha solution found.", "USER_ALREADY_VERIFIED": "This solution has already been verified. User should complete a new captcha.", "UNKNOWN": "Unknown API error", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", "SITE_KEY_NOT_REGISTERED": "Site key not registered", "INCORRECT_CAPTCHA_TYPE": "Incorrect CAPTCHA type", "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", diff --git a/packages/locale/src/locales/es/translation.json b/packages/locale/src/locales/es/translation.json index 6b7ad3f9eb..5f98a9d2e0 100644 --- a/packages/locale/src/locales/es/translation.json +++ b/packages/locale/src/locales/es/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Firma inválida", "SITE_KEY_MISSING": "Falta la CLAVE DEL SITIO", "ACCOUNT_NOT_FOUND": "Cuenta no encontrada", - "INVALID_TIMESTAMP": "Marca de tiempo inválida" + "INVALID_TIMESTAMP": "Marca de tiempo inválida", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permisos insuficientes para realizar esta acción", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Las solicitudes desde redes móviles no están permitidas para este sitio", "SATELLITE_BLOCKED": "Las solicitudes desde conexiones satelitales no están permitidas para este sitio", "CRAWLER_BLOCKED": "Las solicitudes desde rastreadores conocidos no están permitidas para este sitio", - "DISALLOWED_WEBVIEW": "No se permiten solicitudes desde navegadores integrados en aplicaciones (WebView) para este sitio" + "DISALLOWED_WEBVIEW": "No se permiten solicitudes desde navegadores integrados en aplicaciones (WebView) para este sitio", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "No se puede modificar el usuario propietario", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "No se encontraron sugerencias", "CANNOT_DEACTIVATE_LAST_SITE": "No se puede desactivar el último sitio activo", "DUPLICATE_SITE_NAME": "Ya existe un sitio con este nombre", - "INVALID_SITE_NAME": "El nombre del sitio solo debe contener letras, números, guiones y guiones bajos" + "INVALID_SITE_NAME": "El nombre del sitio solo debe contener letras, números, guiones y guiones bajos", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Extensión Polkadot no encontrada" @@ -121,7 +135,15 @@ "TABLES_UNDEFINED": "Tablas indefinidas", "CONNECTION_UNDEFINED": "Conexión indefinida", "COMMITMENT_FLAG_FAILED": "Error al marcar el compromiso como procesado", - "UNKNOWN": "Error desconocido de base de datos" + "UNKNOWN": "Error desconocido de base de datos", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Error al analizar el captcha", @@ -139,7 +161,11 @@ "IP_ADDRESS_MISMATCH": "Las direcciones IP no coinciden", "INVALID_TOKEN": "Token inválido", "INVALID_SOLUTION": "Solución inválida", - "DECRYPTION_ERROR": "Error de descifrado" + "DECRYPTION_ERROR": "Error de descifrado", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Parámetro inválido" @@ -150,7 +176,8 @@ "MISSING_PROVIDER_PAIR": "Falta el par de proveedor", "MISSING_ENV_VARIABLE": "Falta una variable de entorno", "GENERAL": "Error general de desarrollo, ver contexto", - "METHOD_NOT_IMPLEMENTED": "Método no implementado" + "METHOD_NOT_IMPLEMENTED": "Método no implementado", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Archivo no encontrado", @@ -455,5 +482,10 @@ "sword": "espada", "syringe": "jeringa", "tambourine": "pandereta" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/fi/translation.json b/packages/locale/src/locales/fi/translation.json index 5b56aacfa2..2285628439 100644 --- a/packages/locale/src/locales/fi/translation.json +++ b/packages/locale/src/locales/fi/translation.json @@ -15,7 +15,11 @@ "INVALID_CAPTCHA_ID": "Virheellinen captcha-tunnus", "INVALID_ITEM_FORMAT": "Vain kuvan ja tekstin tyyppiset kohteet sallittu", "INVALID_SOLUTION_TYPE": "Virheellinen ratkaisun tyyppi", - "INVALID_ITEM_HASH": "Virheellinen kohteen hajautusarvo" + "INVALID_ITEM_HASH": "Virheellinen kohteen hajautusarvo", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "API": { "CAPTCHA_FAILED": "Vastasit yhteen tai useampaan captchan kysymykseen väärin. Yritä uudelleen", @@ -56,7 +60,13 @@ "MOBILE_BLOCKED": "Mobiiliverkkoyhteyksien pyynnöt eivät ole sallittuja tälle sivustolle", "SATELLITE_BLOCKED": "Satelliittiyhteyksien pyynnöt eivät ole sallittuja tälle sivustolle", "CRAWLER_BLOCKED": "Tunnettujen indeksoijien pyynnöt eivät ole sallittuja tälle sivustolle", - "DISALLOWED_WEBVIEW": "Sovelluksen sisäisistä selaimista (WebView) tulevia pyyntöjä ei sallita tällä sivustolla" + "DISALLOWED_WEBVIEW": "Sovelluksen sisäisistä selaimista (WebView) tulevia pyyntöjä ei sallita tällä sivustolla", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "CLI": { "PARAMETER_ERROR": "Virheellinen parametri" @@ -67,7 +77,8 @@ "MISSING_PROVIDER_PAIR": "Puuttuva palveluntarjoaja pari", "MISSING_ENV_VARIABLE": "Puuttuva ympäristömuuttuja", "GENERAL": "Yleinen kehitysvirhe, katso konteksti", - "METHOD_NOT_IMPLEMENTED": "Menetelmää ei ole toteutettu" + "METHOD_NOT_IMPLEMENTED": "Menetelmää ei ole toteutettu", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Tiedostoa ei löytynyt", @@ -85,7 +96,12 @@ "INVALID_SIGNATURE": "Virheellinen allekirjoitus", "SITE_KEY_MISSING": "Sivuston avain puuttuu", "ACCOUNT_NOT_FOUND": "Tiliä ei löytynyt", - "INVALID_TIMESTAMP": "Virheellinen aikaleima" + "INVALID_TIMESTAMP": "Virheellinen aikaleima", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Ei voida muokata omistajakäyttäjää", @@ -95,7 +111,10 @@ "NO_SUGGESTIONS_FOUND": "Ei ehdotuksia löytynyt", "CANNOT_DEACTIVATE_LAST_SITE": "Viimeistä aktiivista sivustoa ei voi poistaa", "DUPLICATE_SITE_NAME": "Tämänniminen sivusto on jo olemassa", - "INVALID_SITE_NAME": "Sivuston nimi saa sisältää vain kirjaimia, numeroita, väliviivoja ja alaviivoja" + "INVALID_SITE_NAME": "Sivuston nimi saa sisältää vain kirjaimia, numeroita, väliviivoja ja alaviivoja", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "ilmailukoneet", @@ -454,6 +473,19 @@ "TABLES_UNDEFINED": "Tauluja ei määritelty", "CONNECTION_UNDEFINED": "Yhteyttä ei määritelty", "COMMITMENT_FLAG_FAILED": "Sitoumuksen merkitseminen käsitellyksi epäonnistui", - "UNKNOWN": "Tuntematon tietokantavirhe" + "UNKNOWN": "Tuntematon tietokantavirhe", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/fr/translation.json b/packages/locale/src/locales/fr/translation.json index 64700eeef2..a838f3f0c0 100644 --- a/packages/locale/src/locales/fr/translation.json +++ b/packages/locale/src/locales/fr/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Signature invalide", "SITE_KEY_MISSING": "CLÉ DU SITE manquante", "ACCOUNT_NOT_FOUND": "Compte non trouvé", - "INVALID_TIMESTAMP": "Horodatage invalide" + "INVALID_TIMESTAMP": "Horodatage invalide", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permissions insuffisantes pour effectuer cette action", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Les requêtes provenant de réseaux mobiles ne sont pas autorisées pour ce site", "SATELLITE_BLOCKED": "Les requêtes provenant de connexions satellite ne sont pas autorisées pour ce site", "CRAWLER_BLOCKED": "Les requêtes provenant de robots d'exploration connus ne sont pas autorisées pour ce site", - "DISALLOWED_WEBVIEW": "Les requêtes provenant de navigateurs intégrés aux applications (WebView) ne sont pas autorisées pour ce site" + "DISALLOWED_WEBVIEW": "Les requêtes provenant de navigateurs intégrés aux applications (WebView) ne sont pas autorisées pour ce site", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Impossible de modifier le propriétaire de l'utilisateur", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Aucune suggestion trouvée", "CANNOT_DEACTIVATE_LAST_SITE": "Impossible de désactiver le dernier site actif", "DUPLICATE_SITE_NAME": "Un site avec ce nom existe déjà", - "INVALID_SITE_NAME": "Le nom du site ne doit contenir que des lettres, des chiffres, des tirets et des traits de soulignement" + "INVALID_SITE_NAME": "Le nom du site ne doit contenir que des lettres, des chiffres, des tirets et des traits de soulignement", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Extension Polkadot non trouvée" @@ -121,7 +135,15 @@ "TABLES_UNDEFINED": "Tables non définies", "CONNECTION_UNDEFINED": "Connexion non définie", "COMMITMENT_FLAG_FAILED": "Échec du marquage de l'engagement comme traité", - "UNKNOWN": "Erreur de base de données inconnue" + "UNKNOWN": "Erreur de base de données inconnue", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Erreur d'analyse du captcha", @@ -139,7 +161,11 @@ "IP_ADDRESS_MISMATCH": "Les adresses IP ne correspondent pas", "INVALID_TOKEN": "Jeton invalide", "INVALID_SOLUTION": "Solution invalide", - "DECRYPTION_ERROR": "Erreur de décryptage" + "DECRYPTION_ERROR": "Erreur de décryptage", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Paramètre invalide" @@ -150,7 +176,8 @@ "MISSING_PROVIDER_PAIR": "Paire de fournisseurs manquante", "MISSING_ENV_VARIABLE": "Variable d'environnement manquante", "GENERAL": "Erreur Dev Générale, voir le contexte", - "METHOD_NOT_IMPLEMENTED": "Méthode non implémentée" + "METHOD_NOT_IMPLEMENTED": "Méthode non implémentée", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Fichier non trouvé", @@ -455,5 +482,10 @@ "sword": "épée", "syringe": "seringue", "tambourine": "tambourin" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/hi/translation.json b/packages/locale/src/locales/hi/translation.json index 73288b631b..5249a8e769 100644 --- a/packages/locale/src/locales/hi/translation.json +++ b/packages/locale/src/locales/hi/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "अवध हसतकषर", "SITE_KEY_MISSING": "सइट कज गयब ह", "ACCOUNT_NOT_FOUND": "खत नह मल", - "INVALID_TIMESTAMP": "अवध समय चहन" + "INVALID_TIMESTAMP": "अवध समय चहन", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "TARGET": { "pelecaniformes": "पलकनफरमस", @@ -350,7 +355,13 @@ "MOBILE_BLOCKED": "इस साइट के लिए मोबाइल नेटवर्क कनेक्शन से अनुरोध की अनुमति नहीं है", "SATELLITE_BLOCKED": "इस साइट के लिए सैटेलाइट कनेक्शन से अनुरोध की अनुमति नहीं है", "CRAWLER_BLOCKED": "इस साइट के लिए ज्ञात क्रॉलर से अनुरोध की अनुमति नहीं है", - "DISALLOWED_WEBVIEW": "इस साइट के लिए इन-ऐप ब्राउज़र (WebView) से अनुरोध की अनुमति नहीं है" + "DISALLOWED_WEBVIEW": "इस साइट के लिए इन-ऐप ब्राउज़र (WebView) से अनुरोध की अनुमति नहीं है", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "मलक उपयगकरत क सशधत नह कय ज सकत", @@ -360,7 +371,10 @@ "NO_SUGGESTIONS_FOUND": "कई सझव नह मल", "CANNOT_DEACTIVATE_LAST_SITE": "अतम सकरय सइट क नषकरय नह कय ज सकत", "DUPLICATE_SITE_NAME": "इस नाम की साइट पहले से मौजूद है", - "INVALID_SITE_NAME": "साइट के नाम में केवल अक्षर, संख्याएँ, हाइफ़न और अंडरस्कोर होने चाहिए" + "INVALID_SITE_NAME": "साइट के नाम में केवल अक्षर, संख्याएँ, हाइफ़न और अंडरस्कोर होने चाहिए", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "पलकडट एकसटशन नह मल" @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "टबल अनरदषट", "CONNECTION_UNDEFINED": "कनकशन अनरदषट", "COMMITMENT_FLAG_FAILED": "परतबदधत क परससकरण क रप म झड चढन म वफल", - "UNKNOWN": "अजञत डटबस तरट" + "UNKNOWN": "अजञत डटबस तरट", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "कपच क परस करन म तरट", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP पते मेल नहीं खाते", "INVALID_TOKEN": "अमनय टकन", "INVALID_SOLUTION": "अमनय समधन", - "DECRYPTION_ERROR": "डकरपशन तरट" + "DECRYPTION_ERROR": "डकरपशन तरट", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "अमनय परमटर" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "परवइडर जड गयब ह", "MISSING_ENV_VARIABLE": "परयवरण म गयब चर", "GENERAL": "समनय डव तरट, सदरभ दख", - "METHOD_NOT_IMPLEMENTED": "वध क लग नह कय गय ह" + "METHOD_NOT_IMPLEMENTED": "वध क लग नह कय गय ह", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "फइल नह मल", "FILE_ALREADY_EXISTS": "फइल पहल स मजद ह", "INVALID_DIR_FORMAT": "अमनय नरदशक पररप" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/hu/translation.json b/packages/locale/src/locales/hu/translation.json index 2fd6bf0b95..39a27e57ea 100644 --- a/packages/locale/src/locales/hu/translation.json +++ b/packages/locale/src/locales/hu/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Érvénytelen aláírás", "SITE_KEY_MISSING": "Az oldal kulcs hiányzik", "ACCOUNT_NOT_FOUND": "Felhasználó nem található", - "INVALID_TIMESTAMP": "Érvénytelen időbélyeg" + "INVALID_TIMESTAMP": "Érvénytelen időbélyeg", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Nincs elegendő engedély a művelet végrehajtásához", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Mobilhálózati kapcsolatokról érkező kérések nem engedélyezettek ezen az oldalon", "SATELLITE_BLOCKED": "Műholdas kapcsolatokról érkező kérések nem engedélyezettek ezen az oldalon", "CRAWLER_BLOCKED": "Ismert webrobotoktól érkező kérések nem engedélyezettek ezen az oldalon", - "DISALLOWED_WEBVIEW": "Az alkalmazáson belüli böngészőkből (WebView) érkező kérések nem engedélyezettek ezen az oldalon" + "DISALLOWED_WEBVIEW": "Az alkalmazáson belüli böngészőkből (WebView) érkező kérések nem engedélyezettek ezen az oldalon", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Nem lehet módosítani a tulajdonos felhasználót", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Nincsenek javaslatok", "CANNOT_DEACTIVATE_LAST_SITE": "Nem lehet inaktiválni az utolsó aktív webhelyet", "DUPLICATE_SITE_NAME": "Már létezik ilyen nevű webhely", - "INVALID_SITE_NAME": "A webhely neve csak betűket, számokat, kötőjeleket és aláhúzásjeleket tartalmazhat" + "INVALID_SITE_NAME": "A webhely neve csak betűket, számokat, kötőjeleket és aláhúzásjeleket tartalmazhat", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "repülőgépek", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Táblák nincsenek meghatározva", "CONNECTION_UNDEFINED": "Kapcsolat meghatározatlan", "COMMITMENT_FLAG_FAILED": "Az elkötelezettség jelzése feldolgozottként sikertelen", - "UNKNOWN": "Ismeretlen adatbázis hiba" + "UNKNOWN": "Ismeretlen adatbázis hiba", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Hiba a captcha elemzésekor", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "Az IP címek nem egyeznek", "INVALID_TOKEN": "Érvénytelen token", "INVALID_SOLUTION": "Érvénytelen megoldás", - "DECRYPTION_ERROR": "Hiba a titkosítás közben" + "DECRYPTION_ERROR": "Hiba a titkosítás közben", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Érvénytelen paraméter" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Hiányzó ellátópár", "MISSING_ENV_VARIABLE": "Hiányzó környezeti változó", "GENERAL": "Általános fejlesztői hiba, lásd a kontextust", - "METHOD_NOT_IMPLEMENTED": "Az eljárás nincs implementálva" + "METHOD_NOT_IMPLEMENTED": "Az eljárás nincs implementálva", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Fájl nem található", "FILE_ALREADY_EXISTS": "A fájl már létezik", "INVALID_DIR_FORMAT": "Érvénytelen könyvtár formátum" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/id/translation.json b/packages/locale/src/locales/id/translation.json index 7af8df3436..e2d7eeca40 100644 --- a/packages/locale/src/locales/id/translation.json +++ b/packages/locale/src/locales/id/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Tanda tangan tidak valid", "SITE_KEY_MISSING": "SITE KEY hilang", "ACCOUNT_NOT_FOUND": "Akun tidak ditemukan", - "INVALID_TIMESTAMP": "Timestamp tidak valid" + "INVALID_TIMESTAMP": "Timestamp tidak valid", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Permintaan dari koneksi jaringan seluler tidak diizinkan untuk situs ini", "SATELLITE_BLOCKED": "Permintaan dari koneksi satelit tidak diizinkan untuk situs ini", "CRAWLER_BLOCKED": "Permintaan dari crawler yang dikenal tidak diizinkan untuk situs ini", - "DISALLOWED_WEBVIEW": "Permintaan dari peramban dalam aplikasi (WebView) tidak diizinkan untuk situs ini" + "DISALLOWED_WEBVIEW": "Permintaan dari peramban dalam aplikasi (WebView) tidak diizinkan untuk situs ini", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Cannot modify owner user", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "No suggestions found", "CANNOT_DEACTIVATE_LAST_SITE": "Cannot deactivate the last active site", "DUPLICATE_SITE_NAME": "Situs dengan nama ini sudah ada", - "INVALID_SITE_NAME": "Nama situs hanya boleh berisi huruf, angka, tanda hubung, dan garis bawah" + "INVALID_SITE_NAME": "Nama situs hanya boleh berisi huruf, angka, tanda hubung, dan garis bawah", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "eroplano", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tabel tidak ditentukan", "CONNECTION_UNDEFINED": "Koneksi tidak ditentukan", "COMMITMENT_FLAG_FAILED": "Gagal menandai komitmen sebagai diproses", - "UNKNOWN": "Kesalahan database tidak dikenal" + "UNKNOWN": "Kesalahan database tidak dikenal", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Kesalahan parsing captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "Alamat IP tidak cocok", "INVALID_TOKEN": "Invalid token", "INVALID_SOLUTION": "Invalid solution", - "DECRYPTION_ERROR": "Error decrypting" + "DECRYPTION_ERROR": "Error decrypting", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Invalid parameter" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Missing provider pair", "MISSING_ENV_VARIABLE": "Missing environment variable", "GENERAL": "General Dev Error, see context", - "METHOD_NOT_IMPLEMENTED": "Method not implemented" + "METHOD_NOT_IMPLEMENTED": "Method not implemented", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "File not found", "FILE_ALREADY_EXISTS": "File already exists", "INVALID_DIR_FORMAT": "Invalid directory format" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/it/translation.json b/packages/locale/src/locales/it/translation.json index 666f287e82..9ffe073c56 100644 --- a/packages/locale/src/locales/it/translation.json +++ b/packages/locale/src/locales/it/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Firma non valida", "SITE_KEY_MISSING": "Manca la chiave del sito", "ACCOUNT_NOT_FOUND": "Account non trovato", - "INVALID_TIMESTAMP": "Timestamp non valido" + "INVALID_TIMESTAMP": "Timestamp non valido", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permissões insuficientes para realizar esta ação", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Le richieste da connessioni di rete mobile non sono consentite per questo sito", "SATELLITE_BLOCKED": "Le richieste da connessioni satellitari non sono consentite per questo sito", "CRAWLER_BLOCKED": "Le richieste da crawler noti non sono consentite per questo sito", - "DISALLOWED_WEBVIEW": "Le richieste da browser integrati nelle app (WebView) non sono consentite per questo sito" + "DISALLOWED_WEBVIEW": "Le richieste da browser integrati nelle app (WebView) non sono consentite per questo sito", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Não é possível modificar o usuário proprietário", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Nenhuma sugestão encontrada", "CANNOT_DEACTIVATE_LAST_SITE": "Não é possível desativar o último site ativo", "DUPLICATE_SITE_NAME": "Esiste già un sito con questo nome", - "INVALID_SITE_NAME": "Il nome del sito deve contenere solo lettere, numeri, trattini e trattini bassi" + "INVALID_SITE_NAME": "Il nome del sito deve contenere solo lettere, numeri, trattini e trattini bassi", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Estensione Polkadot non trovata" @@ -121,7 +135,15 @@ "TABLES_UNDEFINED": "Tabelle indefinite", "CONNECTION_UNDEFINED": "Connessione indefinita", "COMMITMENT_FLAG_FAILED": "Impossibile contrassegnare l'impegno come elaborato", - "UNKNOWN": "Errore database sconosciuto" + "UNKNOWN": "Errore database sconosciuto", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Errore nell'analisi del captcha", @@ -139,7 +161,11 @@ "IP_ADDRESS_MISMATCH": "Gli indirizzi IP non corrispondono", "INVALID_TOKEN": "Token non valido", "INVALID_SOLUTION": "Soluzione non valida", - "DECRYPTION_ERROR": "Errore di decodifica" + "DECRYPTION_ERROR": "Errore di decodifica", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Parametro non valido" @@ -150,7 +176,8 @@ "MISSING_PROVIDER_PAIR": "Coppia di fornitori mancante", "MISSING_ENV_VARIABLE": "Variabile d'ambiente mancante", "GENERAL": "Errore di sviluppo generale, vedere il contesto", - "METHOD_NOT_IMPLEMENTED": "Metodo non implementato" + "METHOD_NOT_IMPLEMENTED": "Metodo non implementato", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "File non trovato", @@ -455,5 +482,10 @@ "sword": "spada", "syringe": "siringa", "tambourine": "tamburello" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/ja/translation.json b/packages/locale/src/locales/ja/translation.json index 08a33c49a6..92ebb32486 100644 --- a/packages/locale/src/locales/ja/translation.json +++ b/packages/locale/src/locales/ja/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "無効な署名", "SITE_KEY_MISSING": "SITE KEYが見つかりません", "ACCOUNT_NOT_FOUND": "アカウントが見つかりません", - "INVALID_TIMESTAMP": "無効なタイムスタンプ" + "INVALID_TIMESTAMP": "無効なタイムスタンプ", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "TARGET": { "homer-simpson": "homer-simpson", @@ -350,7 +355,13 @@ "MOBILE_BLOCKED": "このサイトではモバイルネットワーク接続からのリクエストは許可されていません", "SATELLITE_BLOCKED": "このサイトでは衛星接続からのリクエストは許可されていません", "CRAWLER_BLOCKED": "このサイトでは既知のクローラーからのリクエストは許可されていません", - "DISALLOWED_WEBVIEW": "このサイトではアプリ内ブラウザ (WebView) からのリクエストは許可されていません" + "DISALLOWED_WEBVIEW": "このサイトではアプリ内ブラウザ (WebView) からのリクエストは許可されていません", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "所有者ユーザーを変更できません", @@ -360,7 +371,10 @@ "NO_SUGGESTIONS_FOUND": "提案は見つかりません", "CANNOT_DEACTIVATE_LAST_SITE": "最後のアクティブサイトを無効にすることはできません", "DUPLICATE_SITE_NAME": "この名前のサイトはすでに存在しています", - "INVALID_SITE_NAME": "サイト名には英字、数字、ハイフン、アンダースコアのみ使用できます" + "INVALID_SITE_NAME": "サイト名には英字、数字、ハイフン、アンダースコアのみ使用できます", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Polkadot拡張機能が見つかりません" @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "テーブルが未定義です", "CONNECTION_UNDEFINED": "接続が未定義です", "COMMITMENT_FLAG_FAILED": "コミットメントを処理済みとフラグ付けするのに失敗しました", - "UNKNOWN": "不明なデータベースエラー" + "UNKNOWN": "不明なデータベースエラー", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "キャプチャの解析エラー", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IPアドレスが一致しません", "INVALID_TOKEN": "無効なトークン", "INVALID_SOLUTION": "無効なソリューション", - "DECRYPTION_ERROR": "復号エラー" + "DECRYPTION_ERROR": "復号エラー", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "無効なパラメーター" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "プロバイダーペアがありません", "MISSING_ENV_VARIABLE": "環境変数が見つかりません", "GENERAL": "一般的なDevエラー、コンテキストを参照してください", - "METHOD_NOT_IMPLEMENTED": "メソッドは実装されていません" + "METHOD_NOT_IMPLEMENTED": "メソッドは実装されていません", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "ファイルが見つかりません", "FILE_ALREADY_EXISTS": "ファイルはすでに存在します", "INVALID_DIR_FORMAT": "無効なディレクトリ形式" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/jv/translation.json b/packages/locale/src/locales/jv/translation.json index 57c442fb1a..d0f98f3a61 100644 --- a/packages/locale/src/locales/jv/translation.json +++ b/packages/locale/src/locales/jv/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Tanda tangan tidak valid", "SITE_KEY_MISSING": "Kunci SITUS hilang", "ACCOUNT_NOT_FOUND": "Akun tidak ditemukan", - "INVALID_TIMESTAMP": "Cap waktu tidak valid" + "INVALID_TIMESTAMP": "Cap waktu tidak valid", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Panjaluk saka sambungan jaringan seluler ora diidini kanggo situs iki", "SATELLITE_BLOCKED": "Panjaluk saka sambungan satelit ora diidini kanggo situs iki", "CRAWLER_BLOCKED": "Panjaluk saka crawler sing dikenal ora diidini kanggo situs iki", - "DISALLOWED_WEBVIEW": "Panjaluk saka browser ing aplikasi (WebView) ora diidini kanggo situs iki" + "DISALLOWED_WEBVIEW": "Panjaluk saka browser ing aplikasi (WebView) ora diidini kanggo situs iki", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Cannot modify owner user", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "No suggestions found", "CANNOT_DEACTIVATE_LAST_SITE": "Cannot deactivate the last active site", "DUPLICATE_SITE_NAME": "Situs kanthi jeneng iki wis ana", - "INVALID_SITE_NAME": "Jeneng situs mung kena ngemot aksara, angka, tanda hubung, lan garis ngisor" + "INVALID_SITE_NAME": "Jeneng situs mung kena ngemot aksara, angka, tanda hubung, lan garis ngisor", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "airplanes", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tabel-tabel tidak ditentukan", "CONNECTION_UNDEFINED": "Koneksi tidak ditentukan", "COMMITMENT_FLAG_FAILED": "Gagal menandai komitmen sebagai diproses", - "UNKNOWN": "Kesalahan database yang tidak diketahui" + "UNKNOWN": "Kesalahan database yang tidak diketahui", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Kesalahan memparsing captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "Alamat IP ora cocok", "INVALID_TOKEN": "Token tidak valid", "INVALID_SOLUTION": "Solusi tidak valid", - "DECRYPTION_ERROR": "Kesalahan dekripsi" + "DECRYPTION_ERROR": "Kesalahan dekripsi", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Parameter tidak valid" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Pasangan penyedia hilang", "MISSING_ENV_VARIABLE": "Variabel lingkungan hilang", "GENERAL": "Kesalahan Dev Umum, lihat konteks", - "METHOD_NOT_IMPLEMENTED": "Metode tidak diimplementasikan" + "METHOD_NOT_IMPLEMENTED": "Metode tidak diimplementasikan", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "File tidak ditemukan", "FILE_ALREADY_EXISTS": "File sudah ada", "INVALID_DIR_FORMAT": "Format direktori tidak valid" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/ko/translation.json b/packages/locale/src/locales/ko/translation.json index cf2ed51441..fea37ecacc 100644 --- a/packages/locale/src/locales/ko/translation.json +++ b/packages/locale/src/locales/ko/translation.json @@ -10,7 +10,12 @@ "ENVIRONMENT_NOT_READY": "환경이 준비되지 않음", "INVALID_SIGNATURE": "잘못된 서명", "SITE_KEY_MISSING": "SITE KEY가 누락됨", - "INVALID_TIMESTAMP": "잘못된 타임스탬프" + "INVALID_TIMESTAMP": "잘못된 타임스탬프", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "이 작업을 수행할 권한이 충분하지 않음", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "이 사이트에서는 모바일 네트워크 연결의 요청이 허용되지 않습니다", "SATELLITE_BLOCKED": "이 사이트에서는 위성 연결의 요청이 허용되지 않습니다", "CRAWLER_BLOCKED": "이 사이트에서는 알려진 크롤러의 요청이 허용되지 않습니다", - "DISALLOWED_WEBVIEW": "이 사이트에서는 인앱 브라우저(WebView)의 요청이 허용되지 않습니다" + "DISALLOWED_WEBVIEW": "이 사이트에서는 인앱 브라우저(WebView)의 요청이 허용되지 않습니다", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "소유자 사용자를 수정할 수 없음", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "제안을 찾을 수 없음", "CANNOT_DEACTIVATE_LAST_SITE": "마지막 활성 사이트를 비활성화할 수 없음", "DUPLICATE_SITE_NAME": "이 이름의 사이트가 이미 존재합니다", - "INVALID_SITE_NAME": "사이트 이름에는 문자, 숫자, 하이픈 및 밑줄만 포함할 수 있습니다" + "INVALID_SITE_NAME": "사이트 이름에는 문자, 숫자, 하이픈 및 밑줄만 포함할 수 있습니다", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "비행기", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "테이블 정의되지 않음", "CONNECTION_UNDEFINED": "연결 정의되지 않음", "COMMITMENT_FLAG_FAILED": "커밋먼트를 처리된 것으로 플래그 처리 실패함", - "UNKNOWN": "알 수 없는 데이터베이스 오류" + "UNKNOWN": "알 수 없는 데이터베이스 오류", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "캡차 구문 분석 오류", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP 주소가 일치하지 않습니다", "INVALID_TOKEN": "유효하지 않은 토큰", "INVALID_SOLUTION": "유효하지 않은 솔루션", - "DECRYPTION_ERROR": "해제 오류" + "DECRYPTION_ERROR": "해제 오류", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "잘못된 매개변수" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "누락된 제공자 쌍", "MISSING_ENV_VARIABLE": "누락된 환경 변수", "GENERAL": "일반 개발 오류, 상황을 확인하십시오", - "METHOD_NOT_IMPLEMENTED": "구현되지 않은 메소드" + "METHOD_NOT_IMPLEMENTED": "구현되지 않은 메소드", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "파일을 찾을 수 없습니다", "FILE_ALREADY_EXISTS": "파일이 이미 존재합니다", "INVALID_DIR_FORMAT": "유효하지 않은 디렉토리 형식" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/ml/translation.json b/packages/locale/src/locales/ml/translation.json index 9e1d3c922c..cc15296609 100644 --- a/packages/locale/src/locales/ml/translation.json +++ b/packages/locale/src/locales/ml/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Invalid nga lagda", "SITE_KEY_MISSING": "Nanginahanglan og SITE KEY", "ACCOUNT_NOT_FOUND": "Account dili makita", - "INVALID_TIMESTAMP": "Invalid nga timestamp" + "INVALID_TIMESTAMP": "Invalid nga timestamp", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficientway ermissionspay otay erformpay histay actionway", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "ഈ സൈറ്റിന് മൊബൈൽ നെറ്റ്വർക്ക് കണക്ഷനുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല", "SATELLITE_BLOCKED": "ഈ സൈറ്റിന് ഉപഗ്രഹ കണക്ഷനുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല", "CRAWLER_BLOCKED": "ഈ സൈറ്റിന് അറിയപ്പെടുന്ന ക്രോളറുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല", - "DISALLOWED_WEBVIEW": "ഈ സൈറ്റിന് ഇൻ-ആപ്പ് ബ്രൗസറുകളിൽ (WebView) നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല" + "DISALLOWED_WEBVIEW": "ഈ സൈറ്റിന് ഇൻ-ആപ്പ് ബ്രൗസറുകളിൽ (WebView) നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Annotcay odifymay ownerway seruy", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Onay uggestionssay oundfay", "CANNOT_DEACTIVATE_LAST_SITE": "Annotcay eactivateday ethay astlay activeay itesay", "DUPLICATE_SITE_NAME": "ഈ പേരിൽ ഒരു സൈറ്റ് ഇതിനകം നിലവിലുണ്ട്", - "INVALID_SITE_NAME": "സൈറ്റ് പേരിൽ അക്ഷരങ്ങൾ, അക്കങ്ങൾ, ഹൈഫനുകൾ, അടിവരകൾ എന്നിവ മാത്രമേ അടങ്ങിയിരിക്കാവൂ" + "INVALID_SITE_NAME": "സൈറ്റ് പേരിൽ അക്ഷരങ്ങൾ, അക്കങ്ങൾ, ഹൈഫനുകൾ, അടിവരകൾ എന്നിവ മാത്രമേ അടങ്ങിയിരിക്കാവൂ", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "eroplano", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Mga lamesa way butang", "CONNECTION_UNDEFINED": "Koneksiyon wala pa nahimutang", "COMMITMENT_FLAG_FAILED": "Nahimong pakyas ang pag-flag sa commitment nga giproseso", - "UNKNOWN": "Sayop sa dili masayod na database" + "UNKNOWN": "Sayop sa dili masayod na database", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Sayop sa pag-parse sa captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP വിലാസങ്ങൾ പൊരുത്തപ്പെടുന്നില്ല", "INVALID_TOKEN": "Hindi wastong token", "INVALID_SOLUTION": "Hindi wastong solusyon", - "DECRYPTION_ERROR": "Error sa pagdedecrypt" + "DECRYPTION_ERROR": "Error sa pagdedecrypt", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Hindi wastong parameter" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Nawawalang provider pair", "MISSING_ENV_VARIABLE": "Nawawalang environment variable", "GENERAL": "Pangkalahatang Dev Error, tingnan ang konteksto", - "METHOD_NOT_IMPLEMENTED": "Paraan hindi pa naipatutupad" + "METHOD_NOT_IMPLEMENTED": "Paraan hindi pa naipatutupad", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "File ay hindi natagpuan", "FILE_ALREADY_EXISTS": "File ay mayroon na", "INVALID_DIR_FORMAT": "Hindi wastong format ng directory" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/ms/translation.json b/packages/locale/src/locales/ms/translation.json index afba1cc1ca..da1f6b1816 100644 --- a/packages/locale/src/locales/ms/translation.json +++ b/packages/locale/src/locales/ms/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Invalid signature", "SITE_KEY_MISSING": "SITE KEY missing", "ACCOUNT_NOT_FOUND": "Account not found", - "INVALID_TIMESTAMP": "Invalid timestamp" + "INVALID_TIMESTAMP": "Invalid timestamp", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "TARGET": { "horseshoe-crab": "ladam-ketam", @@ -350,7 +355,13 @@ "MOBILE_BLOCKED": "Permintaan daripada sambungan rangkaian mudah alih tidak dibenarkan untuk laman ini", "SATELLITE_BLOCKED": "Permintaan daripada sambungan satelit tidak dibenarkan untuk laman ini", "CRAWLER_BLOCKED": "Permintaan daripada perangkak yang dikenali tidak dibenarkan untuk laman ini", - "DISALLOWED_WEBVIEW": "Permintaan daripada pelayar dalam aplikasi (WebView) tidak dibenarkan untuk laman ini" + "DISALLOWED_WEBVIEW": "Permintaan daripada pelayar dalam aplikasi (WebView) tidak dibenarkan untuk laman ini", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Cannot modify owner user", @@ -360,7 +371,10 @@ "NO_SUGGESTIONS_FOUND": "No suggestions found", "CANNOT_DEACTIVATE_LAST_SITE": "Cannot deactivate the last active site", "DUPLICATE_SITE_NAME": "Laman dengan nama ini sudah wujud", - "INVALID_SITE_NAME": "Nama laman hanya boleh mengandungi huruf, nombor, tanda sempang dan garis bawah" + "INVALID_SITE_NAME": "Nama laman hanya boleh mengandungi huruf, nombor, tanda sempang dan garis bawah", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Polkadot extension not found" @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tables undefined", "CONNECTION_UNDEFINED": "Connection undefined", "COMMITMENT_FLAG_FAILED": "Failed to flag commitment as processed", - "UNKNOWN": "Unknown database error" + "UNKNOWN": "Unknown database error", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Error parsing captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "Alamat IP tidak sepadan", "INVALID_TOKEN": "Invalid token", "INVALID_SOLUTION": "Invalid solution", - "DECRYPTION_ERROR": "Error decrypting" + "DECRYPTION_ERROR": "Error decrypting", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Invalid parameter" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Missing provider pair", "MISSING_ENV_VARIABLE": "Missing environment variable", "GENERAL": "General Dev Error, see context", - "METHOD_NOT_IMPLEMENTED": "Method not implemented" + "METHOD_NOT_IMPLEMENTED": "Method not implemented", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "File not found", "FILE_ALREADY_EXISTS": "File already exists", "INVALID_DIR_FORMAT": "Invalid directory format" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/nl/translation.json b/packages/locale/src/locales/nl/translation.json index a7af38c340..91dcd27ae1 100644 --- a/packages/locale/src/locales/nl/translation.json +++ b/packages/locale/src/locales/nl/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Ongeldige handtekening", "SITE_KEY_MISSING": "SITE KEY ontbreekt", "ACCOUNT_NOT_FOUND": "Account niet gevonden", - "INVALID_TIMESTAMP": "Ongeldige tijdstempel" + "INVALID_TIMESTAMP": "Ongeldige tijdstempel", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "TARGET": { "yo-yo": "jojo", @@ -350,7 +355,13 @@ "MOBILE_BLOCKED": "Verzoeken van mobiele netwerkverbindingen zijn niet toegestaan voor deze site", "SATELLITE_BLOCKED": "Verzoeken van satellietverbindingen zijn niet toegestaan voor deze site", "CRAWLER_BLOCKED": "Verzoeken van bekende crawlers zijn niet toegestaan voor deze site", - "DISALLOWED_WEBVIEW": "Verzoeken van in-app-browsers (WebView) zijn niet toegestaan voor deze site" + "DISALLOWED_WEBVIEW": "Verzoeken van in-app-browsers (WebView) zijn niet toegestaan voor deze site", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Kan eigenaar gebruiker niet wijzigen", @@ -360,7 +371,10 @@ "NO_SUGGESTIONS_FOUND": "Geen suggesties gevonden", "CANNOT_DEACTIVATE_LAST_SITE": "Kan de laatste actieve site niet deactiveren", "DUPLICATE_SITE_NAME": "Er bestaat al een site met deze naam", - "INVALID_SITE_NAME": "De sitenaam mag alleen letters, cijfers, koppeltekens en underscores bevatten" + "INVALID_SITE_NAME": "De sitenaam mag alleen letters, cijfers, koppeltekens en underscores bevatten", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Polkadot-extensie niet gevonden" @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tabellen niet gedefinieerd", "CONNECTION_UNDEFINED": "Verbinding niet gedefinieerd", "COMMITMENT_FLAG_FAILED": "Mislukt om toezegging te markeren als verwerkt", - "UNKNOWN": "Onbekende databasefout" + "UNKNOWN": "Onbekende databasefout", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Fout bij parseren captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP-adressen komen niet overeen", "INVALID_TOKEN": "Ongeldige token", "INVALID_SOLUTION": "Ongeldige oplossing", - "DECRYPTION_ERROR": "Fout bij decoderen" + "DECRYPTION_ERROR": "Fout bij decoderen", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Ongeldige parameter" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Ontbrekend providerpaar", "MISSING_ENV_VARIABLE": "Ontbrekende omgevingsvariabele", "GENERAL": "Algemene Dev-fout, zie context", - "METHOD_NOT_IMPLEMENTED": "Methode niet geïmplementeerd" + "METHOD_NOT_IMPLEMENTED": "Methode niet geïmplementeerd", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Bestand niet gevonden", "FILE_ALREADY_EXISTS": "Bestand bestaat al", "INVALID_DIR_FORMAT": "Ongeldig mapformaat" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/no/translation.json b/packages/locale/src/locales/no/translation.json index 741ea7125d..c30a0c80d3 100644 --- a/packages/locale/src/locales/no/translation.json +++ b/packages/locale/src/locales/no/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Ugyldig signatur", "SITE_KEY_MISSING": "SITE KEY mangler", "ACCOUNT_NOT_FOUND": "Konto ikke funnet", - "INVALID_TIMESTAMP": "Ugyldig tidsstempel" + "INVALID_TIMESTAMP": "Ugyldig tidsstempel", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Utilstrekkelige tillatelser for å utføre denne handlingen", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Forespørsler fra mobilnettverkstilkoblinger er ikke tillatt for dette nettstedet", "SATELLITE_BLOCKED": "Forespørsler fra satellittilkoblinger er ikke tillatt for dette nettstedet", "CRAWLER_BLOCKED": "Forespørsler fra kjente roboter er ikke tillatt for dette nettstedet", - "DISALLOWED_WEBVIEW": "Forespørsler fra innebygde nettlesere (WebView) er ikke tillatt for dette nettstedet" + "DISALLOWED_WEBVIEW": "Forespørsler fra innebygde nettlesere (WebView) er ikke tillatt for dette nettstedet", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Kan ikke endre eierbruker", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Ingen forslag funnet", "CANNOT_DEACTIVATE_LAST_SITE": "Kan ikke deaktivere siste aktive nettsted", "DUPLICATE_SITE_NAME": "Et nettsted med dette navnet finnes allerede", - "INVALID_SITE_NAME": "Nettstednavnet kan bare inneholde bokstaver, tall, bindestreker og understreker" + "INVALID_SITE_NAME": "Nettstednavnet kan bare inneholde bokstaver, tall, bindestreker og understreker", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "flymaskiner", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tabeller er udefinert", "CONNECTION_UNDEFINED": "Tilkobling udefinert", "COMMITMENT_FLAG_FAILED": "Klarte ikke å flagge engasjement som behandlet", - "UNKNOWN": "Ukjent databasefeil" + "UNKNOWN": "Ukjent databasefeil", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Feil parsing captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP-adresser stemmer ikke overens", "INVALID_TOKEN": "Ugyldig token", "INVALID_SOLUTION": "Ugyldig løsning", - "DECRYPTION_ERROR": "Feil ved dekryptering" + "DECRYPTION_ERROR": "Feil ved dekryptering", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Ugyldig parameter" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Manglende leverandørpar", "MISSING_ENV_VARIABLE": "Manglende miljøvariabel", "GENERAL": "Generell utviklerfeil, se kontekst", - "METHOD_NOT_IMPLEMENTED": "Metoden er ikkje implementert" + "METHOD_NOT_IMPLEMENTED": "Metoden er ikkje implementert", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Filen ble ikkje funnet", "FILE_ALREADY_EXISTS": "Filen eksisterer allerede", "INVALID_DIR_FORMAT": "Ugyldig katalogformat" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/pl/translation.json b/packages/locale/src/locales/pl/translation.json index 954f627280..8a4f67d870 100644 --- a/packages/locale/src/locales/pl/translation.json +++ b/packages/locale/src/locales/pl/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Nieprawidłowe podpisanie", "SITE_KEY_MISSING": "BRAK KLUCZA WITRY", "ACCOUNT_NOT_FOUND": "Nie znaleziono konta", - "INVALID_TIMESTAMP": "Nieprawidłowy znacznik czasu" + "INVALID_TIMESTAMP": "Nieprawidłowy znacznik czasu", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Niewystarczające uprawnienia do wykonania tej akcji", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Żądania z połączeń sieci komórkowych nie są dozwolone dla tej strony", "SATELLITE_BLOCKED": "Żądania z połączeń satelitarnych nie są dozwolone dla tej strony", "CRAWLER_BLOCKED": "Żądania od znanych robotów nie są dozwolone dla tej strony", - "DISALLOWED_WEBVIEW": "Żądania z przeglądarek wbudowanych w aplikacje (WebView) nie są dozwolone dla tej strony" + "DISALLOWED_WEBVIEW": "Żądania z przeglądarek wbudowanych w aplikacje (WebView) nie są dozwolone dla tej strony", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Nie można zmodyfikować właściciela użytkownika", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Nie znaleziono sugestii", "CANNOT_DEACTIVATE_LAST_SITE": "Nie można dezaktywować ostatniej aktywnej witryny", "DUPLICATE_SITE_NAME": "Witryna o tej nazwie już istnieje", - "INVALID_SITE_NAME": "Nazwa witryny może zawierać tylko litery, cyfry, łączniki i podkreślenia" + "INVALID_SITE_NAME": "Nazwa witryny może zawierać tylko litery, cyfry, łączniki i podkreślenia", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "samoloty", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tabele niezdefiniowane", "CONNECTION_UNDEFINED": "Połączenie niezdefiniowane", "COMMITMENT_FLAG_FAILED": "Nie udało się oznaczyć zobowiązania jako przetworzone", - "UNKNOWN": "Nieznany błąd bazy danych" + "UNKNOWN": "Nieznany błąd bazy danych", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Błąd parsowania captchy", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "Adresy IP nie pasują", "INVALID_TOKEN": "Nieprawidłowy token", "INVALID_SOLUTION": "Nieprawidłowe rozwiązanie", - "DECRYPTION_ERROR": "Błąd deszyfrowania" + "DECRYPTION_ERROR": "Błąd deszyfrowania", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Nieprawidłowy parametr" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Brakująca para dostawcy", "MISSING_ENV_VARIABLE": "Brak zmiennej środowiskowej", "GENERAL": "Ogólny błąd dewelopera, zobacz kontekst", - "METHOD_NOT_IMPLEMENTED": "Metoda niezaimplementowana" + "METHOD_NOT_IMPLEMENTED": "Metoda niezaimplementowana", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Plik nie znaleziony", "FILE_ALREADY_EXISTS": "Plik już istnieje", "INVALID_DIR_FORMAT": "Nieprawidłowy format katalogu" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/pt-BR/translation.json b/packages/locale/src/locales/pt-BR/translation.json index 6e4d856bf8..56f736ac7b 100644 --- a/packages/locale/src/locales/pt-BR/translation.json +++ b/packages/locale/src/locales/pt-BR/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Assinatura inválida", "SITE_KEY_MISSING": "Chave do SITE ausente", "ACCOUNT_NOT_FOUND": "Conta não encontrada", - "INVALID_TIMESTAMP": "Timestamp inválido" + "INVALID_TIMESTAMP": "Timestamp inválido", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permissões insuficientes para realizar esta ação", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Solicitações de conexões de rede móvel não são permitidas para este site", "SATELLITE_BLOCKED": "Solicitações de conexões por satélite não são permitidas para este site", "CRAWLER_BLOCKED": "Solicitações de crawlers conhecidos não são permitidas para este site", - "DISALLOWED_WEBVIEW": "Solicitações de navegadores incorporados (WebView) não são permitidas para este site" + "DISALLOWED_WEBVIEW": "Solicitações de navegadores incorporados (WebView) não são permitidas para este site", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Não é possível modificar o usuário proprietário", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Nenhuma sugestão encontrada", "CANNOT_DEACTIVATE_LAST_SITE": "Não é possível desativar o último site ativo", "DUPLICATE_SITE_NAME": "Já existe um site com este nome", - "INVALID_SITE_NAME": "O nome do site deve conter apenas letras, números, hifens e sublinhados" + "INVALID_SITE_NAME": "O nome do site deve conter apenas letras, números, hifens e sublinhados", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Extensão Polkadot não encontrada" @@ -121,7 +135,15 @@ "TABLES_UNDEFINED": "Tabelas indefinidas", "CONNECTION_UNDEFINED": "Conexão indefinida", "COMMITMENT_FLAG_FAILED": "Falha ao marcar compromisso como processado", - "UNKNOWN": "Erro de banco de dados desconhecido" + "UNKNOWN": "Erro de banco de dados desconhecido", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Erro ao analisar captcha", @@ -139,7 +161,11 @@ "IP_ADDRESS_MISMATCH": "Endereços IP não coincidem", "INVALID_TOKEN": "Token inválido", "INVALID_SOLUTION": "Solução inválida", - "DECRYPTION_ERROR": "Erro ao descriptografar" + "DECRYPTION_ERROR": "Erro ao descriptografar", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Parâmetro inválido" @@ -150,7 +176,8 @@ "MISSING_PROVIDER_PAIR": "Par de provedores ausente", "MISSING_ENV_VARIABLE": "Variável de ambiente ausente", "GENERAL": "Erro de desenvolvedor geral, veja o contexto", - "METHOD_NOT_IMPLEMENTED": "Método não implementado" + "METHOD_NOT_IMPLEMENTED": "Método não implementado", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Arquivo não encontrado", @@ -455,5 +482,10 @@ "sword": "espada", "syringe": "seringa", "tambourine": "tamborim" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/pt/translation.json b/packages/locale/src/locales/pt/translation.json index ae5c97d1ef..0e67f1e6d1 100644 --- a/packages/locale/src/locales/pt/translation.json +++ b/packages/locale/src/locales/pt/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Assinatura inválida", "SITE_KEY_MISSING": "CHAVE DO SITE ausente", "ACCOUNT_NOT_FOUND": "Conta não encontrada", - "INVALID_TIMESTAMP": "Carimbo de data/hora inválido" + "INVALID_TIMESTAMP": "Carimbo de data/hora inválido", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permissões insuficientes para realizar esta ação", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Pedidos de ligações de rede móvel não são permitidos para este site", "SATELLITE_BLOCKED": "Pedidos de ligações por satélite não são permitidos para este site", "CRAWLER_BLOCKED": "Pedidos de rastreadores conhecidos não são permitidos para este site", - "DISALLOWED_WEBVIEW": "Pedidos a partir de navegadores incorporados em aplicações (WebView) não são permitidos para este site" + "DISALLOWED_WEBVIEW": "Pedidos a partir de navegadores incorporados em aplicações (WebView) não são permitidos para este site", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Não é possível modificar o utilizador proprietário", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Nenhuma sugestão encontrada", "CANNOT_DEACTIVATE_LAST_SITE": "Não é possível desativar o último site ativo", "DUPLICATE_SITE_NAME": "Já existe um site com este nome", - "INVALID_SITE_NAME": "O nome do site deve conter apenas letras, números, hífenes e sublinhados" + "INVALID_SITE_NAME": "O nome do site deve conter apenas letras, números, hífenes e sublinhados", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "ACCOUNT": { "NO_POLKADOT_EXTENSION": "Extensão Polkadot não encontrada" @@ -121,7 +135,15 @@ "TABLES_UNDEFINED": "Tabelas indefinidas", "CONNECTION_UNDEFINED": "Conexão indefinida", "COMMITMENT_FLAG_FAILED": "Falha ao marcar o compromisso como processado", - "UNKNOWN": "Erro de banco de dados desconhecido" + "UNKNOWN": "Erro de banco de dados desconhecido", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Erro ao analisar o captcha", @@ -139,7 +161,11 @@ "IP_ADDRESS_MISMATCH": "Endereços IP não coincidem", "INVALID_TOKEN": "Token inválido", "INVALID_SOLUTION": "Solução inválida", - "DECRYPTION_ERROR": "Erro de desencriptação" + "DECRYPTION_ERROR": "Erro de desencriptação", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Parâmetro inválido" @@ -150,7 +176,8 @@ "MISSING_PROVIDER_PAIR": "Par de provedor ausente", "MISSING_ENV_VARIABLE": "Variável de ambiente ausente", "GENERAL": "Erro geral de desenvolvimento, veja o contexto", - "METHOD_NOT_IMPLEMENTED": "Método não implementado" + "METHOD_NOT_IMPLEMENTED": "Método não implementado", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Arquivo não encontrado", @@ -455,5 +482,10 @@ "sword": "espada", "syringe": "seringa", "tambourine": "tamborim" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/ro/translation.json b/packages/locale/src/locales/ro/translation.json index e80c4f879b..51563bd284 100644 --- a/packages/locale/src/locales/ro/translation.json +++ b/packages/locale/src/locales/ro/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Semnatura invalida", "SITE_KEY_MISSING": "Cheia SITE lipseste", "ACCOUNT_NOT_FOUND": "Contul nu a fost gasit", - "INVALID_TIMESTAMP": "Timestamp invalid" + "INVALID_TIMESTAMP": "Timestamp invalid", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permisiuni insuficiente pentru a efectua aceasta actiune", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Solicitările de la conexiuni de rețea mobilă nu sunt permise pentru acest site", "SATELLITE_BLOCKED": "Solicitările de la conexiuni prin satelit nu sunt permise pentru acest site", "CRAWLER_BLOCKED": "Solicitările de la crawlere cunoscute nu sunt permise pentru acest site", - "DISALLOWED_WEBVIEW": "Solicitările din browsere încorporate în aplicații (WebView) nu sunt permise pentru acest site" + "DISALLOWED_WEBVIEW": "Solicitările din browsere încorporate în aplicații (WebView) nu sunt permise pentru acest site", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Nu se poate modifica utilizatorul proprietar", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Nu s-au gasit sugestii", "CANNOT_DEACTIVATE_LAST_SITE": "Nu se poate dezactiva ultimul site activ", "DUPLICATE_SITE_NAME": "Există deja un site cu acest nume", - "INVALID_SITE_NAME": "Numele site-ului trebuie să conțină doar litere, cifre, cratime și liniuțe de subliniere" + "INVALID_SITE_NAME": "Numele site-ului trebuie să conțină doar litere, cifre, cratime și liniuțe de subliniere", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "avioane", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tabelele sunt nedefinite", "CONNECTION_UNDEFINED": "Conexiunea este nedefinita", "COMMITMENT_FLAG_FAILED": "Esuata la marcarea angajamentului ca procesat", - "UNKNOWN": "Eroare necunoscuta la baza de date" + "UNKNOWN": "Eroare necunoscuta la baza de date", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Eroare la parsarea captcha-ului", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "Adresele IP nu se potrivesc", "INVALID_TOKEN": "Token invalid", "INVALID_SOLUTION": "Solutie invalida", - "DECRYPTION_ERROR": "Eroare la decriptare" + "DECRYPTION_ERROR": "Eroare la decriptare", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Parametru invalid" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Pereche de furnizori lipsa", "MISSING_ENV_VARIABLE": "Variabila de mediu lipsa", "GENERAL": "Eroare generala de dezvoltare, vezi contextul", - "METHOD_NOT_IMPLEMENTED": "Metoda neimplementata" + "METHOD_NOT_IMPLEMENTED": "Metoda neimplementata", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Fisierul nu a fost gasit", "FILE_ALREADY_EXISTS": "Fisierul deja exista", "INVALID_DIR_FORMAT": "Format de director invalid" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/ru/translation.json b/packages/locale/src/locales/ru/translation.json index 08a3898c81..b1a2ff3411 100644 --- a/packages/locale/src/locales/ru/translation.json +++ b/packages/locale/src/locales/ru/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Неверная подпись", "SITE_KEY_MISSING": "Отсутствует ключ сайта", "ACCOUNT_NOT_FOUND": "Учетная запись не найдена", - "INVALID_TIMESTAMP": "Недопустимый временной штамп" + "INVALID_TIMESTAMP": "Недопустимый временной штамп", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Недостаточно прав для выполнения этого действия", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Запросы с мобильных сетевых подключений не разрешены для этого сайта", "SATELLITE_BLOCKED": "Запросы со спутниковых подключений не разрешены для этого сайта", "CRAWLER_BLOCKED": "Запросы от известных поисковых роботов не разрешены для этого сайта", - "DISALLOWED_WEBVIEW": "Запросы из встроенных браузеров (WebView) не разрешены для этого сайта" + "DISALLOWED_WEBVIEW": "Запросы из встроенных браузеров (WebView) не разрешены для этого сайта", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Невозможно изменить владельца пользователя", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Предложения не найдены", "CANNOT_DEACTIVATE_LAST_SITE": "Нельзя деактивировать последний активный сайт", "DUPLICATE_SITE_NAME": "Сайт с таким именем уже существует", - "INVALID_SITE_NAME": "Имя сайта должно содержать только буквы, цифры, дефисы и символы подчёркивания" + "INVALID_SITE_NAME": "Имя сайта должно содержать только буквы, цифры, дефисы и символы подчёркивания", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "самолеты", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Таблицы не определены", "CONNECTION_UNDEFINED": "Подключение не определено", "COMMITMENT_FLAG_FAILED": "Не удалось пометить коммитмент как обработанный", - "UNKNOWN": "Неизвестная ошибка базы данных" + "UNKNOWN": "Неизвестная ошибка базы данных", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Ошибка парсинга капчи", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP адреса не совпадают", "INVALID_TOKEN": "Недействительный токен", "INVALID_SOLUTION": "Недопустимое решение", - "DECRYPTION_ERROR": "Ошибка расшифровки" + "DECRYPTION_ERROR": "Ошибка расшифровки", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Недопустимый параметр" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Отсутствует пара поставщиков", "MISSING_ENV_VARIABLE": "Отсутствует переменная среды", "GENERAL": "Общая ошибка Dev, см. контекст", - "METHOD_NOT_IMPLEMENTED": "Метод не реализован" + "METHOD_NOT_IMPLEMENTED": "Метод не реализован", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Файл не найден", "FILE_ALREADY_EXISTS": "Файл уже существует", "INVALID_DIR_FORMAT": "Недопустимый формат каталога" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/sr/translation.json b/packages/locale/src/locales/sr/translation.json index 2f639b5252..afebe80a8f 100644 --- a/packages/locale/src/locales/sr/translation.json +++ b/packages/locale/src/locales/sr/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Неважећи потпис", "SITE_KEY_MISSING": "Недостаје кључ сајта", "ACCOUNT_NOT_FOUND": "Налог није пронађен", - "INVALID_TIMESTAMP": "Неважећа временска ознака" + "INVALID_TIMESTAMP": "Неважећа временска ознака", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Недовољне дозволе за обављање ове радње", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Захтеви са мобилних мрежних конекција нису дозвољени за овај сајт", "SATELLITE_BLOCKED": "З��хтеви са сателитских конекција нису дозвољени за овај сајт", "CRAWLER_BLOCKED": "Захтеви од познатих ботова нису дозвољени за овај сајт", - "DISALLOWED_WEBVIEW": "Захтеви из уграђених прегледача (WebView) нису дозвољени за овај сајт" + "DISALLOWED_WEBVIEW": "Захтеви из уграђених прегледача (WebView) нису дозвољени за овај сајт", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Није могуће изменити власника корисника", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Нису пронађени предлози", "CANNOT_DEACTIVATE_LAST_SITE": "Није могуће деактивирати последњи активни сајт", "DUPLICATE_SITE_NAME": "Сајт са овим именом већ постоји", - "INVALID_SITE_NAME": "Назив сајта сме да садржи само слова, бројеве, цртице и доње црте" + "INVALID_SITE_NAME": "Назив сајта сме да садржи само слова, бројеве, цртице и доње црте", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "авиони", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Табеле нису дефинисане", "CONNECTION_UNDEFINED": "Веза није дефинисана", "COMMITMENT_FLAG_FAILED": "Означавање обавезе као обрађене није успело", - "UNKNOWN": "Непозната грешка базе података" + "UNKNOWN": "Непозната грешка базе података", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Грешка при рашчлањивању капче", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP адресе се не подударају", "INVALID_TOKEN": "Неважећи токен", "INVALID_SOLUTION": "Неважеће решење", - "DECRYPTION_ERROR": "Грешка при дешифровању" + "DECRYPTION_ERROR": "Грешка при дешифровању", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Неважећи параметар" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Недостаје пар провајдера", "MISSING_ENV_VARIABLE": "Недостаје променљива окружења", "GENERAL": "Општа грешка развоја, погледајте контекст", - "METHOD_NOT_IMPLEMENTED": "Метода није имплементирана" + "METHOD_NOT_IMPLEMENTED": "Метода није имплементирана", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Датотека није пронађена", "FILE_ALREADY_EXISTS": "Датотека већ постоји", "INVALID_DIR_FORMAT": "Неважећи формат директоријума" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/sv/translation.json b/packages/locale/src/locales/sv/translation.json index bd6e250f35..f163a9d09f 100644 --- a/packages/locale/src/locales/sv/translation.json +++ b/packages/locale/src/locales/sv/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Ogiltig signatur", "SITE_KEY_MISSING": "SITE KEY saknas", "ACCOUNT_NOT_FOUND": "Konto hittades inte", - "INVALID_TIMESTAMP": "Ogiltig tidsstämpel" + "INVALID_TIMESTAMP": "Ogiltig tidsstämpel", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Otillräckliga behörigheter för att utföra denna åtgärd", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Förfrågningar från mobilnätverksanslutningar är inte tillåtna för denna webbplats", "SATELLITE_BLOCKED": "Förfrågningar från satellitanslutningar är inte tillåtna för denna webbplats", "CRAWLER_BLOCKED": "Förfrågningar från kända crawlers är inte tillåtna för denna webbplats", - "DISALLOWED_WEBVIEW": "Förfrågningar från inbäddade webbläsare (WebView) är inte tillåtna för denna webbplats" + "DISALLOWED_WEBVIEW": "Förfrågningar från inbäddade webbläsare (WebView) är inte tillåtna för denna webbplats", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Kan inte ändra ägare användare", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Inga förslag hittades", "CANNOT_DEACTIVATE_LAST_SITE": "Kan inte inaktivera den sista aktiva platsen", "DUPLICATE_SITE_NAME": "En webbplats med detta namn finns redan", - "INVALID_SITE_NAME": "Webbplatsnamnet får bara innehålla bokstäver, siffror, bindestreck och understreck" + "INVALID_SITE_NAME": "Webbplatsnamnet får bara innehålla bokstäver, siffror, bindestreck och understreck", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "flygplan", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tabeller ospecificerade", "CONNECTION_UNDEFINED": "Anslutning ospecificerad", "COMMITMENT_FLAG_FAILED": "Misslyckades att flagga åtagande som behandlad", - "UNKNOWN": "Okänt databasfel" + "UNKNOWN": "Okänt databasfel", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Fel vid tolkning av captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP-adresser matchar inte", "INVALID_TOKEN": "Ogiltig token", "INVALID_SOLUTION": "Ogiltig lösning", - "DECRYPTION_ERROR": "Fel vid avkryptering" + "DECRYPTION_ERROR": "Fel vid avkryptering", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Ogiltig parameter" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Saknad-leverantör-par", "MISSING_ENV_VARIABLE": "Saknad-miljövariabel", "GENERAL": "Allmänt utvecklarfel, se sammanhang", - "METHOD_NOT_IMPLEMENTED": "Metod-inte-implementerad" + "METHOD_NOT_IMPLEMENTED": "Metod-inte-implementerad", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Fil-inte-hittad", "FILE_ALREADY_EXISTS": "Fil-redan-existerar", "INVALID_DIR_FORMAT": "Ogiltigt mappformat" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/th/translation.json b/packages/locale/src/locales/th/translation.json index 71acbc26eb..591da7d7df 100644 --- a/packages/locale/src/locales/th/translation.json +++ b/packages/locale/src/locales/th/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "ลายเซ็นต์ไม่ถูกต้อง", "SITE_KEY_MISSING": "SITE KEY หายไป", "ACCOUNT_NOT_FOUND": "ไม่พบบัญชี", - "INVALID_TIMESTAMP": "เครื่องหมายเวลาไม่ถูกต้อง" + "INVALID_TIMESTAMP": "เครื่องหมายเวลาไม่ถูกต้อง", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "สิทธิ์ไม่เพียงพอในการทำการกระทำนี้", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "คำขอจากการเชื่อมต่อเครือข่ายมือถือไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้", "SATELLITE_BLOCKED": "คำขอจากการเชื่อมต่อดาวเทียมไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้", "CRAWLER_BLOCKED": "คำขอจากโปรแกรมรวบรวมข้อมูลที่รู้จักไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้", - "DISALLOWED_WEBVIEW": "ไม่อนุญาตให้ส่งคำขอจากเบราว์เซอร์ในแอป (WebView) สำหรับเว็บไซต์นี้" + "DISALLOWED_WEBVIEW": "ไม่อนุญาตให้ส่งคำขอจากเบราว์เซอร์ในแอป (WebView) สำหรับเว็บไซต์นี้", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "ไม่สามารถแก้ไขผู้ใช้เจ้าของ", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "ไม่พบข้อเสนอ", "CANNOT_DEACTIVATE_LAST_SITE": "ไม่สามารถปิดใช้งานเว็บไซต์ที่ใช้งานอยู่ครั้งสุดท้าย", "DUPLICATE_SITE_NAME": "มีไซต์ที่ใช้ชื่อนี้อยู่แล้ว", - "INVALID_SITE_NAME": "ชื่อไซต์ต้องประกอบด้วยตัวอักษร ตัวเลข ขีดกลาง และขีดล่างเท่านั้น" + "INVALID_SITE_NAME": "ชื่อไซต์ต้องประกอบด้วยตัวอักษร ตัวเลข ขีดกลาง และขีดล่างเท่านั้น", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "เครื่องบิน", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "ตารางไม่ได้ถูกกำหนดไว้", "CONNECTION_UNDEFINED": "การเชื่อมต่อไม่ได้ถูกกำหนด", "COMMITMENT_FLAG_FAILED": "ล้มเหลวในการทำเครื่องหมายการสนับสนุนว่าดำเนินการแล้ว", - "UNKNOWN": "ข้อผิดพลาดในฐานข้อมูลที่ไม่ทราบสาเหตุ" + "UNKNOWN": "ข้อผิดพลาดในฐานข้อมูลที่ไม่ทราบสาเหตุ", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "ข้อผิดพลาดในการแยกวิเคราะห์ captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "ที่อยู่ IP ไม่ตรงกัน", "INVALID_TOKEN": "โทเคนไม่ถูกต้อง", "INVALID_SOLUTION": "คำตอบไม่ถูกต้อง", - "DECRYPTION_ERROR": "เกิดข้อผิดพลาดในการถอดรหัส" + "DECRYPTION_ERROR": "เกิดข้อผิดพลาดในการถอดรหัส", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "พารามิเตอร์ไม่ถูกต้อง" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "คู่ให้บริการที่ขาดหาย", "MISSING_ENV_VARIABLE": "ตัวแปรสภาพแวดล้อมที่ขาดหาย", "GENERAL": "ข้อผิดพลาด Dev ทั่วไป ดูบริบท", - "METHOD_NOT_IMPLEMENTED": "เมธอดยังไม่ได้นำมาใช้" + "METHOD_NOT_IMPLEMENTED": "เมธอดยังไม่ได้นำมาใช้", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "ไม่พบไฟล์", "FILE_ALREADY_EXISTS": "ไฟล์มีอยู่แล้ว", "INVALID_DIR_FORMAT": "รูปแบบไดเรกทอรีไม่ถูกต้อง" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/tr/translation.json b/packages/locale/src/locales/tr/translation.json index dfd3349a21..023307de7f 100644 --- a/packages/locale/src/locales/tr/translation.json +++ b/packages/locale/src/locales/tr/translation.json @@ -10,7 +10,12 @@ "ENVIRONMENT_NOT_READY": "Çevre hazır değil", "INVALID_SIGNATURE": "Geçersiz imza", "SITE_KEY_MISSING": "SITE KEY eksik", - "INVALID_TIMESTAMP": "Geçersiz zaman damgası" + "INVALID_TIMESTAMP": "Geçersiz zaman damgası", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Bu site için mobil ağ bağlantılarından gelen isteklere izin verilmiyor", "SATELLITE_BLOCKED": "Bu site için uydu bağlantılarından gelen isteklere izin verilmiyor", "CRAWLER_BLOCKED": "Bu site için bilinen tarayıcılardan gelen isteklere izin verilmiyor", - "DISALLOWED_WEBVIEW": "Bu site için uygulama içi tarayıcılardan (WebView) gelen isteklere izin verilmiyor" + "DISALLOWED_WEBVIEW": "Bu site için uygulama içi tarayıcılardan (WebView) gelen isteklere izin verilmiyor", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Cannot modify owner user", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "No suggestions found", "CANNOT_DEACTIVATE_LAST_SITE": "Cannot deactivate the last active site", "DUPLICATE_SITE_NAME": "Bu ada sahip bir site zaten mevcut", - "INVALID_SITE_NAME": "Site adı yalnızca harf, rakam, tire ve alt çizgi içermelidir" + "INVALID_SITE_NAME": "Site adı yalnızca harf, rakam, tire ve alt çizgi içermelidir", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "uçaklar", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tablolar tanımsız", "CONNECTION_UNDEFINED": "Bağlantı tanımsız", "COMMITMENT_FLAG_FAILED": "Taahhütü işlenmiş olarak işaretleme başarısız oldu", - "UNKNOWN": "Bilinmeyen veritabanı hatası" + "UNKNOWN": "Bilinmeyen veritabanı hatası", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Captcha ayrıştırma hatası", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP adresleri eşleşmiyor", "INVALID_TOKEN": "Geçersiz belirteç", "INVALID_SOLUTION": "Geçersiz çözüm", - "DECRYPTION_ERROR": "Şifreleme hatası" + "DECRYPTION_ERROR": "Şifreleme hatası", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Geçersiz parametre" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Eksik sağlayıcı çifti", "MISSING_ENV_VARIABLE": "Eksik ortam değişkeni", "GENERAL": "Genel Geliştirici Hatası, bağlamı kontrol edin", - "METHOD_NOT_IMPLEMENTED": "Yöntem uygulanmadı" + "METHOD_NOT_IMPLEMENTED": "Yöntem uygulanmadı", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Dosya bulunamadı", "FILE_ALREADY_EXISTS": "Dosya zaten var", "INVALID_DIR_FORMAT": "Geçersiz dizin biçimi" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/uk/translation.json b/packages/locale/src/locales/uk/translation.json index 1f2d637e90..7517d48254 100644 --- a/packages/locale/src/locales/uk/translation.json +++ b/packages/locale/src/locales/uk/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Invalid signature", "SITE_KEY_MISSING": "SITE KEY missing", "ACCOUNT_NOT_FOUND": "Account not found", - "INVALID_TIMESTAMP": "Invalid timestamp" + "INVALID_TIMESTAMP": "Invalid timestamp", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Запити з мобільних мережевих з'єднань не дозволені для цього сайту", "SATELLITE_BLOCKED": "Запити із супутникових з'єднань не дозволені для цього сайту", "CRAWLER_BLOCKED": "Запити від відомих пошукових роботів не дозволені для цього сайту", - "DISALLOWED_WEBVIEW": "Запити з вбудованих браузерів (WebView) не дозволені для цього сайту" + "DISALLOWED_WEBVIEW": "Запити з вбудованих браузерів (WebView) не дозволені для цього сайту", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Cannot modify owner user", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "No suggestions found", "CANNOT_DEACTIVATE_LAST_SITE": "Cannot deactivate the last active site", "DUPLICATE_SITE_NAME": "Сайт із такою назвою вже існує", - "INVALID_SITE_NAME": "Назва сайту повинна містити лише літери, цифри, дефіси та символи підкреслення" + "INVALID_SITE_NAME": "Назва сайту повинна містити лише літери, цифри, дефіси та символи підкреслення", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "aeroplanes", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Tables undefined", "CONNECTION_UNDEFINED": "Connection undefined", "COMMITMENT_FLAG_FAILED": "Failed to flag commitment as processed", - "UNKNOWN": "Unknown database error" + "UNKNOWN": "Unknown database error", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Error parsing captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "IP address mismatch detected", "INVALID_TOKEN": "Invalid token", "INVALID_SOLUTION": "Invalid solution", - "DECRYPTION_ERROR": "Error decrypting" + "DECRYPTION_ERROR": "Error decrypting", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Invalid parameter" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Missing provider pair", "MISSING_ENV_VARIABLE": "Missing environment variable", "GENERAL": "General Dev Error, see context", - "METHOD_NOT_IMPLEMENTED": "Method not implemented" + "METHOD_NOT_IMPLEMENTED": "Method not implemented", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "File not found", "FILE_ALREADY_EXISTS": "File already exists", "INVALID_DIR_FORMAT": "Invalid directory format" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/vi/translation.json b/packages/locale/src/locales/vi/translation.json index f624b4ef19..bd121e987b 100644 --- a/packages/locale/src/locales/vi/translation.json +++ b/packages/locale/src/locales/vi/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "Chữ ký không hợp lệ", "SITE_KEY_MISSING": "SITE KEY thiếu", "ACCOUNT_NOT_FOUND": "Không tìm thấy tài khoản", - "INVALID_TIMESTAMP": "Thời điểm không hợp lệ" + "INVALID_TIMESTAMP": "Thời điểm không hợp lệ", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "Không đủ quyền để thực hiện hành động này", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "Các yêu cầu từ kết nối mạng di động không được phép cho trang web này", "SATELLITE_BLOCKED": "Các yêu cầu từ kết nối vệ tinh không được phép cho trang web này", "CRAWLER_BLOCKED": "Các yêu cầu từ trình thu thập dữ liệu đã biết không được phép cho trang web này", - "DISALLOWED_WEBVIEW": "Yêu cầu từ trình duyệt trong ứng dụng (WebView) không được phép cho trang web này" + "DISALLOWED_WEBVIEW": "Yêu cầu từ trình duyệt trong ứng dụng (WebView) không được phép cho trang web này", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Không thể sửa đổi người dùng chủ sở hữu", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "Không tìm thấy gợi ý nào", "CANNOT_DEACTIVATE_LAST_SITE": "Không thể vô hiệu hóa trang cuối cùng hoạt động", "DUPLICATE_SITE_NAME": "Đã tồn tại một trang web với tên này", - "INVALID_SITE_NAME": "Tên trang web chỉ được chứa chữ cái, chữ số, dấu gạch ngang và dấu gạch dưới" + "INVALID_SITE_NAME": "Tên trang web chỉ được chứa chữ cái, chữ số, dấu gạch ngang và dấu gạch dưới", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "máy bay", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "Bảng chưa xác định", "CONNECTION_UNDEFINED": "Kết nối không xác định", "COMMITMENT_FLAG_FAILED": "Không thể đánh dấu cam kết là đã xử lý", - "UNKNOWN": "Lỗi cơ sở dữ liệu không xác định" + "UNKNOWN": "Lỗi cơ sở dữ liệu không xác định", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "Lỗi phân tích captcha", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "Phát hiện không khớp địa chỉ IP", "INVALID_TOKEN": "Token không hợp lệ", "INVALID_SOLUTION": "Giải pháp không hợp lệ", - "DECRYPTION_ERROR": "Lỗi giải mã" + "DECRYPTION_ERROR": "Lỗi giải mã", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "Tham số không hợp lệ" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "Thiếu cặp nhà cung cấp", "MISSING_ENV_VARIABLE": "Thiếu biến môi trường", "GENERAL": "Lỗi phát triển chung, xem ngữ cảnh", - "METHOD_NOT_IMPLEMENTED": "Phương thức chưa được triển khai" + "METHOD_NOT_IMPLEMENTED": "Phương thức chưa được triển khai", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "Không tìm thấy tệp", "FILE_ALREADY_EXISTS": "Tệp đã tồn tại", "INVALID_DIR_FORMAT": "Định dạng thư mục không hợp lệ" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/locale/src/locales/zh-CN/translation.json b/packages/locale/src/locales/zh-CN/translation.json index ae5ef2df16..ab1651bf96 100644 --- a/packages/locale/src/locales/zh-CN/translation.json +++ b/packages/locale/src/locales/zh-CN/translation.json @@ -10,7 +10,12 @@ "INVALID_SIGNATURE": "无效的签名", "SITE_KEY_MISSING": "缺少SITE KEY", "ACCOUNT_NOT_FOUND": "找不到帐户", - "INVALID_TIMESTAMP": "无效的时间戳" + "INVALID_TIMESTAMP": "无效的时间戳", + "BILLING_ERROR": "Billing error", + "INVALID_JWT": "Invalid authentication token", + "MISSING_AUTH_HEADER": "Missing authentication header", + "OBJECT_COUNT_ERROR": "Unexpected object count", + "SECRET_MISSING": "Secret is missing" }, "API": { "INSUFFICIENT_PERMISSIONS": "权限不足,无法执行此操作", @@ -51,7 +56,13 @@ "MOBILE_BLOCKED": "此网站不允许来自移动网络连接的请求", "SATELLITE_BLOCKED": "此网站不允许来自卫星连接的请求", "CRAWLER_BLOCKED": "此网站不允许来自已知爬虫的请求", - "DISALLOWED_WEBVIEW": "此站点不允许来自应用内浏览器 (WebView) 的请求" + "DISALLOWED_WEBVIEW": "此站点不允许来自应用内浏览器 (WebView) 的请求", + "INTERNAL_SERVER_ERROR": "Internal server error", + "INVALID_DETECTOR_KEY": "Invalid detector key", + "FEATURE_NOT_ENABLED": "This feature is not enabled", + "INVALID_AUTHORIZATION_HEADER": "Invalid authorization header", + "INVALID_DOMAIN": "Invalid domain", + "NO_PERMISSIONS": "No permissions configured for this account" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "无法修改所有者用户", @@ -61,7 +72,10 @@ "NO_SUGGESTIONS_FOUND": "未找到建议", "CANNOT_DEACTIVATE_LAST_SITE": "无法停用最后一个活动站点", "DUPLICATE_SITE_NAME": "已存在同名站点", - "INVALID_SITE_NAME": "站点名称只能包含字母、数字、连字符和下划线" + "INVALID_SITE_NAME": "站点名称只能包含字母、数字、连字符和下划线", + "CANNOT_MODIFY_USER": "Cannot modify this user", + "API_KEYS_INVALID_NUMBER_OF_SITES": "Invalid number of sites for API key generation", + "API_KEYS_NOT_FOUND": "API keys not found" }, "TARGET": { "airplanes": "飞机", @@ -420,7 +434,15 @@ "TABLES_UNDEFINED": "未定义表", "CONNECTION_UNDEFINED": "连接未定义", "COMMITMENT_FLAG_FAILED": "标记承诺为已处理失败", - "UNKNOWN": "未知数据库错误" + "UNKNOWN": "未知数据库错误", + "QUERY_ERROR": "Database query failed", + "SESSION_STORE_FAILED": "Failed to store session", + "SESSION_CHECK_REMOVE_FAILED": "Failed to check and remove session", + "SESSION_GET_FAILED": "Failed to get session", + "CAPTCHA_SAMPLE_SIZE_EXCEEDED": "Captcha sample size exceeded", + "DATABASE_IMPORT_ERROR": "Database import error", + "REDIS_ACCESS_RULES_CONNECTION_UNDEFINED": "Redis access rules connection is undefined", + "USER_ACCESS_RULES_STORAGE_UNDEFINED": "User access rules storage is undefined" }, "CAPTCHA": { "PARSE_ERROR": "解析验证码时出错", @@ -438,7 +460,11 @@ "IP_ADDRESS_MISMATCH": "检测到 IP 地址不匹配", "INVALID_TOKEN": "无效的令牌", "INVALID_SOLUTION": "无效的解决方案", - "DECRYPTION_ERROR": "解密错误" + "DECRYPTION_ERROR": "解密错误", + "FAILED": "Captcha failed", + "PASSED": "Captcha passed", + "SOLUTION_NOT_FOUND": "Solution not found", + "INVALID_TIMESTAMP": "Invalid timestamp" }, "CLI": { "PARAMETER_ERROR": "无效的参数" @@ -449,11 +475,17 @@ "MISSING_PROVIDER_PAIR": "缺少提供商对", "MISSING_ENV_VARIABLE": "缺少环境变量", "GENERAL": "一般开发错误,请参考上下文", - "METHOD_NOT_IMPLEMENTED": "方法未实现" + "METHOD_NOT_IMPLEMENTED": "方法未实现", + "MISSING_SECRET_KEY": "Missing secret key" }, "FS": { "FILE_NOT_FOUND": "未找到文件", "FILE_ALREADY_EXISTS": "文件已存在", "INVALID_DIR_FORMAT": "无效的目录格式" + }, + "BILLING": { + "STRIPE_PORTAL": "Could not open billing portal", + "STRIPE_SESSION_NOT_FOUND": "Billing session not found", + "STRIPE_PAYMENT_FAILED": "Payment failed" } } diff --git a/packages/provider/src/api/captcha/checkSpamEmail.ts b/packages/provider/src/api/captcha/checkSpamEmail.ts index 09462862a2..be13c5fa4c 100644 --- a/packages/provider/src/api/captcha/checkSpamEmail.ts +++ b/packages/provider/src/api/captcha/checkSpamEmail.ts @@ -65,8 +65,6 @@ export default (env: ProviderEnvironment) => return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -79,8 +77,6 @@ export default (env: ProviderEnvironment) => code: 400, siteKey: dapp, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -110,8 +106,6 @@ export default (env: ProviderEnvironment) => return next( new ProsopoApiError("API.INTERNAL_SERVER_ERROR", { context: { code: 500 }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.ts b/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.ts index 720a9f2de8..d60f56e214 100644 --- a/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.ts +++ b/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.ts @@ -348,8 +348,6 @@ const runContextAwareValidation = async ( return next( new ProsopoApiError("API.BAD_REQUEST", { context: { code: 400, siteKey: dapp, user }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge/handler.ts b/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge/handler.ts index e207c8e8a5..248f66bf21 100644 --- a/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge/handler.ts +++ b/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge/handler.ts @@ -145,8 +145,6 @@ export default ( return next( new ProsopoApiError("API.BAD_REQUEST", { context: { code: 400, siteKey: dapp, user }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -157,8 +155,6 @@ export default ( return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -265,8 +261,6 @@ export default ( return next( new ProsopoApiError(reason || "API.BAD_REQUEST", { context: { code: 400, siteKey: dapp, user }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -394,8 +388,6 @@ export default ( return next( new ProsopoApiError("API.BAD_REQUEST", { context: { code: 400, error: err }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/captcha/getPuzzleCaptchaChallenge.ts b/packages/provider/src/api/captcha/getPuzzleCaptchaChallenge.ts index a7a21dbe15..788c9fd0c5 100644 --- a/packages/provider/src/api/captcha/getPuzzleCaptchaChallenge.ts +++ b/packages/provider/src/api/captcha/getPuzzleCaptchaChallenge.ts @@ -50,8 +50,6 @@ export default ( return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -82,8 +80,6 @@ export default ( return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -144,8 +140,6 @@ export default ( siteKey: dapp, user, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -161,8 +155,6 @@ export default ( siteKey: dapp, user, }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -256,8 +248,6 @@ export default ( user: req.body.user, error: err, }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/captcha/submitPuzzleCaptchaSolution.ts b/packages/provider/src/api/captcha/submitPuzzleCaptchaSolution.ts index a04d9efdbb..06c7e0c041 100644 --- a/packages/provider/src/api/captcha/submitPuzzleCaptchaSolution.ts +++ b/packages/provider/src/api/captcha/submitPuzzleCaptchaSolution.ts @@ -55,8 +55,6 @@ export default (env: ProviderEnvironment) => return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err, body: req.body }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -97,8 +95,6 @@ export default (env: ProviderEnvironment) => return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -137,8 +133,6 @@ export default (env: ProviderEnvironment) => siteKey: req.body.dapp, error: err, }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/api/domainMiddleware.ts b/packages/provider/src/api/domainMiddleware.ts index 093709c320..1e0eccf9f5 100644 --- a/packages/provider/src/api/domainMiddleware.ts +++ b/packages/provider/src/api/domainMiddleware.ts @@ -134,8 +134,6 @@ const siteKeyNotRegisteredError = ( ) => { return new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n, - logger, }); }; @@ -146,8 +144,6 @@ const invalidSiteKeyError = ( ) => { return new ProsopoApiError("API.INVALID_SITE_KEY", { context: { code: 400, siteKey: siteKey }, - i18n, - logger, }); }; @@ -158,8 +154,6 @@ const unauthorizedOriginError = ( ) => { return new ProsopoApiError("API.UNAUTHORIZED_ORIGIN_URL", { context: { code: 400, origin }, - i18n, - logger, }); }; @@ -177,7 +171,5 @@ const siteKeyInvalidDomainError = ( siteKey: dapp, domain, }, - i18n, - logger, }); }; diff --git a/packages/provider/src/api/validateAddress.ts b/packages/provider/src/api/validateAddress.ts index dd979f2071..8fc86b4129 100644 --- a/packages/provider/src/api/validateAddress.ts +++ b/packages/provider/src/api/validateAddress.ts @@ -32,13 +32,11 @@ export const validateAddr = ( if (!valid) { throw new ProsopoApiError(translationKey, { context: { code: 400, siteKey: address }, - logger, }); } } catch (err) { throw new ProsopoApiError(translationKey, { context: { code: 400, siteKey: address }, - logger, }); } }; diff --git a/packages/provider/src/api/verify.ts b/packages/provider/src/api/verify.ts index 717a71067f..c433331c2a 100644 --- a/packages/provider/src/api/verify.ts +++ b/packages/provider/src/api/verify.ts @@ -245,8 +245,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", { context: { code: 400, siteKey: dapp }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -299,8 +297,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("API.BAD_REQUEST", { context: { code: 500, error: err }, - i18n: req.i18n, - logger: req.logger, }), ); } @@ -342,8 +338,6 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router { return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { code: 400, error: err, body: req.body }, - i18n: req.i18n, - logger: req.logger, }), ); } diff --git a/packages/provider/src/tasks/client/clientTasks.ts b/packages/provider/src/tasks/client/clientTasks.ts index b7d8985d63..5a42289332 100644 --- a/packages/provider/src/tasks/client/clientTasks.ts +++ b/packages/provider/src/tasks/client/clientTasks.ts @@ -427,7 +427,7 @@ export class ClientTaskManager { async updateDetectorKey(detectorKey: string): Promise { if (!isValidPrivateKey(detectorKey)) { - throw new ProsopoApiError("INVALID_DETECTOR_KEY", { + throw new ProsopoApiError("API.INVALID_DETECTOR_KEY", { context: { detectorKey }, }); } @@ -442,7 +442,7 @@ export class ClientTaskManager { expirationInSeconds?: number, ): Promise { if (!isValidPrivateKey(detectorKey)) { - throw new ProsopoApiError("INVALID_DETECTOR_KEY", { + throw new ProsopoApiError("API.INVALID_DETECTOR_KEY", { context: { detectorKey }, }); } @@ -466,7 +466,6 @@ export class ClientTaskManager { if (scope === DecisionMachineScope.Dapp && !dappAccount) { throw new ProsopoApiError("API.BAD_REQUEST", { context: { scope, dappAccount }, - logger: this.logger, }); } @@ -537,7 +536,6 @@ export class ClientTaskManager { if (!artifact) { throw new ProsopoApiError("API.BAD_REQUEST", { context: { id }, - logger: this.logger, }); } return { @@ -563,7 +561,6 @@ export class ClientTaskManager { if (!success) { throw new ProsopoApiError("API.BAD_REQUEST", { context: { id, message: "Decision machine not found" }, - logger: this.logger, }); } return { diff --git a/packages/provider/src/tasks/imgCaptcha/imgCaptchaTasks.ts b/packages/provider/src/tasks/imgCaptcha/imgCaptchaTasks.ts index 9ab2bea387..d32c1cbed7 100644 --- a/packages/provider/src/tasks/imgCaptcha/imgCaptchaTasks.ts +++ b/packages/provider/src/tasks/imgCaptcha/imgCaptchaTasks.ts @@ -438,7 +438,7 @@ export class ImgCaptchaManager extends CaptchaManager { const writePromises: Promise[] = [ this.db.disapproveDappUserCommitment( commitmentId, - "CAPTCHA.INVALID_SOLUTION", + ResultReason.CAPTCHA_INVALID_SOLUTION, pairs, ), ]; @@ -499,7 +499,7 @@ export class ImgCaptchaManager extends CaptchaManager { const writePromises: Promise[] = [ this.db.disapproveDappUserCommitment( commitmentId, - "CAPTCHA.INVALID_SOLUTION", + ResultReason.CAPTCHA_INVALID_SOLUTION, pairs, ), ]; diff --git a/packages/provider/src/tests/unit/api/validateAddress.unit.test.ts b/packages/provider/src/tests/unit/api/validateAddress.unit.test.ts index b3d7e96863..9d26854e67 100644 --- a/packages/provider/src/tests/unit/api/validateAddress.unit.test.ts +++ b/packages/provider/src/tests/unit/api/validateAddress.unit.test.ts @@ -161,7 +161,7 @@ describe("validateAddr", () => { it("should validate with different translation keys", () => { const invalidAddress = "invalid-address"; - const customTranslationKey = "API.INVALID_USER"; + const customTranslationKey = "API.INVALID_SITE_KEY"; vi.mocked(validateAddress).mockReturnValue(false); diff --git a/packages/types-database/src/types/provider.ts b/packages/types-database/src/types/provider.ts index ba196c702a..196b550240 100644 --- a/packages/types-database/src/types/provider.ts +++ b/packages/types-database/src/types/provider.ts @@ -13,7 +13,7 @@ // limitations under the License. import type { AllKeys } from "@prosopo/common"; -import { type TranslationKey, TranslationKeysSchema } from "@prosopo/locale"; +import { TranslationKeysSchema } from "@prosopo/locale"; import { CaptchaLabel, CaptchaType, @@ -30,6 +30,7 @@ import { type PendingImageCaptchaRequest, type PoWCaptchaStored, type PuzzleCaptchaStored, + type ResultReason, type Session, type SimdReadingsStage, type SolutionRecord, @@ -804,7 +805,7 @@ export interface IProviderDatabase extends IDatabase { disapproveDappUserCommitment( commitmentId: string, - reason?: TranslationKey, + reason?: ResultReason, coords?: [number, number][][], ): Promise; From 8a280bb8cf185b3f99fc759201c0151e5015c4b5 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 11 Jun 2026 23:50:52 +0100 Subject: [PATCH 12/18] style: apply biome formatting to errorKeys, locale index, bundle vite config --- packages/common/src/errorKeys.ts | 6 +++--- packages/locale/src/index.ts | 6 +++++- packages/procaptcha-bundle/vite.config.ts | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/common/src/errorKeys.ts b/packages/common/src/errorKeys.ts index be7b0422bd..2952073fa0 100644 --- a/packages/common/src/errorKeys.ts +++ b/packages/common/src/errorKeys.ts @@ -81,7 +81,7 @@ export const BACKEND_ERROR_KEYS_ARRAY = Object.values(ALL_ERROR_KEYS); // Type-level validation: ensure all error keys exist in translation.json // Each ValidErrorKey must be assignable to TranslationKey (derived from translation JSON) // If this errors, a key is defined here but missing from the translation file. -export const _validateErrorKeysExistInTranslations: Record = {} as Record< +export const _validateErrorKeysExistInTranslations: Record< ValidErrorKey, - TranslationKey ->; + unknown +> = {} as Record; diff --git a/packages/locale/src/index.ts b/packages/locale/src/index.ts index 7d2e3eda96..a93ec0c235 100644 --- a/packages/locale/src/index.ts +++ b/packages/locale/src/index.ts @@ -13,7 +13,11 @@ // limitations under the License. export { default as i18nMiddleware } from "./i18nMiddleware.js"; -export { default as loadI18next, loadI18nextFrontend, loadI18nextBackend } from "./loadI18next.js"; +export { + default as loadI18next, + loadI18nextFrontend, + loadI18nextBackend, +} from "./loadI18next.js"; export { Languages, LanguageSchema } from "./translations.js"; export { isClientSide } from "./util.js"; export { TranslationKeysSchema } from "./translationKey.js"; diff --git a/packages/procaptcha-bundle/vite.config.ts b/packages/procaptcha-bundle/vite.config.ts index 0af07df4e3..1db0da159c 100644 --- a/packages/procaptcha-bundle/vite.config.ts +++ b/packages/procaptcha-bundle/vite.config.ts @@ -15,13 +15,13 @@ import { randomBytes } from "node:crypto"; import * as fs from "node:fs"; import * as path from "node:path"; +import { BACKEND_ERROR_KEYS_ARRAY } from "@prosopo/common"; import { ViteFrontendConfig, VitePluginRemoveUnusedTranslations, } from "@prosopo/config"; import { loadEnv } from "@prosopo/dotenv"; import { at, flatten } from "@prosopo/util"; -import { BACKEND_ERROR_KEYS_ARRAY } from "@prosopo/common"; import fg from "fast-glob"; import { defineConfig } from "vite"; From b2a204517e0e5b86f8761b01cc0e2925a63b3f31 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 12 Jun 2026 00:11:48 +0100 Subject: [PATCH 13/18] fix(ci): sync tsconfig refs/deps, drop demo logger option, expect error keys in integration tests - Sync package.json deps with tsconfig references (lint:refs): remove unused @prosopo/locale from api-express-router; add @prosopo/logger devDep to common; add @prosopo/common to procaptcha-bundle (+ lockfile) - Remove obsolete logger option from client-example-server demo error - Integration tests now assert the locale-stable error key (decoupled errors return the translation key, not the translated message) --- demos/client-example-server/src/app.ts | 1 - package-lock.json | 9 ++++----- packages/api-express-router/package.json | 1 - packages/api-express-router/tsconfig.cjs.json | 3 --- packages/api-express-router/tsconfig.json | 3 --- packages/common/package.json | 1 + packages/procaptcha-bundle/package.json | 1 + packages/procaptcha-bundle/tsconfig.cjs.json | 3 +++ packages/procaptcha-bundle/tsconfig.json | 3 +++ .../tests/integration/imgCaptcha.integration.test.ts | 12 ++++++------ .../tests/integration/powCaptcha.integration.test.ts | 8 ++++---- 11 files changed, 22 insertions(+), 23 deletions(-) diff --git a/demos/client-example-server/src/app.ts b/demos/client-example-server/src/app.ts index 34a9323c2f..c364782637 100644 --- a/demos/client-example-server/src/app.ts +++ b/demos/client-example-server/src/app.ts @@ -101,7 +101,6 @@ async function main() { if (!process.env.PROSOPO_SITE_PRIVATE_KEY) { const mnemonicError = new ProsopoEnvError("GENERAL.MNEMONIC_UNDEFINED", { context: { missingParams: ["PROSOPO_SITE_PRIVATE_KEY"] }, - logger, }); logger.error(() => ({ err: mnemonicError })); diff --git a/package-lock.json b/package-lock.json index 742ea4acb7..e20c740036 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@prosopo/captcha", - "version": "3.6.35", + "version": "3.6.36", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@prosopo/captcha", - "version": "3.6.35", + "version": "3.6.36", "license": "Apache-2.0", "workspaces": [ "dev/*", @@ -35548,7 +35548,6 @@ "@prosopo/api-route": "2.6.46", "@prosopo/common": "3.1.38", "@prosopo/env": "3.5.10", - "@prosopo/locale": "3.2.4", "@prosopo/logger": "1.0.2", "@prosopo/types": "4.4.0", "@prosopo/util-crypto": "13.5.29", @@ -35701,12 +35700,11 @@ "license": "Apache-2.0", "dependencies": { "@prosopo/locale": "3.2.4", - "@prosopo/logger": "1.0.2", - "i18next": "24.1.0", "zod": "3.23.8" }, "devDependencies": { "@prosopo/config": "3.3.1", + "@prosopo/logger": "1.0.2", "@prosopo/types": "4.4.0", "@types/node": "22.10.2", "@vitest/coverage-v8": "3.2.4", @@ -36522,6 +36520,7 @@ "dependencies": { "@emotion/cache": "11.11.0", "@emotion/react": "11.11.1", + "@prosopo/common": "3.1.38", "@prosopo/dotenv": "3.0.43", "@prosopo/locale": "3.2.4", "@prosopo/procaptcha-common": "2.10.19", diff --git a/packages/api-express-router/package.json b/packages/api-express-router/package.json index 292f3e5abb..7c431dbac6 100644 --- a/packages/api-express-router/package.json +++ b/packages/api-express-router/package.json @@ -30,7 +30,6 @@ "@prosopo/common": "3.1.38", "@prosopo/logger": "1.0.2", "@prosopo/env": "3.5.10", - "@prosopo/locale": "3.2.4", "@prosopo/types": "4.4.0", "@prosopo/util-crypto": "13.5.29", "dotenv": "16.4.5", diff --git a/packages/api-express-router/tsconfig.cjs.json b/packages/api-express-router/tsconfig.cjs.json index 4a645e16f7..34a2c59d03 100644 --- a/packages/api-express-router/tsconfig.cjs.json +++ b/packages/api-express-router/tsconfig.cjs.json @@ -21,9 +21,6 @@ { "path": "../env/tsconfig.cjs.json" }, - { - "path": "../locale/tsconfig.cjs.json" - }, { "path": "../types/tsconfig.cjs.json" }, diff --git a/packages/api-express-router/tsconfig.json b/packages/api-express-router/tsconfig.json index e6bd4c9d60..6261eae902 100644 --- a/packages/api-express-router/tsconfig.json +++ b/packages/api-express-router/tsconfig.json @@ -28,9 +28,6 @@ { "path": "../env" }, - { - "path": "../locale" - }, { "path": "../types" }, diff --git a/packages/common/package.json b/packages/common/package.json index f5d322ee85..e2cb604790 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -38,6 +38,7 @@ }, "devDependencies": { "@prosopo/config": "3.3.1", + "@prosopo/logger": "1.0.2", "@types/node": "22.10.2", "@prosopo/types": "4.4.0", "@vitest/coverage-v8": "3.2.4", diff --git a/packages/procaptcha-bundle/package.json b/packages/procaptcha-bundle/package.json index c5019ab15e..3562bcc228 100644 --- a/packages/procaptcha-bundle/package.json +++ b/packages/procaptcha-bundle/package.json @@ -39,6 +39,7 @@ }, "browserslist": ["> 0.5%, last 2 versions, not dead"], "dependencies": { + "@prosopo/common": "3.1.38", "@prosopo/dotenv": "3.0.43", "@prosopo/locale": "3.2.4", "@prosopo/procaptcha-common": "2.10.19", diff --git a/packages/procaptcha-bundle/tsconfig.cjs.json b/packages/procaptcha-bundle/tsconfig.cjs.json index 8e6144723c..be8081e860 100644 --- a/packages/procaptcha-bundle/tsconfig.cjs.json +++ b/packages/procaptcha-bundle/tsconfig.cjs.json @@ -10,6 +10,9 @@ { "path": "../../dev/config/tsconfig.cjs.json" }, + { + "path": "../common/tsconfig.cjs.json" + }, { "path": "../dotenv/tsconfig.cjs.json" }, diff --git a/packages/procaptcha-bundle/tsconfig.json b/packages/procaptcha-bundle/tsconfig.json index e557f55d25..202e89cf17 100644 --- a/packages/procaptcha-bundle/tsconfig.json +++ b/packages/procaptcha-bundle/tsconfig.json @@ -17,6 +17,9 @@ { "path": "../../dev/config/tsconfig.json" }, + { + "path": "../common" + }, { "path": "../dotenv" }, diff --git a/packages/provider/src/tests/integration/imgCaptcha.integration.test.ts b/packages/provider/src/tests/integration/imgCaptcha.integration.test.ts index 593d4de163..357c11b7da 100644 --- a/packages/provider/src/tests/integration/imgCaptcha.integration.test.ts +++ b/packages/provider/src/tests/integration/imgCaptcha.integration.test.ts @@ -370,7 +370,7 @@ describe("Image Captcha Integration Tests", () => { expect(response.status).toBe(400); const data = (await response.json()) as CaptchaResponseBody; expect(data).toHaveProperty("error"); - expect(data.error?.message).toBe("Site key not registered"); + expect(data.error?.message).toBe("API.SITE_KEY_NOT_REGISTERED"); }); it("should not supply an image captcha challenge to a Dapp User if an invalid site key is provided", async () => { @@ -397,7 +397,7 @@ describe("Image Captcha Integration Tests", () => { const data = (await response.json()) as CaptchaResponseBody; expect(response.status).toBe(400); expect(data).toHaveProperty("error"); - expect(data.error?.message).toBe("Invalid site key"); + expect(data.error?.message).toBe("API.INVALID_SITE_KEY"); }); it("should fail if datasetID is incorrect", async () => { @@ -446,10 +446,10 @@ describe("Image Captcha Integration Tests", () => { expect(response.status).toBe(400); const data = (await response.json()) as CaptchaResponseBody; expect(data).toHaveProperty("error"); - expect(data.error?.message).toBe("Incorrect CAPTCHA type"); + expect(data.error?.message).toBe("API.INCORRECT_CAPTCHA_TYPE"); expect(data.error?.code).toBe(400); }); - it("should return a translated error if the captcha type is set to pow and the language is set to es", async () => { + it("should return the locale-stable error key regardless of Accept-Language (es)", async () => { const origin = "https://localhost"; const getImageCaptchaURL = `${baseUrl}${ClientApiPaths.GetImageCaptchaChallenge}`; await registerSiteKeyInDb(env, dappAccount, CaptchaType.pow); @@ -473,7 +473,7 @@ describe("Image Captcha Integration Tests", () => { expect(response.status).toBe(400); const data = (await response.json()) as CaptchaResponseBody; expect(data).toHaveProperty("error"); - expect(data.error?.message).toBe("Tipo de CAPTCHA incorrecto"); + expect(data.error?.message).toBe("API.INCORRECT_CAPTCHA_TYPE"); expect(data.error?.code).toBe(400); }); }); @@ -500,7 +500,7 @@ describe("Image Captcha Integration Tests", () => { expect(response.status).toBe(400); const data = (await response.json()) as CaptchaResponseBody; expect(data).toHaveProperty("error"); - expect(data.error?.message).toBe("Incorrect CAPTCHA type"); + expect(data.error?.message).toBe("API.INCORRECT_CAPTCHA_TYPE"); expect(data.error?.code).toBe(400); }); diff --git a/packages/provider/src/tests/integration/powCaptcha.integration.test.ts b/packages/provider/src/tests/integration/powCaptcha.integration.test.ts index a8c62fde00..44539f505d 100644 --- a/packages/provider/src/tests/integration/powCaptcha.integration.test.ts +++ b/packages/provider/src/tests/integration/powCaptcha.integration.test.ts @@ -596,7 +596,7 @@ describe("PoW Integration Tests", () => { const data = (await captchaRes.json()) as GetPowCaptchaResponse; expect(data).toHaveProperty("error"); - expect(data.error?.message).toBe("Site key not registered"); + expect(data.error?.message).toBe("API.SITE_KEY_NOT_REGISTERED"); }); }); @@ -619,7 +619,7 @@ describe("PoW Integration Tests", () => { const challengeBody = (await captchaRes.json()) as GetPowCaptchaResponse; expect(challengeBody).toHaveProperty("error"); - expect(challengeBody.error?.message).toBe("Invalid site key"); + expect(challengeBody.error?.message).toBe("API.INVALID_SITE_KEY"); }); it("should return an error if the captcha type is set to image", async () => { @@ -645,7 +645,7 @@ describe("PoW Integration Tests", () => { const challengeBody = (await captchaRes.json()) as GetPowCaptchaResponse; expect(challengeBody).toHaveProperty("error"); - expect(challengeBody.error?.message).toBe("Incorrect CAPTCHA type"); + expect(challengeBody.error?.message).toBe("API.INCORRECT_CAPTCHA_TYPE"); expect(challengeBody.error?.code).toBe(400); }); it("should return an error if the captcha type is set to frictionless and no sessionID is sent", async () => { @@ -671,7 +671,7 @@ describe("PoW Integration Tests", () => { const challengeBody = (await captchaRes.json()) as GetPowCaptchaResponse; expect(challengeBody).toHaveProperty("error"); - expect(challengeBody.error?.message).toBe("Incorrect CAPTCHA type"); + expect(challengeBody.error?.message).toBe("API.INCORRECT_CAPTCHA_TYPE"); expect(challengeBody.error?.code).toBe(400); }); }); From b46663665aeaacb896646f1d53febab07f95a897 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 12 Jun 2026 00:21:36 +0100 Subject: [PATCH 14/18] fix(provider-mock): drop obsolete logLevel error option --- demos/provider-mock/src/api.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/demos/provider-mock/src/api.ts b/demos/provider-mock/src/api.ts index 81b8f0afa3..eb73a1abb2 100644 --- a/demos/provider-mock/src/api.ts +++ b/demos/provider-mock/src/api.ts @@ -53,7 +53,6 @@ export function prosopoRouter(): Router { return next( new ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { error: err, code: 400 }, - logLevel: "info", }), ); } From 6e26e966cdcc1ea9474aef18e2bc24f264f0a92f Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 12 Jun 2026 08:09:43 +0100 Subject: [PATCH 15/18] refactor(common): ProsopoApiError accepts TranslationKey only, not Error Production code never constructs an ApiError from a raw Error; doing so leaked the internal error message to the HTTP response with no client key. Restricting the first arg to TranslationKey enforces that every API error carries an intentional, localizable key. Error chaining still flows through context.error, which unwrapError walks. Base ProsopoBaseError keeps Error | TranslationKey for internal/CLI errors that wrap raw Errors. --- .../src/tests/unit/errorHandler.unit.test.ts | 8 ++++++-- packages/common/src/error.ts | 7 ++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts b/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts index afca012e9c..6ac6db0ad5 100644 --- a/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts +++ b/packages/api-express-router/src/tests/unit/errorHandler.unit.test.ts @@ -124,8 +124,12 @@ describe("handleErrors", () => { } as unknown as Response; const mockNext = vi.fn() as unknown as NextFunction; - const envError = new ProsopoEnvError("GENERAL.ENVIRONMENT_NOT_READY"); - const apiError = new ProsopoApiError(envError); + const envError = new ProsopoEnvError("GENERAL.ENVIRONMENT_NOT_READY", { + context: { code: 500 }, + }); + const apiError = new ProsopoApiError("API.UNKNOWN", { + context: { error: envError }, + }); handleErrors(apiError, mockRequest, mockResponse, mockNext); diff --git a/packages/common/src/error.ts b/packages/common/src/error.ts index c60d7fee2b..4d30af04e9 100644 --- a/packages/common/src/error.ts +++ b/packages/common/src/error.ts @@ -86,7 +86,7 @@ export class ProsopoApiError extends ProsopoBaseError { code: number; constructor( - error: Error | TranslationKey, + translationKey: TranslationKey, options?: BaseErrorOptions, ) { const code = options?.context?.code || 500; @@ -95,12 +95,9 @@ export class ProsopoApiError extends ProsopoBaseError { context: { ...options?.context, code, - ...(error instanceof ProsopoBaseError && error.translationKey - ? { translationKey: error.translationKey } - : {}), }, }; - super(error, optionsAll); + super(translationKey, optionsAll); this.code = code; } } From 04a55e71ef1314d4ed6ef4626121aaa4f9c0978a Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 12 Jun 2026 08:59:51 +0100 Subject: [PATCH 16/18] =?UTF-8?q?refactor(common):=20uniform=20error=20mod?= =?UTF-8?q?el=20=E2=80=94=20required=20TranslationKey=20+=20optional=20cau?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every Prosopo*Error now takes a required TranslationKey as its first arg and an optional causing Error via options.cause (its message becomes the fallback message). Removes the mutually-exclusive Error|TranslationKey first arg. - Base constructor + BaseErrorOptions reworked (drop options.translationKey, add options.cause) - Migrated datasets-fs (16 sites) to key-first + message, keeping their existing FS.*/DATASET.* keys - env getters and authMiddleware migrated; keyless internal errors use the new GENERAL.UNKNOWN placeholder with detail in options.message - Added GENERAL.UNKNOWN to all 32 locales - Updated common error tests for the new model --- .changeset/decouple-error-i18n.md | 2 + .../src/middlewares/authMiddleware.ts | 4 +- packages/common/src/error.ts | 23 +++++----- packages/common/src/tests/error.unit.test.ts | 22 +++++---- packages/datasets-fs/src/commands/generate.ts | 18 +++----- .../datasets-fs/src/commands/generateV1.ts | 45 +++++++------------ .../datasets-fs/src/commands/generateV2.ts | 31 +++++-------- packages/datasets-fs/src/commands/labels.ts | 4 +- packages/datasets-fs/src/commands/resize.ts | 18 +++----- packages/datasets-fs/src/utils/input.ts | 9 ++-- packages/datasets-fs/src/utils/output.ts | 9 ++-- packages/env/src/env.ts | 18 ++++---- .../locale/src/locales/ar/translation.json | 3 +- .../locale/src/locales/az/translation.json | 3 +- .../locale/src/locales/cs/translation.json | 3 +- .../locale/src/locales/de/translation.json | 3 +- .../locale/src/locales/el/translation.json | 3 +- .../locale/src/locales/en/translation.json | 1 + .../locale/src/locales/es/translation.json | 3 +- .../locale/src/locales/fi/translation.json | 3 +- .../locale/src/locales/fr/translation.json | 3 +- .../locale/src/locales/hi/translation.json | 3 +- .../locale/src/locales/hu/translation.json | 3 +- .../locale/src/locales/id/translation.json | 3 +- .../locale/src/locales/it/translation.json | 3 +- .../locale/src/locales/ja/translation.json | 3 +- .../locale/src/locales/jv/translation.json | 3 +- .../locale/src/locales/ko/translation.json | 3 +- .../locale/src/locales/ml/translation.json | 3 +- .../locale/src/locales/ms/translation.json | 3 +- .../locale/src/locales/nl/translation.json | 3 +- .../locale/src/locales/no/translation.json | 3 +- .../locale/src/locales/pl/translation.json | 3 +- .../locale/src/locales/pt-BR/translation.json | 3 +- .../locale/src/locales/pt/translation.json | 3 +- .../locale/src/locales/ro/translation.json | 3 +- .../locale/src/locales/ru/translation.json | 3 +- .../locale/src/locales/sr/translation.json | 3 +- .../locale/src/locales/sv/translation.json | 3 +- .../locale/src/locales/th/translation.json | 3 +- .../locale/src/locales/tr/translation.json | 3 +- .../locale/src/locales/uk/translation.json | 3 +- .../locale/src/locales/vi/translation.json | 3 +- .../locale/src/locales/zh-CN/translation.json | 3 +- 44 files changed, 141 insertions(+), 156 deletions(-) diff --git a/.changeset/decouple-error-i18n.md b/.changeset/decouple-error-i18n.md index a65ee0ce3b..568f4dc2f5 100644 --- a/.changeset/decouple-error-i18n.md +++ b/.changeset/decouple-error-i18n.md @@ -6,6 +6,7 @@ "@prosopo/types-database": patch "@prosopo/procaptcha-pow": patch "@prosopo/database": patch +"@prosopo/datasets-fs": patch "@prosopo/provider": patch "@prosopo/common": patch "@prosopo/locale": patch @@ -19,3 +20,4 @@ Decouple error classes from i18n and logging, and move translations into a conve - Removed the `i18n`, `logger` and `logLevel` constructor options from the error classes; callers log explicitly via their own logger. - Error keys are validated against the translation files at compile time (`TranslationKey`), and the curated backend error-key registry (`BACKEND_ERROR_KEYS_ARRAY`) is preserved in the frontend bundle. - Added the translation keys referenced by backend errors to every locale so the locale key sets stay in sync. +- Every error class now takes a required `TranslationKey` as its first argument and an optional causing `Error` via `options.cause` (whose message becomes the fallback). `ProsopoApiError` no longer accepts a raw `Error`. Internal/CLI errors that have no user-facing key use the `GENERAL.UNKNOWN` placeholder with the detail carried in `options.message`. diff --git a/packages/api-express-router/src/middlewares/authMiddleware.ts b/packages/api-express-router/src/middlewares/authMiddleware.ts index 7116902177..5f1f1f0f32 100644 --- a/packages/api-express-router/src/middlewares/authMiddleware.ts +++ b/packages/api-express-router/src/middlewares/authMiddleware.ts @@ -26,8 +26,6 @@ export const authMiddleware = ( try { const jwt = extractJWT(req); - let error: ProsopoApiError | undefined; - if (authAccount?.jwtVerify(jwt).isValid) { next(); return; @@ -39,7 +37,7 @@ export const authMiddleware = ( } res.status(401).json({ - error: new ProsopoEnvError(error || "API.UNAUTHORIZED", { + error: new ProsopoEnvError("API.UNAUTHORIZED", { context: { code: 401 }, }), }); diff --git a/packages/common/src/error.ts b/packages/common/src/error.ts index 4d30af04e9..541f3b8bc1 100644 --- a/packages/common/src/error.ts +++ b/packages/common/src/error.ts @@ -18,8 +18,10 @@ import { ZodError } from "zod"; type BaseErrorOptions = { name?: string; - translationKey?: TranslationKey; message?: string; + // The underlying error that caused this one, if any. Its message is used as + // the fallback `message` when no explicit `message` is provided. + cause?: Error; context?: ContextType; }; @@ -48,20 +50,15 @@ export abstract class ProsopoBaseError< cause: Error | undefined; constructor( - error: Error | TranslationKey, + translationKey: TranslationKey, options?: BaseErrorOptions, ) { - if (error instanceof Error) { - super(error.message); - this.cause = error; - this.translationKey = options?.translationKey; - this.message = options?.message || error.message; - } else { - const fallback = options?.message || error; - super(fallback); - this.translationKey = error; - this.message = fallback; - } + const message = + options?.message ?? options?.cause?.message ?? translationKey; + super(message); + this.translationKey = translationKey; + this.message = message; + this.cause = options?.cause; this.context = options?.context; this.name = options?.name || this.constructor.name; } diff --git a/packages/common/src/tests/error.unit.test.ts b/packages/common/src/tests/error.unit.test.ts index 8aab430f49..708e285711 100644 --- a/packages/common/src/tests/error.unit.test.ts +++ b/packages/common/src/tests/error.unit.test.ts @@ -39,10 +39,10 @@ describe("ProsopoBaseError construction is decoupled from i18n and logging", () expect(err.message).toBe("Invalid site key"); }); - it("wraps an underlying Error, preserving its message as the cause", () => { + it("keeps the causing Error as `cause` and uses its message as the fallback", () => { const inner = new Error("kaboom"); - const err = new ProsopoEnvError(inner, { - translationKey: "API.UNKNOWN", + const err = new ProsopoEnvError("API.UNKNOWN", { + cause: inner, context: { code: 500 }, }); @@ -51,10 +51,12 @@ describe("ProsopoBaseError construction is decoupled from i18n and logging", () expect(err.translationKey).toBe("API.UNKNOWN"); }); - it("falls back to the wrapped Error message when no translation key is given", () => { - const err = new ProsopoEnvError(new Error("raw failure")); + it("falls back to the cause's message while keeping a placeholder key", () => { + const err = new ProsopoEnvError("GENERAL.UNKNOWN", { + cause: new Error("raw failure"), + }); - expect(err.translationKey).toBeUndefined(); + expect(err.translationKey).toBe("GENERAL.UNKNOWN"); expect(err.message).toBe("raw failure"); }); @@ -118,12 +120,8 @@ describe("unwrapError produces a JSON response carrying the translation key", () expect(code).toBe(400); }); - it("defaults the key to API.UNKNOWN when the error carries no translation key", () => { - const err = new ProsopoEnvError(new Error("raw failure"), { - context: { code: 500 }, - }); - - const { jsonError } = unwrapError(err); + it("defaults the key to API.UNKNOWN for a non-Prosopo error that has no translation key", () => { + const { jsonError } = unwrapError(new SyntaxError("raw failure")); expect(jsonError.key).toBe("API.UNKNOWN"); expect(jsonError.message).toBe("raw failure"); }); diff --git a/packages/datasets-fs/src/commands/generate.ts b/packages/datasets-fs/src/commands/generate.ts index 86c17aea66..06c46d29e7 100644 --- a/packages/datasets-fs/src/commands/generate.ts +++ b/packages/datasets-fs/src/commands/generate.ts @@ -90,21 +90,15 @@ export abstract class Generate< // if specified, check files exist const labelledMapFile: string | undefined = args.labelled; if (labelledMapFile && !fs.existsSync(labelledMapFile)) { - throw new ProsopoDatasetError( - new Error(`labelled map file does not exist: ${labelledMapFile}`), - { - translationKey: "FS.FILE_NOT_FOUND", - }, - ); + throw new ProsopoDatasetError("FS.FILE_NOT_FOUND", { + message: `labelled map file does not exist: ${labelledMapFile}`, + }); } const unlabelledMapFile: string | undefined = args.unlabelled; if (unlabelledMapFile && !fs.existsSync(unlabelledMapFile)) { - throw new ProsopoDatasetError( - new Error(`unlabelled map file does not exist: ${unlabelledMapFile}`), - { - translationKey: "FS.FILE_NOT_FOUND", - }, - ); + throw new ProsopoDatasetError("FS.FILE_NOT_FOUND", { + message: `unlabelled map file does not exist: ${unlabelledMapFile}`, + }); } this.labelledMapFile = labelledMapFile || ""; this.unlabelledMapFile = unlabelledMapFile || ""; diff --git a/packages/datasets-fs/src/commands/generateV1.ts b/packages/datasets-fs/src/commands/generateV1.ts index a01b16db2b..595b552cd1 100644 --- a/packages/datasets-fs/src/commands/generateV1.ts +++ b/packages/datasets-fs/src/commands/generateV1.ts @@ -84,12 +84,9 @@ export class GenerateV1 extends Generate { bar.increment(); if (this.targets.length <= 1) { - throw new ProsopoDatasetError( - new Error("not enough different labels in labelled data"), - { - translationKey: "DATASET.NOT_ENOUGH_LABELS", - }, - ); + throw new ProsopoDatasetError("DATASET.NOT_ENOUGH_LABELS", { + message: "not enough different labels in labelled data", + }); } // uniformly sample targets @@ -107,20 +104,14 @@ export class GenerateV1 extends Generate { ); if (targetItems.length < nCorrect) { - throw new ProsopoEnvError( - new Error(`not enough images for target (${target})`), - { - translationKey: "DATASET.NOT_ENOUGH_IMAGES", - }, - ); + throw new ProsopoEnvError("DATASET.NOT_ENOUGH_IMAGES", { + message: `not enough images for target (${target})`, + }); } if (notTargetItems.length < nIncorrect) { - throw new ProsopoDatasetError( - new Error(`not enough non-matching images for target (${target})`), - { - translationKey: "DATASET.NOT_ENOUGH_IMAGES", - }, - ); + throw new ProsopoDatasetError("DATASET.NOT_ENOUGH_IMAGES", { + message: `not enough non-matching images for target (${target})`, + }); } // get the correct items @@ -180,22 +171,16 @@ export class GenerateV1 extends Generate { for (let i = 0; i < unsolved; i++) { bar.increment(); if (this.unlabelled.length <= size) { - throw new ProsopoDatasetError( - new Error("unlabelled map file does not contain enough data"), - { - translationKey: "DATASET.NOT_ENOUGH_IMAGES", - }, - ); + throw new ProsopoDatasetError("DATASET.NOT_ENOUGH_IMAGES", { + message: "unlabelled map file does not contain enough data", + }); } // pick a random label to be the target // note that these are potentially different to the labelled data labels if (this.labels.length <= 0) { - throw new ProsopoDatasetError( - new Error("no labels found for unlabelled data"), - { - translationKey: "DATASET.NOT_ENOUGH_LABELS", - }, - ); + throw new ProsopoDatasetError("DATASET.NOT_ENOUGH_LABELS", { + message: "no labels found for unlabelled data", + }); } const index = _.random(0, this.labels.length - 1); const target = at(this.labels, index); diff --git a/packages/datasets-fs/src/commands/generateV2.ts b/packages/datasets-fs/src/commands/generateV2.ts index 8eb2025a97..a9ec62db07 100644 --- a/packages/datasets-fs/src/commands/generateV2.ts +++ b/packages/datasets-fs/src/commands/generateV2.ts @@ -95,12 +95,9 @@ export class GenerateV2 extends Generate { private setupTarget(i: number) { const _ = lodash(); if (this.targets.length <= 1) { - throw new ProsopoDatasetError( - new Error("not enough different labels in labelled data"), - { - translationKey: "DATASET.NOT_ENOUGH_LABELS", - }, - ); + throw new ProsopoDatasetError("DATASET.NOT_ENOUGH_LABELS", { + message: "not enough different labels in labelled data", + }); } // uniformly sample targets @@ -122,25 +119,19 @@ export class GenerateV2 extends Generate { ); if (this.unlabelled.length > 0 && nUnlabelled > this.unlabelled.length) { - throw new ProsopoDatasetError(new Error("not enough unlabelled data"), { - translationKey: "DATASET.NOT_ENOUGH_IMAGES", + throw new ProsopoDatasetError("DATASET.NOT_ENOUGH_IMAGES", { + message: "not enough unlabelled data", }); } if (nCorrect > targetItems.length) { - throw new ProsopoDatasetError( - new Error(`not enough images for target (${target})`), - { - translationKey: "DATASET.NOT_ENOUGH_IMAGES", - }, - ); + throw new ProsopoDatasetError("DATASET.NOT_ENOUGH_IMAGES", { + message: `not enough images for target (${target})`, + }); } if (nIncorrect > notTargetItems.length) { - throw new ProsopoDatasetError( - new Error(`not enough non-matching images for target (${target})`), - { - translationKey: "DATASET.NOT_ENOUGH_IMAGES", - }, - ); + throw new ProsopoDatasetError("DATASET.NOT_ENOUGH_IMAGES", { + message: `not enough non-matching images for target (${target})`, + }); } this.#nCorrect = nCorrect; diff --git a/packages/datasets-fs/src/commands/labels.ts b/packages/datasets-fs/src/commands/labels.ts index 6d2130114f..046be109a2 100644 --- a/packages/datasets-fs/src/commands/labels.ts +++ b/packages/datasets-fs/src/commands/labels.ts @@ -46,8 +46,8 @@ export class Labels extends InputOutputCliCommand { const file = args.input; if (!fs.existsSync(file)) { - throw new ProsopoDatasetError(new Error(`file does not exist: ${file}`), { - translationKey: "FS.FILE_NOT_FOUND", + throw new ProsopoDatasetError("FS.FILE_NOT_FOUND", { + message: `file does not exist: ${file}`, }); } diff --git a/packages/datasets-fs/src/commands/resize.ts b/packages/datasets-fs/src/commands/resize.ts index 73cdb0bb3f..8e95cef862 100644 --- a/packages/datasets-fs/src/commands/resize.ts +++ b/packages/datasets-fs/src/commands/resize.ts @@ -68,22 +68,16 @@ export class Resize extends InputOutputCliCommand { const mapFile: string = args.input; if (!fs.existsSync(mapFile)) { - throw new ProsopoDatasetError( - new Error(`Map file does not exist: ${mapFile}`), - { - translationKey: "FS.FILE_NOT_FOUND", - }, - ); + throw new ProsopoDatasetError("FS.FILE_NOT_FOUND", { + message: `Map file does not exist: ${mapFile}`, + }); } const outDir: string = args.output; const overwrite = args.overwrite || false; if (!overwrite && fs.existsSync(outDir)) { - throw new ProsopoEnvError( - new Error(`Output directory already exists: ${outDir}`), - { - translationKey: "FS.FILE_NOT_FOUND", - }, - ); + throw new ProsopoEnvError("FS.FILE_NOT_FOUND", { + message: `Output directory already exists: ${outDir}`, + }); } // create the output directory diff --git a/packages/datasets-fs/src/utils/input.ts b/packages/datasets-fs/src/utils/input.ts index 64ed4094ee..2a03532716 100644 --- a/packages/datasets-fs/src/utils/input.ts +++ b/packages/datasets-fs/src/utils/input.ts @@ -47,12 +47,9 @@ export class InputCliCommand< await super._check(args); // input must exist if (!fs.existsSync(args.input)) { - throw new ProsopoDatasetError( - new Error(`input path does not exist: ${args.input}`), - { - translationKey: "FS.FILE_NOT_FOUND", - }, - ); + throw new ProsopoDatasetError("FS.FILE_NOT_FOUND", { + message: `input path does not exist: ${args.input}`, + }); } } } diff --git a/packages/datasets-fs/src/utils/output.ts b/packages/datasets-fs/src/utils/output.ts index 051746ec39..2de8f22a03 100644 --- a/packages/datasets-fs/src/utils/output.ts +++ b/packages/datasets-fs/src/utils/output.ts @@ -64,12 +64,9 @@ export class OutputCliCommand< // output must not exist, unless overwrite is true if (this.outputExists()) { if (!args.overwrite) { - throw new ProsopoEnvError( - new Error(`output path already exists: ${args.output}`), - { - translationKey: "FS.FILE_ALREADY_EXISTS", - }, - ); + throw new ProsopoEnvError("FS.FILE_ALREADY_EXISTS", { + message: `output path already exists: ${args.output}`, + }); } } } diff --git a/packages/env/src/env.ts b/packages/env/src/env.ts index 4cef033630..e2e5ee645e 100644 --- a/packages/env/src/env.ts +++ b/packages/env/src/env.ts @@ -97,27 +97,27 @@ export class Environment implements ProsopoEnvironment { getDb(): ProviderDatabase { if (this.db === undefined) { - throw new ProsopoEnvError( - new Error("db not setup! Please call isReady() first"), - ); + throw new ProsopoEnvError("GENERAL.UNKNOWN", { + message: "db not setup! Please call isReady() first", + }); } return this.db; } getAssetsResolver(): AssetsResolver { if (this.assetsResolver === undefined) { - throw new ProsopoEnvError( - new Error("assetsResolver not setup! Please call isReady() first"), - ); + throw new ProsopoEnvError("GENERAL.UNKNOWN", { + message: "assetsResolver not setup! Please call isReady() first", + }); } return this.assetsResolver; } getPair(): KeyringPair { if (this.pair === undefined) { - throw new ProsopoEnvError( - new Error("pair not setup! Please call isReady() first"), - ); + throw new ProsopoEnvError("GENERAL.UNKNOWN", { + message: "pair not setup! Please call isReady() first", + }); } return this.pair; } diff --git a/packages/locale/src/locales/ar/translation.json b/packages/locale/src/locales/ar/translation.json index 86b082eff8..a3ada4b153 100644 --- a/packages/locale/src/locales/ar/translation.json +++ b/packages/locale/src/locales/ar/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "صلاحيات غير كافية لأداء هذا الإجراء", diff --git a/packages/locale/src/locales/az/translation.json b/packages/locale/src/locales/az/translation.json index 736aca714c..80539d044c 100644 --- a/packages/locale/src/locales/az/translation.json +++ b/packages/locale/src/locales/az/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Bu əməliyyatı yerinə yetirmək üçün kifayət qədər icazə yoxdur", diff --git a/packages/locale/src/locales/cs/translation.json b/packages/locale/src/locales/cs/translation.json index 06da7e7aaa..d83e5ac6ce 100644 --- a/packages/locale/src/locales/cs/translation.json +++ b/packages/locale/src/locales/cs/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Nedostatečná oprávnění k provedení této akce", diff --git a/packages/locale/src/locales/de/translation.json b/packages/locale/src/locales/de/translation.json index c85fc6af11..559dbbb631 100644 --- a/packages/locale/src/locales/de/translation.json +++ b/packages/locale/src/locales/de/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Unzureichende Berechtigungen zur Ausführung dieser Aktion", diff --git a/packages/locale/src/locales/el/translation.json b/packages/locale/src/locales/el/translation.json index 98ae811215..7151c428b4 100644 --- a/packages/locale/src/locales/el/translation.json +++ b/packages/locale/src/locales/el/translation.json @@ -361,7 +361,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Δεν είναι δυνατή η τροποποίηση του χρήστη-κατόχου", diff --git a/packages/locale/src/locales/en/translation.json b/packages/locale/src/locales/en/translation.json index 4c7deadd45..88cab62658 100644 --- a/packages/locale/src/locales/en/translation.json +++ b/packages/locale/src/locales/en/translation.json @@ -24,6 +24,7 @@ "SITE_KEY_MISSING": "SITE KEY missing", "ACCOUNT_NOT_FOUND": "Account not found", "SITE_KEY_NOT_FOUND": "Site key not found", + "UNKNOWN": "Unknown error", "INVALID_TIMESTAMP": "Invalid timestamp", "MISSING_SECRET_KEY": "Missing secret key", "BILLING_ERROR": "Billing error", diff --git a/packages/locale/src/locales/es/translation.json b/packages/locale/src/locales/es/translation.json index 5f98a9d2e0..04f810489c 100644 --- a/packages/locale/src/locales/es/translation.json +++ b/packages/locale/src/locales/es/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permisos insuficientes para realizar esta acción", diff --git a/packages/locale/src/locales/fi/translation.json b/packages/locale/src/locales/fi/translation.json index 2285628439..74f1ad19e4 100644 --- a/packages/locale/src/locales/fi/translation.json +++ b/packages/locale/src/locales/fi/translation.json @@ -101,7 +101,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "PORTAL": { "CANNOT_MODIFY_OWNER_USER": "Ei voida muokata omistajakäyttäjää", diff --git a/packages/locale/src/locales/fr/translation.json b/packages/locale/src/locales/fr/translation.json index a838f3f0c0..76f6085886 100644 --- a/packages/locale/src/locales/fr/translation.json +++ b/packages/locale/src/locales/fr/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permissions insuffisantes pour effectuer cette action", diff --git a/packages/locale/src/locales/hi/translation.json b/packages/locale/src/locales/hi/translation.json index 5249a8e769..9a616cf270 100644 --- a/packages/locale/src/locales/hi/translation.json +++ b/packages/locale/src/locales/hi/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "TARGET": { "pelecaniformes": "पलकनफरमस", diff --git a/packages/locale/src/locales/hu/translation.json b/packages/locale/src/locales/hu/translation.json index 39a27e57ea..3225ab4a67 100644 --- a/packages/locale/src/locales/hu/translation.json +++ b/packages/locale/src/locales/hu/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Nincs elegendő engedély a művelet végrehajtásához", diff --git a/packages/locale/src/locales/id/translation.json b/packages/locale/src/locales/id/translation.json index e2d7eeca40..fa6a1fb15c 100644 --- a/packages/locale/src/locales/id/translation.json +++ b/packages/locale/src/locales/id/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", diff --git a/packages/locale/src/locales/it/translation.json b/packages/locale/src/locales/it/translation.json index 9ffe073c56..247ec37702 100644 --- a/packages/locale/src/locales/it/translation.json +++ b/packages/locale/src/locales/it/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permissões insuficientes para realizar esta ação", diff --git a/packages/locale/src/locales/ja/translation.json b/packages/locale/src/locales/ja/translation.json index 92ebb32486..9689350700 100644 --- a/packages/locale/src/locales/ja/translation.json +++ b/packages/locale/src/locales/ja/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "TARGET": { "homer-simpson": "homer-simpson", diff --git a/packages/locale/src/locales/jv/translation.json b/packages/locale/src/locales/jv/translation.json index d0f98f3a61..14e7514f21 100644 --- a/packages/locale/src/locales/jv/translation.json +++ b/packages/locale/src/locales/jv/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", diff --git a/packages/locale/src/locales/ko/translation.json b/packages/locale/src/locales/ko/translation.json index fea37ecacc..90b0fc47b8 100644 --- a/packages/locale/src/locales/ko/translation.json +++ b/packages/locale/src/locales/ko/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "이 작업을 수행할 권한이 충분하지 않음", diff --git a/packages/locale/src/locales/ml/translation.json b/packages/locale/src/locales/ml/translation.json index cc15296609..aa25073e88 100644 --- a/packages/locale/src/locales/ml/translation.json +++ b/packages/locale/src/locales/ml/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficientway ermissionspay otay erformpay histay actionway", diff --git a/packages/locale/src/locales/ms/translation.json b/packages/locale/src/locales/ms/translation.json index da1f6b1816..6cba70cc54 100644 --- a/packages/locale/src/locales/ms/translation.json +++ b/packages/locale/src/locales/ms/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "TARGET": { "horseshoe-crab": "ladam-ketam", diff --git a/packages/locale/src/locales/nl/translation.json b/packages/locale/src/locales/nl/translation.json index 91dcd27ae1..f65e166b0c 100644 --- a/packages/locale/src/locales/nl/translation.json +++ b/packages/locale/src/locales/nl/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "TARGET": { "yo-yo": "jojo", diff --git a/packages/locale/src/locales/no/translation.json b/packages/locale/src/locales/no/translation.json index c30a0c80d3..b39f2310b6 100644 --- a/packages/locale/src/locales/no/translation.json +++ b/packages/locale/src/locales/no/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Utilstrekkelige tillatelser for å utføre denne handlingen", diff --git a/packages/locale/src/locales/pl/translation.json b/packages/locale/src/locales/pl/translation.json index 8a4f67d870..b447974ffb 100644 --- a/packages/locale/src/locales/pl/translation.json +++ b/packages/locale/src/locales/pl/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Niewystarczające uprawnienia do wykonania tej akcji", diff --git a/packages/locale/src/locales/pt-BR/translation.json b/packages/locale/src/locales/pt-BR/translation.json index 56f736ac7b..f346a76489 100644 --- a/packages/locale/src/locales/pt-BR/translation.json +++ b/packages/locale/src/locales/pt-BR/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permissões insuficientes para realizar esta ação", diff --git a/packages/locale/src/locales/pt/translation.json b/packages/locale/src/locales/pt/translation.json index 0e67f1e6d1..cbf9f1f5c9 100644 --- a/packages/locale/src/locales/pt/translation.json +++ b/packages/locale/src/locales/pt/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permissões insuficientes para realizar esta ação", diff --git a/packages/locale/src/locales/ro/translation.json b/packages/locale/src/locales/ro/translation.json index 51563bd284..6bb5f4229d 100644 --- a/packages/locale/src/locales/ro/translation.json +++ b/packages/locale/src/locales/ro/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Permisiuni insuficiente pentru a efectua aceasta actiune", diff --git a/packages/locale/src/locales/ru/translation.json b/packages/locale/src/locales/ru/translation.json index b1a2ff3411..3690e8a21d 100644 --- a/packages/locale/src/locales/ru/translation.json +++ b/packages/locale/src/locales/ru/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Недостаточно прав для выполнения этого действия", diff --git a/packages/locale/src/locales/sr/translation.json b/packages/locale/src/locales/sr/translation.json index afebe80a8f..da6cd263ef 100644 --- a/packages/locale/src/locales/sr/translation.json +++ b/packages/locale/src/locales/sr/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Недовољне дозволе за обављање ове радње", diff --git a/packages/locale/src/locales/sv/translation.json b/packages/locale/src/locales/sv/translation.json index f163a9d09f..fb3768981b 100644 --- a/packages/locale/src/locales/sv/translation.json +++ b/packages/locale/src/locales/sv/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Otillräckliga behörigheter för att utföra denna åtgärd", diff --git a/packages/locale/src/locales/th/translation.json b/packages/locale/src/locales/th/translation.json index 591da7d7df..36320988c9 100644 --- a/packages/locale/src/locales/th/translation.json +++ b/packages/locale/src/locales/th/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "สิทธิ์ไม่เพียงพอในการทำการกระทำนี้", diff --git a/packages/locale/src/locales/tr/translation.json b/packages/locale/src/locales/tr/translation.json index 023307de7f..2db43a0f68 100644 --- a/packages/locale/src/locales/tr/translation.json +++ b/packages/locale/src/locales/tr/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", diff --git a/packages/locale/src/locales/uk/translation.json b/packages/locale/src/locales/uk/translation.json index 7517d48254..1da11cd487 100644 --- a/packages/locale/src/locales/uk/translation.json +++ b/packages/locale/src/locales/uk/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Insufficient permissions to perform this action", diff --git a/packages/locale/src/locales/vi/translation.json b/packages/locale/src/locales/vi/translation.json index bd121e987b..07fa4c7d5c 100644 --- a/packages/locale/src/locales/vi/translation.json +++ b/packages/locale/src/locales/vi/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "Không đủ quyền để thực hiện hành động này", diff --git a/packages/locale/src/locales/zh-CN/translation.json b/packages/locale/src/locales/zh-CN/translation.json index ab1651bf96..cb6de16af3 100644 --- a/packages/locale/src/locales/zh-CN/translation.json +++ b/packages/locale/src/locales/zh-CN/translation.json @@ -15,7 +15,8 @@ "INVALID_JWT": "Invalid authentication token", "MISSING_AUTH_HEADER": "Missing authentication header", "OBJECT_COUNT_ERROR": "Unexpected object count", - "SECRET_MISSING": "Secret is missing" + "SECRET_MISSING": "Secret is missing", + "UNKNOWN": "Unknown error" }, "API": { "INSUFFICIENT_PERMISSIONS": "权限不足,无法执行此操作", From 46b2538c3d3aef93cf6f8178aa28f55cf1472708 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 12 Jun 2026 09:19:03 +0100 Subject: [PATCH 17/18] fix: address PR review comments - errorKeys: make _validateErrorKeysExistInTranslations a real compile-time check (assign BACKEND_ERROR_KEYS_ARRAY to readonly TranslationKey[]) instead of a no-op cast - loadI18next: only short-circuit once the instance is initialized (avoid handing back a half-ready i18n to concurrent callers); clear the cached promise on failure so initialization can be retried - errorHandler: guard request.logger?.error so a missing request-logger middleware doesn't throw and mask the original error - validateAddr: mark the now-unused logger param as _logger --- .../api-express-router/src/errorHandler.ts | 5 +- packages/common/src/errorKeys.ts | 13 +- packages/locale/src/loadI18next.ts | 126 ++++++++++-------- packages/provider/src/api/validateAddress.ts | 4 +- 4 files changed, 85 insertions(+), 63 deletions(-) diff --git a/packages/api-express-router/src/errorHandler.ts b/packages/api-express-router/src/errorHandler.ts index 89c73ca9bd..1aedb44204 100644 --- a/packages/api-express-router/src/errorHandler.ts +++ b/packages/api-express-router/src/errorHandler.ts @@ -24,7 +24,10 @@ export const handleErrors = ( response: Response, next: NextFunction, ) => { - request.logger.error(() => ({ err })); + // `request.logger` is populated by the request-logger middleware; guard the + // call so a missing logger (e.g. a minimal app without that middleware) + // doesn't throw and mask the original error. + request.logger?.error(() => ({ err })); const { code, statusMessage, jsonError } = unwrapError(err); response.statusMessage = statusMessage; response.set("content-type", "application/json"); diff --git a/packages/common/src/errorKeys.ts b/packages/common/src/errorKeys.ts index 2952073fa0..a654f2456a 100644 --- a/packages/common/src/errorKeys.ts +++ b/packages/common/src/errorKeys.ts @@ -78,10 +78,9 @@ export type ValidErrorKey = // Array of all keys for the Vite plugin export const BACKEND_ERROR_KEYS_ARRAY = Object.values(ALL_ERROR_KEYS); -// Type-level validation: ensure all error keys exist in translation.json -// Each ValidErrorKey must be assignable to TranslationKey (derived from translation JSON) -// If this errors, a key is defined here but missing from the translation file. -export const _validateErrorKeysExistInTranslations: Record< - ValidErrorKey, - unknown -> = {} as Record; +// Compile-time check: every backend error key must exist in the locale JSON. +// `BACKEND_ERROR_KEYS_ARRAY` is typed `ValidErrorKey[]`; assigning it to a +// `TranslationKey[]` fails to compile if any `ValidErrorKey` is not a +// `TranslationKey` (i.e. a key here is missing from the translation files). +export const _validateErrorKeysExistInTranslations: readonly TranslationKey[] = + BACKEND_ERROR_KEYS_ARRAY; diff --git a/packages/locale/src/loadI18next.ts b/packages/locale/src/loadI18next.ts index 57ce040e61..3ef8fd3132 100644 --- a/packages/locale/src/loadI18next.ts +++ b/packages/locale/src/loadI18next.ts @@ -23,36 +23,45 @@ let backendPromise: Promise | undefined; let backendInitialized = false; export async function loadI18nextFrontend(): Promise { - if (frontendInstance) return frontendInstance; + // Only short-circuit once the instance is fully initialized; while + // initialization is in flight, fall through to the shared promise so + // concurrent callers await readiness instead of a half-ready instance. + if (frontendInstance?.isInitialized) return frontendInstance; if (!frontendPromise) { - frontendPromise = import("./i18nFrontend.js").then( - ({ default: initializeI18n }) => - new Promise((resolve, reject) => { - try { - // Prevent race conditions: only initialize once - if (frontendInitialized) { - if (frontendInstance) return resolve(frontendInstance); - // Still initializing, wait for completion - return; - } - frontendInitialized = true; + frontendPromise = import("./i18nFrontend.js") + .then( + ({ default: initializeI18n }) => + new Promise((resolve, reject) => { + try { + // Prevent race conditions: only initialize once + if (frontendInitialized) { + if (frontendInstance) return resolve(frontendInstance); + // Still initializing, wait for completion + return; + } + frontendInitialized = true; - frontendInstance = initializeI18n((i18n) => { - frontendInstance = i18n; - resolve(i18n); - }); - // If init is synchronous and callback wasn't called, resolve with instance - if (frontendInstance && !frontendInstance.isInitialized) { - // Instance created but not yet initialized, let callback handle it - } else if (frontendInstance) { - resolve(frontendInstance); + frontendInstance = initializeI18n((i18n) => { + frontendInstance = i18n; + resolve(i18n); + }); + // If init was synchronous (already initialized), resolve now; + // otherwise the loaded callback above resolves it. + if (frontendInstance?.isInitialized) { + resolve(frontendInstance); + } + } catch (e) { + frontendInitialized = false; + reject(e); } - } catch (e) { - frontendInitialized = false; - reject(e); - } - }), - ); + }), + ) + .catch((e) => { + // Reset cached state so a future call can retry after a failure. + frontendPromise = undefined; + frontendInitialized = false; + throw e; + }); } return frontendPromise; } @@ -62,36 +71,45 @@ export function getFrontendI18n(): i18n | undefined { } export async function loadI18nextBackend(): Promise { - if (backendInstance) return backendInstance; + // Only short-circuit once the instance is fully initialized; while + // initialization is in flight, fall through to the shared promise so + // concurrent callers await readiness instead of a half-ready instance. + if (backendInstance?.isInitialized) return backendInstance; if (!backendPromise) { - backendPromise = import("./i18nBackend.js").then( - ({ default: initializeI18n }) => - new Promise((resolve, reject) => { - try { - // Prevent race conditions: only initialize once - if (backendInitialized) { - if (backendInstance) return resolve(backendInstance); - // Still initializing, wait for completion - return; - } - backendInitialized = true; + backendPromise = import("./i18nBackend.js") + .then( + ({ default: initializeI18n }) => + new Promise((resolve, reject) => { + try { + // Prevent race conditions: only initialize once + if (backendInitialized) { + if (backendInstance) return resolve(backendInstance); + // Still initializing, wait for completion + return; + } + backendInitialized = true; - backendInstance = initializeI18n((i18n) => { - backendInstance = i18n; - resolve(i18n); - }); - // If init is synchronous and callback wasn't called, resolve with instance - if (backendInstance && !backendInstance.isInitialized) { - // Instance created but not yet initialized, let callback handle it - } else if (backendInstance) { - resolve(backendInstance); + backendInstance = initializeI18n((i18n) => { + backendInstance = i18n; + resolve(i18n); + }); + // If init was synchronous (already initialized), resolve now; + // otherwise the loaded callback above resolves it. + if (backendInstance?.isInitialized) { + resolve(backendInstance); + } + } catch (e) { + backendInitialized = false; + reject(e); } - } catch (e) { - backendInitialized = false; - reject(e); - } - }), - ); + }), + ) + .catch((e) => { + // Reset cached state so a future call can retry after a failure. + backendPromise = undefined; + backendInitialized = false; + throw e; + }); } return backendPromise; } diff --git a/packages/provider/src/api/validateAddress.ts b/packages/provider/src/api/validateAddress.ts index 8fc86b4129..e68ccc63a8 100644 --- a/packages/provider/src/api/validateAddress.ts +++ b/packages/provider/src/api/validateAddress.ts @@ -24,7 +24,9 @@ export const validateSiteKey = (siteKey: string, logger?: Logger) => { export const validateAddr = ( address: string, translationKey: TranslationKey = "CONTRACT.INVALID_ADDRESS", - logger?: Logger, + // Errors no longer log at construction (decoupled from i18n/logging); the + // param is kept for call-site compatibility but intentionally unused. + _logger?: Logger, ) => { try { const valid = validateAddress(address, false, 42); From 1dffa97ef0d25d6db5510a27bdf3a32ff0007970 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 12 Jun 2026 13:41:58 +0100 Subject: [PATCH 18/18] fix: address PR review comments - resize: use FS.FILE_ALREADY_EXISTS when output dir exists (was FILE_NOT_FOUND) - errorKeys: preserve GENERAL.MISSING_AUTH_HEADER/INVALID_JWT/UNKNOWN in the frontend bundle (thrown by backend, returned to frontend as the error key) --- packages/common/src/errorKeys.ts | 6 ++++++ packages/datasets-fs/src/commands/resize.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/common/src/errorKeys.ts b/packages/common/src/errorKeys.ts index a654f2456a..79e1ee8356 100644 --- a/packages/common/src/errorKeys.ts +++ b/packages/common/src/errorKeys.ts @@ -40,6 +40,12 @@ export const GENERAL_ERROR_KEYS = { SITE_KEY_NOT_FOUND: "GENERAL.SITE_KEY_NOT_FOUND", MISSING_SECRET_KEY: "GENERAL.MISSING_SECRET_KEY", INVALID_SIGNATURE: "GENERAL.INVALID_SIGNATURE", + // Thrown by the backend (auth middleware / env guards) and returned to the + // frontend as the error key, so they must be preserved in the frontend + // translation bundle by the Vite pruning plugin. + MISSING_AUTH_HEADER: "GENERAL.MISSING_AUTH_HEADER", + INVALID_JWT: "GENERAL.INVALID_JWT", + UNKNOWN: "GENERAL.UNKNOWN", } as const; export const PORTAL_ERROR_KEYS = { diff --git a/packages/datasets-fs/src/commands/resize.ts b/packages/datasets-fs/src/commands/resize.ts index 8e95cef862..070545b267 100644 --- a/packages/datasets-fs/src/commands/resize.ts +++ b/packages/datasets-fs/src/commands/resize.ts @@ -75,7 +75,7 @@ export class Resize extends InputOutputCliCommand { const outDir: string = args.output; const overwrite = args.overwrite || false; if (!overwrite && fs.existsSync(outDir)) { - throw new ProsopoEnvError("FS.FILE_NOT_FOUND", { + throw new ProsopoEnvError("FS.FILE_ALREADY_EXISTS", { message: `Output directory already exists: ${outDir}`, }); }