-
-
Notifications
You must be signed in to change notification settings - Fork 165
feat(i18n): add vue-i18n infrastructure with Simplified Chinese locale #895
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
32d7ccc
ad47544
33d36c7
7a26a6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import Vue from 'vue'; | ||
| import VueI18n from 'vue-i18n'; | ||
| import en from './locales/en.json'; | ||
| import zhCN from './locales/zh-CN.json'; | ||
|
|
||
| Vue.use(VueI18n); | ||
|
|
||
| const LOCALE_KEY = 'aw-locale'; | ||
|
|
||
| export const SUPPORTED_LOCALES = [ | ||
| { code: 'en', label: 'English' }, | ||
| { code: 'zh-CN', label: '中文(简体)' }, | ||
| ]; | ||
|
|
||
| function isSupportedLocale(locale) { | ||
| return SUPPORTED_LOCALES.some(l => l.code === locale); | ||
| } | ||
|
|
||
| function readStoredLocale() { | ||
| try { | ||
| const locale = localStorage.getItem(LOCALE_KEY); | ||
| return isSupportedLocale(locale) ? locale : 'en'; | ||
| } catch { | ||
| return 'en'; | ||
| } | ||
| } | ||
|
|
||
| const savedLocale = readStoredLocale(); | ||
|
|
||
| export const i18n = new VueI18n({ | ||
| locale: savedLocale, | ||
| fallbackLocale: 'en', | ||
| messages: { | ||
| en, | ||
| 'zh-CN': zhCN, | ||
| }, | ||
| }); | ||
|
|
||
| export function setLocale(locale) { | ||
| if (!isSupportedLocale(locale)) { | ||
| console.warn(`[i18n] Unsupported locale: ${locale}`); | ||
| return; | ||
| } | ||
|
|
||
| i18n.locale = locale; | ||
| try { | ||
| localStorage.setItem(LOCALE_KEY, locale); | ||
| } catch { | ||
| // Storage can be blocked in private browsing or hardened browser profiles. | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| { | ||
| "nav": { | ||
| "activity": "Activity", | ||
| "timeline": "Timeline", | ||
| "stopwatch": "Stopwatch", | ||
| "tools": "Tools", | ||
| "search": "Search", | ||
| "workReport": "Work Report", | ||
| "trends": "Trends", | ||
| "report": "Report", | ||
| "alerts": "Alerts", | ||
| "timespiral": "Timespiral", | ||
| "query": "Query", | ||
| "graph": "Graph", | ||
| "rawData": "Raw Data", | ||
| "settings": "Settings", | ||
| "noActivityReports": "No activity reports available", | ||
| "noActivityReportsHint": "Make sure you have both an AFK and window watcher running", | ||
| "loading": "Loading..." | ||
| }, | ||
| "home": { | ||
| "title": "Welcome to ActivityWatch", | ||
| "survey": "Fill out our user survey", | ||
| "voteFeatures": "vote on features on the forum", | ||
| "spreadWord": "Spread the word", | ||
| "support": "Support us!" | ||
| }, | ||
| "timeline": { | ||
| "title": "Timeline", | ||
| "filters": "Filters", | ||
| "host": "Host:", | ||
| "client": "Client:", | ||
| "duration": "Duration:", | ||
| "afk": "AFK:", | ||
| "all": "All", | ||
| "filterAFK": "AFK filtered", | ||
| "filterMerged": "merged by app", | ||
| "filterNone": "none", | ||
| "filterCategories": "{count} category | {count} categories", | ||
| "swimlaneNone": "None", | ||
| "swimlaneCategory": "Group by category", | ||
| "swimlaneBucketType": "Group by bucket type" | ||
| }, | ||
| "buckets": { | ||
| "title": "Buckets", | ||
| "docsHint": "Are you looking to collect more data? Check out {link} for more watchers.", | ||
| "docsLinkText": "the docs", | ||
| "lastUpdated": "Last updated", | ||
|
Comment on lines
+46
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| "noEvents": "No events recorded yet", | ||
| "thisDevice": "this device" | ||
| }, | ||
| "settings": { | ||
| "title": "Settings", | ||
| "groups": { | ||
| "general": "General", | ||
| "generalHelp": "Defaults that shape how time periods, the timeline, and landing page behave.", | ||
| "appearance": "Appearance", | ||
| "appearanceHelp": "Theme and visualization colors.", | ||
| "categorization": "Categorization", | ||
| "categorizationHelp": "Rules that classify events into categories, plus AFK/active-pattern overrides.", | ||
| "privacy": "Privacy", | ||
| "privacyHelp": "Filters that drop or redact sensitive event data before it is stored.", | ||
| "developer": "Developer", | ||
| "language": "Language", | ||
| "languageHelp": "Choose the display language for ActivityWatch." | ||
| } | ||
| }, | ||
| "language": { | ||
| "label": "Language", | ||
| "en": "English", | ||
| "zh-CN": "中文(简体)" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| { | ||
| "nav": { | ||
| "activity": "活动", | ||
| "timeline": "时间线", | ||
| "stopwatch": "秒表", | ||
| "tools": "工具", | ||
| "search": "搜索", | ||
| "workReport": "工作报告", | ||
| "trends": "趋势", | ||
| "report": "报告", | ||
| "alerts": "提醒", | ||
| "timespiral": "时间螺旋", | ||
| "query": "查询", | ||
| "graph": "图表", | ||
| "rawData": "原始数据", | ||
| "settings": "设置", | ||
| "noActivityReports": "暂无活动报告", | ||
| "noActivityReportsHint": "请确保 AFK 监控器和窗口监控器均已运行", | ||
| "loading": "加载中..." | ||
| }, | ||
| "home": { | ||
| "title": "欢迎使用 ActivityWatch", | ||
| "survey": "填写用户调查", | ||
| "voteFeatures": "在论坛中为功能投票", | ||
| "spreadWord": "传播", | ||
| "support": "支持我们!" | ||
| }, | ||
| "timeline": { | ||
| "title": "时间线", | ||
| "filters": "筛选", | ||
| "host": "主机:", | ||
| "client": "客户端:", | ||
| "duration": "持续时间:", | ||
| "afk": "离开状态:", | ||
| "all": "全部", | ||
| "filterAFK": "AFK 过滤", | ||
| "filterMerged": "按应用合并", | ||
| "filterNone": "无", | ||
| "filterCategories": "{count} 个分类", | ||
| "swimlaneNone": "无", | ||
| "swimlaneCategory": "按分类分组", | ||
| "swimlaneBucketType": "按数据桶类型分组" | ||
| }, | ||
| "buckets": { | ||
| "title": "数据桶", | ||
| "docsHint": "想收集更多数据?请查阅 {link} 获取更多监控器。", | ||
| "docsLinkText": "文档", | ||
| "lastUpdated": "最后更新", | ||
| "noEvents": "暂无事件记录", | ||
| "thisDevice": "此设备" | ||
| }, | ||
| "settings": { | ||
| "title": "设置", | ||
| "groups": { | ||
| "general": "通用", | ||
| "generalHelp": "影响时间段、时间线和落地页行为的默认设置。", | ||
| "appearance": "外观", | ||
| "appearanceHelp": "主题与可视化颜色。", | ||
| "categorization": "分类", | ||
| "categorizationHelp": "将事件分类的规则,以及 AFK/活跃模式覆盖设置。", | ||
| "privacy": "隐私", | ||
| "privacyHelp": "在存储前过滤或遮盖敏感事件数据的过滤器。", | ||
| "developer": "开发者", | ||
| "language": "语言", | ||
| "languageHelp": "选择 ActivityWatch 的界面语言。" | ||
| } | ||
| }, | ||
| "language": { | ||
| "label": "语言", | ||
| "en": "English", | ||
| "zh-CN": "中文(简体)" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setLocalewrites arbitrary strings to localStorage without validation. A tampered or corruptedaw-localevalue (e.g. via browser devtools) is read back on page load and set asi18n.localewithout checking whether the code is inSUPPORTED_LOCALES. WhilefallbackLocale: 'en'prevents a hard crash, invalid locale codes can still be persisted indefinitely and produce subtle translation-key resolution warnings in the console.