这一篇要写的是scratch-gui的多语言国际化翻译。LLK团队为了让非英语地区的同学参与scratch,构建了scratch-l10n项目,当然scratch-gui的语言国际化采用的便是scratch-l10n,然后通过react-intl国家化组件格式化。简单的说,就是scratch-l10n项目导出各种语言的翻译文件,scratch-gui导入并配置再由react-intl组件来格式化成用户所选择的语言。下面我们削微了解一下,之后做点有用的事情。
scratch-gui如何实现多语言
- 引入react-intl
”src\containers\gui.jsx”是从gui入口我们遇到的第一个用到多语言的地方,它引入了
import {defineMessages, injectIntl, intlShape} from 'react-intl';
然后还需要在组件中添加:
- defineMessages定义一个多语言对象,id相当于一个key,将来在scratch-l10导出的多语言文件中查找翻译字符串,defaultMessage表示默认显示,如果id没有对应的值,就显示默认的,description表示属性描述。
const messages = defineMessages({
defaultProjectTitle: {
id: 'gui.gui.defaultProjectTitle',
description: 'Default title for project',
defaultMessage: 'Scratch Project'
}
});
- 在组件中注入 injectIntl
const ConnectedGUI = injectIntl(connect(
mapStateToProps,
mapDispatchToProps,
)(GUI));
- 通过 LocalizationHOC 高阶组件为GUI提供本地化状态
react-int包还需要用 <IntlProvider />包裹在组件的最外层,scratch-gui中通过LocalizationHOC高阶组件来封装:
const WrappedGui = compose(
LocalizationHOC, // 提供本地化状态的高阶组件。
ErrorBoundaryHOC('Top Level App'), // 提供封装组件错误边界的高阶组件,传入一个string类型的action,用于GA跟踪错误标签
FontLoaderHOC, // 加载字体高阶组件
QueryParserHOC, // 从URL查询字符串获取参数并初始化redux状态的高阶组件
ProjectFetcherHOC, // 提供通过id加载项目的行为的高阶组件。如果没有id,则加载默认的项目。
ProjectSaverHOC, // 提供项目保存功能的高阶组件
vmListenerHOC, // 监听VM事件的高阶组件
vmManagerHOC, // 发送VM事件的高阶组件
cloudManagerHOC // 连接云服务器的高阶组件
)(ConnectedGUI);
看下LocalizationHOC组件:
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import ConnectedIntlProvider from './connected-intl-provider.jsx';
/*
* Higher Order Component to provide localiztion state. Creates a nested IntlProvider
* to handle Gui intl context. The component accepts an onSetLanguage callback that is
* called when the locale chagnes.
* @param {React.Component} WrappedComponent - component to provide state for
* @returns {React.Component} component with intl state provided from redux
*/
// 提供本地化状态的高阶组件。创建一个嵌套的IntlProvider来处理Gui intl上下文。组件接受一个onSetLanguage回调函数,该函数在语言环境chagnes时调用。
const LocalizationHOC = function (WrappedComponent) {
class LocalizationWrapper extends React.Component {
componentDidUpdate (prevProps) {
if (prevProps.locale !== this.props.locale) {
this.props.onSetLanguage(this.props.locale);
}
}
render () {
const {
locale, // eslint-disable-line no-unused-vars
onSetLanguage, // eslint-disable-line no-unused-vars
...componentProps
} = this.props;
return (
<ConnectedIntlProvider>
<WrappedComponent {...componentProps} />
</ConnectedIntlProvider>
);
}
}
LocalizationWrapper.propTypes = {
locale: PropTypes.string,
onSetLanguage: PropTypes.func
};
LocalizationWrapper.defaultProps = {
onSetLanguage: () => {}
};
const mapStateToProps = state => ({
locale: state.locales.locale
});
const mapDispatchToProps = () => ({});
return connect(
mapStateToProps,
mapDispatchToProps
)(LocalizationWrapper);
};
export default LocalizationHOC;
- 添加 localedata
定义在“src\reducers\locales.js”:
import {addLocaleData} from 'react-intl';
import {localeData} from 'scratch-l10n';
// 国际化只取中英文
import editorMessages from '../locales/editor-msgs';
// import editorMessages from 'scratch-l10n/locales/editor-msgs';
import {isRtl} from 'scratch-l10n';
addLocaleData(localeData);
const UPDATE_LOCALES = 'scratch-gui/locales/UPDATE_LOCALES';
const SELECT_LOCALE = 'scratch-gui/locales/SELECT_LOCALE';
const initialState = {
isRtl: false,
locale: 'en',
messagesByLocale: editorMessages,
messages: editorMessages.en
};
...
这里我们可以看到 scratch-l10n的多语言文件由
node-modlue
包的scratch-l10n/locales/editor-msgs.js
文件导出,这里我进行了一个处理,后面再介绍
到这里,多语言就配置好了,下面是使用
- 应用 FormattedMessage 来格式化多语言
setReduxTitle (newTitle) {
if (newTitle === null || typeof newTitle === 'undefined') {
this.props.onUpdateReduxProjectTitle(
this.props.intl.formatMessage(messages.defaultProjectTitle)
);
} else {
this.props.onUpdateReduxProjectTitle(newTitle);
}
}
这里是gui组件挂载完成后 ,componentDidMount()生命周期函数调用:
componentDidMount () {
setIsScratchDesktop(this.props.isScratchDesktop);
this.setReduxTitle(this.props.projectTitle);
this.props.onStorageInit(storage);
}
我们可以看到挂载完成后,设置项目标题,并保存到redux中,在这里用到了多语言
this.props.intl.formatMessage(messages.defaultProjectTitle)
。在第一步injectIntl注入到组件后,组件的prop上就有了一个intl
对象,然后formatMessage来应用。
添加新的翻译
今天我想添加一下新的翻译,大概看了下官方的方法也查阅了网上各位前辈的方法,大概总结一下吧:
官方的scratch-l10n库基本是不维护的,而是通过Transifex 来实现自动化构建的(难怪源码里.tx和translations文件夹不知道是干啥的)。也就是说你可以fork一下,然后在Transifex上创建自己的库。这也太吓人了。。。忽略
官方的wiki里也给另一个方法:
- 将新资源添加到.tx/config-例如,添加笔扩展名字符串:
[experimental-scratch.pen]
file_filter = pen/<lang>.json
source_file = pen/en.json
source_lang = en
type = CHROME - 运行tx pull -a -s以下拉所有翻译和源文件。[注意:这还将更新具有新翻译的其他资源。如果您只想更新新资源,则将添加-r 'experimental-scratch.pen'(即资源段)到命令中。
- 将新组件添加到中的组件列表中build-data.js。脚本假定组件的名称是包含翻译的文件夹的名称。
我没试过,因为我下载的是gui的zip文件,大概意思就是通过这个方式向Transifex提交新的翻译?,不管,我还是觉得麻烦
网上找到一篇文章,他介绍了如何添加新的翻译,但是我看到他把scratch-l10n项目clone下来操作,感觉还是麻烦。于是我按照我的需求改进了一下:
新建翻译文件
我参考卡拉的版本魔改,因此只保留中英文,但是每次都要在那么多资源里改,太麻烦,于是新建了一个资源,替换了原来的资源导入:
// 国际化只取中英文
import editorMessages from '../locales/editor-msgs';
// import editorMessages from 'scratch-l10n/locales/editor-msgs';
这样做有两个好处:
- 新增修改量小,当然如果你要把所有语言都翻译一边,量没差,不过找的也累。。
- 因为修改了node-module里的文件,git通常会忽略这个文件夹,这样做其实是非常不好的,所以我在src里,新增了locals文件,在这里独立处理中英文翻译。
写的有些啰嗦,溜了。