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

在 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低代码应用平台体验

(0)
汤乾华的头像汤乾华数式员工
上一篇 2025年7月21日 pm8:49
下一篇 2025年8月14日 pm5:55

相关推荐

  • oio-select 选择器

    API Select props 参数 说明 类型 默认值 版本 allowClear 支持清除 boolean false autofocus 默认获取焦点 boolean false clearIcon 自定义的多选框清空图标 VNode | slot – disabled 是否禁用 boolean false dropdownClassName 下拉菜单的 className 属性 string – dropdownRender 自定义下拉框内容 ({menuNode: VNode, props}) => VNode | v-slot – filterOption 是否根据输入项进行筛选。当其为一个函数时,会接收 inputValue option 两个参数,当 option 符合筛选条件时,应返回 true,反之则返回 false。 boolean | function(inputValue, option) true getTriggerContainer 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 function(triggerNode) () => document.body menuItemSelectedIcon 自定义当前选中的条目图标 VNode | slot – options options 数据,如果设置则不需要手动构造 selectOption 节点 array<{value, label, [disabled, key, title]}> [] placeholder 选择框默认文字 string|slot – removeIcon 自定义的多选框清除图标 VNode | slot – suffixIcon 自定义的选择框后缀图标 VNode | slot – value(v-model:value) 指定当前选中的条目 string|string[]|number|number[] – 注意,如果发现下拉菜单跟随页面滚动,或者需要在其他弹层中触发 Select,请尝试使用 getPopupContainer={triggerNode => triggerNode.parentNode} 将下拉弹层渲染节点固定在触发器的父元素中。 事件 事件名称 说明 回调参数 blur 失去焦点的时回调 function change 选中 option,或 input 的 value 变化(combobox 模式下)时,调用此函数 function(value, option:Option/Array<Option>) deselect 取消选中时调用,参数为选中项的 value (或 key) 值,仅在 multiple 或 tags 模式下生效 function(value,option:Option) dropdownVisibleChange 展开下拉菜单的回调 function(open) focus 获得焦点时回调 function inputKeyDown 键盘按下时回调 function mouseenter 鼠标移入时回调 function mouseleave 鼠标移出时回调 function popupScroll 下拉列表滚动时的回调 function search 文本框值变化时回调 function(value: string) select 被选中时调用,参数为选中项的 value (或 key) 值 function(value, option:Option)

    2023年12月18日
    81900
  • 如何实现页面间的跳转

    介绍 在日常的业务中,我们经常需要在多个模型的页面之间跳转,例如从商品的行可以直接点击链接跳转到类目详情,还有查看该订单发起的售后单列表,这里将给大家展示如何在oinone中如何实现这些功能。 方法一、通过界面设计器的无代码能力配置 表格行跳转到表单页/详情页 拖入一个跳转动作到表格行,保存动作后,在左侧的动作属性面板底部有个请求配置,里面的上下文属性就是配置跳转参数的地方,点击添加按钮可以增加一行参数 点击添加按钮后,可以看到新增了一行,行内有2个输入框,左侧输入框为目标视图模型的字段,右侧输入框为当前视图模型的表达式 注意 表达式中activeRecord关键字代表当前行的数据对象 “上下文”相关知识点 当前页面的模型和跳转后的页面模型相同的情况下,会字段带上当前行数据的id作为路由参数 上下文是从当前页面跳转到下个页面带的自定义参数 上下文会作为跳转后的页面数据加载函数的入参,后端的该函数需要根据该条件查询到数据返回给前端,典型的例子就是编辑页,根据id查询对象的其他字段信息返回 跳转后页面的数据加载函数可以在动作场景的时候选择加载函数,也可以在页面的加载函数处设置 方法二、通过低代码方式在自定义代码中调用 oinone提供了内置函数executeViewAction实现该功能 import { DefaultComparisonOperator, executeViewAction, QueryExpression, RuntimeViewAction, ViewActionTarget, ViewType } from '@kunlun/dependencies'; export class JumpActionWidget { protected goToObjectView() { executeViewAction( { viewType: ViewType.Form, moduleName: 'resource', model: 'resource.ResourceCountry', name: 'redirectUpdatePage', target: ViewActionTarget.Router } as RuntimeViewAction, undefined, undefined, { // 此处为id参数,目前只有表单和详情页需要 id: '12223', // 此处为上下文参数,context内对象的key是目标页面需要传递的默认值 context: JSON.stringify({code: 'xxx'}), // 此处为跳转后左侧菜单展开选中的配置 menu: JSON.stringify({"selectedKeys":["国家"],"openKeys":["地址库","地区"]}) } ); } protected goToListView() { const searchConditions: QueryExpression[] = []; searchConditions.push({ leftValue: ['countryCode'], // 查询条件的字段 operator: DefaultComparisonOperator.EQUAL, right: 'CN' // 字段的值 }); executeViewAction( { viewType: ViewType.Table, moduleName: 'resource', model: 'resource.ResourceCity', name: 'resource#市', target: ViewActionTarget.OpenWindow } as RuntimeViewAction, undefined, undefined, { // searchConditions相当于domain,不会随页面搜索项重置动作一起被清空 searchConditions: encodeURIComponent(JSON.stringify(searchConditions)), // searchBody的字段会填充搜索区域的字段组件,会随页面搜索项重置动作一起被清空 searchBody: JSON.stringify({code: 'CN'}), menu: JSON.stringify({"selectedKeys":["国家"],"openKeys":["地址库","地区"]}) } ); } } 扩展知识点 为什么executeViewAction跳转到的新页面不是入参的moduleName属性对应的模块? 答:跳转后所在的模块优先级为: 第一个入参的resModuleName属性对应的模块 执行executeViewAction时所在的模块 第一个入参的moduleName属性对应的模块 如何快速获取选中菜单的值? 答:先通过页面菜单手动打开页面,然后在浏览器自带调试工具的控制台执行decodeURIComponent(location.href),其中的menu参数就是我们需要的值

    2024年5月13日
    2.8K00
  • 动作API

    ActionWidget 动作组件的基类,包含了动作组件的通用属性和方法 示例 class MyActionWidget extends ActionWidget { } 动作属性 属性名 说明 类型 可选值 默认值 label 动作的名称 String – 当前动作的displayName action 当前动作的元数据 RuntimeAction – model 运行时模型 RuntimeModel – viewAction 运行时视图动作 RuntimeViewAction – view 运行时视图 RuntimeViewAction – initialValue 视图初始值 ActiveRecord[] – initialContext 视图初始上下文 Object – urlParameters 获取url参数 UrlQueryParameters – scene 场景 String – loading 动作加载状态 Boolean – false disabled 是否禁用 Boolean – false disabledTitle 禁用时的按钮名称 String – – invisible 当前字段是否不可见 Boolean – false validateForm 点击动作后是否校验表单 Boolean – false actionDomain 动作的domain查询条件 String – undefined goBack 点击动作后是否返回上一页 Boolean – false isDialog 是否为弹窗内动作 Boolean – 弹窗下的动作默认为true closeDialog 点击动作后是否关闭弹窗 Boolean – 默认为isDialog的值 isDrawer 是否为抽屉内动作 Boolean – 抽屉下的动作默认为true closeDrawer 点击动作后是否关闭抽屉 Boolean – 默认为isDrawer的值 isInnerPopup 是否为页内弹出层动作 Boolean – 页内弹出层下的动作默认为true isAsync 是否为异步动作 Boolean – true refreshRoot 是否刷新根视图 Boolean – false refreshData 是否刷新数据 Boolean – true type 动作的类型 ButtonType – 行内动作默认为ButtonType.link,其他动作为ButtonType.primary bizStyle 动作的业务类型 ButtonBizStyle – ButtonBizStyle.default icon 动作的图标 String – – enableConfirm 是否开启二次确认 Boolean – true confirmType 二次确认的类型 ConfirmType – – confirm 二次确认的内容 String – – confirmText 二次确认的提示内容 String – – confirmPosition 二次确认提示的展示位置 PopconfirmPlacement – PopconfirmPlacement.BM enterText 二次确认的确定按钮文字 String – – cancelText 二次确认的取消按钮文字 String – – searchBody 列表页的动作可以拿到搜索区域的搜索条件 ActiveRecord…

    2024年3月8日
    1.3K00
  • 弹窗生命周期实践

    在oinone平台中,弹窗、抽屉是用户界面设计中最为常见的,而对于开发者而言,能够监听弹窗的生命周期事件通常是十分重要的,因为它提供了一个机会去执行一些逻辑。在这篇文章中,我们将深入探讨如何监听弹窗、抽屉生命周期事件,并讨论一些可能的应用场景。 首先,我们来实现一个监听弹窗销毁的事件。 让我们看一下提供的代码片段: // 1: 自定义打开弹窗的动作 @SPI.ClassFactory( BaseActionWidget.Token({ actionType: [ActionType.View], target: [ViewActionTarget.Dialog], model: 'model', name: 'name' }) ) export class MyDialogViewActionWidget extends DialogViewActionWidget { protected subscribePopupDispose = (manager: IPopupManager, instance: IPopupInstance, action) => { // 自定义销毁弹窗后的逻辑 }; protected mounted() { PopupManager.INSTANCE.onDispose(this.subscribePopupDispose.bind(this)); } protected unmounted() { PopupManager.INSTANCE.clearOnDispose(this.subscribePopupDispose.bind(this)); } } 在上面的代码中,我们自定义了打开弹窗的动作,并且监听了弹窗销毁事件。 让我们逐步解析这段代码: 1: subscribePopupDispose 是一个函数,作为弹窗销毁事件的处理程序。它接收三个参数:manager、instance 和 action。 manager: 弹窗事件管理器 instance: 弹窗实例 action: 操作弹窗的动作,如果是点击弹窗右上角的关闭按钮,那action为null 2: 组件挂载的时候,进行监听. 4: 最后组件销毁的时候需要清除对应的监听 那么,如果监听到弹窗销毁,我们可以执行什么样的逻辑呢? 1: 更新相关组件状态: 弹窗销毁后,可能需要更新其他组件的状态。通过 popupWidget 可以获取到弹窗相关的信息,进而执行一些状态更新操作。 2: 处理弹窗销毁时的数据或动作: 在 subscribePopupDispose 函数中,action 参数含一些关于弹窗销毁时的动作信息,如果是点击弹窗右上角的销毁按钮,那action为null。我们可以根据这些信息执行相应的逻辑,例如更新界面状态、保存用户输入等 3: 触发其他操作: 弹窗销毁后,可能需要触发一些后续操作,比如显示另一个弹窗、发起网络请求等。 完整的生命周期 方法名 功能描述 onPush(fn) 监听 弹出窗口被推入时的事件处理器 clearOnPush(fn) 清除onPush事件的监听 onCreated(fn) 监听 弹出窗口创建时的事件处理器 clearOnCreated(fn) 清除onCreated事件的监听 onOpen(fn) 监听 弹出窗口打开时的事件处理器 clearOnOpen(fn) 清除onOpen事件的监听 onClose(fn) 监听 弹出窗口关闭时的事件处理器 clearOnClose(fn) 清除onClose事件的监听 onDispose(fn) 监听 弹出窗口被销毁时的事件处理器 clearOnDispose(fn) 清除onDispose事件的监听 onDisposeAll(fn) 监听 所有弹出窗口被销毁时的事件处理器 clearOnDisposeAll(fn) 清除onDisposeAll事件的监听 结语 开发者可以更灵活地响应用户操作,提升用户体验。在实际项目中,根据应用需求和设计,可以根据以上优化逻辑定制具体的处理流程。希望这篇文章为你提供了更深入的理解。

    2023年11月17日
    1.1K00
  • 后端:如何自定义表达式实现特殊需求?扩展内置函数表达式

    平台提供了很多的表达式,如果这些表达式不满足场景?那我们应该如何新增表达式去满足项目的需求?目前平台支持的表达式内置函数,参考 1. 扩展表达式的场景 注解@Validation的rule字段支持配置表达式校验如果需要判断入参List类型字段中的某一个参数进行NULL校验,发现平台的内置函数不支持该场景的配置,这里就可以通过平台的机制,对内置函数进行扩展。 常见的一些代码场景,如下: package pro.shushi.pamirs.demo.core.action; ……引用类 @Model.model(PetShopProxy.MODEL_MODEL) @Component public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> { @Override protected PetShopProxy fetchData(PetShopProxy data) { return data.queryById(); } @Validation(ruleWithTips = { @Validation.Rule(value = "!IS_BLANK(data.code)", error = "编码为必填项"), @Validation.Rule(value = "LEN(data.name) < 128", error = "名称过长,不能超过128位"), }) @Action(displayName = "启用") @Action.Advanced(invisible="!(activeRecord.code !== undefined && !IS_BLANK(activeRecord.code))") public PetShopProxy dataStatusEnable(PetShopProxy data){ data = super.dataStatusEnable(data); data.updateById(); return data; } ……其他代码 } 2. 新建一个自定义表达式的函数 校验入参如果是个集合对象的情况下,单个对象的某个字段如果为空,返回false的函数。 例子:新建一个CustomCollectionFunctions类 package xxx.xxx.xxx; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.common.constants.NamespaceConstants; import pro.shushi.pamirs.meta.util.FieldUtils; import java.util.List; import static pro.shushi.pamirs.meta.enmu.FunctionCategoryEnum.COLLECTION; import static pro.shushi.pamirs.meta.enmu.FunctionLanguageEnum.JAVA; import static pro.shushi.pamirs.meta.enmu.FunctionOpenEnum.LOCAL; import static pro.shushi.pamirs.meta.enmu.FunctionSceneEnum.EXPRESSION; /** * 自定义内置函数 */ @Fun(NamespaceConstants.expression) @Component public class CustomCollectionFunctions { /** * LIST_FIELD_NULL 就是我们自定义的表达式,不能与已经存在的表达式重复!!! * * @param list * @param field * @return */ @Function.Advanced( displayName = "校验集成的参数是否为null", language = JAVA, builtin = true, category = COLLECTION ) @Function.fun("LIST_FIELD_NULL") @Function(name = "LIST_FIELD_NULL", scene = {EXPRESSION}, openLevel = LOCAL, summary = "函数示例: LIST_FIELD_NULL(list,field),函数说明: 传入一个对象集合,校验集合的字段是否为空" ) public Boolean listFieldNull(List list, String field) { if (null == list) { return false; } if (CollectionUtils.isEmpty(list)) { return false; } for (Object data : list) { Object value =…

    2024年5月30日
    2.4K00

Leave a Reply

登录后才能评论