前端日期组件国际化支持方案

在 oinone 平台中,系统默认支持基础的国际化翻译功能。但由于日期时间组件的国际化依赖对应语言包,而全量引入语言包会显著增加打包体积,因此前端默认仅集成了中、英文的日期时间支持。若需为日期时间组件扩展其他语言(如日语)的国际化支持,需手动导入对应语言包并完成配置,具体步骤如下:

前端日期组件国际化支持方案
前端日期组件国际化支持方案

假设我们现在国际化翻译切换成了日语,那么我们在日期时间也要支持日语,那么需要如下操作:

1: 重写 RootWidget

继承平台默认的 RootWidget,SPI 注册条件保持跟平台一致即可覆盖平台默认的RootWidget

// CustomRootWidget.ts

import { RootComponentSPI, RootWidget, SPIFactory } from '@oinone/kunlun-dependencies';
import Root from './Root.vue';

// 通过SPI注册覆盖平台默认的root组件
@SPIFactory.Register(RootComponentSPI.Token({ widget: 'root' }))
export class CustomRootWidget extends RootWidget {
  public initialize() {
    super.initialize();
    this.setComponent(Root);
    return this;
  }
}

2: 覆盖 Root 组件的 Vue 文件

自定义的 Vue 文件需负责导入目标语言(如日语)的语言包,并根据当前语言环境动态切换配置。这里需要同时处理 ant-design-vue、element-plus 组件库及 dayjs 工具的语言包,确保日期组件的展示和交互统一适配目标语言。

<!-- Root.vue -->
<template>
  <a-config-provider :locale="antLocale">
    <el-config-provider :locale="eleLocale">
      <match :rootToken="root">
        <template v-for="page in pages" :key="page.widget">
          <route v-if="page.widget" :path="page.path" :slotName="page.slotName" :widget="page.widget">
            <slot :name="page.slotName" />
          </route>
        </template>

        <route :path="pagePath" slotName="page" :widgets="{ page: widgets.page }">
          <slot name="page" />
        </route>

        <route path="/" slotName="homePage">
          <slot name="homePage" />
        </route>
      </match>
    </el-config-provider>
  </a-config-provider>
</template>

<script lang="ts">
import { CurrentLanguage, EN_US_CODE, UrlHelper, ZH_CN_CODE } from '@oinone/kunlun-dependencies';
import { ConfigProvider as AConfigProvider } from 'ant-design-vue';
import { ElConfigProvider } from 'element-plus';
import dayjs from 'dayjs';

// 导入ant-design-vue语言包
import enUS from 'ant-design-vue/es/locale/en_US';
import zhCN from 'ant-design-vue/lib/locale/zh_CN';
import jaJP from 'ant-design-vue/lib/locale/ja_JP'; // 新增:日语语言包

// 导入 dayjs的语言包
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/ja'; // 新增:日语语言包

// 导入element-plus语言包
import elEn from 'element-plus/dist/locale/en.mjs';
import elZhCn from 'element-plus/dist/locale/zh-cn.mjs';
import elJaJP from 'element-plus/dist/locale/ja.mjs'; // 新增:日语语言包

import { computed, defineComponent, onMounted, onUnmounted, ref } from 'vue';

