Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): react-typed-i18n页面国际化 #833

Merged
merged 75 commits into from
Sep 25, 2023
Merged

Conversation

piccaSun
Copy link
Contributor

@piccaSun piccaSun commented Aug 30, 2023

实现方案:

使用react-typed-i18n实现国际化
https://github.com/ddadaal/react-typed-i18n

开发文档:

https://jgf29kqp7z.feishu.cn/wiki/PH98witE4iO2p0khJYPcpWxqnpb

利用Provider外包UI组件

语言切换时保存语言类别languageId到cookies,

在_app.tsx的getInitialProps中获取languageId传给MyApp定义Provider的初始语言信息id

_app.tsx

function MyApp({ Component, pageProps, extra }: Props) {  

...    
return(
  <>
  <Head />
  <Provider initialLanguage={{
        id: extra.languageId,
        definitions: extra.languageId === "en" ? en : zh_cn,
      }}
      >
         <Component  />
   </Provider>
)

对于蚂蚁组件的国际化与之前无差别,只需将languageId作为蚂蚁国际化locale同样从getInitialProps传入MyApp`

<AntdConfigProvider color={primaryColor} locale="zh_cn">

在Component内部利用createI18n所提供的useI18n的翻译函数或者Localized组件进行翻译,支持强类型化

/component.ts

const i18n = useI18n();
...
return (
  <Section
      title={useI18n.translateToString("dashboard.job.title")}
      extra={(
        <Link href="/user/runningJobs">
          <Localized id="dashboard.job.extra"></Localized>
        </Link>
      )}
)

因为采用Provider方案,包括路由导航栏在内的所有UI国际化渲染均类似,实现简单

优点:
1.实现简单,结构清晰
2.翻译文本资源key的prefix单独定义,在同一个翻译函数下简单使用不同的prefix
3.Localized使页面组件不需要再次引入翻译函数就可以直接进行翻译

缺点:
一次引入所有翻译资源可能使加载速度变慢
但是目前简单测试一两个页面没有感觉到什么影响
甚至因为没有全局对象异步等待等问题语言切换时显示速度很快

翻译文本资源整理

配置文件参考下列表单(demo示例和登录页面配置)
https://jgf29kqp7z.feishu.cn/wiki/BFFUwHLgaiYxK5k9QGwci4X0nMe
其他页面展示文本参考下表各系统zh_cn.ts和en.ts工作表
https://jgf29kqp7z.feishu.cn/wiki/RFXDw06rCiodzMkbOYqcEAclnkg

配置文件中可国际化配置由原来的字符串类型变更为字符串或i18n对象
eg:
xxxx.config
# 允许兼容原来的字符串类型
# title: "开源算力中心门户和管理平台"
# 当想实现国际化功能时,不再配置原来的字符串类型,配置成下列展示的国际化类型
# 包含默认值(字符串类型),英文文本(字符串类型),中文文本(字符串类型)
title:
i18n:
default: "开源算力中心门户和管理平台"
en: "Open-source Compute Center Portal and Management Platform"
zh_cn: "开源算力中心门户和管理平台"

修改移动web后端中文文字到前端并实现国际化
主要包括以下错误信息的文字提示:

管理系统
1.修改作业时限时,时长错误信息 "设置作业时限需要大于该作业的运行时长。"
2.个人信息: 修改密码时错误信息: 来源于common配置文件中的配置的密码错误的错误信息
3.各创建租户和创建用户页面
用户Id错误信息: 来源于mis.yaml配置的创建用户id错误信息
密码错误信息: 来源于common配置文件中的配置的密码错误的错误信息
4.在账户中添加用户的原”用户已经存在于此账户中!“因为是grpc返回错误,直接使用后端错误信息元数据的英文展示,不做国际化

门户系统
1.个人信息中修改密码的错误信息: 来源于common配置文件中的配置的密码错误的错误信息

@changeset-bot
Copy link

changeset-bot bot commented Aug 30, 2023

🦋 Changeset detected

Latest commit: f4205e9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@scow/config Patch
@scow/portal-server Minor
@scow/portal-web Minor
@scow/mis-web Minor
@scow/lib-server Patch
@scow/auth Minor
@scow/lib-web Minor
@scow/docs Patch
@scow/test-adapter Patch
@scow/grpc-api Patch
@scow/lib-hook Patch
@scow/lib-operation-log Patch
@scow/audit-server Patch
@scow/cli Minor
@scow/mis-server Minor
@scow/protos Patch
@scow/lib-scheduler-adapter Patch
@scow/gateway Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov
Copy link

codecov bot commented Aug 31, 2023

Codecov Report

Patch coverage: 42.62% and project coverage change: -0.44% ⚠️

Comparison is base (5bb922f) 71.01% compared to head (f4205e9) 70.58%.
Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #833      +/-   ##
==========================================
- Coverage   71.01%   70.58%   -0.44%     
==========================================
  Files         129      130       +1     
  Lines        3664     3722      +58     
  Branches      481      497      +16     
==========================================
+ Hits         2602     2627      +25     
- Misses        970     1001      +31     
- Partials       92       94       +2     
Files Changed Coverage Δ
libs/server/src/i18n.ts 21.87% <21.87%> (ø)
apps/portal-server/tests/file/utils.ts 84.28% <42.85%> (-4.61%) ⬇️
apps/auth/src/auth/bindOtpHtml.ts 36.84% <50.00%> (+3.50%) ⬆️
apps/portal-server/src/services/app.ts 48.88% <71.42%> (+1.26%) ⬆️
apps/auth/src/auth/loginHtml.ts 61.29% <77.77%> (+11.29%) ⬆️
apps/auth/src/config/auth.ts 59.57% <100.00%> (+1.79%) ⬆️

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

};
}

export default useI18nTranslateToString;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

目前JS生态的共识是尽量少用export default,直接export function useI18nTranslateToString()就好

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这两个函数可以直接写在src/i18n/index.ts里

@piccaSun piccaSun changed the title feat(web): react-typed-i18n国际化实现测试 feat(web): react-typed-i18n页面国际化 Sep 5, 2023
@piccaSun
Copy link
Contributor Author

@ddadaal

1移动翻译函数至i18n/index.ts中对之前的review意见做出了修改

2从Cookies或header中获取语言id
变更了系统启动时从cookie中获取语言id,如果cookie语言不存在或违法从accept-header中获取

2自定义配置文件中的国际化文本示例
更改可自定义配置语言文本类型由string变更为string | i18nType
原则上不影响原有runtimePublic中的配置项


     # publicRuntimeConfig: 
    // 兼容原有系统的字符串形式的配置项
    PASSWORD_PATTERN_MESSAGE: typeof commonConfig.passwordPattern?.errorMessage === "string"
      ? commonConfig.passwordPattern?.errorMessage : commonConfig.passwordPattern?.errorMessage.i18n.default,
    #  在publicRuntimeConfig下新增  
      CUSTOM_I18N_CONFIG: {
      // 只记录i18n类型的配置项
      COMMON_PASSWORD_PATTERN_MESSAGE: typeof commonConfig.passwordPattern?.errorMessage === "string"
        ? undefined : commonConfig.passwordPattern?.errorMessage,
    },

页面调用时,如果碰到原有字符串判定逻辑仍然使用publicConfig.PASSWORD_PATTERN_MESSAGE
在显示文字时通过checkI18nValue()函数新增显示判定
页面显示时需useEffect监听当前国际化语言id的变化

以上的问题请确认

关于第三点,目前placeholder里面的显示只是最简单基础的一项,common.yaml文件中也要求必须配置
比如说有一些需要如没有配置就显示默认值的情况,Type.Union下试了一下不太好实现
实际上配置项的情况各种各样,感觉让原有配置项兼容string | i18nType的形式不如再增加一个只有i18n类型的配置项实现起来逻辑更简单?
另外第三点的页面渲染问题,每个页面都要useEffect追加监听也略显繁琐,这是我理解错了吗有更简单明了的实现办法嘛?

const cookies = parseCookies({ req });

if (cookies && cookies.language) {
return cookies.language;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

检查cookie中的language值是否合法

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

整个流程是,先从cookie或者header中获取已经有的值,然后检查获取到的值是否合法

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

把默认语言、可以接受的语言全部定义为常量(数组常量),这样以后修改起来方便一些

Comment on lines 36 to 42
errorMessage: Type.Union([
Type.String({
description: "如果密码不符合规则显示什么",
default: "必须包含字母、数字和符号,长度大于等于8位",
}),
Type.Object({ i18n: I18nTypeSchema }, { description: "系统内展示文本是否采用国际化类型" }),
]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

目前应该所有可以配置国际化的名字的配置项都是(string | { i18n: { cn: string ...} }}这个类型,所以可以定义一处然后所有地方都用

Comment on lines 15 to 21
export type I18nType = {
i18n: {
default: string,
zh_cn: string,
en: string,
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为何要单独定义?下面代码有问题吗?

const I18nStringSchema = Type.Union([
    Type.String({ }),
    Type.Object({ i18n: Type.Object({ default: Type.String(), ... } })
]);

@piccaSun
Copy link
Contributor Author

@ddadaal
请确认基于昨天的评论修改的逻辑,
1 修改cookie合法判断
增加系统合法语言类型,请求头合法语言类型,默认语言类型常量,追加合法判断

2 配置文件文本i18n refactor

(1)配置文本i18n逻辑重构
libs/config/src/type.ts下定义公共类型

// 创建配置文件中显示文字项的配置类型
export const createI18nStringSchema = (description: string, defaultValue?: string) => {
  return Type.Union([
    Type.String(),
    Type.Object({
      i18n: Type.Object({
        default: Type.String({ description: "国际化类型默认值" }),
        en: Type.String({ description: "国际化类型英文值" }),
        zh_cn: Type.String({ description: "国际化类型简体中文值" }),
      }),
    }),
  ], { description, default: defaultValue });
};

(2) 变更配置文本类型

  submitJobPromptText:
  Type.Optional(createI18nStringSchema("提交作业命令框中的提示语", "#此处参数设置的优先级高于页面其它地方,两者冲突时以此处为准")),

(3)config.ts下的runtimeConfig或serverConfig中定义i18n文本类型

  SERVER_I18N_CONFIG_TEXTS: {
    defaultHomeTitle: I18nStringType,
    defaultHomeText: I18nStringType,
    submitJopPromptText?: I18nStringType,
  }

(4)config.ts下增加按语言ID获取对应字符串函数,这个地方如果逻辑没问题想确认一下不存在时的返回值
因为这个地方不存在这个变量如果有默认值则会按字符串返回,如果不存在变量又没有默认值又是optional时是否可以返回“”

type ServerI18nConfigKeys = keyof typeof runtimeConfig.SERVER_I18N_CONFIG_TEXTS;
export const getSeverI18nConfigText = (languageId: string, key: ServerI18nConfigKeys): string => {
  const i18nConfigText = runtimeConfig.SERVER_I18N_CONFIG_TEXTS[key];

  if (!i18nConfigText) return "";
  return getI18nConfigCurrentText(i18nConfigText, languageId);

};

src/util/constants.ts
(5)前端页面调用时,获取当前语言id,获取I18n文本文字
为了避免i18nText文本出错,将对应字符串都定义为常量

// 定义配置文件中i18n复合类型文字的Key值
// common.yml
export const PASSWORD_PATTERN_MESSAGE = "passwordPatternMessage";
// portal.yaml
export const DEFAULT_HOME_TEXT = "defaultHomeText";
export const DEFAULT_HOME_TITLE = "defaultHomeText";
export const SUBMIT_JOB_PROMPT_TEXT = "submitJopPromptText";

页面
const languageId = useI18n().currentLanguage.id;
const submitJobPromptText = getSeverI18nConfigText(languageId, SUBMIT_JOB_PROMPT_TEXT);

@ddadaal
Copy link
Member

ddadaal commented Sep 12, 2023

(4) 可以使用类型系统更精确地确定返回值:

  RUNTIME_I18N_CONFIG_TEXTS: {
    passwordPatternMessage: I18nStringType | undefined,
    test: I18nStringType;
  }


export const getRuntimeI18nConfigText = <TKey extends RuntimeI18nConfigKeys>(
  languageId: string, key: TKey,
): ((typeof publicConfig.RUNTIME_I18N_CONFIG_TEXTS)[TKey]) extends (I18nStringType)
  ? (string) : string | undefined => {

  const i18nConfigText = publicConfig.RUNTIME_I18N_CONFIG_TEXTS[key];

  if (!i18nConfigText) {
    return undefined as any;
  }

  return getI18nConfigCurrentText(i18nConfigText, languageId);
};

const a = getRuntimeI18nConfigText("zh-CN", "passwordPatternMessage"); // string | undefined
const b = getRuntimeI18nConfigText("zh-CN", "test"); // string

(5) 不需要单独定义常量,因为typescript会在写错的时候报错

@lyl-available
Copy link
Contributor

lyl-available commented Sep 19, 2023

(已修复)图片验证码及opt验证配置为true时,placeholder显示异常,且图片验证码无法点击更新
image

@github-actions
Copy link
Contributor

github-actions bot commented Sep 25, 2023

PR Preview Action v1.4.4
Preview removed because the pull request was closed.
2023-09-25 15:57 UTC

@piccaSun
Copy link
Contributor Author

@ddadaal
除之前的基本内容外
后续增加内容如下
1.i18n defautl追加判断,实现默认语言配置
2.操作日志后端返回类型变更,操作日志国际化重构
3.登录节点按地址确定,国际化分支合并
4.后端出现的语言文本(基本全部为错误信息处理)移动到前端并实现国际化
5.交互式应用配置增加grpc的i18nStringType类型,在前端实现显示信息国际化
6.追加文档及changeset文件等内容
请确认

@piccaSun piccaSun requested a review from ddadaal September 25, 2023 12:14
Copy link
Member

@ddadaal ddadaal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在apps/cli/assets/config下的配置文件示例中,在每个支持国际化文本的配置项上加注释“此文本支持国际化”,如下:

# 主页标题
homeTitle:
  # 默认文本
  # 此文本支持国际化
  defaultText: "Super Computing on Web"

@@ -43,7 +44,8 @@
"pino": "8.15.0",
"nodemailer": "6.9.4",
"qrcode": "1.5.3",
"speakeasy": "2.0.0"
"speakeasy": "2.0.0",
"react-typed-i18n": "^2.3.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不要^,SCOW的package.json中的依赖版本都确定为具体的版本

@piccaSun
Copy link
Contributor Author

@ddadaal 上述意见已修改,请确认~

@ddadaal ddadaal merged commit ccbde14 into master Sep 25, 2023
10 checks passed
@ddadaal ddadaal deleted the use-react-typed-i18n branch September 25, 2023 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants