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

在 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
下一篇 2022年11月8日 pm2:11

相关推荐

  • PC端、移动端默认Mask模板

    PC端 系统默认母版布局 <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </container> </mask> 系统默认把多tabs放入视图内母版布局 <mask> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <block height="100%" flex="1 0 0" flexDirection="column" alignContent="flex-start" flexWrap="nowrap" overflow="hidden"> <multi-tabs inline="true" /> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </block> </container> </mask> 移动端 <mask> <widget widget="user" /> <widget widget="nav-menu" app-switcher="true" menu="true" /> <widget widget="main-view" height="100%" /> </mask>

    2024年12月11日
    70500
  • 母版-布局-DSL 渲染基础(v4)

    概述 不论是母版、布局还是DSL,我们统一使用XML进行定义,可以更好的提供结构化表述。 参考文档: XML百度百科 XML语法参考 下面文档中未介绍到的Mask母版和Layout布局,可以去数据库中base库的表base_layout_definition和base_mask_definition的template字段查看 母版 确定了主题、非主内容分发区域所使用组件和主内容分发区域联动方式的页面配置。 母版内容分为主内容分发区域与非主内容分发区域。非主内容分发区域一般包含顶部栏、底部栏和侧边栏。侧边栏可以放置菜单,菜单与主内容分发区域内容进行联动。 默认母板 <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </container> </mask> 该模板中包含了如下几个组件: mask:母版根标签 multi-tabs:多选项卡 header:顶部栏 container:容器 sldebar:侧边栏 nav-menu:导航菜单 content:主内容 breadcrumb:面包屑 block:div块 main-view:主视图;用于渲染布局和DSL等相关内容; 母版将整个页面的大体框架进行了描述,接下来将主要介绍布局和DSL是如何在main-view中进行渲染的。关于自定义母版组件的相关内容 点击查看 布局 布局是将页面拆分成一个一个的小单元,按照从上到下、从左到右进行顺序排列 布局主要用于控制页面中元素的展示的相对位置,原则上不建议将元数据相关内容在布局中进行使用,可最大化布局的利用率。 默认表格视图(TABLE) <view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" /> </view> </pack> <pack widget="group" slot="tableGroup"> <element widget="actionBar" slot="actionBar"> <xslot name="actions" /> </element> <element widget="table" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" /> <element widget="rowActions" slot="rowActions" /> </element> </pack> </view> 该模板中包含了如下几个组件: view:视图;用于定义当前视图类型,不同的视图类型会有不同的数据交互,以及渲染不同的组件。 pack:容器类型相关组件。 element:元素组件;包含各种各样的组件,根据组件实现有不同的作用。 xslot:DSL插槽;用于将DSL中定义的模板分别插入到对应的槽中; 特别的,任何XML标签上的slot属性都具备DSL插槽的全部能力。当学习完DSL相关内容后,我们将会对DSL插槽有比较清晰的理解。 PS:在下面的内容中,将使用该布局进行描述。 DSL 准备工作 为了方便描述DSL和元数据之间的关系,我们需要先定义一个简单模型,这个模型里面包含字段和动作。这些通常是服务端定义的。(对服务端不感兴趣的同学可以跳过代码部分) DemoModel.java @Model.model(DemoModel.MODEL_MODEL) @Model(displayName = "演示模型", labelFields = {"name"}) public class DemoModel extends IdModel { private static final long serialVersionUID = -7211802945795866154L; public static final String MODEL_MODEL = "demo.DemoModel"; @Field(displayName = "名称") private String name; @Field(displayName = "是否启用") private Boolean isEnabled; } DemoModelAction.java @Model.model(DemoModel.MODEL_MODEL) @UxRouteButton( action = @UxAction(name = "redirectCreatePage", displayName = "创建", contextType = ActionContextTypeEnum.CONTEXT_FREE), value = @UxRoute(model =…

    2023年11月1日
    1.8K10
  • 自定义组件之手动渲染弹出层(v4)

    阅读之前 你应该: 了解自定义组件相关内容。 自定义组件之手动渲染基础(v4) 弹出层组件 我们内置了两个弹出层组件,弹窗(Dialog)和抽屉(Drawer),以下所有内容全部围绕弹窗(Dialog)进行描述,抽屉相关内容与弹窗完全一致。 下面这个对照表格可以帮助你区分两个弹出层组件的异同: 弹出层相关功能 弹窗(Dialog) 抽屉(Drawer) API方法 Dialog#create Drawer#create 内置组件 DialogWidget DrawerWidget 内置插槽 header, default, footer header, default, footer 渲染弹出层的方式 我们提供了三种渲染弹出层组件的方式,根据不同的情况使用不同的方式可以让实现变得更简单。 使用Dialog#createAPI方法创建弹窗:一般用于简单场景,动作区无法进行自定义。 使用DSL渲染能力创建弹窗 调用ActionWidget#click方法打开弹窗(适用于自动渲染场景) 使用createWidget创建DialogWidget并打开弹窗(适用于手动渲染场景) 使用第三方组件创建弹窗:一般用于弹窗需要自定义的场景中,性能最优,可用于任何场景。 使用Dialog#createAPI方法创建弹窗 以下是一个自定义组件的完整示例,其使用ViewCache#compule方法获取视图。 view.ts export const template = `<view> <field data="id" invisible="true" /> <field data="code" label="编码" /> <field data="name" label="名称" /> </view>`; ManualDemoWidget.ts import { BaseElementWidget, createRuntimeContextForWidget, Dialog, FormWidget, MessageHub, RuntimeView, SPI, ViewCache, ViewType, Widget } from '@kunlun/dependencies'; import ManualDemo from './ManualDemo.vue'; import { template } from './view'; @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'ManualDemo' })) export class ManualDemoWidget extends BaseElementWidget { public initialize(props) { super.initialize(props); this.setComponent(ManualDemo); return this; } @Widget.Method() public async openPopup() { // 获取运行时视图 const view = await this.fetchViewByCompile(); if (!view) { console.error('Invalid view'); return; } // 创建运行时上下文 const runtimeContext = createRuntimeContextForWidget(view); const runtimeContextHandle = runtimeContext.handle; // 获取初始化数据 const formData = await runtimeContext.getInitialValue(); // 创建弹窗 const dialogWidget = Dialog.create(); // 设置弹窗属性 dialogWidget.setTitle('这是一个演示弹窗'); // 创建所需组件 dialogWidget.createWidget(FormWidget, undefined, { metadataHandle: runtimeContextHandle, rootHandle: runtimeContextHandle, dataSource: formData, activeRecords: formData, template: runtimeContext.viewTemplate, inline: true }); dialogWidget.on('ok', () => { MessageHub.info('click ok'); // 关闭弹窗 dialogWidget.onVisibleChange(false); }); dialogWidget.on('cancel', () => { MessageHub.info('click cancel'); // 关闭弹窗 dialogWidget.onVisibleChange(false); }); // 打开弹窗…

    前端 2023年11月1日
    78900
  • 前端环境和启动前端工程

    本节核心是带大家直观的感受下我们上节构建的demo模块,并搭建前端环境为后续学习打下基础 环境准备 配置NPM源 npm config set registry http://nexus.shushi.pro/repository/kunlun/ 登录NPM源账号 npm login –registry "http://nexus.shushi.pro/repository/kunlun/" # username、password、email 获取方式: # 1、请见oinone开源社区群公告,也可以联系oinone合作伙伴或服务人员; # 2、参考数式发过去的部署包(部署.zip)中的账号说明:docker-mvn-npm账号.md npm info underscore 环境准备参考 [前端环境准备Mac版本]https://doc.oinone.top/oio4/9225.html [前端环境准备Windows版本]https://doc.oinone.top/oio4/9226.html 启动前端工程 1、下载前端工程本地运行 [ss-front-modules.zip]ss-front-modules 2、解压下载后的工程,可以查看README.MD快速上手指南; 找到vue.config.js文件,修改devServer.proxy.pamirs.target为后端服务的地址和端口 const WidgetLoaderPlugin = require('@kunlun/widget-loader/dist/plugin.js').default; const Dotenv = require('dotenv-webpack'); module.exports = { lintOnSave: false, runtimeCompiler: true, configureWebpack: { module: { rules: [ { test: /\.widget$/, loader: '@kunlun/widget-loader' } ] }, plugins: [new WidgetLoaderPlugin(), new Dotenv()], resolveLoader: { alias: { '@kunlun/widget-loader': require.resolve('@kunlun/widget-loader') } } }, devServer: { port: 8081, disableHostCheck: true, progress: false, proxy: { pamirs: { // 支持跨域 changeOrigin: true, // 改成本地后端对应的IP和端口; 本地后端未启动的情况也可改成无代码后端IP和端口 target: 'http://192.168.0.121:8190' } } } }; 3、 安装依赖和运行在工程目录ss-front-modules下执行 # 安装依赖 npm i # 运行 npm run dev 4、若安装失败 检查本地node、npm、vue对应的版本 5、 如果启动报错 清除node_modules后重新 npm i mac清除命令:npm run cleanOs windows清除命令: npm run clean 注:要用localhost域名访问,.env文件这里也要改成localhost。如果开发中一定要出现前后端域名不一致,老版本Chrome会有问题,修改可以请参https://www.cnblogs.com/willingtolove/p/12350429.html。或者下载新版本Chrome 进入前端工程ss-front-modules文件目录下,执行 npm run dev,最后出现下图就代表启动成功 6、使用 http://127.0.0.1:8081/login 进行访问,并用admin账号登陆,默认密码为admin 5、点击左上角进行应用切换,会进入App Finder页面,可以看到所有已经安装的应用,可以对照boot的yml配置文件看。 在后续的学习过程中我们会不断完善示例中的模块。至此恭喜您,前端工程已经启动完成。 示例工程分层说明 # ss-boot 不做业务研发,只做包的组装和依赖 # ss-oinone 与Oinone结合层,这个工程结构可以把数式Oinone的改造收口,也是业务工程依赖的核心层 # ss-admin-widget 与界面设计器无代码的结合工程,在这个工程结构里可以把组件放在无代码平台上使用 # ss-project 模拟的项目工程,做某个项目的个性化开发

    2024年5月28日
    1.6K00
  • 弹窗生命周期实践

    在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日
    64800

Leave a Reply

登录后才能评论