export default defineComponent({
  components: { AConfigProvider, ElConfigProvider },
  props: ['widgets', 'loginUrl', 'root', 'pages'],
  inheritAttrs: false,
  setup() {
    const pagePath = computed(() => UrlHelper.append(UrlHelper.absolutePath(process.env.BASE_PATH), 'page'));

    const locale = ref(zhCN.locale); // 初始化语言环境变量(默认中文)
    const antLocale = ref(zhCN); // ant-design-vue的语言配置
    const eleLocale = ref(elZhCn); // element-plus的语言配置

    const refreshLocale = (languageCode: string) => {
      switch (languageCode) {
        case ZH_CN_CODE: // 中文
          locale.value = zhCN.locale;
          antLocale.value = zhCN;
          eleLocale.value = elZhCn;
          break;
        case EN_US_CODE: // 英文
          locale.value = enUS.locale;
          antLocale.value = enUS;
          eleLocale.value = elEn;
          break;
        case 'jp-JP': // 新增:日语判断
          locale.value = jaJP.locale;
          antLocale.value = jaJP;
          eleLocale.value = elJaJP;
          break;
        default:
          locale.value = zhCN.locale;
          break;
      }

      // 更新dayjs的全局语言配置
      dayjs.locale(locale.value);
    };

    onMounted(() => {
      refreshLocale(CurrentLanguage.getCodeByLocalStorage());
      CurrentLanguage.onRefreshLocalStorage(refreshLocale);
    });

    onUnmounted(() => {
      CurrentLanguage.clearOnRefreshLocalStorage(refreshLocale);
    });

    return {
      zhCN,
      enUS,
      elZhCn,
      elEn,
      locale,
      pagePath,
      antLocale,
      eleLocale
    };
  }
});
</script>

3: 处理动态弹窗的国际化(特殊场景)

若业务中使用executeViewAction动态打开弹窗,弹窗内的日期组件可能无法继承全局语言配置,因此需额外重写 DialogContainerWidget 以确保弹窗内的语言环境一致。

3.1 重写 DialogContainerWidget 类

import { DialogContainerWidget, RootComponentSPI, SPIFactory } from '@oinone/kunlun-dependencies';
import Dialog from './Dialog.vue';

// 注册自定义弹窗容器,覆盖默认组件
@SPIFactory.Register(RootComponentSPI.Token({ widget: 'dialog-container' }))
export class CustomDialogContainerWidget extends DialogContainerWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(Dialog);
    return this;
  }
}
<template>
  <a-config-provider :locale="antLocale">
    <el-config-provider :locale="eleLocale">
      <slot />
    </el-config-provider>
  </a-config-provider>
</template>

<script lang="ts">
import { ConfigProvider as AConfigProvider } from 'ant-design-vue';
import dayjs from 'dayjs';
import { ElConfigProvider } from 'element-plus';
import { defineComponent, onMounted, ref } from 'vue';

// 导入ant-design-vue语言包
import enUS from 'ant-design-vue/es/locale/en_US';
import zhCN from 'ant-design-vue/lib/locale/zh_CN';
import jaJP from 'ant-design-vue/lib/locale/ja_JP'; // 日语

// 导入 dayjs的语言包
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/ja'; // 日语

// 导入element-plus语言包
import elEn from 'element-plus/dist/locale/en.mjs';
import elZhCn from 'element-plus/dist/locale/zh-cn.mjs';
import elJaJP from 'element-plus/dist/locale/ja.mjs'; // 日语
import { EN_US_CODE, ZH_CN_CODE } from '@oinone/kunlun-vue-ui-antd';

export default defineComponent({
  components: { AConfigProvider, ElConfigProvider },
  inheritAttrs: false,
  setup() {
    const locale = ref(zhCN.locale);
    const antLocale = ref(zhCN);
    const eleLocale = ref(elZhCn);
    onMounted(() => {
      const lang = localStorage.getItem('language') || ('zh-CN' as any);
      switch (lang) {
        case ZH_CN_CODE:
          locale.value = zhCN.locale;
          antLocale.value = zhCN;
          eleLocale.value = elZhCn;
          break;
        case EN_US_CODE:
          locale.value = enUS.locale;
          antLocale.value = enUS;
          eleLocale.value = elEn;
          break;
        case 'jp-JP': // 新增日语判断
          locale.value = jaJP.locale;
          antLocale.value = jaJP;
          eleLocale.value = elJaJP;
          break;
        default:
          locale.value = zhCN.locale;
          break;
      }
      dayjs.locale(locale.value);
    });

    return {
      zhCN,
      enUS,
      elZhCn,
      elEn,
      locale,
      antLocale,
      eleLocale
    };
  }
});
</script>

