mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
Merge pull request #46 from logto-io/gao--add-phrases-package
feat(phrases): add package and refactor error code
This commit is contained in:
commit
ee06a61503
32 changed files with 269 additions and 120 deletions
40
.github/workflows/phrases-main.yml
vendored
Normal file
40
.github/workflows/phrases-main.yml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
name: Phrases
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths: [ 'packages/phrases/**' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths: [ 'packages/phrases/**' ]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install packages
|
||||
run: yarn
|
||||
|
||||
- name: Lint
|
||||
working-directory: packages/phrases
|
||||
run: yarn lint
|
||||
|
||||
- name: Build
|
||||
working-directory: packages/phrases
|
||||
run: yarn build
|
|
@ -14,12 +14,14 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@logto/essentials": "^1.1.0-rc.1",
|
||||
"@logto/phrases": "^0.1.0",
|
||||
"@logto/schemas": "^0.1.0",
|
||||
"dayjs": "^1.10.5",
|
||||
"decamelize": "^5.0.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"formidable": "^1.2.2",
|
||||
"got": "^11.8.2",
|
||||
"i18next": "^20.3.5",
|
||||
"koa": "^2.13.1",
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-logger": "^3.2.1",
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export enum GuardErrorCode {
|
||||
InvalidInput = 'guard.invalid_input',
|
||||
}
|
||||
|
||||
export const guardErrorMessage: Record<GuardErrorCode, string> = {
|
||||
[GuardErrorCode.InvalidInput]: 'The request input is invalid.',
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
export enum OidcErrorCode {
|
||||
Aborted = 'oidc.aborted',
|
||||
}
|
||||
|
||||
export const oidcErrorMessage: Record<OidcErrorCode, string> = {
|
||||
[OidcErrorCode.Aborted]: 'The end-user aborted interaction.',
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
export enum RegisterErrorCode {
|
||||
UsernameExists = 'register.username_exists',
|
||||
}
|
||||
|
||||
export const registerErrorMessage: Record<RegisterErrorCode, string> = {
|
||||
[RegisterErrorCode.UsernameExists]: 'The username already exists.',
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
export enum SignInErrorCode {
|
||||
InvalidCredentials = 'sign_in.invalid_credentials',
|
||||
InvalidSignInMethod = 'sign_in.invalid_sign_in_method',
|
||||
InsufficientInfo = 'sign_in.insufficient_info',
|
||||
}
|
||||
|
||||
export const signInErrorMessage: Record<SignInErrorCode, string> = {
|
||||
[SignInErrorCode.InvalidCredentials]: 'Invalid credentials. Please check your input.',
|
||||
[SignInErrorCode.InvalidSignInMethod]: 'Current sign-in method is not available.',
|
||||
[SignInErrorCode.InsufficientInfo]: 'Insufficent sign-in info.',
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
export enum SwaggerErrorCode {
|
||||
InvalidZodType = 'swagger.invalid_zod_type',
|
||||
}
|
||||
|
||||
export const swaggerErrorMessage: Record<SwaggerErrorCode, string> = {
|
||||
[SwaggerErrorCode.InvalidZodType]: 'Invalid Zod type, please check route guard config.',
|
||||
};
|
|
@ -1,19 +1,19 @@
|
|||
import pick from 'lodash.pick';
|
||||
import { requestErrorMessage } from './message';
|
||||
import { RequestErrorBody, RequestErrorCode, RequestErrorMetadata } from './types';
|
||||
import i18next from 'i18next';
|
||||
import { LogtoErrorCode } from '@logto/phrases';
|
||||
import { RequestErrorBody, RequestErrorMetadata } from './types';
|
||||
|
||||
export * from './types';
|
||||
export * from './message';
|
||||
|
||||
export default class RequestError extends Error {
|
||||
code: RequestErrorCode;
|
||||
code: LogtoErrorCode;
|
||||
status: number;
|
||||
expose: boolean;
|
||||
data: unknown;
|
||||
|
||||
constructor(input: RequestErrorMetadata | RequestErrorCode, data?: unknown) {
|
||||
constructor(input: RequestErrorMetadata | LogtoErrorCode, data?: unknown) {
|
||||
const { code, status = 400 } = typeof input === 'string' ? { code: input } : input;
|
||||
const message = requestErrorMessage[code];
|
||||
const message = i18next.t<string, LogtoErrorCode>(code);
|
||||
|
||||
super(message);
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import { RequestErrorCode } from './types';
|
||||
import { guardErrorMessage } from './collection/guard-errors';
|
||||
import { oidcErrorMessage } from './collection/oidc-errors';
|
||||
import { registerErrorMessage } from './collection/register-errors';
|
||||
import { swaggerErrorMessage } from './collection/swagger-errors';
|
||||
import { signInErrorMessage } from './collection/sign-in-errors';
|
||||
|
||||
export const requestErrorMessage: Record<RequestErrorCode, string> = {
|
||||
...guardErrorMessage,
|
||||
...oidcErrorMessage,
|
||||
...registerErrorMessage,
|
||||
...swaggerErrorMessage,
|
||||
...signInErrorMessage,
|
||||
};
|
|
@ -1,20 +1,7 @@
|
|||
import { GuardErrorCode } from './collection/guard-errors';
|
||||
import { OidcErrorCode } from './collection/oidc-errors';
|
||||
import { RegisterErrorCode } from './collection/register-errors';
|
||||
import { SwaggerErrorCode } from './collection/swagger-errors';
|
||||
import { SignInErrorCode } from './collection/sign-in-errors';
|
||||
|
||||
export { GuardErrorCode, OidcErrorCode, SwaggerErrorCode, RegisterErrorCode, SignInErrorCode };
|
||||
|
||||
export type RequestErrorCode =
|
||||
| GuardErrorCode
|
||||
| OidcErrorCode
|
||||
| RegisterErrorCode
|
||||
| SwaggerErrorCode
|
||||
| SignInErrorCode;
|
||||
import { LogtoErrorCode } from '@logto/phrases';
|
||||
|
||||
export type RequestErrorMetadata = {
|
||||
code: RequestErrorCode;
|
||||
code: LogtoErrorCode;
|
||||
status?: number;
|
||||
};
|
||||
|
||||
|
|
|
@ -6,12 +6,14 @@ import dotenv from 'dotenv';
|
|||
dotenv.config();
|
||||
|
||||
import Koa from 'koa';
|
||||
import initApp from './init';
|
||||
import initI18n from './init/i18n';
|
||||
import initApp from './init/app';
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await initI18n();
|
||||
await initApp(app);
|
||||
} catch (error: unknown) {
|
||||
console.log('Error while initializing app', error);
|
||||
|
|
9
packages/core/src/init/i18n.ts
Normal file
9
packages/core/src/init/i18n.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import i18next from 'i18next';
|
||||
import resources from '@logto/phrases';
|
||||
|
||||
export default async function initI18n() {
|
||||
await i18next.init({
|
||||
lng: 'en',
|
||||
resources,
|
||||
});
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import RequestError, { GuardErrorCode } from '@/errors/RequestError';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { has } from '@logto/essentials';
|
||||
import { Middleware } from 'koa';
|
||||
import koaBody from 'koa-body';
|
||||
|
@ -69,7 +69,7 @@ export default function koaGuard<
|
|||
params: params?.parse(ctx.params),
|
||||
} as Guarded<GuardQueryT, GuardBodyT, GuardParametersT>; // Have to do t His since it's too complicated for TS
|
||||
} catch (error: unknown) {
|
||||
throw new RequestError(GuardErrorCode.InvalidInput, error);
|
||||
throw new RequestError('guard.invalid_input', error);
|
||||
}
|
||||
|
||||
await next();
|
||||
|
|
|
@ -5,7 +5,7 @@ import { hasUser, hasUserWithId, insertUser } from '@/queries/user';
|
|||
import { customAlphabet, nanoid } from 'nanoid';
|
||||
import { PasswordEncryptionMethod } from '@logto/schemas';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import RequestError, { RegisterErrorCode } from '@/errors/RequestError';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
|
||||
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
const userId = customAlphabet(alphabet, 12);
|
||||
|
@ -37,7 +37,7 @@ export default function registerRoutes() {
|
|||
const { username, password } = ctx.guard.body;
|
||||
|
||||
if (await hasUser(username)) {
|
||||
throw new RequestError(RegisterErrorCode.UsernameExists);
|
||||
throw new RequestError('register.username_exists');
|
||||
}
|
||||
|
||||
const id = await generateUserId();
|
||||
|
|
|
@ -6,7 +6,8 @@ import { findUserByUsername } from '@/queries/user';
|
|||
import { Provider } from 'oidc-provider';
|
||||
import { conditional } from '@logto/essentials';
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import RequestError, { OidcErrorCode, SignInErrorCode } from '@/errors/RequestError';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { LogtoErrorCode } from '@logto/phrases';
|
||||
|
||||
export default function signInRoutes(provider: Provider) {
|
||||
const router = new Router();
|
||||
|
@ -22,7 +23,7 @@ export default function signInRoutes(provider: Provider) {
|
|||
if (name === 'login') {
|
||||
const { username, password } = ctx.guard.body;
|
||||
|
||||
assert(username && password, new RequestError(SignInErrorCode.InsufficientInfo));
|
||||
assert(username && password, new RequestError('sign_in.insufficient_info'));
|
||||
|
||||
try {
|
||||
const { id, passwordEncrypted, passwordEncryptionMethod, passwordEncryptionSalt } =
|
||||
|
@ -30,12 +31,12 @@ export default function signInRoutes(provider: Provider) {
|
|||
|
||||
assert(
|
||||
passwordEncrypted && passwordEncryptionMethod && passwordEncryptionSalt,
|
||||
new RequestError(SignInErrorCode.InvalidSignInMethod)
|
||||
new RequestError('sign_in.invalid_sign_in_method')
|
||||
);
|
||||
assert(
|
||||
encryptPassword(id, password, passwordEncryptionSalt, passwordEncryptionMethod) ===
|
||||
passwordEncrypted,
|
||||
new RequestError(SignInErrorCode.InvalidCredentials)
|
||||
new RequestError('sign_in.invalid_credentials')
|
||||
);
|
||||
|
||||
const redirectTo = await provider.interactionResult(
|
||||
|
@ -49,7 +50,7 @@ export default function signInRoutes(provider: Provider) {
|
|||
ctx.body = { redirectTo };
|
||||
} catch (error: unknown) {
|
||||
if (!(error instanceof RequestError)) {
|
||||
throw new RequestError(SignInErrorCode.InvalidCredentials);
|
||||
throw new RequestError('sign_in.invalid_credentials');
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
@ -98,8 +99,9 @@ export default function signInRoutes(provider: Provider) {
|
|||
|
||||
router.post('/sign-in/abort', async (ctx) => {
|
||||
await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const error: LogtoErrorCode = 'oidc.aborted';
|
||||
const redirectTo = await provider.interactionResult(ctx.req, ctx.res, {
|
||||
error: OidcErrorCode.Aborted,
|
||||
error,
|
||||
});
|
||||
ctx.body = { redirectTo };
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { ZodArray, ZodBoolean, ZodNumber, ZodObject, ZodOptional, ZodString } from 'zod';
|
||||
import RequestError, { SwaggerErrorCode } from '@/errors/RequestError';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { conditional } from '@logto/essentials';
|
||||
|
||||
export const zodTypeToSwagger = (config: unknown): OpenAPIV3.SchemaObject => {
|
||||
|
@ -46,5 +46,5 @@ export const zodTypeToSwagger = (config: unknown): OpenAPIV3.SchemaObject => {
|
|||
};
|
||||
}
|
||||
|
||||
throw new RequestError(SwaggerErrorCode.InvalidZodType, config);
|
||||
throw new RequestError('swagger.invalid_zod_type', config);
|
||||
};
|
||||
|
|
11
packages/phrases/README.md
Normal file
11
packages/phrases/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# `@logto/phrases`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const phrases = require('@logto/phrases');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
43
packages/phrases/package.json
Normal file
43
packages/phrases/package.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "@logto/phrases",
|
||||
"version": "0.1.0",
|
||||
"description": "Logto shared phrases (l10n).",
|
||||
"author": "Gao Sun <gaosun.dev@gmail.com>",
|
||||
"homepage": "https://github.com/logto-io/logto#readme",
|
||||
"license": "UNLICENSED",
|
||||
"main": "lib/index.js",
|
||||
"private": true,
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.yarnpkg.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/logto-io/logto.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rm -rf lib/ && tsc",
|
||||
"lint": "eslint --format pretty \"src/**\"",
|
||||
"prepack": "yarn build"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/logto-io/logto/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@logto/eslint-config": "^0.1.0-rc.14",
|
||||
"@logto/ts-config": "^0.1.0-rc.14",
|
||||
"eslint": "^7.31.0",
|
||||
"eslint-formatter-pretty": "^4.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@logto"
|
||||
},
|
||||
"prettier": "@logto/eslint-config/.prettierrc",
|
||||
"dependencies": {
|
||||
"@logto/schemas": "^0.1.0"
|
||||
}
|
||||
}
|
12
packages/phrases/src/index.ts
Normal file
12
packages/phrases/src/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import en from './locales/en';
|
||||
import zhCN from './locales/zh-cn';
|
||||
import { Normalize, Resource } from './types';
|
||||
|
||||
export type LogtoErrorCode = Normalize<typeof en.errors>;
|
||||
|
||||
const resource: Resource = {
|
||||
en,
|
||||
'zh-CN': zhCN,
|
||||
};
|
||||
|
||||
export default resource;
|
39
packages/phrases/src/locales/en.ts
Normal file
39
packages/phrases/src/locales/en.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
const translation = {
|
||||
sign_in: {
|
||||
title: 'Sign In',
|
||||
loading: 'Signing in...',
|
||||
error: 'Username or password is invalid.',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
},
|
||||
register: {
|
||||
create_account: 'Create an Account',
|
||||
},
|
||||
};
|
||||
|
||||
const errors = {
|
||||
guard: {
|
||||
invalid_input: 'The request input is invalid.',
|
||||
},
|
||||
oidc: {
|
||||
aborted: 'The end-user aborted interaction.',
|
||||
},
|
||||
register: {
|
||||
username_exists: 'The username already exists.',
|
||||
},
|
||||
sign_in: {
|
||||
invalid_credentials: 'Invalid credentials. Please check your input.',
|
||||
invalid_sign_in_method: 'Current sign-in method is not available.',
|
||||
insufficient_info: 'Insufficent sign-in info.',
|
||||
},
|
||||
swagger: {
|
||||
invalid_zod_type: 'Invalid Zod type, please check route guard config.',
|
||||
},
|
||||
};
|
||||
|
||||
const en = Object.freeze({
|
||||
translation,
|
||||
errors,
|
||||
});
|
||||
|
||||
export default en;
|
41
packages/phrases/src/locales/zh-cn.ts
Normal file
41
packages/phrases/src/locales/zh-cn.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import en from './en';
|
||||
|
||||
const translation = {
|
||||
sign_in: {
|
||||
title: '登录',
|
||||
loading: '登录中...',
|
||||
error: '用户名或密码错误。',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
},
|
||||
register: {
|
||||
create_account: '新用户注册',
|
||||
},
|
||||
};
|
||||
|
||||
const errors = {
|
||||
guard: {
|
||||
invalid_input: '请求内容有误。',
|
||||
},
|
||||
oidc: {
|
||||
aborted: '用户终止了交互。',
|
||||
},
|
||||
register: {
|
||||
username_exists: '用户名已存在。',
|
||||
},
|
||||
sign_in: {
|
||||
invalid_credentials: '用户名或密码错误,请检查您的输入。',
|
||||
invalid_sign_in_method: '当前登录方式不可用。',
|
||||
insufficient_info: '登录信息缺失,请检查您的输入。',
|
||||
},
|
||||
swagger: {
|
||||
invalid_zod_type: '无效的 Zod 类型,请检查路由 guard 配置。',
|
||||
},
|
||||
};
|
||||
|
||||
const zhCN: typeof en = Object.freeze({
|
||||
translation,
|
||||
errors,
|
||||
});
|
||||
|
||||
export default zhCN;
|
29
packages/phrases/src/types.ts
Normal file
29
packages/phrases/src/types.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* eslint-disable @typescript-eslint/consistent-indexed-object-style */
|
||||
|
||||
/* Copied from i18next/index.d.ts */
|
||||
export interface Resource {
|
||||
[language: string]: ResourceLanguage;
|
||||
}
|
||||
|
||||
export interface ResourceLanguage {
|
||||
[namespace: string]: ResourceKey;
|
||||
}
|
||||
|
||||
export type ResourceKey =
|
||||
| string
|
||||
| {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
/* Copied from react-i18next/ts4.1/index.d.ts */
|
||||
// Normalize single namespace
|
||||
type AppendKeys<K1, K2> = `${K1 & string}.${K2 & string}`;
|
||||
type AppendKeys2<K1, K2> = `${K1 & string}.${Exclude<K2, keyof any[]> & string}`;
|
||||
type Normalize2<T, K = keyof T> = K extends keyof T
|
||||
? T[K] extends Record<string, any>
|
||||
? T[K] extends readonly any[]
|
||||
? AppendKeys2<K, keyof T[K]> | AppendKeys2<K, Normalize2<T[K]>>
|
||||
: AppendKeys<K, keyof T[K]> | AppendKeys<K, Normalize2<T[K]>>
|
||||
: never
|
||||
: never;
|
||||
export type Normalize<T> = keyof T | Normalize2<T>;
|
8
packages/phrases/tsconfig.json
Normal file
8
packages/phrases/tsconfig.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "@logto/ts-config/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
"test": "razzle test --env=jsdom"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/phrases": "^0.1.0",
|
||||
"classnames": "^2.3.1",
|
||||
"i18next": "^20.3.3",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { ReactChildren } from 'react';
|
||||
import React, { ReactChild } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
className?: string;
|
||||
children: ReactChildren;
|
||||
children: ReactChild;
|
||||
href: string;
|
||||
};
|
||||
|
||||
|
|
2
packages/ui/src/include.d/react-i18next.d.ts
vendored
2
packages/ui/src/include.d/react-i18next.d.ts
vendored
|
@ -2,7 +2,7 @@
|
|||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import 'react-i18next';
|
||||
import en from '@/locales/en.json';
|
||||
import en from '@logto/phrases/lib/locales/en.js';
|
||||
|
||||
declare module 'react-i18next' {
|
||||
interface CustomTypeOptions {
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
import i18n from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import en from '@/locales/en.json';
|
||||
import zhCN from '@/locales/zh-CN.json';
|
||||
import resources from '@logto/phrases';
|
||||
|
||||
const initI18n = () => {
|
||||
void i18n
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
resources: {
|
||||
en,
|
||||
'zh-CN': zhCN,
|
||||
},
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"translation": {
|
||||
"sign_in": "Sign In",
|
||||
"sign_in.loading": "Signing in...",
|
||||
"sign_in.error": "Username or password is invalid.",
|
||||
"sign_in.username": "Username",
|
||||
"sign_in.password": "Password",
|
||||
"register.create_account": "Create an Account"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"translation": {
|
||||
"sign_in": "登录",
|
||||
"sign_in.loading": "登录中...",
|
||||
"sign_in.error": "用户名或密码错误。",
|
||||
"sign_in.username": "用户名",
|
||||
"sign_in.password": "密码",
|
||||
"register.create_account": "新用户注册"
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ const Home = () => {
|
|||
<Input
|
||||
autoComplete="username"
|
||||
isDisabled={isLoading}
|
||||
placeholder={t('sign_in.username')}
|
||||
placeholder={t('sign_in.password')}
|
||||
value={username}
|
||||
onChange={setUsername}
|
||||
/>
|
||||
|
@ -50,7 +50,7 @@ const Home = () => {
|
|||
)}
|
||||
<Button
|
||||
isDisabled={isLoading}
|
||||
value={isLoading ? t('sign_in.loading') : t('sign_in')}
|
||||
value={isLoading ? t('sign_in.loading') : t('sign_in.title')}
|
||||
onClick={signIn}
|
||||
/>
|
||||
<TextLink className={styles.createAccount} href="/register">
|
||||
|
|
|
@ -6294,7 +6294,7 @@ eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
|
||||
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
|
||||
|
||||
eslint@^7.30.0:
|
||||
eslint@^7.30.0, eslint@^7.31.0:
|
||||
version "7.31.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.31.0.tgz#f972b539424bf2604907a970860732c5d99d3aca"
|
||||
integrity sha512-vafgJpSh2ia8tnTkNUkwxGmnumgckLh5aAbLa1xRmIn9+owi8qBNGKL+B881kNKNTy7FFqTEkpNkUvmw0n6PkA==
|
||||
|
@ -7748,7 +7748,7 @@ i18next-browser-languagedetector@^6.1.2:
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.14.6"
|
||||
|
||||
i18next@^20.3.3:
|
||||
i18next@^20.3.3, i18next@^20.3.5:
|
||||
version "20.3.5"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-20.3.5.tgz#14308b79a3f1cafb24fdcd8e182d3673baf1e979"
|
||||
integrity sha512-//MGeU6n4TencJmCgG+TCrpdgAD/NDEU/KfKQekYbJX6QV7sD/NjWQdVdBi+bkT0snegnSoB7QhjSeatrk3a0w==
|
||||
|
|
Loading…
Add table
Reference in a new issue