Skip to content

Commit

Permalink
feat: localisation support
Browse files Browse the repository at this point in the history
Added support to multiple locales and the en_US locale is now provided alongside the zh_CN one. The locale name can be passed via the "locale" option to Cherry.
zh_CN is the default locale, for backward compatibility.
Note that this patch offers basic support to localisation, but a lot of work is still missing:
- Passing a wrong or incomplete locale may result in Cherry not working or misbehaving.
- Many Chinese strings are still hardcoded in the project. Use `grep -rP '\'[\p{Han}]' src/` and similar regexes to find some.
- README.CN.md hasn't been updated.

Fixed #217
  • Loading branch information
peoro authored and lyngai committed Nov 28, 2022
1 parent 56ceedc commit 7ca12b7
Show file tree
Hide file tree
Showing 15 changed files with 204 additions and 100 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ registerPlugin().then(() => {
autoScrollByCursor: true,
// Whether to force output to the body when the outer container does not exist
forceAppend: true,
// The locale Cherry is going to use. Locales live in /src/locales/
locale: 'zh_CN',
}
```
### Close float menu or bubble menu
Expand Down
2 changes: 2 additions & 0 deletions src/Cherry.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ const defaultConfig = {
autoScrollByCursor: true,
// 外层容器不存在时,是否强制输出到body上
forceAppend: true,
// The locale Cherry is going to use. Locales live in /src/locales/
locale: 'zh_CN',
};

export default cloneDeep(defaultConfig);
4 changes: 4 additions & 0 deletions src/Cherry.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import defaultConfig from './Cherry.config';
import './sass/cherry.scss';
import cloneDeep from 'lodash/cloneDeep';
import Event from './Event';
import locales from '@/locales/index';

import { urlProcessorProxy } from './UrlCache';
import { CherryStatic } from './CherryStatic';
Expand Down Expand Up @@ -62,6 +63,9 @@ export default class Cherry extends CherryStatic {
*/
this.options = mergeWith({}, defaultConfigCopy, options, customizer);

// loading the locale
this.locale = locales[this.options.locale];

if (typeof this.options.engine.global.urlProcessor === 'function') {
this.options.engine.global.urlProcessor = urlProcessorProxy(this.options.engine.global.urlProcessor);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export default class Editor {
currentCursor.line = range[0].anchor.line;
currentCursor.ch = range[0].anchor.ch;
codemirrorDoc.replaceSelection(mdText);
pasteHelper.showSwitchBtnAfterPasteHtml(currentCursor, codemirror, htmlText, mdText);
pasteHelper.showSwitchBtnAfterPasteHtml(this.$cherry, currentCursor, codemirror, htmlText, mdText);
} else {
codemirrorDoc.replaceSelection(mdText);
}
Expand Down
11 changes: 0 additions & 11 deletions src/Locales.js

This file was deleted.

81 changes: 81 additions & 0 deletions src/locales/en_US.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Copyright (C) 2021 THL A29 Limited, a Tencent company.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default {
bold: 'Bold',
code: 'Code',
graph: 'Graph',
h1: 'Heading 1',
h2: 'Heading 2',
h3: 'Heading 3',
h4: 'Heading 4',
h5: 'Heading 5',
header: 'Header',
insert: 'Insert',
italic: 'Italic',
list: 'List',
quickTable: 'Quick Table',
quote: 'Quote',
size: 'Size',
color: 'Color',
strikethrough: 'Strikethrough',
sub: 'Sub',
sup: 'Sup',
togglePreview: 'Toggle Preview',
fullScreen: 'Full Screen',
image: 'Image',
audio: 'Audio',
video: 'Video',
link: 'Link',
hr: 'Horizontal Rule',
br: 'New Line',
toc: 'Table Of Content',
pdf: 'PDF',
word: 'Word',
table: 'Table',
'line-table': 'Line Table',
'bar-table': 'Bar Table',
formula: 'Formula',
insertFormula: 'Insert Formula',
insertFlow: 'Insert Flow',
insertSeq: 'Insert Seq',
insertState: 'Insert State',
insertClass: 'Insert Class',
insertPie: 'Insert Pie',
insertGantt: 'Insert Gantt',
checklist: 'Checklist',
ol: 'Ordered List',
ul: 'Unordered List',
undo: 'Undo',
redo: 'Redo',
previewClose: 'Preview Close',
codeTheme: 'Code Theme',
switchModel: 'Switch Model',
switchPreview: 'Switch Preview',
switchEdit: 'Switch Edit',
classicBr: 'Classic New Line',
normalBr: 'Normal New Line',
settings: 'Settings',
mobilePreview: 'Mobile Preview',
copy: 'Copy',
export: 'Export',
underline: 'Underline',
pinyin: 'Pinyin',
pastePlain: 'Paste as Plain Text',
pasteMarkdown: 'Paste as Markdown',
hide: 'Hide (ctrl+0)',
exportToPdf: 'Export to PDF',
exportScreenshot: 'Screenshot',
};
67 changes: 6 additions & 61 deletions src/locales/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,66 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import zhCn from './zh_CN';
import enUs from './en_US';

export default {
zh_CN: {
bold: '加粗', // 加粗
code: '代码', // 代码
graph: '画图', // 画图
h1: '一级标题', // 一级标题
h2: '二级标题', // 二级标题
h3: '三级标题', // 三级标题
h4: '四级标题', // 四级标题
h5: '五级标题', // 五级标题
header: '标题', // 标题
insert: '插入', // 插入
italic: '斜体', // 斜体
list: '列表', // 列表
quickTable: '表格', // 表格
quote: '引用', // 引用
size: '大小', // 大小
color: '文字颜色&背景', // 文字颜色&背景
strikethrough: '删除线', // 删除线
sub: '下标', // 下标
sup: '上标', // 上标
togglePreview: '预览', // 预览
fullScreen: '全屏', // 全屏
image: '图片', // 图片
audio: '音频', // 音频
video: '视频', // 视频
link: '超链接', // 超链接
hr: '分隔线', // 分隔线
br: '换行', // 换行
toc: '目录', // 目录
pdf: 'pdf', // pdf
word: 'word', // word
table: '表格', // 表格
'line-table': '折线表格', // 折线表格
'bar-table': '柱状表格', // 柱状表格
formula: '公式', // 公式
insertFormula: '公式', // 公式
insertFlow: '流程图', // 流程图
insertSeq: '时序图', // 时序图
insertState: '状态图', // 状态图
insertClass: '类图', // 类图
insertPie: '饼图', // 饼图
insertGantt: '甘特图', // 甘特图
checklist: '清单', // 清单
ol: '有序列表', // 有序列表
ul: '无序列表', // 无序列表
undo: '撤销', // 撤销
redo: '恢复', // 恢复
previewClose: '关闭预览', // 关闭预览
codeTheme: '代码主题', // 代码主题
switchModel: '模式切换', // 模式切换
switchPreview: '预览', // 预览
switchEdit: '返回编辑', // 返回编辑
classicBr: '经典换行', // 经典换行
normalBr: '常规换行', // 常规换行
settings: '设置', // 设置
mobilePreview: '移动端预览', // 移动端预览
copy: '复制内容', // 复制内容
export: '导出', // 导出PDF、长图
underline: '下划线', // 下划线
pinyin: '拼音', // 拼音
},
zh_CN: zhCn,
en_US: enUs,
};
81 changes: 81 additions & 0 deletions src/locales/zh_CN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Copyright (C) 2021 THL A29 Limited, a Tencent company.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default {
bold: '加粗', // 加粗
code: '代码', // 代码
graph: '画图', // 画图
h1: '一级标题', // 一级标题
h2: '二级标题', // 二级标题
h3: '三级标题', // 三级标题
h4: '四级标题', // 四级标题
h5: '五级标题', // 五级标题
header: '标题', // 标题
insert: '插入', // 插入
italic: '斜体', // 斜体
list: '列表', // 列表
quickTable: '表格', // 表格
quote: '引用', // 引用
size: '大小', // 大小
color: '文字颜色&背景', // 文字颜色&背景
strikethrough: '删除线', // 删除线
sub: '下标', // 下标
sup: '上标', // 上标
togglePreview: '预览', // 预览
fullScreen: '全屏', // 全屏
image: '图片', // 图片
audio: '音频', // 音频
video: '视频', // 视频
link: '超链接', // 超链接
hr: '分隔线', // 分隔线
br: '换行', // 换行
toc: '目录', // 目录
pdf: 'pdf', // pdf
word: 'word', // word
table: '表格', // 表格
'line-table': '折线表格', // 折线表格
'bar-table': '柱状表格', // 柱状表格
formula: '公式', // 公式
insertFormula: '公式', // 公式
insertFlow: '流程图', // 流程图
insertSeq: '时序图', // 时序图
insertState: '状态图', // 状态图
insertClass: '类图', // 类图
insertPie: '饼图', // 饼图
insertGantt: '甘特图', // 甘特图
checklist: '清单', // 清单
ol: '有序列表', // 有序列表
ul: '无序列表', // 无序列表
undo: '撤销', // 撤销
redo: '恢复', // 恢复
previewClose: '关闭预览', // 关闭预览
codeTheme: '代码主题', // 代码主题
switchModel: '模式切换', // 模式切换
switchPreview: '预览', // 预览
switchEdit: '返回编辑', // 返回编辑
classicBr: '经典换行', // 经典换行
normalBr: '常规换行', // 常规换行
settings: '设置', // 设置
mobilePreview: '移动端预览', // 移动端预览
copy: '复制内容', // 复制内容
export: '导出', // 导出PDF、长图
underline: '下划线', // 下划线
pinyin: '拼音', // 拼音
pastePlain: '粘贴为纯文本格式', // 粘贴为纯文本格式
pasteMarkdown: '粘贴为markdown格式', // 粘贴为markdown格式
hide: '隐藏(ctrl+0)', // 隐藏(ctrl+0)
exportToPdf: '导出PDF', // 导出PDF
exportScreenshot: '导出长图', // 导出长图
};
10 changes: 5 additions & 5 deletions src/toolbars/MenuBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
// @ts-check
import Logger from '@/Logger';
import locale from '@/locales/index';
import { escapeHTMLSpecialCharOnce as $e } from '@/utils/sanitize';
import { createElement } from '@/utils/dom';

Expand Down Expand Up @@ -57,6 +56,7 @@ export default class MenuBase {
this.subMenu = null; // 子菜单实例
this.name = ''; // 菜单项Name
this.editor = $cherry.editor; // markdown实例
this.locale = $cherry.locale;
this.dom = null;
this.updateMarkdown = true; // 是否更新markdown原文
this.subMenuConfig = []; // 子菜单配置
Expand Down Expand Up @@ -128,7 +128,7 @@ export default class MenuBase {
? 'cherry-dropdown-item'
: `cherry-toolbar-button cherry-toolbar-${this.iconName ? this.iconName : this.name}`;
const span = createElement('span', classNames, {
title: locale.zh_CN[this.name] || $e(this.name),
title: this.locale[this.name] || $e(this.name),
});
// 如果有图标,则添加图标
if (this.iconName && !this.noIcon) {
Expand All @@ -137,7 +137,7 @@ export default class MenuBase {
}
// 二级菜单强制显示文字,没有图标的按钮也显示文字
if (asSubMenu || this.noIcon) {
span.innerHTML += locale.zh_CN[this.name] || $e(this.name);
span.innerHTML += this.locale[this.name] || $e(this.name);
}
// 只有一级菜单才保存dom,且只保存一次
if (!asSubMenu && !this.dom) {
Expand All @@ -149,13 +149,13 @@ export default class MenuBase {
createSubBtnByConfig(config) {
const { name, iconName, onclick } = config;
const span = createElement('span', 'cherry-dropdown-item', {
title: locale.zh_CN[name] || $e(name),
title: this.locale[name] || $e(name),
});
if (iconName) {
const icon = createElement('i', `ch-icon ch-icon-${iconName}`);
span.appendChild(icon);
}
span.innerHTML += locale.zh_CN[name] || $e(name);
span.innerHTML += this.locale[name] || $e(name);
span.addEventListener('click', onclick, false);
return span;
}
Expand Down
4 changes: 2 additions & 2 deletions src/toolbars/hooks/Export.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export default class Export extends MenuBase {
this.noIcon = true;
this.updateMarkdown = false;
this.subMenuConfig = [
{ noIcon: true, name: '导出PDF', onclick: this.bindSubClick.bind(this, 'pdf') },
{ noIcon: true, name: '导出长图', onclick: this.bindSubClick.bind(this, 'screenShot') },
{ noIcon: true, name: 'exportToPdf', onclick: this.bindSubClick.bind(this, 'pdf') },
{ noIcon: true, name: 'exportScreenshot', onclick: this.bindSubClick.bind(this, 'screenShot') },
];
}

Expand Down
11 changes: 5 additions & 6 deletions src/toolbars/hooks/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* limitations under the License.
*/
import MenuBase from '@/toolbars/MenuBase';
import locale from '@/locales/index';
import Event from '@/Event';
import { saveIsClassicBrToLocal, getIsClassicBrFromLocal, testKeyInLocal } from '@/utils/config';

Expand Down Expand Up @@ -42,7 +41,7 @@ export default class Settings extends MenuBase {
this.subMenuConfig = [
{ iconName: classicBrIconName, name: classicBrName, onclick: this.bindSubClick.bind(this, 'classicBr') },
{ iconName: previewIcon, name: previewName, onclick: this.bindSubClick.bind(this, 'previewClose') },
{ iconName: '', name: '隐藏(ctrl+0)', onclick: this.bindSubClick.bind(this, 'toggleToolbar') },
{ iconName: '', name: 'hide', onclick: this.bindSubClick.bind(this, 'toggleToolbar') },
];
this.attachEventListeners();
this.shortcutKeyMaps = [
Expand Down Expand Up @@ -89,10 +88,10 @@ export default class Settings extends MenuBase {
const icon = /** @type {HTMLElement} */ (dropdown.querySelector('.ch-icon-previewClose,.ch-icon-preview'));
icon.classList.toggle('ch-icon-previewClose');
icon.classList.toggle('ch-icon-preview');
icon.title = locale.zh_CN[previewName];
icon.title = this.locale[previewName];
icon.parentElement.innerHTML = icon.parentElement.innerHTML.replace(
/<\/i>.+$/,
`</i>${locale.zh_CN[previewName]}`,
`</i>${this.locale[previewName]}`,
);
}
} else {
Expand Down Expand Up @@ -139,10 +138,10 @@ export default class Settings extends MenuBase {
i = i ? i : this.$cherry.wrapperDom.querySelector('.cherry-dropdown .ch-icon-br');
if (targetIsClassicBr) {
i.classList.replace('ch-icon-normal', 'ch-icon-br');
i.parentElement.childNodes[1].textContent = locale.zh_CN.classicBr;
i.parentElement.childNodes[1].textContent = this.locale.classicBr;
} else {
i.classList.replace('ch-icon-br', 'ch-icon-normal');
i.parentElement.childNodes[1].textContent = locale.zh_CN.normalBr;
i.parentElement.childNodes[1].textContent = this.locale.normalBr;
}
this.engine.$cherry.previewer.update('');
this.engine.$cherry.initText(this.engine.$cherry.editor.editor);
Expand Down
Loading

0 comments on commit 7ca12b7

Please sign in to comment.