Skip to content

Commit 2a9a86b

Browse files
committed
Support change language by user (#69)
1 parent 7098b42 commit 2a9a86b

File tree

16 files changed

+238
-81
lines changed

16 files changed

+238
-81
lines changed

global.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ declare namespace Timer {
4646
* @since 0.3.3
4747
*/
4848
displayBadgeText: boolean
49+
/**
50+
* The language of this extension
51+
*
52+
* @since 0.7.2
53+
*/
54+
locale: LocaleOption
4955
}
5056

5157
type StatisticsOption = {
@@ -67,4 +73,13 @@ declare namespace Timer {
6773
}
6874

6975
type Option = PopupOption & AppearanceOption & StatisticsOption
76+
77+
/**
78+
* @since 0.7.2
79+
*/
80+
type Locale = 'zh_CN' | 'en' | 'ja'
81+
/**
82+
* @since 0.7.2
83+
*/
84+
type LocaleOption = Locale | "default"
7085
}

src/app/components/option/components/appearance.ts

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,78 @@
55
* https://opensource.org/licenses/MIT
66
*/
77

8-
import { ElCard, ElDivider, ElSwitch } from "element-plus"
8+
import { ElCard, ElDivider, ElIcon, ElMessageBox, ElOption, ElSelect, ElSwitch, ElTooltip } from "element-plus"
99
import { defineComponent, h, Ref, ref } from "vue"
1010
import optionService from "@service/option-service"
1111
import { defaultAppearance } from "@util/constant/option"
1212
import { t } from "@app/locale"
13-
import { renderHeader, renderOptionItem, tagText } from "../common"
13+
import { renderHeader, renderOptionItem, tagText, tooltip } from "../common"
14+
import localeMessages from "@util/i18n/components/locale"
15+
import { InfoFilled } from "@element-plus/icons"
1416

1517
const optionRef: Ref<Timer.AppearanceOption> = ref(defaultAppearance())
1618
optionService.getAllOption().then(option => optionRef.value = option)
1719

18-
function updateOptionVal(key: keyof Timer.AppearanceOption, newVal: boolean) {
19-
const value = optionRef.value
20-
value[key] = newVal
21-
optionService.setAppearanceOption(value)
22-
}
23-
2420
const displayWhitelist = () => h(ElSwitch, {
2521
modelValue: optionRef.value.displayWhitelistMenu,
26-
onChange: (newVal: boolean) => updateOptionVal('displayWhitelistMenu', newVal)
22+
onChange: (newVal: boolean) => {
23+
optionRef.value.displayWhitelistMenu = newVal
24+
optionService.setAppearanceOption(optionRef.value)
25+
}
2726
})
2827

2928
const displayBadgeText = () => h(ElSwitch, {
3029
modelValue: optionRef.value.displayBadgeText,
31-
onChange: (newVal: boolean) => updateOptionVal('displayBadgeText', newVal)
30+
onChange: (newVal: boolean) => {
31+
optionRef.value.displayBadgeText = newVal
32+
optionService.setAppearanceOption(optionRef.value)
33+
}
34+
})
35+
36+
const allLocaleOptions: Timer.LocaleOption[] = ["default", "zh_CN", "en", "ja"]
37+
const locale = () => h(ElSelect, {
38+
modelValue: optionRef.value.locale,
39+
size: 'mini',
40+
style: { width: '120px' },
41+
onChange: async (newVal: Timer.LocaleOption) => {
42+
optionRef.value.locale = newVal
43+
await optionService.setAppearanceOption(optionRef.value)
44+
ElMessageBox({
45+
message: t(msg => msg.option.appearance.locale.changeConfirm),
46+
type: "success",
47+
confirmButtonText: t(msg => msg.option.appearance.locale.reloadButton),
48+
// Cant close this on press ESC
49+
closeOnPressEscape: false
50+
}).then(() => { location.reload?.() })
51+
.catch(() => {/* do nothing */ })
52+
}
53+
}, {
54+
default: () => allLocaleOptions.map(
55+
locale => h(ElOption, {
56+
value: locale, label: locale === "default"
57+
? t(msg => msg.option.appearance.locale.default)
58+
: localeMessages[locale].name
59+
})
60+
)
3261
})
3362

3463
const options = () => [
64+
renderOptionItem({
65+
input: locale(),
66+
info: h(ElTooltip, {}, {
67+
default: () => h(ElIcon, { size: 15 }, () => h(InfoFilled)),
68+
content: () => [
69+
t(msg => msg.option.appearance.locale.infoL1),
70+
h('br'),
71+
h('br'),
72+
t(msg => msg.option.appearance.locale.infoL2)
73+
]
74+
})
75+
},
76+
msg => msg.appearance.locale.label,
77+
t(msg => msg.option.appearance.locale.default)
78+
),
79+
h(ElDivider),
3580
renderOptionItem({
3681
input: displayWhitelist(),
3782
whitelist: tagText(msg => msg.option.appearance.whitelistItem),

src/app/components/option/components/popup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const durationOptions = () => ALL_POPUP_DURATION.map(item => h(ElOption, { value
4343
const durationSelect = () => h(ElSelect, {
4444
modelValue: optionRef.value.defaultDuration,
4545
size: 'mini',
46-
style: { width: '80px' },
46+
style: { width: t(msg => msg.option.popup.durationWidth) },
4747
onChange: (val: Timer.PopupDuration) => {
4848
optionRef.value.defaultDuration = val
4949
optionService.setPopupOption(optionRef.value)

src/app/components/option/components/statistics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const options = () => [
4545
h(ElDivider),
4646
renderOptionItem({
4747
input: countLocalFiles(),
48-
localFileTime: tagText(msg => msg.option.statistics.localFilesTime),
48+
localFileTime: tagText(msg => msg.option.statistics.localFileTime),
4949
info: tooltip(msg => msg.option.statistics.localFilesInfo)
5050
}, msg => msg.statistics.countLocalFiles, t(msg => msg.option.no)),
5151
h(ElDivider),

src/app/index.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,22 @@ import './styles' // global css
1212
import installRouter from "./router"
1313
import '../common/timer'
1414
import ElementPlus from 'element-plus'
15-
import { locale as appLocale, Locale } from "@util/i18n"
15+
import { initLocale, locale as appLocale } from "@util/i18n"
1616
import { Language } from "element-plus/lib/locale"
1717

18-
const locales: { [locale in Locale]: () => Promise<{ default: Language }> } = {
18+
const locales: { [locale in Timer.Locale]: () => Promise<{ default: Language }> } = {
1919
zh_CN: () => import('element-plus/lib/locale/lang/zh-cn'),
2020
en: () => import('element-plus/lib/locale/lang/en'),
2121
ja: () => import('element-plus/lib/locale/lang/ja')
2222
}
2323

24-
const app: App = createApp(Main)
25-
installRouter(app)
26-
app.mount('#app')
24+
async function main() {
25+
await initLocale()
26+
const app: App = createApp(Main)
27+
installRouter(app)
28+
app.mount('#app')
2729

28-
locales[appLocale]?.()?.then(locale => app.use(ElementPlus, { locale: locale.default }))
30+
locales[appLocale]?.()?.then(locale => app.use(ElementPlus, { locale: locale.default }))
31+
}
32+
33+
main()

src/app/layout/menu.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
import { defineComponent, h, onMounted, ref, Ref } from "vue"
99
import { ElIcon, ElMenu, ElMenuItem, ElMenuItemGroup, MenuItemRegistered } from "element-plus"
1010
import { RouteLocationNormalizedLoaded, Router, useRoute, useRouter } from "vue-router"
11-
import { I18nKey, t, locale } from "@app/locale"
12-
import { Locale } from "@util/i18n"
11+
import { I18nKey, t } from "@app/locale"
1312
import { MenuMessage } from "@app/locale/components/menu"
1413
import { GITHUB_ISSUE_ADD, HOME_PAGE, MEAT_URL, ZH_FEEDBACK_PAGE } from "@util/constant/url"
1514
import { Aim, Calendar, ChatSquare, Folder, Food, HotWater, Rank, SetUp, Stopwatch, Sugar, Tickets, Timer } from "@element-plus/icons"
1615
import ElementIcon from "../element-ui/icon"
16+
import { locale } from "@util/i18n"
1717

1818
declare type MenuItem = {
1919
title: keyof MenuMessage
@@ -33,7 +33,7 @@ declare type MenuGroup = {
3333
*
3434
* @since 0.3.2
3535
*/
36-
const realFeedbackLink: string = locale === Locale.ZH_CN ? ZH_FEEDBACK_PAGE : GITHUB_ISSUE_ADD
36+
const realFeedbackLink: string = locale === "zh_CN" ? ZH_FEEDBACK_PAGE : GITHUB_ISSUE_ADD
3737

3838
const OTHER_MENU_ITEMS: MenuItem[] = [{
3939
title: 'feedback',

src/app/locale/components/option.ts

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type OptionMessage = {
1717
defaultDisplay: string
1818
displaySiteName: string
1919
duration: PopupDurationMessage
20+
durationWidth: string
2021
}
2122
appearance: {
2223
title: string
@@ -28,14 +29,22 @@ export type OptionMessage = {
2829
displayBadgeText: string
2930
icon: string
3031
badgeTextContent: string
32+
locale: {
33+
label: string
34+
default: string
35+
changeConfirm: string
36+
reloadButton: string
37+
infoL1: string
38+
infoL2: string
39+
}
3140
}
3241
statistics: {
3342
title: string
3443
countWhenIdle: string
3544
idleTime: string
3645
idleTimeInfo: string
3746
countLocalFiles: string
38-
localFilesTime: string
47+
localFileTime: string
3948
localFilesInfo: string
4049
collectSiteName: string
4150
siteNameUsage: string
@@ -55,7 +64,8 @@ const _default: Messages<OptionMessage> = {
5564
max: '只显示前 {input} 条今日数据,剩下的条目合并显示',
5665
defaultDisplay: "打开时显示 {duration} {type}",
5766
displaySiteName: '{input} 显示时是否使用 {siteName} 来代替域名',
58-
duration: popupDurationMessages.zh_CN
67+
duration: popupDurationMessages.zh_CN,
68+
durationWidth: "80px"
5969
},
6070
appearance: {
6171
title: '外观',
@@ -64,15 +74,23 @@ const _default: Messages<OptionMessage> = {
6474
contextMenu: '浏览器的右键菜单',
6575
displayBadgeText: '{input} 是否在 {icon} 上,显示 {timeInfo}',
6676
icon: '扩展图标',
67-
badgeTextContent: '当前网站的今日浏览时长'
77+
badgeTextContent: '当前网站的今日浏览时长',
78+
locale: {
79+
label: "语言设置 {info} {input}",
80+
infoL1: "由于源码的结构缺陷,语言设置功能目前只对功能页有效,今日数据弹窗页的语言设置始终跟随浏览器。",
81+
infoL2: "作者将在后续版本中重构代码,实现弹窗页的语言切换功能,谢谢您的包涵!❤️❤️",
82+
default: "跟随浏览器",
83+
changeConfirm: "语言设置成功,请刷新页面!",
84+
reloadButton: "刷新"
85+
}
6886
},
6987
statistics: {
7088
title: '统计',
7189
countWhenIdle: '{input} 是否统计 {idleTime} {info}',
7290
idleTime: '休眠时间',
7391
idleTimeInfo: '长时间不操作(比如全屏观看视频),浏览器会自动进入休眠状态',
7492
countLocalFiles: '{input} 是否统计使用浏览器 {localFileTime} {info}',
75-
localFilesTime: '阅读本地文件的时间',
93+
localFileTime: '阅读本地文件的时间',
7694
localFilesInfo: '支持 PDF、图片、txt 以及 json 等格式',
7795
collectSiteName: '{input} 访问网站主页时,是否自动收集 {siteName} {siteNameUsage}',
7896
siteName: '网站的名称',
@@ -90,7 +108,8 @@ const _default: Messages<OptionMessage> = {
90108
max: 'Show the first {input} data items of today',
91109
defaultDisplay: "Show {duration} {type} when opened",
92110
displaySiteName: '{input} Whether to display {siteName} instead of host',
93-
duration: popupDurationMessages.en
111+
duration: popupDurationMessages.en,
112+
durationWidth: "110px"
94113
},
95114
appearance: {
96115
title: 'Appearance',
@@ -99,16 +118,24 @@ const _default: Messages<OptionMessage> = {
99118
contextMenu: 'the context menu',
100119
displayBadgeText: '{input} Whether to display {timeInfo} in {icon}',
101120
icon: 'the icon of extension',
102-
badgeTextContent: 'the browse time of current website'
121+
badgeTextContent: 'the browse time of current website',
122+
locale: {
123+
label: "Language {info} {input}",
124+
infoL1: "Due to the structural defects of the source code, the language setting function is currently only valid for this page. The language setting of today's data popup page is always the same as the browser.",
125+
infoL2: "I will refactor the code in subsequent versions to realize the language switching of the popup page. Thank you for your patience! ❤️❤️",
126+
default: "Same as browser",
127+
changeConfirm: "The language has been changed successfully, please reload this page!",
128+
reloadButton: "Reload"
129+
}
103130
},
104131
statistics: {
105132
title: 'Statistics',
106133
countWhenIdle: '{input} Whether to count {idleTime} {info}',
107134
idleTime: 'idle time',
108135
idleTimeInfo: 'If you do not operate for a long time (such as watching a video in full screen), the browser will automatically enter the idle state',
109-
countLocalFiles: '{input} Whether to count the time spent {localFilesTime} {info} in the browser',
110-
localFilesTime: ' reading the local file ',
111-
localFilesInfo: 'Contains formats such as PDF, image, txt and json',
136+
countLocalFiles: '{input} Whether to count the time spent {localFileTime} {info} in the browser',
137+
localFileTime: ' reading the local file ',
138+
localFilesInfo: 'Including PDF, image, txt and json',
112139
collectSiteName: '{input} Whether to automatically collect the name of the website when visiting the homepage of the website',
113140
siteName: 'Site Name',
114141
siteNameUsage: 'The data only exists locally and will be used for display instead of the host to increase recognition.'
@@ -126,31 +153,40 @@ const _default: Messages<OptionMessage> = {
126153
max: '今日のデータは、最初の {input} データを示しています',
127154
defaultDisplay: "開くと {duration} {type} が表示されます",
128155
// Not translated
129-
displaySiteName: '{input} Whether to display {siteName} instead of host',
130-
duration: popupDurationMessages.ja
156+
displaySiteName: '{input} ホストの代わりに {siteName} を表示するかどうか',
157+
duration: popupDurationMessages.ja,
158+
durationWidth: "100px"
131159
},
132160
appearance: {
133161
title: '外観',
134162
// Not translated
135-
displayWhitelist: '{input} Whether to display {whitelist} in {contextMenu}',
136-
whitelistItem: 'the whitelist item',
137-
contextMenu: 'the context menu',
138-
displayBadgeText: '{input} Whether to display {timeInfo} in {icon}',
139-
icon: 'the icon of extension',
140-
badgeTextContent: 'the browse time of current website'
163+
displayWhitelist: '{input} {contextMenu} に {whitelist} を表示するかどうか',
164+
whitelistItem: 'ホワイトリスト機能',
165+
contextMenu: 'コンテキストメニュー',
166+
displayBadgeText: '{input} {icon} に {timeInfo} を表示するかどうか',
167+
icon: '拡張機能のアイコン',
168+
badgeTextContent: '現在のウェブサイトの閲覧時間',
169+
locale: {
170+
label: "言語設定 {info} {input}",
171+
default: "ブラウザと同じ",
172+
infoL1: "ソースコードの構造上の欠陥により、言語設定機能は現在このページでのみ有効です。今日のデータポップアップページの言語設定は常にブラウザと同じです。",
173+
infoL2: "ポップアップページの言語切り替えを実現するために、以降のバージョンでコードをリファクタリングします。もうしばらくお待ちください。❤️❤️",
174+
changeConfirm: "言語が正常に変更されました。このページをリロードしてください。",
175+
reloadButton: "リロード"
176+
}
141177
},
142178
statistics: {
143179
title: '統計',
144-
countWhenIdle: '{input} Whether to count {idleTime} {info}',
145-
idleTime: 'idle time',
146-
idleTimeInfo: 'If you do not operate for a long time (such as watching a video in full screen), the browser will automatically enter the idle state',
147-
countLocalFiles: '{input} Whether to count the time spent {localFilesTime} {info} in the browser',
148-
localFilesTime: ' reading the local file ',
149-
localFilesInfo: 'Contains formats such as PDF, image, txt and json',
150-
collectSiteName: '{input} Whether to automatically collect the name of the website when visiting the homepage of the website',
151-
siteName: 'site name',
152-
siteNameUsage: 'The data only exists locally and will be used for display instead of the host to increase recognition.'
153-
+ 'Of course you can customize the name of each website.'
180+
countWhenIdle: '{input} {idleTime} をカウントするかどうか {info}',
181+
idleTime: 'アイドルタイム',
182+
idleTimeInfo: '長時間操作しない場合(フルスクリーンでビデオを見るなど)、ブラウザは自動的にアイドル状態になります',
183+
countLocalFiles: '{input} ブラウザで {localFileTime} {info} に費やされた時間をカウントするかどうか',
184+
localFileTime: ' ローカルファイルの読み取り ',
185+
localFilesInfo: 'PDF、画像、txt、jsonを含む',
186+
collectSiteName: '{input} ウェブサイトのホームページにアクセスしたときにウェブサイトの名前を自動的に収集するかどうか',
187+
siteName: 'サイト名',
188+
siteNameUsage: 'データはローカルにのみ存在し、認識を高めるためにホストの代わりに表示に使用されます。'
189+
+ 'もちろん、各Webサイトの名前をカスタマイズできます。'
154190
},
155191
resetButton: 'リセット',
156192
resetSuccess: 'デフォルトに正常にリセット',

src/app/locale/components/whitelist.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ const _default: Messages<WhitelistMessage> = {
4242
addConfirmMsg: '{url} がホワイトリストに追加されると、このWebサイトの統計はカウントされなくなります。',
4343
removeConfirmMsg: '{url} はホワイトリストから削除されます',
4444
duplicateMsg: '繰り返される',
45-
infoAlertTitle: 'You can set the whitelist of site in this page',
46-
infoAlert0: 'Sites in the whitelist will not be counted.',
47-
infoAlert1: 'Sites in the whitelist will not be limited.',
45+
infoAlertTitle: 'このページでサイトのホワイトリストを設定できます',
46+
infoAlert0: 'ホワイトリストのサイトはカウントされません。',
47+
infoAlert1: 'ホワイトリストのサイトは制限されません。',
4848
placeholder: 'URL',
49-
errorInput: "Invalid URL"
49+
errorInput: "無効なURL"
5050
}
5151
}
5252

src/app/locale/index.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,17 @@
55
* https://opensource.org/licenses/MIT
66
*/
77

8-
import { I18nKey as _I18nKey, locale as _locale, t as _t } from "@util/i18n"
8+
import { I18nKey as _I18nKey, locale, t as _t } from "@util/i18n"
99
import { tN as _tN } from "@util/i18n/i18n-vue"
1010
import messages, { AppMessage } from "./messages"
1111

12-
const message = messages[_locale]
13-
1412
export type I18nKey = _I18nKey<AppMessage>
1513

1614
export function t(key: I18nKey, param?: any) {
1715
const props = { key, param }
18-
return _t<AppMessage>(message, props)
16+
return _t<AppMessage>(messages[locale], props)
1917
}
2018

2119
export function tN(key: I18nKey, param?: any) {
22-
return _tN<AppMessage>(message, { key, param })
23-
}
24-
25-
export const locale = _locale
20+
return _tN<AppMessage>(messages[locale], { key, param })
21+
}

0 commit comments

Comments
 (0)