mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(phrases,shared,connector-core): move language key to shared (#1838)
This commit is contained in:
parent
3d92f35589
commit
d952d8660d
29 changed files with 165 additions and 316 deletions
|
@ -18,7 +18,7 @@
|
|||
"prepack": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/phrases": "^1.0.0-beta.6",
|
||||
"@logto/shared": "^1.0.0-beta.6",
|
||||
"@silverhand/essentials": "^1.2.0",
|
||||
"zod": "^3.14.3"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Language } from '@logto/phrases';
|
||||
import type { LanguageKey } from '@logto/shared';
|
||||
import { Nullable } from '@silverhand/essentials';
|
||||
import { z, ZodType } from 'zod';
|
||||
|
||||
|
@ -14,8 +14,8 @@ export enum ConnectorPlatform {
|
|||
Web = 'Web',
|
||||
}
|
||||
|
||||
type I18nPhrases = { [Language.English]: string } & {
|
||||
[key in Exclude<Language, Language.English>]?: string;
|
||||
type I18nPhrases = { en: string } & {
|
||||
[K in Exclude<LanguageKey, 'en'>]?: string;
|
||||
};
|
||||
|
||||
export type ConnectorMetadata = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Language } from '@logto/phrases';
|
||||
import { useLogto } from '@logto/react';
|
||||
import { AppearanceMode } from '@logto/schemas';
|
||||
import { languageKeys } from '@logto/shared';
|
||||
import { Nullable, Optional } from '@silverhand/essentials';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
@ -11,7 +11,7 @@ import { themeStorageKey } from '@/consts';
|
|||
import useApi, { RequestError } from './use-api';
|
||||
|
||||
const userPreferencesGuard = z.object({
|
||||
language: z.nativeEnum(Language).optional(),
|
||||
language: z.enum(languageKeys).optional(),
|
||||
appearanceMode: z.nativeEnum(AppearanceMode),
|
||||
experienceNoticeConfirmed: z.boolean().optional(),
|
||||
getStartedHidden: z.boolean().optional(),
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import resources, { Language } from '@logto/phrases';
|
||||
import resources from '@logto/phrases';
|
||||
import type { LanguageKey } from '@logto/shared';
|
||||
import i18next from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
const initI18n = async (language?: Language) =>
|
||||
const initI18n = async (language?: LanguageKey) =>
|
||||
i18next
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { languageEnumGuard } from '@logto/phrases';
|
||||
import { ConnectorDto, ConnectorType } from '@logto/schemas';
|
||||
import { languageKeyGuard } from '@logto/shared';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import i18next from 'i18next';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
@ -33,7 +33,7 @@ const Guide = ({ connector, onClose }: Props) => {
|
|||
const { id: connectorId, type: connectorType, name, configTemplate, readme } = connector;
|
||||
|
||||
const localeRaw = i18next.language;
|
||||
const result = languageEnumGuard.safeParse(localeRaw);
|
||||
const result = languageKeyGuard.safeParse(localeRaw);
|
||||
const connectorName = result.success ? name[result.data] : name.en;
|
||||
|
||||
const isSocialConnector =
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Language, languageOptions } from '@logto/phrases';
|
||||
import { languageOptions } from '@logto/phrases';
|
||||
import { AppearanceMode } from '@logto/schemas';
|
||||
import { languageKeyGuard, languageKeys } from '@logto/shared';
|
||||
import classNames from 'classnames';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
@ -24,9 +25,7 @@ const Settings = () => {
|
|||
i18n: { language },
|
||||
} = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const defaultLanguage = Object.values<string>(Language).includes(language)
|
||||
? language
|
||||
: Language.English;
|
||||
const defaultLanguage = languageKeyGuard.default('en').parse(language);
|
||||
|
||||
const { data, error, update, isLoading, isLoaded } = useUserPreferences();
|
||||
const {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// FIXME: @sijie
|
||||
/* eslint-disable react/iframe-missing-sandbox */
|
||||
import { Language, languageOptions } from '@logto/phrases-ui';
|
||||
import { languageOptions } from '@logto/phrases-ui';
|
||||
import { AppearanceMode, ConnectorDto, ConnectorMetadata, SignInExperience } from '@logto/schemas';
|
||||
import type { LanguageKey } from '@logto/shared';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import classNames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
|
@ -24,7 +25,7 @@ type Props = {
|
|||
|
||||
const Preview = ({ signInExperience, className }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const [language, setLanguage] = useState<Language>(Language.English);
|
||||
const [language, setLanguage] = useState<LanguageKey>('en');
|
||||
const [mode, setMode] = useState<AppearanceMode>(AppearanceMode.LightMode);
|
||||
const [platform, setPlatform] = useState<'desktopWeb' | 'mobile' | 'mobileWeb'>('desktopWeb');
|
||||
const { data: allConnectors } = useSWR<ConnectorDto[], RequestError>('/api/connectors');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Language } from '@logto/phrases';
|
||||
import { SignInExperience, SignInMethodKey } from '@logto/schemas';
|
||||
import type { LanguageKey } from '@logto/shared';
|
||||
|
||||
export enum LanguageMode {
|
||||
Auto = 'Auto',
|
||||
|
@ -17,8 +17,8 @@ export type SignInExperienceForm = Omit<SignInExperience, 'signInMethods' | 'lan
|
|||
};
|
||||
languageInfo: {
|
||||
mode: LanguageMode;
|
||||
fixedLanguage: Language;
|
||||
fallbackLanguage: Language;
|
||||
fixedLanguage: LanguageKey;
|
||||
fallbackLanguage: LanguageKey;
|
||||
};
|
||||
createAccountEnabled: boolean;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Language } from '@logto/phrases';
|
||||
import {
|
||||
Branding,
|
||||
BrandingStyle,
|
||||
|
@ -28,8 +27,8 @@ export const mockSignInExperience: SignInExperience = {
|
|||
},
|
||||
languageInfo: {
|
||||
autoDetect: true,
|
||||
fallbackLanguage: Language.English,
|
||||
fixedLanguage: Language.Chinese,
|
||||
fallbackLanguage: 'en',
|
||||
fixedLanguage: 'zh-CN',
|
||||
},
|
||||
signInMethods: {
|
||||
username: SignInMethodState.Primary,
|
||||
|
@ -60,8 +59,8 @@ export const mockTermsOfUse: TermsOfUse = {
|
|||
|
||||
export const mockLanguageInfo: LanguageInfo = {
|
||||
autoDetect: true,
|
||||
fallbackLanguage: Language.English,
|
||||
fixedLanguage: Language.Chinese,
|
||||
fallbackLanguage: 'en',
|
||||
fixedLanguage: 'zh-CN',
|
||||
};
|
||||
|
||||
export const mockSignInMethods: SignInMethods = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Language } from '@logto/phrases';
|
||||
import { CreateSignInExperience, SignInExperience, SignInMethodState } from '@logto/schemas';
|
||||
import { languageKeys } from '@logto/shared';
|
||||
|
||||
import {
|
||||
mockAliyunDmConnector,
|
||||
|
@ -104,7 +104,7 @@ describe('languageInfo', () => {
|
|||
});
|
||||
});
|
||||
|
||||
const validLanguages = Object.values(Language);
|
||||
const validLanguages = languageKeys;
|
||||
const invalidLanguages = [undefined, null, '', ' \t\n\r', 'abc'];
|
||||
|
||||
describe('fallbackLanguage', () => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { OpenAPIV3 } from 'openapi-types';
|
|||
import {
|
||||
ZodArray,
|
||||
ZodBoolean,
|
||||
ZodEnum,
|
||||
ZodLiteral,
|
||||
ZodNativeEnum,
|
||||
ZodNullable,
|
||||
|
@ -111,7 +112,7 @@ export const zodTypeToSwagger = (config: unknown): OpenAPIV3.SchemaObject => {
|
|||
};
|
||||
}
|
||||
|
||||
if (config instanceof ZodNativeEnum) {
|
||||
if (config instanceof ZodNativeEnum || config instanceof ZodEnum) {
|
||||
return {
|
||||
type: 'string',
|
||||
enum: Object.values(config.enum),
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import resources, { Language } from '@logto/phrases';
|
||||
import resources from '@logto/phrases';
|
||||
import type { LanguageKey } from '@logto/shared';
|
||||
import i18next from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
const initI18n = async (language?: Language) =>
|
||||
const initI18n = async (language?: LanguageKey) =>
|
||||
i18next
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"url": "https://github.com/logto-io/logto/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/shared": "^1.0.0-beta.6",
|
||||
"@silverhand/essentials": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -5,18 +5,18 @@ import fr from './locales/fr';
|
|||
import koKR from './locales/ko-kr';
|
||||
import trTR from './locales/tr-tr';
|
||||
import zhCN from './locales/zh-cn';
|
||||
import { Resource, Language } from './types';
|
||||
import { Resource } from './types';
|
||||
|
||||
export { Language, languageOptions } from './types';
|
||||
export { languageOptions } from './types';
|
||||
|
||||
export type I18nKey = NormalizeKeyPaths<typeof en.translation>;
|
||||
|
||||
const resource: Resource = {
|
||||
[Language.English]: en,
|
||||
[Language.French]: fr,
|
||||
[Language.Chinese]: zhCN,
|
||||
[Language.Korean]: koKR,
|
||||
[Language.Turkish]: trTR,
|
||||
en,
|
||||
fr,
|
||||
'zh-CN': zhCN,
|
||||
'ko-KR': koKR,
|
||||
'tr-TR': trTR,
|
||||
};
|
||||
|
||||
export default resource;
|
||||
|
|
|
@ -1,28 +1,20 @@
|
|||
import { LanguageKey, languageKeyGuard } from '@logto/shared';
|
||||
|
||||
/* Copied from i18next/index.d.ts */
|
||||
export type Resource = Record<Language, ResourceLanguage>;
|
||||
export type Resource = Record<LanguageKey, ResourceLanguage>;
|
||||
|
||||
export type ResourceLanguage = Record<string, ResourceKey>;
|
||||
|
||||
export type ResourceKey = string | Record<string, unknown>;
|
||||
|
||||
export enum Language {
|
||||
English = 'en',
|
||||
French = 'fr',
|
||||
Chinese = 'zh-CN',
|
||||
Turkish = 'tr-TR',
|
||||
Korean = 'ko-KR',
|
||||
}
|
||||
|
||||
const languageCodeAndDisplayNameMappings: Record<Language, string> = {
|
||||
[Language.English]: 'English',
|
||||
[Language.French]: 'Français',
|
||||
[Language.Chinese]: '简体中文',
|
||||
[Language.Turkish]: 'Türkçe',
|
||||
[Language.Korean]: '한국어',
|
||||
const languageCodeAndDisplayNameMappings: Record<LanguageKey, string> = {
|
||||
en: 'English',
|
||||
fr: 'Français',
|
||||
'zh-CN': '简体中文',
|
||||
'tr-TR': 'Türkçe',
|
||||
'ko-KR': '한국어',
|
||||
};
|
||||
|
||||
export const languageOptions = Object.entries(languageCodeAndDisplayNameMappings)
|
||||
.filter((entry): entry is [Language, string] =>
|
||||
Object.values<string>(Language).includes(entry[0])
|
||||
)
|
||||
.map(([key, value]) => ({ value: key, title: value }));
|
||||
export const languageOptions = Object.entries(languageCodeAndDisplayNameMappings).map(
|
||||
([key, value]) => ({ value: languageKeyGuard.parse(key), title: value })
|
||||
);
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
"url": "https://github.com/logto-io/logto/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@silverhand/essentials": "^1.2.0",
|
||||
"zod": "^3.14.3"
|
||||
"@logto/shared": "^1.0.0-beta.6",
|
||||
"@silverhand/essentials": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@silverhand/eslint-config": "1.0.0-rc.2",
|
||||
|
|
|
@ -5,9 +5,9 @@ import fr from './locales/fr';
|
|||
import koKR from './locales/ko-kr';
|
||||
import trTR from './locales/tr-tr';
|
||||
import zhCN from './locales/zh-cn';
|
||||
import { Resource, Language } from './types';
|
||||
import { Resource } from './types';
|
||||
|
||||
export { Language, languageOptions, languageEnumGuard } from './types';
|
||||
export { languageOptions } from './types';
|
||||
export type Translation = typeof en.translation;
|
||||
export type Errors = typeof en.errors;
|
||||
export type LogtoErrorCode = NormalizeKeyPaths<Errors>;
|
||||
|
@ -16,11 +16,11 @@ export type I18nKey = NormalizeKeyPaths<Translation>;
|
|||
export type AdminConsoleKey = NormalizeKeyPaths<typeof en.translation.admin_console>;
|
||||
|
||||
const resource: Resource = {
|
||||
[Language.English]: en,
|
||||
[Language.French]: fr,
|
||||
[Language.Chinese]: zhCN,
|
||||
[Language.Korean]: koKR,
|
||||
[Language.Turkish]: trTR,
|
||||
en,
|
||||
fr,
|
||||
'zh-CN': zhCN,
|
||||
'ko-KR': koKR,
|
||||
'tr-TR': trTR,
|
||||
};
|
||||
|
||||
export default resource;
|
||||
|
|
|
@ -1,32 +1,20 @@
|
|||
import { z } from 'zod';
|
||||
import { LanguageKey, languageKeyGuard } from '@logto/shared';
|
||||
|
||||
/* Copied from i18next/index.d.ts */
|
||||
export type Resource = Record<Language, ResourceLanguage>;
|
||||
export type Resource = Record<LanguageKey, ResourceLanguage>;
|
||||
|
||||
export type ResourceLanguage = Record<string, ResourceKey>;
|
||||
|
||||
export type ResourceKey = string | Record<string, unknown>;
|
||||
|
||||
export enum Language {
|
||||
English = 'en',
|
||||
French = 'fr',
|
||||
Chinese = 'zh-CN',
|
||||
Turkish = 'tr-TR',
|
||||
Korean = 'ko-KR',
|
||||
}
|
||||
|
||||
export const languageEnumGuard = z.nativeEnum(Language);
|
||||
|
||||
const languageCodeAndDisplayNameMappings: Record<Language, string> = {
|
||||
[Language.English]: 'English',
|
||||
[Language.French]: 'Français',
|
||||
[Language.Chinese]: '简体中文',
|
||||
[Language.Turkish]: 'Türkçe',
|
||||
[Language.Korean]: '한국어',
|
||||
const languageCodeAndDisplayNameMappings: Record<LanguageKey, string> = {
|
||||
en: 'English',
|
||||
fr: 'Français',
|
||||
'zh-CN': '简体中文',
|
||||
'tr-TR': 'Türkçe',
|
||||
'ko-KR': '한국어',
|
||||
};
|
||||
|
||||
export const languageOptions = Object.entries(languageCodeAndDisplayNameMappings)
|
||||
.filter((entry): entry is [Language, string] =>
|
||||
Object.values<string>(Language).includes(entry[0])
|
||||
)
|
||||
.map(([key, value]) => ({ value: key, title: value }));
|
||||
export const languageOptions: Array<{ value: LanguageKey; title: string }> = Object.entries(
|
||||
languageCodeAndDisplayNameMappings
|
||||
).map(([key, value]) => ({ value: languageKeyGuard.parse(key), title: value }));
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Language } from '@logto/phrases-ui';
|
||||
import { hexColorRegEx } from '@logto/shared';
|
||||
import { hexColorRegEx, languageKeys } from '@logto/shared';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
|
@ -103,8 +102,8 @@ export type TermsOfUse = z.infer<typeof termsOfUseGuard>;
|
|||
|
||||
export const languageInfoGuard = z.object({
|
||||
autoDetect: z.boolean(),
|
||||
fallbackLanguage: z.nativeEnum(Language),
|
||||
fixedLanguage: z.nativeEnum(Language),
|
||||
fallbackLanguage: z.enum(languageKeys),
|
||||
fixedLanguage: z.enum(languageKeys),
|
||||
});
|
||||
|
||||
export type LanguageInfo = z.infer<typeof languageInfoGuard>;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { Language } from '@logto/phrases';
|
||||
|
||||
import { CreateSetting } from '../db-entries';
|
||||
import { AppearanceMode } from '../foundations';
|
||||
|
||||
|
@ -9,7 +7,7 @@ export const createDefaultSetting = (): Readonly<CreateSetting> =>
|
|||
Object.freeze({
|
||||
id: defaultSettingId,
|
||||
adminConsole: {
|
||||
language: Language.English,
|
||||
language: 'en',
|
||||
appearanceMode: AppearanceMode.SyncWithSystem,
|
||||
demoChecked: false,
|
||||
applicationCreated: false,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Language } from '@logto/phrases-ui';
|
||||
import { generateDarkColor } from '@logto/shared';
|
||||
|
||||
import { CreateSignInExperience, SignInMode } from '../db-entries';
|
||||
|
@ -20,8 +19,8 @@ export const defaultSignInExperience: Readonly<CreateSignInExperience> = {
|
|||
},
|
||||
languageInfo: {
|
||||
autoDetect: true,
|
||||
fallbackLanguage: Language.English,
|
||||
fixedLanguage: Language.English,
|
||||
fallbackLanguage: 'en',
|
||||
fixedLanguage: 'en',
|
||||
},
|
||||
termsOfUse: {
|
||||
enabled: false,
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"nanoid": "^3.1.23"
|
||||
"nanoid": "^3.1.23",
|
||||
"zod": "^3.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@silverhand/eslint-config": "1.0.0-rc.2",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './utilities';
|
||||
export * from './regex';
|
||||
export * from './language';
|
||||
|
|
5
packages/shared/src/language.ts
Normal file
5
packages/shared/src/language.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const languageKeys = ['en', 'fr', 'zh-CN', 'tr-TR', 'ko-KR'] as const;
|
||||
export const languageKeyGuard = z.enum(languageKeys);
|
||||
export type LanguageKey = z.infer<typeof languageKeyGuard>;
|
|
@ -1,4 +1,3 @@
|
|||
import { Language } from '@logto/phrases-ui';
|
||||
import {
|
||||
BrandingStyle,
|
||||
ConnectorPlatform,
|
||||
|
@ -166,8 +165,8 @@ export const mockSignInExperience: SignInExperience = {
|
|||
},
|
||||
languageInfo: {
|
||||
autoDetect: true,
|
||||
fallbackLanguage: Language.English,
|
||||
fixedLanguage: Language.Chinese,
|
||||
fallbackLanguage: 'en',
|
||||
fixedLanguage: 'zh-CN',
|
||||
},
|
||||
signInMethods: {
|
||||
username: SignInMethodState.Primary,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Language } from '@logto/phrases-ui';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -20,7 +19,7 @@ const SocialLinkButton = ({ isDisabled, className, target, name, logo, onClick }
|
|||
i18n: { language },
|
||||
} = useTranslation();
|
||||
|
||||
const localName = name[language] ?? name[Language.English];
|
||||
const localName = name[language] ?? name.en;
|
||||
|
||||
return (
|
||||
<button
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Language } from '@logto/phrases-ui';
|
||||
import { languageKeyGuard } from '@logto/shared';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -48,7 +48,7 @@ const SocialSignInDropdown = ({ isOpen, onClose, connectors, anchorRef }: Props)
|
|||
>
|
||||
{connectors.map((connector) => {
|
||||
const { id, name, logo, logoDark } = connector;
|
||||
const localName = isKeyOf(language, name) ? name[language] : name[Language.English];
|
||||
const localName = name[languageKeyGuard.default('en').parse(language)];
|
||||
|
||||
return (
|
||||
<DropdownItem
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Language } from '@logto/phrases-ui';
|
||||
import { SignInExperience, ConnectorMetadata, AppearanceMode } from '@logto/schemas';
|
||||
import type { LanguageKey } from '@logto/shared';
|
||||
|
||||
export type UserFlow = 'sign-in' | 'register';
|
||||
export type SignInMethod = 'username' | 'email' | 'sms' | 'social';
|
||||
|
@ -36,7 +36,7 @@ export enum TermsOfUseModalMessage {
|
|||
|
||||
export type PreviewConfig = {
|
||||
signInExperience: SignInExperienceSettingsResponse;
|
||||
language: Language;
|
||||
language: LanguageKey;
|
||||
mode: AppearanceMode.LightMode | AppearanceMode.DarkMode;
|
||||
platform: Platform;
|
||||
isNative: boolean;
|
||||
|
|
281
pnpm-lock.yaml
generated
281
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue