mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-18 08:44:25 +08:00
support Nextcloud OAuth 2.0 authentication
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export const OAUTH2_PROVIDER_DISPLAY_NAME: Record<string, string> = {
|
||||
'nextcloud': 'Nextcloud'
|
||||
}
|
||||
@@ -3,6 +3,14 @@ function getServerSetting(key: string): string | number | boolean | Record<strin
|
||||
return settings[key];
|
||||
}
|
||||
|
||||
export function isInternalAuthEnabled(): boolean {
|
||||
return getServerSetting('a') !== 0;
|
||||
}
|
||||
|
||||
export function isOAuth2Enabled(): boolean {
|
||||
return getServerSetting('o') === 1;
|
||||
}
|
||||
|
||||
export function isUserRegistrationEnabled(): boolean {
|
||||
return getServerSetting('r') === 1;
|
||||
}
|
||||
@@ -31,6 +39,14 @@ export function isDataImportingEnabled(): boolean {
|
||||
return getServerSetting('i') === 1;
|
||||
}
|
||||
|
||||
export function getOAuth2Provider(): string {
|
||||
return getServerSetting('op') as string;
|
||||
}
|
||||
|
||||
export function getOIDCCustomDisplayNames(): Record<string, string>{
|
||||
return getServerSetting('ocn') as Record<string, string>;
|
||||
}
|
||||
|
||||
export function isMCPServerEnabled(): boolean {
|
||||
return getServerSetting('mcp') === 1;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@ import type {
|
||||
UserProfileUpdateRequest,
|
||||
UserProfileUpdateResponse
|
||||
} from '@/models/user.ts';
|
||||
import type {
|
||||
OAuth2CallbackLoginRequest
|
||||
} from '@/models/oauth2.ts';
|
||||
import type {
|
||||
UserApplicationCloudSettingsUpdateRequest
|
||||
} from '@/models/user_app_cloud_setting.ts';
|
||||
@@ -265,6 +268,13 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
authorizeOAuth2: ({ req, token }: { req: OAuth2CallbackLoginRequest, token: string }): ApiResponsePromise<AuthResponse> => {
|
||||
return axios.post<ApiResponse<AuthResponse>>('oauth2/authorize.json', req, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
},
|
||||
register: (req: UserRegisterRequest): ApiResponsePromise<RegisterResponse> => {
|
||||
return axios.post<ApiResponse<RegisterResponse>>('register.json', req);
|
||||
},
|
||||
@@ -695,6 +705,9 @@ export default {
|
||||
cancelRequest: (cancelableUuid: string) => {
|
||||
cancelableRequests[cancelableUuid] = true;
|
||||
},
|
||||
generateOAuth2LoginUrl: (platform: 'mobile' | 'desktop', clientSessionId: string): string => {
|
||||
return `${getBasePath()}/oauth2/login?platform=${platform}&client_session_id=${clientSessionId}`;
|
||||
},
|
||||
generateQrCodeUrl: (qrCodeName: string): string => {
|
||||
return `${getBasePath()}${BASE_QRCODE_PATH}/${qrCodeName}.png`;
|
||||
},
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} Stunde(n) hinter der Standardzeitzone",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} Stunde(n) vor der Standardzeitzone",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} Stunde(n) und {minutes} Minute(n) hinter der Standardzeitzone",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "Ein Aktivierungslink wurde an Ihre E-Mail-Adresse gesendet: {email}. Wenn Sie die E-Mail nicht erhalten haben, geben Sie bitte das Passwort erneut ein und klicken Sie auf die Schaltfläche unten, um die Bestätigungs-E-Mail erneut zu senden.",
|
||||
"resendValidationEmailTip": "Wenn Sie die E-Mail nicht erhalten haben, geben Sie bitte das Passwort erneut ein und klicken Sie auf die Schaltfläche unten, um die Bestätigungs-E-Mail an: {email} erneut zu senden."
|
||||
"resendValidationEmailTip": "Wenn Sie die E-Mail nicht erhalten haben, geben Sie bitte das Passwort erneut ein und klicken Sie auf die Schaltfläche unten, um die Bestätigungs-E-Mail an: {email} erneut zu senden.",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "Dateierweiterung des Benutzeravatars ist ungültig",
|
||||
"exceed the maximum size of user avatar file": "Hochgeladener Benutzeravatar überschreitet die maximal zulässige Dateigröße",
|
||||
"not permitted to perform this action": "Sie sind nicht berechtigt, diese Aktion auszuführen",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Unbefugter Zugriff",
|
||||
"current token is invalid": "Aktuelles Token ist ungültig",
|
||||
"current token is expired": "Aktuelles Token ist abgelaufen",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "Abfrageelemente dürfen nicht leer sein",
|
||||
"query items too much": "Zu viele Abfrageelemente",
|
||||
"query items have invalid item": "Ungültiges Element in Abfrageelementen",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Vorgang",
|
||||
"Open": "Open",
|
||||
"Close": "Schließen",
|
||||
"or": "or",
|
||||
"Submit": "Einreichen",
|
||||
"Add": "Hinzufügen",
|
||||
"Import": "Importieren",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "Dieser Monat oder später",
|
||||
"This year or later": "Dieses Jahr oder später",
|
||||
"Log In": "Anmelden",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Hier klicken, um sich anzumelden",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Zurück zur Anmeldeseite",
|
||||
"Back to home page": "Zurück zur Startseite",
|
||||
"Don't have an account?": "Sie haben kein Konto?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} hour(s) behind default timezone",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} hour(s) ahead of default timezone",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} hour(s) and {minutes} minutes behind default timezone",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "Account activation link has been sent to your email address: {email}, If you don't receive the mail, please fill password again and click the button below to resend the validation mail.",
|
||||
"resendValidationEmailTip": "If you don't receive the mail, please fill password again and click the button below to resend the validation mail to: {email}"
|
||||
"resendValidationEmailTip": "If you don't receive the mail, please fill password again and click the button below to resend the validation mail to: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "User avatar file extension is invalid",
|
||||
"exceed the maximum size of user avatar file": "The uploaded user avatar exceeds the maximum allowed file size",
|
||||
"not permitted to perform this action": "You are not permitted to perform this action",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Unauthorized access",
|
||||
"current token is invalid": "Current token is invalid",
|
||||
"current token is expired": "Current token is expired",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "There are no query items",
|
||||
"query items too much": "There are too many query items",
|
||||
"query items have invalid item": "There is invalid item in query items",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Operation",
|
||||
"Open": "Open",
|
||||
"Close": "Close",
|
||||
"or": "or",
|
||||
"Submit": "Submit",
|
||||
"Add": "Add",
|
||||
"Import": "Import",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "This month or later",
|
||||
"This year or later": "This year or later",
|
||||
"Log In": "Log In",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Click here to log in",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Back to login page",
|
||||
"Back to home page": "Back to home page",
|
||||
"Don't have an account?": "Don't have an account?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} hora(s) de retraso en la zona horaria predeterminada",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} hora(s) por delante de la zona horaria predeterminada",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} hora(s) y {minutes} minutos de retraso en la zona horaria predeterminada",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "El enlace de activación de la cuenta se envió a su dirección de correo electrónico: {email}. Si no recibe el correo, ingrese la contraseña nuevamente y haga clic en el botón a continuación para reenviar el correo de validación.",
|
||||
"resendValidationEmailTip": "Si no recibe el correo, complete nuevamente la contraseña y haga clic en el botón a continuación para reenviar el correo de validación a: {email}"
|
||||
"resendValidationEmailTip": "Si no recibe el correo, complete nuevamente la contraseña y haga clic en el botón a continuación para reenviar el correo de validación a: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "La extensión del archivo de avatar del usuario no es válida",
|
||||
"exceed the maximum size of user avatar file": "El avatar de usuario subido supera el tamaño de archivo máximo permitido",
|
||||
"not permitted to perform this action": "No tienes permiso para realizar esta acción.",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Acceso no autorizado",
|
||||
"current token is invalid": "El token actual no es válido",
|
||||
"current token is expired": "El token actual ha caducado",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "--",
|
||||
"query items too much": "--",
|
||||
"query items have invalid item": "Hay un elemento no válido en los elementos de consulta",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Operación",
|
||||
"Open": "Open",
|
||||
"Close": "Cerrar",
|
||||
"or": "or",
|
||||
"Submit": "Enviar",
|
||||
"Add": "Agregar",
|
||||
"Import": "Importar",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "Este mes o más tarde",
|
||||
"This year or later": "Este año o más tarde",
|
||||
"Log In": "Acceso",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Haga clic aquí para iniciar sesión",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Volver a la página de inicio de sesión",
|
||||
"Back to home page": "Volver a la página de inicio",
|
||||
"Don't have an account?": "¿No tienes una cuenta?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} heure(s) de retard sur le fuseau horaire par défaut",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} heure(s) d'avance sur le fuseau horaire par défaut",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} heure(s) et {minutes} minutes de retard sur le fuseau horaire par défaut",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "Le lien d'activation du compte a été envoyé à votre adresse e-mail : {email}, Si vous ne recevez pas le mail, veuillez remplir à nouveau le mot de passe et cliquer sur le bouton ci-dessous pour renvoyer l'e-mail de validation.",
|
||||
"resendValidationEmailTip": "Si vous ne recevez pas le mail, veuillez remplir à nouveau le mot de passe et cliquer sur le bouton ci-dessous pour renvoyer l'e-mail de validation à : {email}"
|
||||
"resendValidationEmailTip": "Si vous ne recevez pas le mail, veuillez remplir à nouveau le mot de passe et cliquer sur le bouton ci-dessous pour renvoyer l'e-mail de validation à : {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "L'extension du fichier d'avatar utilisateur est invalide",
|
||||
"exceed the maximum size of user avatar file": "L'avatar utilisateur téléchargé dépasse la taille de fichier maximale autorisée",
|
||||
"not permitted to perform this action": "Vous n'êtes pas autorisé à effectuer cette action",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Accès non autorisé",
|
||||
"current token is invalid": "Le token actuel est invalide",
|
||||
"current token is expired": "Le token actuel a expiré",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Le fichier d'image pour la reconnaissance IA est vide",
|
||||
"exceed the maximum size of image file for AI recognition": "L'image téléchargée pour la reconnaissance IA dépasse la taille de fichier maximale autorisée",
|
||||
"no transaction information detected": "Aucune information de transaction détectée",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "Il n'y a pas d'éléments de requête",
|
||||
"query items too much": "Il y a trop d'éléments de requête",
|
||||
"query items have invalid item": "Il y a un élément invalide dans les éléments de requête",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Opération",
|
||||
"Open": "Ouvrir",
|
||||
"Close": "Fermer",
|
||||
"or": "or",
|
||||
"Submit": "Soumettre",
|
||||
"Add": "Ajouter",
|
||||
"Import": "Importer",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "Ce mois ou plus tard",
|
||||
"This year or later": "Cette année ou plus tard",
|
||||
"Log In": "Se connecter",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Cliquez ici pour vous connecter",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Retour à la page de connexion",
|
||||
"Back to home page": "Retour à la page d'accueil",
|
||||
"Don't have an account?": "Vous n'avez pas de compte ?",
|
||||
|
||||
+50
-6
@@ -160,6 +160,7 @@ import { UTC_TIMEZONE, ALL_TIMEZONES } from '@/consts/timezone.ts';
|
||||
import { ALL_CURRENCIES } from '@/consts/currency.ts';
|
||||
import { DEFAULT_EXPENSE_CATEGORIES, DEFAULT_INCOME_CATEGORIES, DEFAULT_TRANSFER_CATEGORIES } from '@/consts/category.ts';
|
||||
import { KnownErrorCode, SPECIFIED_API_NOT_FOUND_ERRORS, PARAMETERIZED_ERRORS } from '@/consts/api.ts';
|
||||
import { OAUTH2_PROVIDER_DISPLAY_NAME } from '@/consts/oauth2.ts';
|
||||
import { DEFAULT_DOCUMENT_LANGUAGE_FOR_IMPORT_FILE, SUPPORTED_DOCUMENT_LANGUAGES_FOR_IMPORT_FILE, SUPPORTED_IMPORT_FILE_CATEGORY_AND_TYPES } from '@/consts/file.ts';
|
||||
|
||||
import {
|
||||
@@ -870,18 +871,18 @@ export function useI18n() {
|
||||
return textArray.join(separator);
|
||||
}
|
||||
|
||||
function getServerTipContent(tipConfig: Record<string, string>): string {
|
||||
if (!tipConfig) {
|
||||
function getServerMultiLanguageConfigContent(multiLanguageConfig: Record<string, string>): string {
|
||||
if (!multiLanguageConfig) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const currentLanguage = getCurrentLanguageTag();
|
||||
|
||||
if (isString(tipConfig[currentLanguage])) {
|
||||
return tipConfig[currentLanguage];
|
||||
if (isString(multiLanguageConfig[currentLanguage])) {
|
||||
return multiLanguageConfig[currentLanguage];
|
||||
}
|
||||
|
||||
return tipConfig['default'] || '';
|
||||
return multiLanguageConfig['default'] || '';
|
||||
}
|
||||
|
||||
function getCurrentLanguageTag(): string {
|
||||
@@ -2139,6 +2140,46 @@ export function useI18n() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getLocalizedOAuth2ProviderName(oauth2Provider: string, oidcDisplayNames: Record<string, string>): string {
|
||||
if (oauth2Provider === 'oidc') {
|
||||
const providerDisplayName = getServerMultiLanguageConfigContent(oidcDisplayNames);
|
||||
|
||||
if (providerDisplayName) {
|
||||
return providerDisplayName;
|
||||
} else {
|
||||
return 'Connect ID';
|
||||
}
|
||||
} else {
|
||||
const providerDisplayName = OAUTH2_PROVIDER_DISPLAY_NAME[oauth2Provider];
|
||||
|
||||
if (providerDisplayName) {
|
||||
return providerDisplayName;
|
||||
} else {
|
||||
return 'OAuth 2.0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLocalizedOAuth2LoginText(oauth2Provider: string, oidcDisplayNames: Record<string, string>): string {
|
||||
if (oauth2Provider === 'oidc') {
|
||||
const providerDisplayName = getServerMultiLanguageConfigContent(oidcDisplayNames);
|
||||
|
||||
if (providerDisplayName) {
|
||||
return t('format.misc.loginWithCustomProvider', { name: providerDisplayName });
|
||||
} else {
|
||||
return t('Log in with Connect ID');
|
||||
}
|
||||
} else {
|
||||
const providerDisplayName = OAUTH2_PROVIDER_DISPLAY_NAME[oauth2Provider];
|
||||
|
||||
if (providerDisplayName) {
|
||||
return t('format.misc.loginWithCustomProvider', { name: providerDisplayName });
|
||||
} else {
|
||||
return t('Log in with OAuth 2.0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setLanguage(languageKey: string | null, force?: boolean): LocaleDefaultSettings | null {
|
||||
if (!languageKey) {
|
||||
languageKey = getDefaultLanguage();
|
||||
@@ -2266,7 +2307,7 @@ export function useI18n() {
|
||||
ti: translateIf,
|
||||
te: translateError,
|
||||
joinMultiText,
|
||||
getServerTipContent,
|
||||
getServerMultiLanguageConfigContent,
|
||||
// get current language info
|
||||
getCurrentLanguageTag,
|
||||
getCurrentLanguageInfo,
|
||||
@@ -2411,6 +2452,9 @@ export function useI18n() {
|
||||
getAdaptiveAmountRate,
|
||||
getAmountPrependAndAppendText,
|
||||
getCategorizedAccountsWithDisplayBalance,
|
||||
// other format functions
|
||||
getLocalizedOAuth2ProviderName,
|
||||
getLocalizedOAuth2LoginText,
|
||||
// localization setting functions
|
||||
setLanguage,
|
||||
setTimeZone,
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "Indietro di {hours} ore rispetto al fuso orario standard",
|
||||
"hoursAheadOfDefaultTimezone": "Avanti di {hours} ore rispetto al fuso orario standard",
|
||||
"hoursMinutesBehindDefaultTimezone": "Indietro di {hours} ore e {minutes} minuti rispetto al fuso orario standard",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "Abbiamo inviato un link per l'attivazione del tuo account all'indirizzo {email}. Se non hai ricevuto la mail, inserisci nuovamente la password e premi il bottone per ritentare l'invio.",
|
||||
"resendValidationEmailTip": "Se non hai ricevuto la mail, inserisci nuovamente la password e premi il bottone per ritentare l'invio all'indirizzo: {email}"
|
||||
"resendValidationEmailTip": "Se non hai ricevuto la mail, inserisci nuovamente la password e premi il bottone per ritentare l'invio all'indirizzo: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "Estensione del file avatar utente non valida",
|
||||
"exceed the maximum size of user avatar file": "L'avatar utente caricato supera la dimensione massima consentita del file",
|
||||
"not permitted to perform this action": "Non sei autorizzato a eseguire questa azione",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Accesso non autorizzato",
|
||||
"current token is invalid": "Token corrente non valido",
|
||||
"current token is expired": "Token corrente scaduto",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "Non ci sono elementi di query",
|
||||
"query items too much": "Ci sono troppi elementi di query",
|
||||
"query items have invalid item": "C'è un elemento non valido negli elementi di query",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Operazione",
|
||||
"Open": "Open",
|
||||
"Close": "Chiudi",
|
||||
"or": "or",
|
||||
"Submit": "Invia",
|
||||
"Add": "Aggiungi",
|
||||
"Import": "Importa",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "Questo mese o successivo",
|
||||
"This year or later": "Quest'anno o successivo",
|
||||
"Log In": "Accedi",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Clicca qui per accedere",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Torna alla pagina di accesso",
|
||||
"Back to home page": "Torna alla home page",
|
||||
"Don't have an account?": "Non hai un account?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": "、",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "デフォルトのタイムゾーンより{hours}時間遅れています",
|
||||
"hoursAheadOfDefaultTimezone": "デフォルトのタイムゾーンから{hours}時間進んでいます",
|
||||
"hoursMinutesBehindDefaultTimezone": "デフォルトのタイムゾーンより{hours}時間{minutes}分遅れています",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "アカウントの有効化リンクがメールアドレスに送信されました:{email}、メールが届かない場合はパスワードをもう一度入力して下のボタンをクリックして認証メールを再送信してください。",
|
||||
"resendValidationEmailTip": "メールが届かない場合は、パスワードをもう一度入力の上、以下のボタンをクリックして検証メールを再送信してください: {email}"
|
||||
"resendValidationEmailTip": "メールが届かない場合は、パスワードをもう一度入力の上、以下のボタンをクリックして検証メールを再送信してください: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "ユーザーアバターファイルの拡張子が無効です",
|
||||
"exceed the maximum size of user avatar file": "アップロードされたユーザーアバターは最大ファイルサイズを超えています",
|
||||
"not permitted to perform this action": "このアクションを実行は許可されていません",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "不正アクセス",
|
||||
"current token is invalid": "現在のトークンは無効です",
|
||||
"current token is expired": "現在のトークンの有効期限が切れています",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "クエリ項目がありません",
|
||||
"query items too much": "クエリ項目が多すぎます",
|
||||
"query items have invalid item": "クエリ項目に無効な項目があります",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "操作",
|
||||
"Open": "Open",
|
||||
"Close": "閉じる",
|
||||
"or": "or",
|
||||
"Submit": "送信",
|
||||
"Add": "追加",
|
||||
"Import": "インポート",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "今月以降",
|
||||
"This year or later": "今年以降",
|
||||
"Log In": "ログイン",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "ここをクリックしてログインしてください",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "ログインページに戻る",
|
||||
"Back to home page": "Back to home page",
|
||||
"Don't have an account?": "Don't have an account?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "기본 시간대보다 {hours}시간 느립니다",
|
||||
"hoursAheadOfDefaultTimezone": "기본 시간대보다 {hours}시간 빠릅니다",
|
||||
"hoursMinutesBehindDefaultTimezone": "기본 시간대보다 {hours}시간 {minutes}분 느립니다",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "이 작업은 되돌릴 수 없습니다. {account}의 거래 데이터를 지웁니다. 계속하시려면 현재 비밀번호를 입력하세요.",
|
||||
"accountActivationAndResendValidationEmailTip": "계정 활성화 링크가 귀하의 이메일 주소({email})로 전송되었습니다. 메일을 받지 못하신 경우, 비밀번호를 다시 입력하고 아래 버튼을 클릭하여 확인 메일을 재전송하십시오.",
|
||||
"resendValidationEmailTip": "메일을 받지 못하신 경우, 비밀번호를 다시 입력하고 아래 버튼을 클릭하여 확인 메일을 {email}로 재전송하십시오."
|
||||
"resendValidationEmailTip": "메일을 받지 못하신 경우, 비밀번호를 다시 입력하고 아래 버튼을 클릭하여 확인 메일을 {email}로 재전송하십시오.",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "사용자 아바타 파일 확장자가 유효하지 않습니다",
|
||||
"exceed the maximum size of user avatar file": "업로드된 사용자 아바타가 허용된 최대 파일 크기를 초과합니다",
|
||||
"not permitted to perform this action": "이 작업을 수행할 수 있는 권한이 없습니다",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "권한이 없는 접근입니다",
|
||||
"current token is invalid": "현재 토큰이 유효하지 않습니다",
|
||||
"current token is expired": "현재 토큰이 만료되었습니다",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "AI 인식을 위한 이미지 파일이 비어 있습니다.",
|
||||
"exceed the maximum size of image file for AI recognition": "AI 인식을 위한 업로드된 이미지가 허용된 최대 파일 크기를 초과합니다.",
|
||||
"no transaction information detected": "거래 정보가 감지되지 않았습니다.",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "쿼리 항목이 비어 있을 수 없습니다.",
|
||||
"query items too much": "쿼리 항목이 너무 많습니다.",
|
||||
"query items have invalid item": "쿼리 항목에 유효하지 않은 항목이 있습니다.",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "작업",
|
||||
"Open": "열기",
|
||||
"Close": "닫기",
|
||||
"or": "or",
|
||||
"Submit": "제출",
|
||||
"Add": "추가",
|
||||
"Import": "가져오기",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "이번 달 이후",
|
||||
"This year or later": "올해 이후",
|
||||
"Log In": "로그인",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "여기를 클릭하여 로그인",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "로그인 페이지로 돌아가기",
|
||||
"Back to home page": "홈 페이지로 돌아가기",
|
||||
"Don't have an account?": "계정이 없으신가요?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} uur achter standaardtijdzone",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} uur voor op standaardtijdzone",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} uur en {minutes} minuten achter standaardtijdzone",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "Een activatielink is verzonden naar je e-mailadres: {email}. Als je de e-mail niet ontvangt, vul dan je wachtwoord opnieuw in en klik op de knop hieronder om de validatiemail opnieuw te verzenden.",
|
||||
"resendValidationEmailTip": "Als je de e-mail niet ontvangt, vul dan je wachtwoord opnieuw in en klik op de knop hieronder om de validatiemail opnieuw te verzenden naar: {email}"
|
||||
"resendValidationEmailTip": "Als je de e-mail niet ontvangt, vul dan je wachtwoord opnieuw in en klik op de knop hieronder om de validatiemail opnieuw te verzenden naar: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "Bestandsextensie van gebruikersavatar is ongeldig",
|
||||
"exceed the maximum size of user avatar file": "Geüploade avatar overschrijdt de maximaal toegestane bestandsgrootte",
|
||||
"not permitted to perform this action": "Je hebt geen toestemming om deze actie uit te voeren",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Ongeautoriseerde toegang",
|
||||
"current token is invalid": "Huidige token is ongeldig",
|
||||
"current token is expired": "Huidige token is verlopen",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "Geen zoekitems opgegeven",
|
||||
"query items too much": "Te veel zoekitems",
|
||||
"query items have invalid item": "Ongeldig item in zoekitems",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Bewerking",
|
||||
"Open": "Openen",
|
||||
"Close": "Sluiten",
|
||||
"or": "or",
|
||||
"Submit": "Verzenden",
|
||||
"Add": "Toevoegen",
|
||||
"Import": "Importeren",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "Deze maand of later",
|
||||
"This year or later": "Dit jaar of later",
|
||||
"Log In": "Inloggen",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Klik hier om in te loggen",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Terug naar inlogpagina",
|
||||
"Back to home page": "Terug naar startpagina",
|
||||
"Don't have an account?": "Nog geen account?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} hora(s) atrás do fuso horário padrão",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} hora(s) à frente do fuso horário padrão",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} hora(s) e {minutes} minutos atrás do fuso horário padrão",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "O link de ativação da conta foi enviado para seu endereço de e-mail: {email}. Se você não receber o e-mail, por favor preencha a senha novamente e clique no botão abaixo para reenviar o e-mail de validação.",
|
||||
"resendValidationEmailTip": "Se você não receber o e-mail, por favor preencha a senha novamente e clique no botão abaixo para reenviar o e-mail de validação para: {email}"
|
||||
"resendValidationEmailTip": "Se você não receber o e-mail, por favor preencha a senha novamente e clique no botão abaixo para reenviar o e-mail de validação para: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "A extensão do arquivo de avatar do usuário é inválida",
|
||||
"exceed the maximum size of user avatar file": "O avatar de usuário enviado excede o tamanho máximo permitido de arquivo",
|
||||
"not permitted to perform this action": "Você não tem permissão para realizar esta ação",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Acesso não autorizado",
|
||||
"current token is invalid": "Token atual é inválido",
|
||||
"current token is expired": "Token atual está expirado",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "Não há itens de consulta",
|
||||
"query items too much": "Há muitos itens de consulta",
|
||||
"query items have invalid item": "Há item inválido nos itens de consulta",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Operação",
|
||||
"Open": "Open",
|
||||
"Close": "Fechar",
|
||||
"or": "or",
|
||||
"Submit": "Enviar",
|
||||
"Add": "Adicionar",
|
||||
"Import": "Importar",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "Este mês ou depois",
|
||||
"This year or later": "Este ano ou depois",
|
||||
"Log In": "Fazer Login",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Clique aqui para fazer login",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Voltar para a página de login",
|
||||
"Back to home page": "Voltar para a página inicial",
|
||||
"Don't have an account?": "Não tem uma conta?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} час(ов) позади часового пояса по умолчанию",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} час(ов) впереди часового пояса по умолчанию",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} час(ов) и {minutes} минут позади часового пояса по умолчанию",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "Ссылка для активации учетной записи была отправлена на ваш электронный адрес: {email}. Если вы не получили письмо, заполните пароль снова и нажмите кнопку ниже, чтобы отправить письмо повторно.",
|
||||
"resendValidationEmailTip": "Если вы не получили письмо, заполните пароль снова и нажмите кнопку ниже, чтобы отправить письмо повторно на: {email}"
|
||||
"resendValidationEmailTip": "Если вы не получили письмо, заполните пароль снова и нажмите кнопку ниже, чтобы отправить письмо повторно на: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "Недопустимое расширение файла аватара пользователя",
|
||||
"exceed the maximum size of user avatar file": "Загруженный аватар пользователя превышает максимально допустимый размер файла",
|
||||
"not permitted to perform this action": "Вам не разрешено выполнять это действие",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Несанкционированный доступ",
|
||||
"current token is invalid": "Текущий токен недействителен",
|
||||
"current token is expired": "Текущий токен истек",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "Нет элементов запроса",
|
||||
"query items too much": "Слишком много элементов запроса",
|
||||
"query items have invalid item": "В элементах запроса присутствует недопустимый элемент",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Операция",
|
||||
"Open": "Open",
|
||||
"Close": "Закрыть",
|
||||
"or": "or",
|
||||
"Submit": "Отправить",
|
||||
"Add": "Добавить",
|
||||
"Import": "Импорт",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "В этом месяце или позже",
|
||||
"This year or later": "В этом году или позже",
|
||||
"Log In": "Войти",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Нажмите здесь, чтобы войти",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Вернуться на страницу входа",
|
||||
"Back to home page": "Вернуться на главную страницу",
|
||||
"Don't have an account?": "Нет учетной записи?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "ช้ากว่าเขตเวลาเริ่มต้น {hours} ชั่วโมง",
|
||||
"hoursAheadOfDefaultTimezone": "เร็วกว่าเขตเวลาเริ่มต้น {hours} ชั่วโมง",
|
||||
"hoursMinutesBehindDefaultTimezone": "ช้ากว่าเขตเวลาเริ่มต้น {hours} ชั่วโมง {minutes} นาที",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "คุณไม่สามารถยกเลิกการกระทำนี้ได้ การกระทำนี้จะลบข้อมูลธุรกรรมทั้งหมดใน {account} โปรดป้อนรหัสผ่านปัจจุบันเพื่อยืนยัน",
|
||||
"accountActivationAndResendValidationEmailTip": "ลิงก์สำหรับเปิดใช้งานบัญชีได้ถูกส่งไปยังอีเมลของคุณแล้ว: {email} หากคุณไม่ได้รับอีเมล โปรดกรอกรหัสผ่านอีกครั้งแล้วกดปุ่มด้านล่างเพื่อส่งอีเมลยืนยันอีกครั้ง",
|
||||
"resendValidationEmailTip": "หากคุณไม่ได้รับอีเมล โปรดกรอกรหัสผ่านอีกครั้งแล้วกดปุ่มด้านล่างเพื่อส่งอีเมลยืนยันไปยัง: {email}"
|
||||
"resendValidationEmailTip": "หากคุณไม่ได้รับอีเมล โปรดกรอกรหัสผ่านอีกครั้งแล้วกดปุ่มด้านล่างเพื่อส่งอีเมลยืนยันไปยัง: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "นามสกุลไฟล์รูปประจำตัวผู้ใช้ไม่ถูกต้อง",
|
||||
"exceed the maximum size of user avatar file": "ขนาดไฟล์รูปประจำตัวผู้ใช้เกินขนาดสูงสุดที่อนุญาต",
|
||||
"not permitted to perform this action": "คุณไม่ได้รับอนุญาตให้ดำเนินการนี้",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "การเข้าถึงไม่ได้รับอนุญาต",
|
||||
"current token is invalid": "โทเค็นปัจจุบันไม่ถูกต้อง",
|
||||
"current token is expired": "โทเค็นปัจจุบันหมดอายุ",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "ไฟล์รูปภาพสำหรับการจดจำด้วย AI ว่างเปล่า",
|
||||
"exceed the maximum size of image file for AI recognition": "ไฟล์รูปภาพสำหรับการจดจำด้วย AI เกินขนาดสูงสุดที่อนุญาต",
|
||||
"no transaction information detected": "ไม่พบข้อมูลธุรกรรม",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "ไม่มีรายการสำหรับค้นหา",
|
||||
"query items too much": "รายการค้นหามากเกินไป",
|
||||
"query items have invalid item": "มีรายการไม่ถูกต้องในรายการค้นหา",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "การดำเนินการ",
|
||||
"Open": "เปิด",
|
||||
"Close": "ปิด",
|
||||
"or": "or",
|
||||
"Submit": "ส่ง",
|
||||
"Add": "เพิ่ม",
|
||||
"Import": "นำเข้า",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "เดือนนี้หรือหลังจากนี้",
|
||||
"This year or later": "ปีนี้หรือหลังจากนี้",
|
||||
"Log In": "เข้าสู่ระบบ",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "คลิกที่นี่เพื่อเข้าสู่ระบบ",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "กลับไปยังหน้าล็อกอิน",
|
||||
"Back to home page": "กลับไปยังหน้าหลัก",
|
||||
"Don't have an account?": "ยังไม่มีบัญชี?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} год позаду часового поясу за замовчуванням",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} год попереду часового поясу за замовчуванням",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} год і {minutes} хв позаду часового поясу за замовчуванням",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "Посилання для активації облікового запису було надіслано на вашу електронну адресу: {email}. Якщо ви не отримали лист, введіть пароль ще раз і натисніть кнопку нижче, щоб надіслати лист повторно.",
|
||||
"resendValidationEmailTip": "Якщо ви не отримали лист, введіть пароль ще раз і натисніть кнопку нижче, щоб надіслати лист повторно на адресу: {email}"
|
||||
"resendValidationEmailTip": "Якщо ви не отримали лист, введіть пароль ще раз і натисніть кнопку нижче, щоб надіслати лист повторно на адресу: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "Недопустиме розширення файлу аватара користувача",
|
||||
"exceed the maximum size of user avatar file": "Завантажений аватар перевищує максимально допустимий розмір",
|
||||
"not permitted to perform this action": "Вам не дозволено виконувати цю дію",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Несанкціонований доступ",
|
||||
"current token is invalid": "Поточний токен недійсний",
|
||||
"current token is expired": "Поточний токен прострочений",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "Елементи запиту не можуть бути порожніми",
|
||||
"query items too much": "Занадто багато елементів запиту",
|
||||
"query items have invalid item": "Запит містить недійсний елемент",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Дія",
|
||||
"Open": "Open",
|
||||
"Close": "Закрити",
|
||||
"or": "or",
|
||||
"Submit": "Підтвердити",
|
||||
"Add": "Додати",
|
||||
"Import": "Імпортувати",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "Цього місяця або пізніше",
|
||||
"This year or later": "Цього року або пізніше",
|
||||
"Log In": "Увійти",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Натисніть тут, щоб увійти",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Повернутися на сторінку входу",
|
||||
"Back to home page": "Повернутися на головну сторінку",
|
||||
"Don't have an account?": "Немає облікового запису?",
|
||||
|
||||
+19
-1
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"loginWithCustomProvider": "Log in with {name}",
|
||||
"hoursBehindDefaultTimezone": "{hours} giờ sau múi giờ mặc định",
|
||||
"hoursAheadOfDefaultTimezone": "{hours} giờ trước múi giờ mặc định",
|
||||
"hoursMinutesBehindDefaultTimezone": "{hours} giờ và {minutes} phút sau múi giờ mặc định",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "You CANNOT undo this action. This will move all transactions from {fromAccount} to {toAccount}.",
|
||||
"clearTransactionsInAccountTip": "You CANNOT undo this action. This will clear your transactions data in {account}. Please enter your current password to confirm.",
|
||||
"accountActivationAndResendValidationEmailTip": "Liên kết kích hoạt tài khoản đã được gửi tới email của bạn: {email}. Nếu bạn không nhận được email, vui lòng nhập lại mật khẩu và nhấp nút bên dưới để gửi lại email xác nhận.",
|
||||
"resendValidationEmailTip": "Nếu bạn không nhận được email, vui lòng nhập lại mật khẩu và nhấp nút bên dưới để gửi lại email xác nhận tới: {email}"
|
||||
"resendValidationEmailTip": "Nếu bạn không nhận được email, vui lòng nhập lại mật khẩu và nhấp nút bên dưới để gửi lại email xác nhận tới: {email}",
|
||||
"oauth2bindTip": "You're signing in to the {userName} user using {providerName}. Please enter your ezBookkeeping password to verify."
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "Đuôi tệp avatar người dùng không hợp lệ",
|
||||
"exceed the maximum size of user avatar file": "Avatar người dùng đã tải lên vượt quá kích thước tệp tối đa cho phép",
|
||||
"not permitted to perform this action": "Bạn không được phép thực hiện hành động này",
|
||||
"cannot login by password": "You cannot login by password",
|
||||
"unauthorized access": "Truy cập trái phép",
|
||||
"current token is invalid": "Mã thông báo hiện tại không hợp lệ",
|
||||
"current token is expired": "Mã thông báo hiện tại đã hết hạn",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "Image for AI recognition file is empty",
|
||||
"exceed the maximum size of image file for AI recognition": "The uploaded image for AI recognition exceeds the maximum allowed file size",
|
||||
"no transaction information detected": "No transaction information detected",
|
||||
"user external auth is not found": "User external authentication is not found",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 is not enabled",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 auto registration is not enabled",
|
||||
"invalid oauth 2.0 login request": "Invalid OAuth 2.0 login request",
|
||||
"invalid oauth 2.0 callback": "Invalid OAuth 2.0 callback",
|
||||
"missing state in oauth 2.0 callback": "Missing state parameter in OAuth 2.0 callback",
|
||||
"missing code in oauth 2.0 callback": "Missing code parameter in OAuth 2.0 callback",
|
||||
"invalid state in oauth 2.0 callback": "Invalid state parameter in OAuth 2.0 callback",
|
||||
"cannot retrieve oauth 2.0 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth 2.0 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"query items cannot be blank": "Không có mục truy vấn",
|
||||
"query items too much": "Có quá nhiều mục truy vấn",
|
||||
"query items have invalid item": "Có mục không hợp lệ trong các mục truy vấn",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "Thao tác",
|
||||
"Open": "Open",
|
||||
"Close": "Đóng",
|
||||
"or": "or",
|
||||
"Submit": "Gửi",
|
||||
"Add": "Thêm",
|
||||
"Import": "Nhập",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "Tháng này trở đi",
|
||||
"This year or later": "Năm nay trở đi",
|
||||
"Log In": "Đăng nhập",
|
||||
"Log in with OAuth 2.0": "Log in with OAuth 2.0",
|
||||
"Log in with Connect ID": "Log in with Connect ID",
|
||||
"Click here to log in": "Nhấp vào đây để đăng nhập",
|
||||
"Logging in...": "Logging in...",
|
||||
"Back to login page": "Quay lại trang đăng nhập",
|
||||
"Back to home page": "Quay lại trang chủ",
|
||||
"Don't have an account?": "Bạn chưa có tài khoản?",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": "、",
|
||||
"loginWithCustomProvider": "使用 {name} 登录",
|
||||
"hoursBehindDefaultTimezone": "比默认时区晚{hours}小时",
|
||||
"hoursAheadOfDefaultTimezone": "比默认时区早{hours}小时",
|
||||
"hoursMinutesBehindDefaultTimezone": "比默认时区晚{hours}小时{minutes}分",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "您不能撤销该操作。该操作将会把 {fromAccount} 账户中所有的交易数据移动到 {toAccount}。",
|
||||
"clearTransactionsInAccountTip": "您不能撤销该操作。该操作将会清除您在 {account} 账户中的交易数据。请输入您当前的密码以确认。",
|
||||
"accountActivationAndResendValidationEmailTip": "账号激活链接已经发送到您的邮箱地址:{email},如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件。",
|
||||
"resendValidationEmailTip": "如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件到:{email}"
|
||||
"resendValidationEmailTip": "如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件到:{email}",
|
||||
"oauth2bindTip": "您正在使用 {providerName} 登录 \"{userName}\" 用户,请输入你的 ezBookkeeping 的密码进行验证。"
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "用户头像文件扩展名无效",
|
||||
"exceed the maximum size of user avatar file": "上传的用户头像超出了允许的最大文件大小",
|
||||
"not permitted to perform this action": "您不能执行该操作",
|
||||
"cannot login by password": "您不能使用密码登录",
|
||||
"unauthorized access": "未授权的登录",
|
||||
"current token is invalid": "当前认证令牌无效",
|
||||
"current token is expired": "当前认证令牌已过期",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "用于AI识别的图片为空",
|
||||
"exceed the maximum size of image file for AI recognition": "用于AI识别的图片超出了允许的最大文件大小",
|
||||
"no transaction information detected": "没有检测到交易信息",
|
||||
"user external auth is not found": "用户外部认证信息不存在",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 没有启用",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 自动注册没有启用",
|
||||
"invalid oauth 2.0 login request": "无效的 OAuth 2.0 登录请求",
|
||||
"invalid oauth 2.0 callback": "无效的 OAuth 2.0 回调请求",
|
||||
"missing state in oauth 2.0 callback": "OAuth 2.0 回调中缺少 state 参数",
|
||||
"missing code in oauth 2.0 callback": "OAuth 2.0 回调中缺少 code 参数",
|
||||
"invalid state in oauth 2.0 callback": "OAuth 2.0 回调中的 state 参数无效",
|
||||
"cannot retrieve oauth 2.0 token": "无法获取 OAuth 2.0 令牌",
|
||||
"invalid oauth 2.0 token": "无效的 OAuth 2.0 令牌",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "无法从 OAuth 2.0 提供者获取用户信息",
|
||||
"query items cannot be blank": "请求项目不能为空",
|
||||
"query items too much": "请求项目过多",
|
||||
"query items have invalid item": "请求项目中有非法项目",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "操作",
|
||||
"Open": "打开",
|
||||
"Close": "关闭",
|
||||
"or": "或",
|
||||
"Submit": "提交",
|
||||
"Add": "添加",
|
||||
"Import": "导入",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "本月或更晚",
|
||||
"This year or later": "今年或更晚",
|
||||
"Log In": "登录",
|
||||
"Log in with OAuth 2.0": "使用 OAuth 2.0 登录",
|
||||
"Log in with Connect ID": "使用 Connect ID 登录",
|
||||
"Click here to log in": "点击这里登录",
|
||||
"Logging in...": "正在登录...",
|
||||
"Back to login page": "返回登录页",
|
||||
"Back to home page": "返回首页",
|
||||
"Don't have an account?": "还没有账号?",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": "、",
|
||||
"loginWithCustomProvider": "使用 {name} 登入",
|
||||
"hoursBehindDefaultTimezone": "比預設時區晚{hours}小時",
|
||||
"hoursAheadOfDefaultTimezone": "比預設時區早{hours}小時",
|
||||
"hoursMinutesBehindDefaultTimezone": "比預設時區晚{hours}小時{minutes}分",
|
||||
@@ -129,7 +130,8 @@
|
||||
"moveTransactionsInAccountTip": "您不能還原此操作。此操作將會把 {fromAccount} 帳戶中的所有交易資料移動到 {toAccount}。",
|
||||
"clearTransactionsInAccountTip": "您不能還原此操作。此操作將會清除您在 {account} 帳戶中的交易資料。請輸入您目前的密碼以確認。",
|
||||
"accountActivationAndResendValidationEmailTip": "帳號啟用連結已經傳送到您的信箱地址:{email},如果您沒有收到郵件,請再次輸入密碼並點擊下方的按鈕重新發送驗證郵件。",
|
||||
"resendValidationEmailTip": "如果您沒有收到郵件,請再次輸入密碼並點擊下方的按鈕重新發送驗證郵件到:{email}"
|
||||
"resendValidationEmailTip": "如果您沒有收到郵件,請再次輸入密碼並點擊下方的按鈕重新發送驗證郵件到:{email}",
|
||||
"oauth2bindTip": "您正在使用 {providerName} 登入 \"{userName}\" 使用者,請輸入您的 ezBookkeeping 的密碼以進行驗證。"
|
||||
}
|
||||
},
|
||||
"dataExport": {
|
||||
@@ -1086,6 +1088,7 @@
|
||||
"user avatar file extension invalid": "使用者頭像檔案副檔名無效",
|
||||
"exceed the maximum size of user avatar file": "上傳的使用者頭像超過允許的最大檔案大小",
|
||||
"not permitted to perform this action": "您不能執行該操作",
|
||||
"cannot login by password": "您不能使用密碼登入",
|
||||
"unauthorized access": "未授權的登入",
|
||||
"current token is invalid": "目前認證令牌無效",
|
||||
"current token is expired": "目前認證令牌已過期",
|
||||
@@ -1238,6 +1241,17 @@
|
||||
"image for AI recognition is empty": "用於AI識別的圖片檔案為空",
|
||||
"exceed the maximum size of image file for AI recognition": "用於AI識別的圖片超出了允許的最大檔案大小",
|
||||
"no transaction information detected": "沒有檢測到交易資訊",
|
||||
"user external auth is not found": "使用者外部驗證資訊不存在",
|
||||
"oauth 2.0 not enabled": "OAuth 2.0 未啟用",
|
||||
"oauth 2.0 auto registration not enabled": "OAuth 2.0 自動註冊未啟用",
|
||||
"invalid oauth 2.0 login request": "無效的 OAuth 2.0 登入請求",
|
||||
"invalid oauth 2.0 callback": "無效的 OAuth 2.0 回調請求",
|
||||
"missing state in oauth 2.0 callback": "OAuth 2.0 回調中缺少 state 參數",
|
||||
"missing code in oauth 2.0 callback": "OAuth 2.0 回調中缺少 code 參數",
|
||||
"invalid state in oauth 2.0 callback": "OAuth 2.0 回調中的 state 參數無效",
|
||||
"cannot retrieve oauth 2.0 token": "無法獲取 OAuth 2.0 令牌",
|
||||
"invalid oauth 2.0 token": "無效的 OAuth 2.0 令牌",
|
||||
"cannot retrieve user info from oauth 2.0 provider": "無法從 OAuth 2.0 提供者獲取使用者資訊",
|
||||
"query items cannot be blank": "查詢項目不能為空",
|
||||
"query items too much": "查詢項目過多",
|
||||
"query items have invalid item": "查詢項目中有非法項目",
|
||||
@@ -1391,6 +1405,7 @@
|
||||
"Operation": "操作",
|
||||
"Open": "開啟",
|
||||
"Close": "關閉",
|
||||
"or": "或",
|
||||
"Submit": "提交",
|
||||
"Add": "新增",
|
||||
"Import": "匯入",
|
||||
@@ -1572,7 +1587,10 @@
|
||||
"This month or later": "本月或更晚",
|
||||
"This year or later": "今年或更晚",
|
||||
"Log In": "登入",
|
||||
"Log in with OAuth 2.0": "使用 OAuth 2.0 登入",
|
||||
"Log in with Connect ID": "使用 Connect ID 登入",
|
||||
"Click here to log in": "點擊這裡登入",
|
||||
"Logging in...": "正在登入...",
|
||||
"Back to login page": "返回登入頁面",
|
||||
"Back to home page": "返回首頁",
|
||||
"Don't have an account?": "還沒有帳號?",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface OAuth2CallbackLoginRequest {
|
||||
readonly provider?: string;
|
||||
readonly password?: string;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import SignUpPage from '@/views/desktop/SignupPage.vue';
|
||||
import VerifyEmailPage from '@/views/desktop/VerifyEmailPage.vue';
|
||||
import ForgetPasswordPage from '@/views/desktop/ForgetPasswordPage.vue';
|
||||
import ResetPasswordPage from '@/views/desktop/ResetPasswordPage.vue';
|
||||
import OAuth2CallbackPage from '@/views/desktop/OAuth2CallbackPage.vue';
|
||||
import UnlockPage from '@/views/desktop/UnlockPage.vue';
|
||||
|
||||
import HomePage from '@/views/desktop/HomePage.vue';
|
||||
@@ -226,6 +227,17 @@ const router = createRouter({
|
||||
token: route.query['token']
|
||||
})
|
||||
},
|
||||
{
|
||||
path: '/oauth2_callback',
|
||||
component: OAuth2CallbackPage,
|
||||
props: route => ({
|
||||
token: route.query['token'],
|
||||
provider: route.query['provider'],
|
||||
platform: route.query['platform'],
|
||||
userName: route.query['userName'],
|
||||
error: route.query['error']
|
||||
})
|
||||
},
|
||||
{
|
||||
path: '/unlock',
|
||||
component: UnlockPage,
|
||||
|
||||
+57
-1
@@ -21,8 +21,8 @@ import type {
|
||||
UserProfileUpdateRequest,
|
||||
UserProfileUpdateResponse
|
||||
} from '@/models/user.ts';
|
||||
import type { LocalizedPresetCategory } from '@/core/category.ts';
|
||||
import type { ForgetPasswordRequest } from '@/models/forget_password.ts';
|
||||
import type { LocalizedPresetCategory } from '@/core/category.ts';
|
||||
|
||||
import {
|
||||
isObject,
|
||||
@@ -77,6 +77,10 @@ export const useRootStore = defineStore('root', () => {
|
||||
currentNotification.value = content;
|
||||
}
|
||||
|
||||
function generateOAuth2LoginUrl(platform: 'mobile' | 'desktop', clientSessionId: string): string {
|
||||
return services.generateOAuth2LoginUrl(platform, clientSessionId);
|
||||
}
|
||||
|
||||
function authorize(req: UserLoginRequest): Promise<AuthResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.authorize(req).then(response => {
|
||||
@@ -187,6 +191,56 @@ export const useRootStore = defineStore('root', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function authorizeOAuth2({ provider, password, token }: { provider: string, password?: string, token: string }): Promise<AuthResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.authorizeOAuth2({
|
||||
req: {
|
||||
provider,
|
||||
password
|
||||
},
|
||||
token
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result || !data.result.token) {
|
||||
reject({ message: 'Unable to log in' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsStore.appSettings.applicationLock || hasUserAppLockState()) {
|
||||
const appLockState = getUserAppLockState();
|
||||
|
||||
if (!appLockState || appLockState.username !== data.result.user?.username) {
|
||||
clearCurrentTokenAndUserInfo(true);
|
||||
settingsStore.setEnableApplicationLock(false);
|
||||
settingsStore.setEnableApplicationLockWebAuthn(false);
|
||||
clearWebAuthnConfig();
|
||||
}
|
||||
}
|
||||
|
||||
settingsStore.setApplicationSettingsFromCloudSettings(data.result.applicationCloudSettings);
|
||||
|
||||
updateCurrentToken(data.result.token);
|
||||
|
||||
if (data.result.user && isObject(data.result.user)) {
|
||||
userStore.storeUserBasicInfo(data.result.user);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to login', error);
|
||||
|
||||
if (error && error.processed) {
|
||||
reject(error);
|
||||
} else if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else {
|
||||
reject({ message: 'Unable to log in' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function register({ user, presetCategories }: { user: User, presetCategories?: LocalizedPresetCategory[] }): Promise<RegisterResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.register(user.toRegisterRequest(presetCategories)).then(response => {
|
||||
@@ -588,8 +642,10 @@ export const useRootStore = defineStore('root', () => {
|
||||
currentNotification,
|
||||
// functions
|
||||
setNotificationContent,
|
||||
generateOAuth2LoginUrl,
|
||||
authorize,
|
||||
authorize2FA,
|
||||
authorizeOAuth2,
|
||||
register,
|
||||
lock,
|
||||
logout,
|
||||
|
||||
@@ -8,12 +8,12 @@ import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
|
||||
|
||||
import type { AuthResponse } from '@/models/auth_response.ts';
|
||||
|
||||
import { getLoginPageTips } from '@/lib/server_settings.ts';
|
||||
import { getOAuth2Provider, getOIDCCustomDisplayNames, getLoginPageTips } from '@/lib/server_settings.ts';
|
||||
import { getClientDisplayVersion } from '@/lib/version.ts';
|
||||
import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
|
||||
|
||||
export function useLoginPageBase() {
|
||||
const { getServerTipContent, setLanguage } = useI18n();
|
||||
export function useLoginPageBase(platform: 'mobile' | 'desktop') {
|
||||
const { getServerMultiLanguageConfigContent, getLocalizedOAuth2LoginText, setLanguage } = useI18n();
|
||||
|
||||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -27,6 +27,7 @@ export function useLoginPageBase() {
|
||||
const backupCode = ref<string>('');
|
||||
const tempToken = ref<string>('');
|
||||
const twoFAVerifyType = ref<string>('passcode');
|
||||
const oauth2ClientSessionId = ref<string>('');
|
||||
|
||||
const logining = ref<boolean>(false);
|
||||
const verifying = ref<boolean>(false);
|
||||
@@ -40,7 +41,9 @@ export function useLoginPageBase() {
|
||||
}
|
||||
});
|
||||
|
||||
const tips = computed<string>(() => getServerTipContent(getLoginPageTips()));
|
||||
const oauth2LoginUrl = computed<string>(() => rootStore.generateOAuth2LoginUrl(platform, oauth2ClientSessionId.value));
|
||||
const oauth2LoginDisplayName = computed<string>(() => getLocalizedOAuth2LoginText(getOAuth2Provider(), getOIDCCustomDisplayNames()));
|
||||
const tips = computed<string>(() => getServerMultiLanguageConfigContent(getLoginPageTips()));
|
||||
|
||||
function doAfterLogin(authResponse: AuthResponse): void {
|
||||
if (authResponse.user) {
|
||||
@@ -69,11 +72,14 @@ export function useLoginPageBase() {
|
||||
backupCode,
|
||||
tempToken,
|
||||
twoFAVerifyType,
|
||||
oauth2ClientSessionId,
|
||||
logining,
|
||||
verifying,
|
||||
// computed states
|
||||
inputIsEmpty,
|
||||
twoFAInputIsEmpty,
|
||||
oauth2LoginUrl,
|
||||
oauth2LoginDisplayName,
|
||||
tips,
|
||||
// functions
|
||||
doAfterLogin
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
<v-card variant="flat" class="w-100 mt-0 px-4 pt-12" max-width="500">
|
||||
<v-card-text>
|
||||
<h4 class="text-h4 mb-2">{{ tt('Welcome to ezBookkeeping') }}</h4>
|
||||
<p class="mb-0">{{ tt('Please log in with your ezBookkeeping account') }}</p>
|
||||
<p class="mb-0" v-if="isInternalAuthEnabled()">{{ tt('Please log in with your ezBookkeeping account') }}</p>
|
||||
<p class="mt-1 mb-0" v-if="tips">{{ tips }}</p>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text class="pb-0 mb-6">
|
||||
<v-form>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-col cols="12" v-if="isInternalAuthEnabled()">
|
||||
<v-text-field
|
||||
type="text"
|
||||
autocomplete="username"
|
||||
@@ -45,7 +45,7 @@
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-col cols="12" v-if="isInternalAuthEnabled()">
|
||||
<v-text-field
|
||||
autocomplete="current-password"
|
||||
ref="passwordInput"
|
||||
@@ -101,10 +101,20 @@
|
||||
|
||||
<v-col cols="12">
|
||||
<v-btn block :disabled="inputIsEmpty || logining || verifying"
|
||||
@click="login" v-if="!show2faInput">
|
||||
@click="login" v-if="isInternalAuthEnabled() && !show2faInput">
|
||||
{{ tt('Log In') }}
|
||||
<v-progress-circular indeterminate size="22" class="ms-2" v-if="logining"></v-progress-circular>
|
||||
</v-btn>
|
||||
|
||||
<v-col cols="12" class="d-flex align-center px-0" v-if="isInternalAuthEnabled() && isOAuth2Enabled()">
|
||||
<v-divider class="me-3" />
|
||||
{{ tt('or') }}
|
||||
<v-divider class="ms-3" />
|
||||
</v-col>
|
||||
|
||||
<v-btn block :disabled="logining || verifying" :href="oauth2LoginUrl" v-if="isOAuth2Enabled()">
|
||||
{{ oauth2LoginDisplayName }}
|
||||
</v-btn>
|
||||
<v-btn block :disabled="twoFAInputIsEmpty || logining || verifying"
|
||||
@click="verify" v-else-if="show2faInput">
|
||||
{{ tt('Continue') }}
|
||||
@@ -112,7 +122,7 @@
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" class="text-center text-base">
|
||||
<v-col cols="12" class="text-center text-base" v-if="isInternalAuthEnabled()">
|
||||
<span class="me-1">{{ tt('Don\'t have an account?') }}</span>
|
||||
<router-link class="text-primary" to="/signup"
|
||||
:class="{'disabled': !isUserRegistrationEnabled()}">
|
||||
@@ -170,7 +180,14 @@ import { ThemeType } from '@/core/theme.ts';
|
||||
import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts';
|
||||
import { KnownErrorCode } from '@/consts/api.ts';
|
||||
|
||||
import { isUserRegistrationEnabled, isUserForgetPasswordEnabled, isUserVerifyEmailEnabled } from '@/lib/server_settings.ts';
|
||||
import { generateRandomUUID } from '@/lib/misc.ts';
|
||||
import {
|
||||
isUserRegistrationEnabled,
|
||||
isUserForgetPasswordEnabled,
|
||||
isUserVerifyEmailEnabled,
|
||||
isInternalAuthEnabled,
|
||||
isOAuth2Enabled
|
||||
} from '@/lib/server_settings.ts';
|
||||
|
||||
import {
|
||||
mdiOnepassword,
|
||||
@@ -194,13 +211,16 @@ const {
|
||||
backupCode,
|
||||
tempToken,
|
||||
twoFAVerifyType,
|
||||
oauth2ClientSessionId,
|
||||
logining,
|
||||
verifying,
|
||||
inputIsEmpty,
|
||||
twoFAInputIsEmpty,
|
||||
oauth2LoginUrl,
|
||||
oauth2LoginDisplayName,
|
||||
tips,
|
||||
doAfterLogin
|
||||
} = useLoginPageBase();
|
||||
} = useLoginPageBase('desktop');
|
||||
|
||||
const passwordInput = useTemplateRef<VTextField>('passwordInput');
|
||||
const passcodeInput = useTemplateRef<VTextField>('passcodeInput');
|
||||
@@ -301,4 +321,6 @@ function verify(): void {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
oauth2ClientSessionId.value = generateRandomUUID();
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="layout-wrapper">
|
||||
<router-link to="/">
|
||||
<div class="auth-logo d-flex align-start gap-x-3">
|
||||
<img alt="logo" class="login-page-logo" :src="APPLICATION_LOGO_PATH" />
|
||||
<h1 class="font-weight-medium leading-normal text-2xl">{{ tt('global.app.title') }}</h1>
|
||||
</div>
|
||||
</router-link>
|
||||
<v-row no-gutters class="auth-wrapper">
|
||||
<v-col cols="12" md="8" class="auth-image-background d-none d-md-flex align-center justify-center position-relative">
|
||||
<div class="d-flex auth-img-footer" v-if="!isDarkMode">
|
||||
<v-img class="img-with-direction" src="img/desktop/background.svg"/>
|
||||
</div>
|
||||
<div class="d-flex auth-img-footer" v-if="isDarkMode">
|
||||
<v-img class="img-with-direction" src="img/desktop/background-dark.svg"/>
|
||||
</div>
|
||||
<div class="d-flex align-center justify-center w-100 pt-10">
|
||||
<v-img class="img-with-direction" max-width="300px" src="img/desktop/people2.svg" v-if="!isDarkMode"/>
|
||||
<v-img class="img-with-direction" max-width="300px" src="img/desktop/people2-dark.svg" v-else-if="isDarkMode"/>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4" class="auth-card d-flex flex-column">
|
||||
<div class="d-flex align-center justify-center h-100">
|
||||
<v-card variant="flat" class="w-100 mt-0 px-4 pt-12" max-width="500">
|
||||
<v-card-text>
|
||||
<h4 class="text-h4 mb-2">{{ oauth2LoginDisplayName }}</h4>
|
||||
<p class="mb-0" v-if="!error && platform && token && !userName">{{ tt('Logging in...') }}</p>
|
||||
<p class="mb-0" v-else-if="!error && userName">{{ tt('format.misc.oauth2bindTip', { providerName: oauth2ProviderDisplayName, userName: userName }) }}</p>
|
||||
<p class="mb-0" v-else-if="error">{{ tt(error) }}</p>
|
||||
<p class="mb-0" v-else>{{ tt('An error occurred') }}</p>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text class="pb-0 mb-6" v-if="!error && userName">
|
||||
<v-form>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
type="password"
|
||||
autocomplete="password"
|
||||
:autofocus="true"
|
||||
:disabled="logining"
|
||||
:label="tt('Password')"
|
||||
:placeholder="tt('Your password')"
|
||||
v-model="password"
|
||||
@keyup.enter="verify"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-btn block type="submit" :disabled="!password || logining" @click="verify">
|
||||
{{ tt('Continue') }}
|
||||
<v-progress-circular indeterminate size="22" class="ms-2" v-if="logining"></v-progress-circular>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<router-link class="d-flex align-center justify-center" to="/login"
|
||||
:class="{ 'disabled': logining }">
|
||||
<v-icon class="icon-with-direction" :icon="mdiChevronLeft"/>
|
||||
<span>{{ tt('Back to login page') }}</span>
|
||||
</router-link>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
<v-spacer/>
|
||||
<div class="d-flex align-center justify-center">
|
||||
<v-card variant="flat" class="w-100 px-4 pb-4" max-width="500">
|
||||
<v-card-text class="pt-0">
|
||||
<v-row>
|
||||
<v-col cols="12" class="text-center">
|
||||
<language-select-button :disabled="logining" />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" class="d-flex align-center pt-0">
|
||||
<v-divider />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" class="text-center text-sm">
|
||||
<span>Powered by </span>
|
||||
<a href="https://github.com/mayswind/ezbookkeeping" target="_blank">ezBookkeeping</a> <span>{{ version }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<snack-bar ref="snackbar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useTheme } from 'vuetify';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
import { useLoginPageBase } from '@/views/base/LoginPageBase.ts';
|
||||
|
||||
import { useRootStore } from '@/stores/index.ts';
|
||||
|
||||
import { ThemeType } from '@/core/theme.ts';
|
||||
import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts';
|
||||
import { KnownErrorCode } from '@/consts/api.ts';
|
||||
|
||||
import {
|
||||
isUserVerifyEmailEnabled,
|
||||
getOAuth2Provider,
|
||||
getOIDCCustomDisplayNames
|
||||
} from '@/lib/server_settings.ts';
|
||||
|
||||
import {
|
||||
mdiChevronLeft
|
||||
} from '@mdi/js';
|
||||
|
||||
type SnackBarType = InstanceType<typeof SnackBar>;
|
||||
|
||||
const props = defineProps<{
|
||||
token?: string;
|
||||
provider?: string;
|
||||
platform?: string;
|
||||
userName?: string;
|
||||
error?: string;
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
const theme = useTheme();
|
||||
|
||||
const { tt, getLocalizedOAuth2ProviderName, getLocalizedOAuth2LoginText } = useI18n();
|
||||
|
||||
const rootStore = useRootStore();
|
||||
|
||||
const {
|
||||
version,
|
||||
password,
|
||||
logining,
|
||||
doAfterLogin
|
||||
} = useLoginPageBase('desktop');
|
||||
|
||||
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||
|
||||
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
|
||||
const oauth2ProviderDisplayName = computed<string>(() => getLocalizedOAuth2ProviderName(getOAuth2Provider(), getOIDCCustomDisplayNames()));
|
||||
const oauth2LoginDisplayName = computed<string>(() => getLocalizedOAuth2LoginText(getOAuth2Provider(), getOIDCCustomDisplayNames()));
|
||||
|
||||
const inputProblemMessage = computed<string | null>(() => {
|
||||
if (!password.value) {
|
||||
return 'Password cannot be blank';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
function verify(): void {
|
||||
const problemMessage = inputProblemMessage.value;
|
||||
|
||||
if (problemMessage) {
|
||||
snackbar.value?.showMessage(problemMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
logining.value = true;
|
||||
|
||||
rootStore.authorizeOAuth2({
|
||||
provider: props.provider || '',
|
||||
password: password.value,
|
||||
token: props.token || ''
|
||||
}).then(authResponse => {
|
||||
logining.value = false;
|
||||
doAfterLogin(authResponse);
|
||||
router.replace('/');
|
||||
}).catch(error => {
|
||||
logining.value = false;
|
||||
|
||||
if (isUserVerifyEmailEnabled() && error.error && error.error.errorCode === KnownErrorCode.UserEmailNotVerified && error.error.context && error.error.context.email) {
|
||||
router.push(`/verify_email?email=${encodeURIComponent(error.error.context.email)}&emailSent=${error.error.context.hasValidEmailVerifyToken || false}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!props.error && props.platform && props.token && !props.userName) {
|
||||
logining.value = true;
|
||||
|
||||
rootStore.authorizeOAuth2({
|
||||
provider: props.provider || '',
|
||||
token: props.token || ''
|
||||
}).then(authResponse => {
|
||||
logining.value = false;
|
||||
doAfterLogin(authResponse);
|
||||
router.replace('/');
|
||||
}).catch(error => {
|
||||
logining.value = false;
|
||||
|
||||
if (isUserVerifyEmailEnabled() && error.error && error.error.errorCode === KnownErrorCode.UserEmailNotVerified && error.error.context && error.error.context.email) {
|
||||
router.push(`/verify_email?email=${encodeURIComponent(error.error.context.email)}&emailSent=${error.error.context.hasValidEmailVerifyToken || false}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -9,7 +9,7 @@
|
||||
<f7-block-footer>{{ tips }}</f7-block-footer>
|
||||
</f7-list>
|
||||
|
||||
<f7-list form dividers class="margin-bottom-half">
|
||||
<f7-list form dividers class="margin-bottom-half" v-if="isInternalAuthEnabled()">
|
||||
<f7-list-input
|
||||
type="text"
|
||||
autocomplete="username"
|
||||
@@ -47,8 +47,14 @@
|
||||
</f7-list>
|
||||
|
||||
<f7-list class="margin-vertical-half">
|
||||
<f7-list-button :class="{ 'disabled': inputIsEmpty || logining }" :text="tt('Log In')" @click="login"></f7-list-button>
|
||||
<f7-block-footer>
|
||||
<f7-list-button :class="{ 'disabled': inputIsEmpty || logining }" :text="tt('Log In')" @click="login" v-if="isInternalAuthEnabled()"></f7-list-button>
|
||||
<f7-list-item class="login-divider display-flex align-items-center" v-if="isInternalAuthEnabled() && isOAuth2Enabled()">
|
||||
<hr class="margin-inline-end-half" />
|
||||
<small>{{ tt('or') }}</small>
|
||||
<hr class="margin-inline-start-half" />
|
||||
</f7-list-item>
|
||||
<f7-list-button external :class="{ 'disabled': logining }" :href="oauth2LoginUrl" :text="oauth2LoginDisplayName" v-if="isOAuth2Enabled()"></f7-list-button>
|
||||
<f7-block-footer v-if="isInternalAuthEnabled()">
|
||||
<span>{{ tt('Don\'t have an account?') }}</span>
|
||||
<f7-link :class="{'disabled': !isUserRegistrationEnabled()}" href="/signup" :text="tt('Create an account')"></f7-link>
|
||||
</f7-block-footer>
|
||||
@@ -176,7 +182,15 @@ import { useRootStore } from '@/stores/index.ts';
|
||||
|
||||
import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts';
|
||||
import { KnownErrorCode } from '@/consts/api.ts';
|
||||
import { isUserRegistrationEnabled, isUserForgetPasswordEnabled, isUserVerifyEmailEnabled } from '@/lib/server_settings.ts';
|
||||
|
||||
import { generateRandomUUID } from '@/lib/misc.ts';
|
||||
import {
|
||||
isUserRegistrationEnabled,
|
||||
isUserForgetPasswordEnabled,
|
||||
isUserVerifyEmailEnabled,
|
||||
isInternalAuthEnabled,
|
||||
isOAuth2Enabled
|
||||
} from '@/lib/server_settings.ts';
|
||||
import { getDesktopVersionPath } from '@/lib/version.ts';
|
||||
import { useI18nUIComponents, showLoading, hideLoading, isModalShowing } from '@/lib/ui/mobile.ts';
|
||||
|
||||
@@ -197,13 +211,16 @@ const {
|
||||
backupCode,
|
||||
tempToken,
|
||||
twoFAVerifyType,
|
||||
oauth2ClientSessionId,
|
||||
logining,
|
||||
verifying,
|
||||
inputIsEmpty,
|
||||
twoFAInputIsEmpty,
|
||||
oauth2LoginUrl,
|
||||
oauth2LoginDisplayName,
|
||||
tips,
|
||||
doAfterLogin
|
||||
} = useLoginPageBase();
|
||||
} = useLoginPageBase('mobile');
|
||||
|
||||
const forgetPasswordEmail = ref<string>('');
|
||||
const resendVerifyEmail = ref<string>('');
|
||||
@@ -389,4 +406,33 @@ function switch2FAVerifyType(): void {
|
||||
twoFAVerifyType.value = 'passcode';
|
||||
}
|
||||
}
|
||||
|
||||
oauth2ClientSessionId.value = generateRandomUUID();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.login-divider > .item-content {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
|
||||
> .item-inner {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
min-height: 0;
|
||||
|
||||
> small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
> hr {display: block;
|
||||
flex: 1 1 100%;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
border-style: solid;
|
||||
border-width: thin 0 0 0;
|
||||
opacity: 0.12;
|
||||
transition: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user