按vue组件实例类型实现非侵入式国际化多语言翻译
#vue3##国际化##本地化##international#
web界面国际化,I18N(Internationalization,国际化),I11L(International,英特纳雄耐尔),L10N(Localization,本地化),显示文字多语言化是其主要内容。
浏览器的js提供了Intl全局对象,html提供了translate属性。
console.log(Intl,window.Intl);
<p translate="no">Don't translate this!</p>
某些浏览器插件支持一键“翻译网页”。其实现很简单,递归遍历找到<span><text>等文字标签,按通用字典对照翻译,并监听dom的变化,对弹出的内容或更新的内容也能即时翻译。这种一网打尽式的翻译,对某些wiki、doc、博客、新闻类网站比较适合,但是对一些ERP等管理系统,不太适合:
- 不精准。不该翻译的地方翻译了,比如用户输入的内容:产品编码、产品型号,Grid的cell内容...除非能用“translate='no'”把不翻译的dom标注得很完整。
- 不专业。翻译的字典是通用的,可能因为行业不同,牛头不对马嘴。插件能支持用我自己的字典? 因此,ERP这种管理系统,一般要有自己的多语言方案。
要做一个翻译方案,要考虑以下问题:
- 字典。字典格式(json,csv,xml)、字典的读取、解析、编辑。
- 国家语言标识。语言代码( Language Code)、国家代码( Country Code)、Windows Language Code Identifier (LCID),一般不再使用旧的Code Page(简体中文936,繁体中文950)。
- 语言切换。LCID存哪里。是否重加载页面。
- 翻译函数。高效的把一个文本字串,查找字典,翻译成对照文本。
- 翻译的时机。在何时调用翻译函数。
i18n(http://npmmirror.com/package/i18n,http://npmmirror.com/package/i18next)组件提供了一套简单的机制。 对vue框架,i18n对照有个vue-i18n(http://npmmirror.com/package/vue-i18n)。 i18n的实现了多语言化的基本机制,需要程序员配合,在合适的地方调用翻译函数。
我这里要讲的是一种非侵入式的,外挂式的多语言转换机制,比较适合已经完成的系统,突然要加多语言机制。 我的这篇老文章里(https://nodejs.blog.csdn.net/article/details/248218)谈到了这种方法,那是在delphi、c#开发的winform程序中,在web开发中,因为有了react、vue这样的框架实现了web组件化,因此也可以使用类似的方案。这个方案的主要思想是:为每个vue类注册一个翻译函数,在必要时把vue组件实例按其类的翻译函数来逐个翻译。
acroMLClassMethod.register('DataGrid',t_DataGrid);
acroMLClassMethod.register('GridColumn',t_GridColumn);
acroMLClassMethod.register('GridFilterButton',t_GridFilterButton);
acroMLClassMethod.register('Menu',t_Menu);
acroMLClassMethod.register('MenuItem',t_MenuItem);
翻译函数,如:v3-easyui组件的翻译函数,像这个样子:
function t_GridColumn(t,instWrap){let inst=instWrap.$;acroMLClassMethod.translateProps(t,inst.props,['title']);
}
function t_GridFilterButton(t,instWrap){let inst=instWrap.$;let items=instWrap.$data.items;if (items){for(let i=0;i<items.length;i++){let item=items[i];acroMLClassMethod.translateProps(t,item,['text']);}}
}
这个方案的几个特点:
- 外挂式。写页面的程序员一般情况下感觉它不存在。特殊的地方还是用显式的使用t函数,或标记translate='no'。
- 相对准确。相比浏览器的自动翻译,不会“误伤”。如DataGrid,会翻译column,footer的title,不会翻译到cell。相对于传统i18n机制显式的使用t函数,没那么精准,如:SelectBox下拉框,当选择的文本用于代码比较时是不能翻译的,还是需要显式的标记translate='no'或挂载beforeTranslate事件告知不翻译。
- 切换语言时能做到不重加载页面就刷新。传统i18n机制可能也能做到,只要t函数是响应式的,能响应locale的变化。但是,某些第3方组件包,其内置的一些文本字串,切换语言后,即使重加载了其对应的内置字典,也不会自动刷新。如devextreme-vue的DataGrid的noDataText。
文无第一,武无第二,各种方案没有绝对的好坏,就像react与vue,暂时无法谁碾压谁,适合不同的场景和不同的团队。
这个方案的几个注意点:
- 如何从vue组件实例找到其类别。
- 注册类别还是注册类别名称。
- 何时调用翻译函数。
- 不同厂家的vue组件属性修改机制的差别。
- 切换语言时如何不重加载页面而刷新。
- 人为设定不翻译某些组件的机制。
- 切换语言时DDKey从哪里找。
1、如何从vue组件实例找到其类别
- instWrap.$options.name
- instWrap.$.constructor.name
- instWrap.$.type.name
translateCom(t,instWrap,isTranslateChildren=true){//console.log(instWrap);let inst=instWrap.$;let typeName=instWrap.$options.name;if (!typeName){if (inst.constructor && (inst.constructor.name!='Object')) typeName=inst.constructor.name;}if (!typeName) typeName=inst.type.name;if (!typeName){for(let i=0;i<classNameGetters.length;i++){typeName=classNameGetters[i](instWrap);if (typeName) break;}}//console.log(typeName,instWrap);let m;if (typeName){m=classMethods[typeName];}else{let {classID,method}=getClassMethod(inst.type);m=method;}if (m){if (instWrap.$attrs.translate!='no'){m(t,instWrap);}}if (isTranslateChildren){acroMLClassMethod.translateVNode(t,inst.subTree,isTranslateChildren);}},
如果从已知渠道拿不到类别名称,那再实现一个机制,让组件厂商告诉你如何获取。如:devextreme-vue组件,上面3个渠道是得不到组件类别名称的。只有特别处理:
acroMLClassMethod.registerClassNameGetter(function(instWrap){let className;if (instWrap.$_instance) className=instWrap.$_instance.NAME;if (!className) className=instWrap.widget;//if (!className) className=instWrap.$options.$_optionName;return className;
});
2、注册类别还是注册类别名称
前面看到注册翻译函数时,类别都是使用的类别名称,如果使用类别,也是可以实现的,即:
acroMLClassMethod.register(DataGrid,t_DataGrid);
acroMLClassMethod.register(Menu,t_Menu);
但是,直接用类别,需要把组件加载到内存中,有几个坏处:
- 组件包的组件全部加载了。实际项目可能没有用到那么多组件,比如devextreme中的甘特图、枢纽分析Grid组件;
- 组件加载机制可能与外部不同。你可能是用一个包加载,外面可能是用分文件加载,这会导致相同组件在内存中有两份,可能导致组件内部逻辑错误。
//加载方式1:
import DX from 'devextreme-vue';//加载方式2:
import {DxDataGrid,DxColumn,DxPager,DxPaging,DxGroupPanel,DxSearchPanel,DxSelection,DxFilterRow,DxScrolling} from 'devextreme-vue/data-grid';
import {DxTextBox,DxButton as DxTextBoxButton} from 'devextreme-vue/text-box';
import DxButton from 'devextreme-vue/button';
import DxCheckBox from 'devextreme-vue/check-box';
import DxColorBox from 'devextreme-vue/color-box';//加载方式3:
import DxButton from 'devextreme-vue/button.mjs';
import DxCheckBox from 'devextreme-vue/check-box.mjs';
3、何时调用翻译函数
谢天谢地,vue3还保留了app.mixin。可以混入mounted、updated、unmounted函数到全部的vue组件中,这是监测vue组件生命周期的好地方。
import acroMLClassMethod from 'acroml/vue/acroML.ClassMethod.mjs';
import {YJEvent} from 'foil/util.yjEvent.mjs';
/*** 用insts来维护vue组件实例列表。* 因为当多语言切换时,从$root不知道如何遍历到DxDropDownBox模版中的DxDataGrid*/
let insts=[];
let isTranslating=false;class YJLocaleTranslator extends YJEvent{init(app){let self=this;app.mixin({mounted(){/*** app.mixin混入到任何组件的生命周期中。只翻译当前组件。* 注意:这里的this是被混入的vue组件实例,不是YJVueTranslator实例*///let inst=Vue.getCurrentInstance();//console.log('DOM mounted:',this.$options.name,this);let instWrap=this;setTimeout(function(){/*** 混入的函数是先于组件的函数执行。* 用setTimeout造成异步效果,等待让DxDataGrid实例的$_instance有值。 * 如果不用setTimeout,混入到mounted函数中,那时DxDataGrid实例的$_instance也不会有值。*/if (self.translateInst(instWrap)){insts.push(instWrap);}}, 0);},unmounted(){let index=insts.indexOf(this);if (index>=0) insts.splice(index,1);},updated(){/*** 翻译可能导致组件再更新,进入死循环。* update可能有很多原因,大多与显示翻译无关。* 组件更新时,可能把父组件文本属性给子组件,必须再次翻译。*/if (isTranslating) return;let instWrap=this;//console.log('beforeUpdate',instWrap);setTimeout(function(){isTranslating=true;try{self.translateInst(instWrap);}finally{/*** 用setTimeout造成异步效果,让“因为翻译引起的组件更新”不再进入翻译*/setTimeout(function(){isTranslating=false;},0);}},0);}});}translate(){let self=this;let t1=new Date();for(let i=0;i<insts.length;i++){let inst=insts[i];self.translateInst(inst);}let t2=new Date();console.log(`acroml translated ${insts.length} instances used ${t2.getTime()-t1.getTime()} ms.`);}translateInst(instWrap){let self=this;let ops={isTranslate:true};self.emit('beforeTranslate',instWrap,ops);if (!ops.isTranslate) return false;acroMLClassMethod.translateCom(t,instWrap,false);//acroMLClassMethod.translateVNode(t,inst.vnode,false);self.emit('afterTranslate',instWrap);return true;}
}let yjLocaleTranslator=new YJLocaleTranslator();export default yjLocaleTranslator;
export {yjLocaleTranslator}
4、不同厂家的vue组件属性修改机制的差别
一般的组件,修改instWrap.$.props就可以,而且能做到不重新加载页面就刷新。如:ant-design-vue的Table:
function t_Table(t,instWrap){//console.log('t_Table',instWrap);let inst=instWrap.$;if (inst.props.columns){for(let i=0;i<inst.props.columns.length;i++){let column=inst.props.columns[i];acroMLClassMethod.translateProp(t,column,'title');}}acroMLClassMethod.translateProps(t,inst.props.locale);inst.props.locale={...inst.props.locale};
}
如:element-plus的ElTable:
function t_ElTable(t, instWrap){let inst=instWrap.$;//console.log(instWrap);acroMLClassMethod.translateProps(t,inst.props,['emptyText','confirmFilter','resetFilter','clearFilter','sumText']);let columns=instWrap.columns;if (columns){for(let i=0;i<columns.length;i++){acroMLClassMethod.translateProp(t,columns[i],'label');}}
}
但是,devextreme-vue比较特殊,这样修改instWrap.$.props无效或不会刷新(问了devexrpess厂家,说官方不支持切换语言后不重加载页面刷新)。因为它的核心组件包devextreme,也用于react和angular,而且对于DataGrid的内部组件,有prop和slot两种定义方式:
<DxDataGrid :show-borders="true":groupPanel="{visible:true,emptyPanelText:'File'}":searchPanel="{visible:true,placeholder:'Edit'}">
</DxDataGrid>
<DxDataGrid :show-borders="true"><DxSearchPanel :visible="true" placeholder='Edit' /><DxGroupPanel :visible="true" emptyPanelText='File' />
</DxDataGrid>
因此,它定义了一套API机制来更新属性。
instWrap.$_instance.option();
instWrap.$_instance.state();
instWrap.$_instance._getDefaultOptions();
因此,我们使用这些API来翻译组件:
function wrap_transDevextremeCom(proc){return function(t,instWrap){let inst=instWrap.$_instance;if (!inst){return;}let state;if (inst.state) state=inst.state();let defaultValues=inst._getDefaultOptions();let tag=switchLocale();switchLocale('en');let defaultDDKeys=inst._getDefaultOptions();switchLocale(tag);options=inst.option();//if (inst.NAME=='dxDataGrid') console.log(2,options);/*** inst.option()返回的类型是object,不知为何修改placeholder后用inst.option(options)放回去不生效。* 但是inst.option({'placeholder':'上海'});或inst.option('placeholder','上海');会刷新。* 必须在修改前用JSON.parse(JSON.stringify(options))或options={...options}处理一下。* 有一个警告:error:35 W0001 - dxPopup - 'closeOnOutsideClick' option is deprecated in 22.1. Use the 'hideOnOutsideClick' option instead.*///options=JSON.parse(JSON.stringify(options));options={...options};inst.beginUpdate();try{proc(t,instWrap,options,defaultDDKeys,defaultValues);inst.option(options);/**设置预设参数可能丢掉了状态,恢复 */if (state) inst.state(state);}finally{inst.endUpdate();}}
}
DxDataGrid的翻译就这样写了,也能做到切换语言时不重加载页面而刷新:
function t_DxDataGrid(t,instWrap,options,defaultDDKeys,defaultValues){//console.log('t_DxDataGrid',instWrap);/*** 不要企图通过instWrap.$.props去修改参数,因为DxDataGrid的某些参数有2种写法:* (1)<DxDataGrid searchPanel="{visible:true,placeholder:'search...'}"><DxDataGrid>* (2)<DxDataGrid><DxSearchPanel visible='true' placeholder='search...' /><DxDataGrid>* 用instWrap.$.props时,第2种写法的参数是找不到的。* 不能通过coumns属性更新column的caption,因为<DxDataGrid><DxColumn dataField='ID />这种写法,columns属性为空* 必须通过method:columnOption来更新column的caption*/acroMLClassMethod.translateProps(t,options,['hint','noDataText'],defaultDDKeys,defaultValues);acroMLClassMethod.translateNodeProps(t,options,['loadPanel'],['text']);acroMLClassMethod.translateNodeProps(t,options,['columnChooser'],['emptyPanelText','title']);acroMLClassMethod.translateNodeProps(t,options,['columnChooser','search','editorOptions'],['placeholder']);acroMLClassMethod.translateNodeProps(t,options,['columnFixing','texts'],['fix','leftPosition','rightPosition','unfix']);acroMLClassMethod.translateNodeProps(t,options,['editing','texts'],['addRow','cancelAllChanges','cancelRowChanges','confirmDeleteMessage','confirmDeleteTitle','deleteRow','editRow','saveAllChanges','saveRowChanges','undeleteRow','validationCancelChanges']);acroMLClassMethod.translateNodeProps(t,options,['export','texts'],['exportAll','exportSelectedRows','exportTo']);acroMLClassMethod.translateNodeProps(t,options,['filterPanel','texts'],['clearFilter','createFilter','filterEnabledHint']);acroMLClassMethod.translateNodeProps(t,options,['filterRow'],['applyFilterText','betweenEndText','betweenStartText','resetOperationText','showAllText']);acroMLClassMethod.translateNodeProps(t,options,['groupPanel'],['emptyPanelText'],defaultDDKeys,defaultValues);acroMLClassMethod.translateNodeProps(t,options,['grouping','texts'],['groupByThisColumn','groupContinuedMessage','groupContinuesMessage','ungroup','ungroupAll']);acroMLClassMethod.translateNodeProps(t,options,['headerFilter','texts'],['cancel','emptyValue','ok']);acroMLClassMethod.translateNodeProps(t,options,['pager'],['inforText']);acroMLClassMethod.translateNodeProps(t,options,['searchPanel'],['placeholder'],defaultDDKeys,defaultValues);acroMLClassMethod.translateNodeProps(t,options,['sorting'],['ascendingText','clearText','descendingText']);acroMLClassMethod.translateNodeProps(t,options,['summary','texts'],['avg','avgOtherColumn','count','max','maxOtherColumn','min','minOtherColumn','sum','sumOtherColumn']);let columns=options['columns'];if (columns){for (let i=0;i<columns.length;i++){let column=columns[i];acroMLClassMethod.translateProp(t,column,'caption',column.dataField);acroMLClassMethod.translateProps(t,column,['trueText','falseText']);}}
}
5、切换语言时如何不重加载页面而刷新
切换语言后,简单粗暴的方式是window.reload()重加载页面。但是全部状态都丢失。一般组件的内置文本,如devextreme-vue的DataGrid的noDataText,groupPanel的emptyPanelText,它是组件创建时按当前内置字典获取的,不会在字典重加载后自动刷新。但是本方案,因为切换语言时,会重翻译全部vue组件实例,所以可以刷新。
6、人为设定不翻译某些组件的机制
如果不翻译某个组件,可以设置translate属性为no,也可以实现一个beforeTranslate事件机制,如:
yjLocaleTranslator.on('beforeTranslate',function(instWrap,ops){//console.log(instWrap,ops);/*** 不翻译某个组件实例的方法:* (1)给组件设置一个attr:translate='no'* (2)挂载beforeTranslate事件,阻止某些组件翻译。*/if (instWrap===self.$refs.dropdownbox) ops.isTranslate=false;
});
7、切换语言时DDKey从哪里找
切换语言前,vue组件的显示字串已经翻译成当前语言了,如:"OK"翻译成了简体“确认”,再切换语言到繁体时,哪里去找OK的词?很简单,翻译前把OK作为DDKey保存起来,直接保存在vue组件实例上。
translateProp(t,obj,propName,propDefaultDDKey,propDefaultValue){if (!obj) return;/*** 没有属性值,没必要处理?* 不行,element-plus的ElTableColumn是不给label就不显示。* 但是devextreme-vue的DxColumn的caption可能没设置,但要按data-field来翻译显示。* DxDataGrid的groupPanel的emptyPanelText如果没有设置,使用官方的字典翻译。*///if (!obj[propName]) return;if (!obj._acroml_DDKeys) obj._acroml_DDKeys={};let DDKey=obj._acroml_DDKeys[propName];if (!DDKey){DDKey=obj[propName];/**保存原始的DDKey */if (!DDKey) DDKey=propDefaultDDKey;else if (DDKey===propDefaultValue) DDKey=propDefaultDDKey;}/**如果DDKey是undefine就让obj[propsName]保持undefined(这样组件会使用其当前字典),如果赋值''就显示空白了 */if (DDKey){let newValue=t(DDKey);if (newValue===DDKey){if (propDefaultValue) newValue=propDefaultValue;}if (obj[propName]!=newValue) obj[propName]=newValue;if (newValue!=DDKey) obj._acroml_DDKeys[propName]=DDKey;}else if (propDefaultValue && obj[propName]!=propDefaultValue) obj[propName]=propDefaultValue;},
注:写文章时,涉及到vue组件版本:
- vue,3.5.12
- devextreme-vue,23.2.8
- v3-easyui,3.0.14
- ant-design-vue,4.2.3
- element-plus,2.8.1
相关文章:
按vue组件实例类型实现非侵入式国际化多语言翻译
#vue3##国际化##本地化##international# web界面国际化,I18N(Internationalization,国际化),I11L(International,英特纳雄耐尔),L10N(Localization,本地化)&…...
Java入门:22.集合的特点,List,Set和Map集合的使用
1 什么是集合 本质就是容器的封装,可以存储多个元素 数组一旦创建,长度就不能再改变了。 数组一旦创建,存储内容的类型不能改变。 数组可以存储基本类型,也可以存储引用类型。 数组可以通过length获得容量的大小,但…...
重生之我在异世界学编程之C语言:深入指针篇(下)
大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 目录 题集(1)指针笔试题1&a…...
理解Parquet文件和Arrow格式:从Hugging Face数据集的角度出发
parquet发音:美 [pɑrˈkeɪ] 镶木地板;拼花木地板 理解Parquet文件和Arrow格式:从Hugging Face数据集的角度出发 引言 在机器学习和大数据处理中,数据的存储和传输格式对于性能至关重要。两种广泛使用的格式是 Parquet 和 Arr…...
下载 M3U8 格式的视频
要下载 M3U8 格式的视频(通常是 HLS 视频流),可以尝试以下几种方法: 方法 1:使用下载工具(推荐) 1. IDM(Internet Download Manager): 安装 IDM 并启用浏…...
Tomcat使用教程
下载地址:https://tomcat.apache.org/ 配置环境变量 变量名: CATALINA_HOME 变量值: D:\tools\apache-tomcat-9.0.97 Path: %CATALINA_HOME%\bin 启动Tomcat(打开命令提示符) startup.bat 解决乱码问题(打开conf\logging.properties) java.util.logging.Conso…...
LabVIEW氢气纯化控制系统
基于LabVIEW的氢气纯化控制系统满足氢气纯化过程中对精确控制的需求,具备参数设置、过程监控、数据记录和报警功能,体现了LabVIEW在复杂工业控制系统中的应用效能。 项目背景 在众多行业中,尤其是石油化工和航天航空领域,氢气作为…...
现在的电商风口已经很明显了
随着电商行业的不断发展,直播带货的热潮似乎正逐渐降温,而货架电商正成为新的焦点。抖音等平台越来越重视货架电商,强调搜索功能的重要性,预示着未来的电商中心将转向货架和搜索。 在这一转型期,AI技术与电商的结合为…...
Uniapp触底刷新
在你的代码中,使用了 scroll-view 来实现一个可滚动的评论区域,并且通过监听 scrolltolower 事件来触发 handleScrollToLower 函数,以实现“触底更新”或加载更多评论的功能。 关键部分分析: scroll-view 组件: scroll-view 是一…...
开源项目 - face parsing 人脸区域分割 人像区域分割 人脸分割 人像区域分割 BiSeNet
开源项目 - face parsing 人脸区域分割 人像区域分割 人脸分割 人像区域分割 BiSeNet 项目地址:GitHub - XIAN-HHappy/face_parsing: face_parsing 脸部分割 示例: 助力快速掌握数据集的信息和使用方式。 数据可以如此美好!...
python游戏设计---飞机大战
1.前言 上次做飞机大战游戏有人这么说: 好好好!今天必须整一个,今天我们来详细讲解一下,底部找素材文件下载!!! 2.游戏制作 目录如下: 1.导入的包 import pygame import sys imp…...
13TB的StarRocks大数据库迁移过程
公司有一套StarRocks的大数据库在大股东的腾讯云环境中,通过腾讯云的对等连接打通,通过dolphinscheduler调度datax离线抽取数据和SQL计算汇总,还有在大股东的特有的Flink集群环境,该环境开发了flink开发程序包部署,实时…...
HTTP代理有那些常见的安全协议?
在数据采集领域,HTTP代理扮演着至关重要的角色,它不仅帮助我们访问互联网资源,还涉及到数据的安全传输。了解HTTP代理中常见的安全协议对于保护数据安全、提高数据采集效率至关重要。那么,有哪些安全协议是在HTTP代理中常用的呢&a…...
Kylin Server V10 下基于Kraft模式搭建Kafka集群
一、Kraft 模式与 ZooKeeper 模式简介 在Kafka 2.8 之前,Kafka 重度依赖 ZooKeeper 集群做元数据管理、Controller 的选举等(统称为共识服务);当ZooKeeper 集群性能发生抖动时,Kafka 的性能也会受到很大的影响。如下图所示: 在 Kafka 2.8 之后,引入了基于 Raft …...
tauri使用github action打包编译多个平台arm架构和inter架构包踩坑记录
这些error的坑,肯定是很多人不想看到的,我的开源软件PakePlus是使用tauri开发的,PakePlus是一个界面化将任何网站打包为轻量级跨平台软件的程序,利用Tauri轻松构建轻量级多端桌面应用和多端手机应用,为了实现发布的时候…...
Python爬虫与窗口实现翻译小工具(仅限学习交流)
Python爬虫与窗口实现翻译小工具(仅限学习交流) 在工作中,遇到一个不懂的单词时,就会去网页找对应的翻译,我们可以用Python爬虫与窗口配合,制作一个简易的翻译小工具,不需要打开网页,自动把翻译结果显示出来。 整个过程比较简单。 # This is a sample Python script. …...
紫光展锐联合上汽海外发布量产车型,赋能汽车智能化
当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。 11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发…...
Maven 打包出现问题解决方案
我执行 mvn install 报如下错误 可是我在 web 模块中能正确引用到 common 的类,于是我把 web 引用到的 common 中的类先移动到 web 模块中,然后把 common 模块的类都删掉,然后再次执行 mvn install,结果报错如下: [ERROR] Faile…...
第四话:JS中的eval函数
theme: channing-cyan 1.不要使用eval! 如果你从来都没有用到过eval这个函数,甚至你都不知道这个函数的作用。那么我只能说:你做了一件正确的事情 o.O 虽然我这篇文章要说一下eval函数的一些能力和注意点,但是我希望࿰…...
歇一歇,写写段子
无聊的日子都在写段子1.0 中学的时候喜欢看意林之类的杂志, 里面的作者用乱七八糟的理由跑去旅游,然后说“阻碍你脚步的永远只有逃离的勇气和对生活的热爱”, 我觉得太对了,可惜 12306 付款方式里没有勇气和热爱,不…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