通过上述步骤,可实现 oinone 平台日期组件对新增语言(如日语)的国际化支持。核心逻辑是:通过重写全局配置组件引入目标语言包,并根据当前语言环境动态切换组件库和工具的语言配置;对于动态弹窗等特殊场景,需额外处理其容器组件以确保语言环境一致。

若需扩展其他语言(如韩语、法语等),只需参照上述步骤,替换对应的语言包并在 switch 逻辑中添加对应判断即可。

Oinone社区 作者:汤乾华原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/21519.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

Like (0)
汤乾华's avatar汤乾华数式员工
Previous 2025年7月21日 pm8:49
Next 2025年8月14日 pm5:55

相关推荐

  • oinone的rsql与传统sql语法对照表

    rsql sql 描述 field01 == "name" field01 = "name" 等于 field01 != "name" field01 != "name" 不等于 field01 =gt= 1 field01 > 1 大于 field01 =ge= 1 field01 >= 1 大于等于 field01 =lt= 1 field01 < 1 小于 field01 =le= 1 field01 <= 1 小于等于 field01 =isnull=true field01 is null 字段为null field01 =notnull= 1 field01 is not null 字段不为null field01 =in= ("foo") field01 in ("foo") 多条件 field01 =out= ("foo") field01 not in ("foo") 不在多条件中 field01 =cole= field02 field01 = field02 字段作为查询参数 field01 =colnt= field02 field01 != field02 字段作为查询参数 field01 =like="foo" field01 like "%foo%" 全模糊匹配,rsql语法中无需拼接通配符”%“ field01 =starts="foo" field01 like "foo%" 前缀模糊匹配,rsql语法中无需拼接通配符”%“ field01 =ends="foo" field01 like “%foo" 后缀模糊匹配,rsql语法中无需拼接通配符”%“ field01 =notlike="foo" field01 not like "%foo%" 全模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =notstarts="foo" field01 not like "foo%" 前缀模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =notends="foo" field01 not like “%foo" 后缀模糊不匹配,rsql语法中无需拼接通配符”%“ field01 =has=(ENUM_NAME1, ENUM_NAME2) 有多值枚举中的几个值 field01 =hasnt=(ENUM_NAME1,ENUM_NAME2) 没有多值枚举中的几个值 field01 =bit=ENUM_NAME1 有二进制枚举中的单个值 field01 =notbit=ENUM_NAME1 没有二进制枚举中的单个值 前端代码中使用工具类拼接rsql 该工具类在oinone的前端基础框架中提供 import { Condition } from '@kunlun/dependencies'; const rsqlCondition = new Condition('field01').equal('foo') .and(new Condition('field02').in(['bar'])) .and(new Condition('field03').notIn(['foo'])) .or(new Condition('field04').greaterThanOrEuqalTo(12)) .or(new Condition('field05').like('foo')) .or(new Condition('field06').notStarts('bar')) .or(new Condition('field07').isNull()) .or(new Condition('field08').notNull()) .and(new Condition('field09').bitEqual('BIT_ENUM_1')) .and(new Condition('field10').bitNotEqual('BIT_ENUM_2')) .and(new Condition('field11').has('ENUM_NAME_1')) .and(new Condition('field12').hasNot(['ENUM_NAME_2', 'ENUM_NAME_3'])); const rsqlStr = rsqlCondition.toString();…

    2023年11月1日
    4.4K00
  • OioMessage 全局提示

    全局展示操作反馈信息。 何时使用 可提供成功、警告和错误等反馈信息。 顶部居中显示并自动消失,是一种不打断用户操作的轻量级提示方式。 API 组件提供了一些静态方法,使用方式和参数如下: OioMessage.success(title, options) OioMessage.error(title, options) OioMessage.info(title, options) OioMessage.warning(title, options) options 参数如下: 参数 说明 类型 默认值 版本 duration 默认 3 秒后自动关闭 number 3 class 自定义 CSS class string –

    2023年12月18日
    1.5K00
  • 【动作】-路由动作跳转后如何主动刷新页面数据

    介绍 当我们使用多tab组件的时候,如果一个viewAction已经打开了一个tab页,再次用该viewAction打开页面的时候,会发现不会根据路由上的业务参数(如详情和编辑页的id参数)主动刷新数据,这个时候可以通过以下方法解决该问题 // 该方法可以在进入新路由页面后刷新数据,推荐将该方法放到工具类 function refreshViewAction(action: any) { const onRefreshTabWithActive = (manager: MultiTabsManager, instance: MultiTabInstance) => { // 进入路由后刷新页面数据 manager.refresh(instance.key); manager.clearOnActive(onRefreshTabWithActive); }; MultiTabsManager.INSTANCE.onActive(onRefreshTabWithActive); executeViewAction(action); } 将原本调用executeViewAction的方法改为refreshViewAction 如果需要扩展executeViewAction的其他入参请自行拓展refreshViewAction的入参

    2024年6月18日
    1.6K00
  • TableWidget 与 FormWidget 浅析

    前言:本文主要聚集于 TableWidget和 FormWidget,对两者以外的内容不做赘述。 TableWidget和 FormWidget作为一个基本的渲染模块与 Mask等不同,TableWidget与 FormWidget有着明确的目的,比如 TableWidget就是作为一个表格视图,这种名称中可以看得出来。其将表格的一系列能力聚拢,如单元格,行列选择排序,翻页等等; TableWidget 我们使用表格到底是在使用什么? 在我们讲述渲染流程前,我们需要提一个问题,在我们使用表格组件时,我们在使用什么,换而言之,我们对表格组件中关注的是什么?应该说我们更关注的是表格所展示的数据。TableWidget或者说整个低代码其实解决的就是这个问题,让我们可以很方便的展示数据。不用关心一些细枝末节 TableWidget 的渲染 TableWidget在整个渲染流程中的负责组装各种 Vue 组件所需要的核心数据或事件回调并传递给其绑定的 Vue 组件即 DefaultTable,如 allowRowClick,rowClickMode等等,这些值会作为 Props 传递给 DefaultTable,DefaultTable则一定意义上充当了一个桥接层,主要做了两件事,处理 Props, 处理 Slot,TableWidget传递给 DefaultTable的 Props 会经过 DefaultTable再次组合,创建新的 Props,同时根据当前的 Props 判断是否有必要新增一些 slot,比如 OioPagination组件是否需要渲染就取决于 Props.showPagination 的值,经过 DefaultTable的处理后, props与 slot会进一步交给 OioTable进行渲染,OioTable则会进一步聚合处理,比如如果没有 defaultSlot则进行空值的渲染,如果存在 footSlot则会构建一个包裹层去包裹它。这些组件协同完成了一个表格的结构,而我们真正关心的数据则由一个个 BaseTableColumnWidget渲染。BaseTableColumnWidget的渲染过程类似于 TableWidget,其负责组装 DefaultTableColumn渲染所需要的 props,然后交给 OioTableColumn进行实际渲染。并且会有多种基于 BaseTableColumnWidget的 widget通过重写 renderDefaultSlot,renderEditSlot,renderContentSlot,renderHeaderSlot等几个 props 实现不同状态,不同类型等组件的渲染。通过 TableWidget与 BaseTableColumnWidget相结合, Table 页面完成了主体框架与内容的渲染。而当数据完成渲染后,不可避免的会有数据交互,比如分页,排序,过滤等,这些交互都由 TableWidget与 BaseTableColumnWidget共同完成。比如排序翻页等,TableWidget会将事件作为 props 向下注入,当事件被调用,TableWidget会进行处理,比如发起请求,或者对内部数据排序等。而除了数据的展示,还有一些动作,即 Action ,Action 被触发时会按照内部的配置进行工作,比如编辑时,将获取 activeRecord,随后触发 Table 的编辑。 TableWidget就是这样去渲染出了一个完整的表格页面。能够完成数据的增删改查等操作 FormWidget FormWidget与 TableWidget一样,也是作为一个渲染模块,但是它与 TableWidget不同的是,FormWidget是作为一个表单视图,其将表单的一系列能力聚拢,如表单提交,表单校验,表单重置等等。其重点在于对数据的增改。所以在提供的能力上也有区别,比如 FormWidget没有提供翻页,排序,过滤等能力,因为这些能力属于表格的能力,而 FormWidget则更关注于表单的能力。提供了数据的存储,提交,校验等能力 Form 在渲染时流程与 Table 大同小异,其同样为三层结构 FormWidget => DefaultForm => FormFieldWidget 一层层向下渲染,不同的在于 FormWidget 更多的关注点在于维护其内部的 FormData 这是整个表单页面所围绕的东西,当页面上的控件发生变化,其变更的值会被收集到 FormData 中,并在后续中使用。同时在编辑已有数据场景下,Form 会将数据加载到 FormData,随后下放给 FormFiledWidget。 异同之处 从介绍中可以看出,Table 侧重于数据的查询展示,Form 则侧重于数据的变动处理,但是抽象的看其核心其实是同一套逻辑,即数据的存储与展示,中间或许会有对数据的某些处理,但是并不是本质上的区别,两者在核心理念上是一致的,即让使用者只需要关心数据本身,而不需要关注于繁琐的视图构造,这是整个低代码甚至前端的发展方向。

    2025年3月25日
    95400
  • 【前端】IOC容器(v4)

    什么是IOC容器? IOC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合,更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的使程序的整个体系结构变得非常灵活。在运行期,在外部容器动态的将依赖对象注入组件,当外部容器启动后,外部容器就会初始化。创建并管理对象实例,以及销毁,这种应用本身不负责依赖对象的创建和维护,依赖对象的创建和维护是由外部容器负责的称为控制反转。 IOC(控制反转)和DI(依赖注入) IOC(Inversion of Control, 控制反转):通过外部容器管理对象实例的一种思想。DI(Dependency Injection, 依赖注入):IOC的一种实现方式。 作者简述 IOC是Spring框架(一种以Java为语言开发的框架)的核心,并贯穿始终。其面向接口的开发能力,使得服务调用方和服务提供方可以做到完全解耦,只要遵循接口定义的规则进行调用,具体服务的实现可以是多样化的。 对于前端,我们使用inversify进行了IOC的实现。其强大的解耦能力可以使得平台进行大量的抽象,而无需关系具体的实现。 接下来,我们将介绍IOC在开发中的基本运用。 API 为了方便起见,我们将IOC相关功能与组件SPI的调用方式放在了一起。(更高版本的平台版本将自动获得该能力) export class SPI { /** * register singleton service */ public static Service; /** * autowired service property/parameter in service */ public static Autowired; /** * service construct after execute method */ public static PostConstruct; /** * autowired service in widget */ public static Instantiate; /** * autowired services in widget */ public static Instantiates; /** * service construct after execute method in widget */ public static InstantiatePostConstruct; } 创建第一个服务 service/ProductService.ts import { ServiceIdentifier } from '@kunlun/dependencies'; /** * 产品 */ export interface Product { id: string; name: string; } /** * 产品服务 */ export interface ProductService { /** * 获取产品列表 */ getProducts(): Promise<Product[]>; /** * 通过ID获取产品 * @param id 产品ID */ getProductById(id: string): Promise<Product | undefined>; } /** * 产品服务Token */ export const ProductServiceToken = ServiceIdentifier<ProductService>('ProductService'); service/impl/ProductServiceImpl.ts import { SPI } from '@kunlun/dependencies'; import { Product, ProductService, ProductServiceToken } from '../ProductService'; @SPI.Service(ProductServiceToken) export class ProductServiceImpl implements ProductService { public async getProducts(): Promise<Product[]> { // request api get products return []; } public async getProductById(id:…

    前端 2023年11月1日
    1.3K00

Leave a Reply

Please Login to Comment