mirror of
https://github.com/OutlineFoundation/outline-server.git
synced 2026-10-22 12:31:26 +00:00
Adds app language selection picker (#696)
* Adds application language selector to side drawer * Auto-formatting * Revert unused message strings * Minor fixes * Rename function * Addresses review comments * Addresses review comments * Addresses review comment * Addresses review comments * Auto-formatting * Addresses review comments * Filter out duplicate language from language picker * Removes zh language code and associated messages file * Adds jsdoc type information for outline-language-picker
This commit is contained in:
parent
2e8b2ba51e
commit
fb1255e6f5
5 changed files with 210 additions and 290 deletions
|
|
@ -1,224 +0,0 @@
|
|||
{
|
||||
"about-outline": "Outline 是由 <a href={jigsawUrl}>Jigsaw</a> 创立的开放源代码项目,旨在为新闻机构和记者提供更安全的互联网访问方式。<br><br> Outline 由 <a href={shadowsocksUrl}>Shadowsocks</a> 提供支持,目前仍处于产品的初级阶段。您可以在 <a href={gitHubUrl}>GitHub</a> 上参与代码编写,还可以通过 <a href={redditUrl}>Reddit</a> 和 <a href={mediumUrl}>Medium</a> 关注我们,及时掌握我们扩展平台范围和添加新功能的最新动态。",
|
||||
"about-version": "{version} 版",
|
||||
"aws-lightsail-firewall-0": "转到 {openLink}Amazon Lightsail{closeLink} 实例页面。",
|
||||
"aws-lightsail-firewall-1": "点击您要用来托管 Outline 的实例。",
|
||||
"aws-lightsail-firewall-2": "转到“网络”(Networking) 标签页。",
|
||||
"aws-lightsail-firewall-3": "在“防火墙”(Firewall) 部分,点击“添加其他规则”(Add another)。",
|
||||
"aws-lightsail-firewall-4": "将“所有 TCP 和 UDP”(All TCP+UDP) 的值设为“应用”(Application)。",
|
||||
"aws-lightsail-firewall-5": "点击“保存”(Save)。",
|
||||
"cancel": "取消",
|
||||
"city-ams": "阿姆斯特丹",
|
||||
"city-blr": "班加罗尔",
|
||||
"city-fra": "法兰克福",
|
||||
"city-lon": "伦敦",
|
||||
"city-nyc": "纽约",
|
||||
"city-sfo": "旧金山",
|
||||
"city-sgp": "新加坡",
|
||||
"city-tor": "多伦多",
|
||||
"close": "关闭",
|
||||
"confirmation-server-destroy": "现有用户将失去访问权限。此操作无法撤消。",
|
||||
"confirmation-server-destroy-title": "要删除服务器吗?",
|
||||
"confirmation-server-remove": "此操作会将您的服务器从 Outline 管理器中移除,但不会屏蔽用户对代理的访问权限。您仍然需要手动将 Outline 服务器从您的主机中删除。",
|
||||
"confirmation-server-remove-title": "要移除服务器吗?",
|
||||
"data-limits": "流量上限",
|
||||
"data-limits-description": "在您为此服务器上的访问密钥设置数据传输流量上限后,系统会对此上限与过去 30 天的已传输流量进行比对,如果已达上限,系统会强制执行流量限制措施。此功能将至少于 {date} 才能提供。",
|
||||
"data-limits-dialog-text": "请转到“设置”标签页以为此服务器上的访问密钥设置数据传输流量上限。",
|
||||
"data-limits-dialog-title": "避免超额使用流量",
|
||||
"data-limits-usage": "已使用 {used},共 {total}",
|
||||
"destroy": "销毁",
|
||||
"digitalocean-disconnect": "断开连接",
|
||||
"digitalocean-disconnect-account": "断开 DigitalOcean 帐号与此应用的连接",
|
||||
"digitalocean-unreachable": "此错误可能是由于您网络上的防火墙所致,也能是因为在与 digitalocean.com 连接时出现暂时性的问题。",
|
||||
"disabled": "已停用的",
|
||||
"done": "完成",
|
||||
"enabled": "已启用的",
|
||||
"error-connectivity": "无法连接到您的 DigitalOcean 帐号。这有时候是因为 DigitalOcean 或您的互联网连接出现了暂时性的问题。如果重试仍然失败,请重新登录 DigitalOcean,这样应该能解决问题。",
|
||||
"error-connectivity-title": "连接问题",
|
||||
"error-do-account-info": "未能获取 DigitalOcean 帐号信息",
|
||||
"error-do-auth": "未通过 DigitalOcean 的身份验证",
|
||||
"error-do-regions": "未能获得可用区域的列表",
|
||||
"error-feedback": "未能提交反馈。请重试。",
|
||||
"error-hostname-invalid": "必须是 IP 地址或正确的主机名。",
|
||||
"error-key-add": "未能添加密钥",
|
||||
"error-key-remove": "未能移除密钥",
|
||||
"error-key-rename": "未能重命名密钥",
|
||||
"error-keys-get": "无法加载密钥",
|
||||
"error-keys-port-bad-input": "端口号必须是介于 1 到 65535 之间的整数。",
|
||||
"error-keys-port-in-use": "该端口已被此服务器使用。",
|
||||
"error-licenses": "无法加载许可。",
|
||||
"error-metrics": "将指标设为已启用时出错",
|
||||
"error-network": "发生网络错误。",
|
||||
"error-not-saved": "未能保存",
|
||||
"error-remove-data-limit": "无法停用访问密钥的流量上限",
|
||||
"error-server-creation": "创建 Outline 服务器时发生错误。",
|
||||
"error-server-destroy": "未能销毁服务器",
|
||||
"error-server-removed": "您的 DigitalOcean 帐号中不会再显示“{serverName}”。",
|
||||
"error-server-rename": "未能重命名服务器",
|
||||
"error-server-unreachable": "虽然您的 Outline 服务器已正确安装,但我们无法连接到该服务器。这很可能是因为该服务器的防火墙规则阻止接入连接。请检查防火墙规则,确保端口 1024 - 65535 均允许接入 TCP 连接。",
|
||||
"error-server-unreachable-title": "无法连接到您的 Outline 服务器",
|
||||
"error-servers-removed": "您的 DigitalOcean 帐号中不会再显示{serverNames}。",
|
||||
"error-set-data-limit": "无法设置访问密钥的流量上限",
|
||||
"error-unexpected": "发生意外错误。",
|
||||
"experiments": "实验功能",
|
||||
"experiments-description": "请在此新功能发布前进行测试并向我们提供反馈。",
|
||||
"experiments-disclaimer": "实验功能正在开发中,并且可能发生变化或从应用中移除。如果您当前正在报告有关指标的情况,指标会包含实验功能的使用情况。要了解详情,请参阅{openLink}数据收集政策{closeLink}。",
|
||||
"experiments-feedback": "有什么建议吗?{openLink}在此提交反馈。{closeLink}",
|
||||
"feedback-cloud-provider": "选择云服务提供商",
|
||||
"feedback-cloud-provider-error": "请选择云服务提供商。",
|
||||
"feedback-connection": "无法连接到我的服务器",
|
||||
"feedback-connection-others": "其他人无法连接到我的服务器",
|
||||
"feedback-disclaimer": "请注意,我们的团队只能用英语回复反馈。",
|
||||
"feedback-email": "电子邮件地址(可选)",
|
||||
"feedback-error": "请输入反馈。",
|
||||
"feedback-explanation-install": "尝试在您的服务器上安装 Outline 时发生错误。如果您找不到解决办法,不妨向我们发送反馈,告诉我们您的电子邮件地址(可选),以便我们回复您。",
|
||||
"feedback-general": "一般反馈",
|
||||
"feedback-install": "安装 Outline 时出现了问题",
|
||||
"feedback-label": "您的反馈",
|
||||
"feedback-management": "管理我的服务器时出现了问题",
|
||||
"feedback-other": "其他",
|
||||
"feedback-privacy": "系统会将您的反馈、电子邮件地址(如果提供的话)以及{openLink}隐私权政策{closeLink}中提及的其他信息发送给 Outline 团队。",
|
||||
"feedback-submit": "提交",
|
||||
"feedback-suggestion": "建议",
|
||||
"feedback-title-generic": "发送反馈",
|
||||
"feedback-title-install": "Outline 服务器安装失败",
|
||||
"gcp-click-create": "点击“创建”(Create)。",
|
||||
"gcp-create-new-project": "{openLink}新建 Google Cloud 项目{closeLink}。",
|
||||
"gcp-create-new-vm": "{openLink}新建虚拟机实例{closeLink}。",
|
||||
"gcp-create-project": "创建 Google Cloud 项目",
|
||||
"gcp-create-server": "创建 Google Cloud 项目",
|
||||
"gcp-create-vm": "创建虚拟机实例",
|
||||
"gcp-firewall-create-0": "在 Compute Engine 项目中{openLink}添加新的防火墙规则{closeLink}。",
|
||||
"gcp-firewall-create-1": "在“名称”(Name) 字段输入“outline”。",
|
||||
"gcp-firewall-create-2": "在“目标标记”(Target tags) 字段中输入“outline”。",
|
||||
"gcp-firewall-create-3": "在“源 IP 范围”(Source IP ranges) 字段中输入“0.0.0.0/0”。",
|
||||
"gcp-firewall-create-4": "在“协议和端口”(Protocols and ports) 下,选择“全部允许”(Allow all)。",
|
||||
"gcp-name-your-project": "在“项目名称”(Project name) 字段为您的项目命名。",
|
||||
"gcp-select-machine-type": "在“机器类型”(Machine type) 下方选择“f1-micro”",
|
||||
"gcp-select-networking": "点击“管理、安全、磁盘、网络、单独租用”(Management, security, disks, networking, sole tenancy),然后点击“网络”(Networking)",
|
||||
"gcp-select-region": "在“区域”(Region) 下方选择一个靠近服务器用户所在位置的区域。",
|
||||
"gcp-type-network-tag": "在“网络标记”(Network tags) 字段中输入“outline”",
|
||||
"gcp-type-outline-server": "在“名称”(Name) 字段中输入“outline-server”。",
|
||||
"key": "密钥 {keyId}",
|
||||
"manual-server-assign-firewall": "指定防火墙规则",
|
||||
"manual-server-assign-group": "分配安全组",
|
||||
"manual-server-create-firewall": "创建防火墙规则",
|
||||
"manual-server-create-group": "创建安全组",
|
||||
"manual-server-description": "您可以按照以下步骤在 {cloudProvider} Linux 服务器上安装 Outline。",
|
||||
"manual-server-firewall": "配置防火墙",
|
||||
"manual-server-install-paste": "将安装脚本的输出结果粘贴到此处。",
|
||||
"manual-server-install-run": "登录您的服务器,然后运行下面的命令。",
|
||||
"manual-server-instructions": "操作说明",
|
||||
"manual-server-show-me": "前往配置界面",
|
||||
"manual-server-title": "请按照以下说明操作",
|
||||
"metrics-description": "分享经过匿名化处理的指标,以帮助提升 Outline 的可靠性和性能,您和服务器的共享对象都能从中受益。{openLink}了解详情。{closeLink}",
|
||||
"metrics-share": "共享指标",
|
||||
"metrics-skip": "跳过",
|
||||
"metrics-title": "指标共享",
|
||||
"nav-about": "关于",
|
||||
"nav-data-collection": "数据收集",
|
||||
"nav-feedback": "反馈",
|
||||
"nav-help": "帮助",
|
||||
"nav-licenses": "许可",
|
||||
"nav-privacy": "隐私权",
|
||||
"nav-terms": "条款",
|
||||
"notification-app-update": "已下载 Outline 管理器的更新版本。系统会在您重启该应用时安装此版本。",
|
||||
"notification-feedback-thanks": "感谢您帮助我们改进产品!欢迎您随时提供反馈。",
|
||||
"notification-key-added": "密钥已添加",
|
||||
"notification-key-removed": "密钥已移除",
|
||||
"notification-server-destroyed": "服务器已销毁",
|
||||
"notification-server-exists": "服务器已添加",
|
||||
"notification-server-removed": "服务器已移除",
|
||||
"oauth-account-active": "您的 DigitalOcean 帐号已激活。",
|
||||
"oauth-account-active-tag": "帐号已激活!正在加载服务器地址…",
|
||||
"oauth-activate-account": "激活您的 DigitalOcean 帐号。",
|
||||
"oauth-billing": "在 digitalocean.com 上输入您的结算信息,输入完毕后再回到此应用。",
|
||||
"oauth-billing-tag": "请输入结算信息…",
|
||||
"oauth-connect-description": "授予对您帐号的访问权限后,Outline 便可以帮助您轻松创建服务器并建立连接。",
|
||||
"oauth-connect-tag": "正在等待连接您的帐号…",
|
||||
"oauth-connect-title": "登录或创建 DigitalOcean 帐号。",
|
||||
"oauth-sign-out": "退出",
|
||||
"oauth-verify": "查看您的收件箱,找到来自 DigitalOcean 的电子邮件,点击其中的链接以确认您的帐号。",
|
||||
"oauth-verify-tag": "确认您的电子邮件地址…",
|
||||
"okay": "确定",
|
||||
"region-description": "您将从该位置接入互联网。",
|
||||
"region-setup": "安装 Outline",
|
||||
"region-title": "选择服务器位置。",
|
||||
"remove": "移除",
|
||||
"retry": "重试",
|
||||
"save": "保存",
|
||||
"saved": "已保存",
|
||||
"saving": "正在保存…",
|
||||
"server-access": "服务器访问密钥",
|
||||
"server-access-key-new": "添加新密钥",
|
||||
"server-access-key-rename": "重命名",
|
||||
"server-access-keys": "访问密钥",
|
||||
"server-connect-devices": "连接您的设备",
|
||||
"server-connections": "连接",
|
||||
"server-data-transfer": "已传输流量 / 过去 30 天",
|
||||
"server-data-used": "流量限额的已用比例 / 过去 30 天",
|
||||
"server-destroy": "销毁服务器",
|
||||
"server-help-access-key-description": "可与朋友分享您的访问密钥,让他们也能连接到您的 Outline 服务器。您的朋友在自己的所有设备上都可以使用这个访问密钥。",
|
||||
"server-help-access-key-next": "下一步",
|
||||
"server-help-access-key-title": "创建密钥,以共享访问权限",
|
||||
"server-help-connection-description": "点击此处即可安装 Outline 客户端应用,安装过程中需要用到您的个人访问密钥来连接 Outline 服务器。",
|
||||
"server-help-connection-ok": "知道了!",
|
||||
"server-help-connection-title": "您尚未连接到服务器!",
|
||||
"server-keys": "密钥",
|
||||
"server-my-access-key": "我的访问密钥",
|
||||
"server-name": "{serverLocation} Outline 服务器",
|
||||
"server-remove": "移除服务器",
|
||||
"server-settings": "设置",
|
||||
"server-unreachable": "无法连接到服务器",
|
||||
"server-unreachable-description": "无法连接到此服务器。",
|
||||
"server-unreachable-managed-description": "请重试,或者将此服务器从应用中移除。",
|
||||
"server-unreachable-manual-description": "请重试,或者销毁此服务器及虚拟主机。",
|
||||
"server-usage": "流量使用情况(过去 30 天)",
|
||||
"servers-add": "添加服务器",
|
||||
"servers-digitalocean": "DigitalOcean 服务器",
|
||||
"servers-manual": "服务器",
|
||||
"settings-access-key-port": "用于新访问密钥的端口",
|
||||
"settings-metrics-header": "共享匿名指标",
|
||||
"settings-server-api-url": "管理 API 的网址",
|
||||
"settings-server-cost": "每月费用",
|
||||
"settings-server-creation": "创建日期",
|
||||
"settings-server-hostname": "主机名",
|
||||
"settings-server-id": "服务器 ID",
|
||||
"settings-server-info": "服务器信息",
|
||||
"settings-server-location": "服务器位置",
|
||||
"settings-server-name": "名称",
|
||||
"settings-server-rename": "为服务器设定新名称。请注意,如果您之前邀请了其他用户连接到此服务器,则这些用户的设备上不会相应地更新此服务器的名称。",
|
||||
"settings-server-version": "服务器版本",
|
||||
"settings-transfer-limit": "数据传输限额",
|
||||
"setup-action": "安装",
|
||||
"setup-advanced": "高级",
|
||||
"setup-anywhere": "随时随地安装 Outline",
|
||||
"setup-create": "创建服务器",
|
||||
"setup-description": "没有服务器?您可以创建一个 DigitalOcean 帐号。",
|
||||
"setup-do-cancel": "可随时取消",
|
||||
"setup-do-cost": "每月只需 5 美元",
|
||||
"setup-do-create": "使用您的 DigitalOcean 帐号创建一个新服务器,就能以每 30 天 5 美元的价格额外获享 1 TB 的数据传输流量。",
|
||||
"setup-do-data": "1 TB 数据传输限额",
|
||||
"setup-do-description": "此过程最长可能需要 2 分钟。您随时可以销毁此服务器。",
|
||||
"setup-do-easiest": "最轻松的设置流程",
|
||||
"setup-do-title": "正在安装 Outline。",
|
||||
"setup-firewall-instructions": "防火墙配置说明",
|
||||
"setup-recommended": "推荐",
|
||||
"setup-simple-commands": "简单易用的安装命令",
|
||||
"setup-step-by-step": "提供分步设置指南",
|
||||
"setup-tested": "在 VULTR、Linode 和 Liquid Web 上测试过",
|
||||
"setup-title": "选择用于安装 Outline 的云服务。",
|
||||
"share-description": "复制此邀请,并使用您信任的通讯工具发送。{openLink}需要帮助?{closeLink}",
|
||||
"share-invite": "诚邀您连接到我的 Outline 服务器。无论您身在何处,都可以通过该服务器访问开放的互联网。请点击下面的邀请链接,按照其中的说明操作来下载 Outline 应用并建立连接。",
|
||||
"share-invite-copied": "已复制到剪贴板",
|
||||
"share-invite-copy": "复制邀请",
|
||||
"share-invite-copy-access-key": "复制您的访问密钥:",
|
||||
"share-invite-instructions": "在 GitHub 上按照我们所发邀请中的说明操作:",
|
||||
"share-invite-trouble": "无法访问邀请链接?",
|
||||
"share-title": "共享访问权限",
|
||||
"survey-data-limits-title": "帮助我们了解如何改进流量限制功能",
|
||||
"survey-decline": "拒绝",
|
||||
"survey-disclaimer": "点击“继续”后,您将转到 Google 表单并需要填写一份简短的调查问卷。我们建议您在连接到 Outline 的同时填写调查问卷。",
|
||||
"survey-go-to-survey": "转到调查问卷",
|
||||
"terms-of-service": "我已阅读并理解了 {openLink}Outline 服务条款{closeLink}"
|
||||
}
|
||||
|
|
@ -247,6 +247,10 @@ export class App {
|
|||
}
|
||||
});
|
||||
|
||||
appRoot.addEventListener('SetLanguageRequested', (event: CustomEvent) => {
|
||||
this.setAppLanguage(event.detail.languageCode, event.detail.languageDir);
|
||||
});
|
||||
|
||||
appRoot.addEventListener('ServerRenameRequested', (event: CustomEvent) => {
|
||||
this.renameServer(event.detail.newName);
|
||||
});
|
||||
|
|
@ -1233,4 +1237,10 @@ export class App {
|
|||
this.showCreateServer();
|
||||
});
|
||||
}
|
||||
|
||||
private setAppLanguage(languageCode: string, languageDir: string) {
|
||||
this.appRoot.setLanguage(languageCode, languageDir);
|
||||
document.documentElement.setAttribute('dir', languageDir);
|
||||
window.localStorage.setItem('overrideLanguage', languageCode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,59 +26,69 @@ import {ManualServerRepository} from './manual_server';
|
|||
import {DEFAULT_PROMPT_IMPRESSION_DELAY_MS, OutlineSurveys} from './survey';
|
||||
import {AppRoot} from './ui_components/app-root.js';
|
||||
|
||||
const SUPPORTED_LANGUAGES: {[key: string]: {id: string, dir: string}} = {
|
||||
'am': {id: 'am', dir: 'ltr'},
|
||||
'ar': {id: 'ar', dir: 'rtl'},
|
||||
'bg': {id: 'bg', dir: 'ltr'},
|
||||
'ca': {id: 'ca', dir: 'ltr'},
|
||||
'cs': {id: 'cs', dir: 'ltr'},
|
||||
'da': {id: 'da', dir: 'ltr'},
|
||||
'de': {id: 'de', dir: 'ltr'},
|
||||
'el': {id: 'el', dir: 'ltr'},
|
||||
'en': {id: 'en', dir: 'ltr'},
|
||||
'es-419': {id: 'es-419', dir: 'ltr'},
|
||||
'fa': {id: 'fa', dir: 'rtl'},
|
||||
'fi': {id: 'fi', dir: 'ltr'},
|
||||
'fil': {id: 'fil', dir: 'ltr'},
|
||||
'fr': {id: 'fr', dir: 'ltr'},
|
||||
'he': {id: 'he', dir: 'rtl'},
|
||||
'hi': {id: 'hi', dir: 'ltr'},
|
||||
'hr': {id: 'hr', dir: 'ltr'},
|
||||
'hu': {id: 'hu', dir: 'ltr'},
|
||||
'id': {id: 'id', dir: 'ltr'},
|
||||
'it': {id: 'it', dir: 'ltr'},
|
||||
'ja': {id: 'ja', dir: 'ltr'},
|
||||
'ko': {id: 'ko', dir: 'ltr'},
|
||||
'km': {id: 'km', dir: 'ltr'},
|
||||
'lt': {id: 'lt', dir: 'ltr'},
|
||||
'lv': {id: 'lv', dir: 'ltr'},
|
||||
'nl': {id: 'nl', dir: 'ltr'},
|
||||
'no': {id: 'no', dir: 'ltr'},
|
||||
'pl': {id: 'pl', dir: 'ltr'},
|
||||
'pt-BR': {id: 'pt-BR', dir: 'ltr'},
|
||||
'ro': {id: 'ro', dir: 'ltr'},
|
||||
'ru': {id: 'ru', dir: 'ltr'},
|
||||
'sk': {id: 'sk', dir: 'ltr'},
|
||||
'sl': {id: 'sl', dir: 'ltr'},
|
||||
'sr': {id: 'sr', dir: 'ltr'},
|
||||
'sr-Latn': {id: 'sr-Latn', dir: 'ltr'},
|
||||
'sv': {id: 'sv', dir: 'ltr'},
|
||||
'th': {id: 'th', dir: 'ltr'},
|
||||
'tr': {id: 'tr', dir: 'ltr'},
|
||||
'uk': {id: 'uk', dir: 'ltr'},
|
||||
'ur': {id: 'ur', dir: 'rtl'},
|
||||
'vi': {id: 'vi', dir: 'ltr'},
|
||||
'zh': {id: 'zh', dir: 'ltr'},
|
||||
'zh-CN': {id: 'zh-CN', dir: 'ltr'},
|
||||
'zh-TW': {id: 'zh-TW', dir: 'ltr'},
|
||||
type LanguageDef = {id: string, name: string, dir: string};
|
||||
const SUPPORTED_LANGUAGES: {[key: string]: LanguageDef} = {
|
||||
'am': {id: 'am', name: 'አማርኛ', dir: 'ltr'},
|
||||
'ar': {id: 'ar', name: 'العربية', dir: 'rtl'},
|
||||
'bg': {id: 'bg', name: 'Български', dir: 'ltr'},
|
||||
'ca': {id: 'ca', name: 'Català', dir: 'ltr'},
|
||||
'cs': {id: 'cs', name: 'Česky', dir: 'ltr'},
|
||||
'da': {id: 'da', name: 'Dansk', dir: 'ltr'},
|
||||
'de': {id: 'de', name: 'Deutsch', dir: 'ltr'},
|
||||
'el': {id: 'el', name: 'Ελληνικά', dir: 'ltr'},
|
||||
'en': {id: 'en', name: 'English', dir: 'ltr'},
|
||||
'es-419': {id: 'es-419', name: 'Español', dir: 'ltr'},
|
||||
'fa': {id: 'fa', name: 'فارسی', dir: 'rtl'},
|
||||
'fi': {id: 'fi', name: 'Suomi', dir: 'ltr'},
|
||||
'fil': {id: 'fil', name: 'Wikang Filipino', dir: 'ltr'},
|
||||
'fr': {id: 'fr', name: 'Français', dir: 'ltr'},
|
||||
'he': {id: 'he', name: 'עברית', dir: 'rtl'},
|
||||
'hi': {id: 'hi', name: 'हिन्दी', dir: 'ltr'},
|
||||
'hr': {id: 'hr', name: 'Hrvatski', dir: 'ltr'},
|
||||
'hu': {id: 'hu', name: 'Magyar', dir: 'ltr'},
|
||||
'id': {id: 'id', name: 'Bahasa Indonesia', dir: 'ltr'},
|
||||
'it': {id: 'it', name: 'Italiano', dir: 'ltr'},
|
||||
'ja': {id: 'ja', name: '日本語', dir: 'ltr'},
|
||||
'km': {id: 'km', name: 'ភាសាខ្មែរ', dir: 'ltr'},
|
||||
'ko': {id: 'ko', name: '한국어', dir: 'ltr'},
|
||||
'lt': {id: 'lt', name: 'Lietuvių', dir: 'ltr'},
|
||||
'lv': {id: 'lv', name: 'Latviešu', dir: 'ltr'},
|
||||
'nl': {id: 'nl', name: 'Nederlands', dir: 'ltr'},
|
||||
'no': {id: 'no', name: 'Norsk (bokmål / riksmål)', dir: 'ltr'},
|
||||
'pl': {id: 'pl', name: 'Polski', dir: 'ltr'},
|
||||
'pt-BR': {id: 'pt-BR', name: 'Português', dir: 'ltr'},
|
||||
'ro': {id: 'ro', name: 'Română', dir: 'ltr'},
|
||||
'ru': {id: 'ru', name: 'Русский', dir: 'ltr'},
|
||||
'sk': {id: 'sk', name: 'Slovenčina', dir: 'ltr'},
|
||||
'sl': {id: 'sl', name: 'Slovenščina', dir: 'ltr'},
|
||||
'sr': {id: 'sr', name: 'Српски', dir: 'ltr'},
|
||||
'sr-Latn': {id: 'sr-Latn', name: 'Srpski', dir: 'ltr'},
|
||||
'sv': {id: 'sv', name: 'Svenska', dir: 'ltr'},
|
||||
'th': {id: 'th', name: 'ไทย', dir: 'ltr'},
|
||||
'tr': {id: 'tr', name: 'Türkçe', dir: 'ltr'},
|
||||
'uk': {id: 'uk', name: 'Українська', dir: 'ltr'},
|
||||
'ur': {id: 'ur', name: 'اردو', dir: 'rtl'},
|
||||
'vi': {id: 'vi', name: 'Việtnam', dir: 'ltr'},
|
||||
'zh-CN': {id: 'zh-CN', name: '简体中文', dir: 'ltr'},
|
||||
'zh-TW': {id: 'zh-TW', name: '繁體中文', dir: 'ltr'},
|
||||
};
|
||||
|
||||
function getLanguageToUse(): i18n.LanguageCode {
|
||||
const supportedLanguages = i18n.languageList(Object.keys(SUPPORTED_LANGUAGES));
|
||||
const preferredLanguages = i18n.getBrowserLanguages();
|
||||
const overrideLanguage = window.localStorage.getItem('overrideLanguage');
|
||||
if (overrideLanguage) {
|
||||
preferredLanguages.unshift(new i18n.LanguageCode(overrideLanguage));
|
||||
}
|
||||
const defaultLanguage = new i18n.LanguageCode('en');
|
||||
const userLanguages = i18n.getBrowserLanguages();
|
||||
return new i18n.LanguageMatcher(supportedLanguages, defaultLanguage)
|
||||
.getBestSupportedLanguage(userLanguages);
|
||||
.getBestSupportedLanguage(preferredLanguages);
|
||||
}
|
||||
|
||||
function sortLanguageDefsByName(languageDefs: LanguageDef[]) {
|
||||
return languageDefs.sort((a, b) => {
|
||||
return a.name > b.name ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('WebComponentsReady', () => {
|
||||
|
|
@ -103,6 +113,9 @@ document.addEventListener('WebComponentsReady', () => {
|
|||
// NOTE: this cast is safe and allows us to leverage Polymer typings since we haven't migrated to
|
||||
// Polymer 3, which adds typescript support.
|
||||
const appRoot = document.getElementById('appRoot') as unknown as AppRoot;
|
||||
|
||||
const filteredLanguageDefs = Object.values(SUPPORTED_LANGUAGES);
|
||||
appRoot.supportedLanguages = sortLanguageDefsByName(filteredLanguageDefs);
|
||||
appRoot.setLanguage(language.string(), languageDirection);
|
||||
new App(
|
||||
appRoot, version, digitalocean_api.createDigitalOceanSession,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import './outline-do-oauth-step.js';
|
|||
import './outline-feedback-dialog.js';
|
||||
import './outline-survey-dialog.js';
|
||||
import './outline-intro-step.js';
|
||||
import './outline-language-picker.js';
|
||||
import './outline-manual-server-entry.js';
|
||||
import './outline-modal-dialog.js';
|
||||
import './outline-region-picker-step.js';
|
||||
|
|
@ -229,12 +230,28 @@ export class AppRoot extends mixinBehaviors
|
|||
#appDrawer a:focus {
|
||||
outline: none;
|
||||
}
|
||||
#links-footer {
|
||||
margin-top: 36px;
|
||||
}
|
||||
.legal-links {
|
||||
margin: 36px -6px 0;
|
||||
margin: 0 -6px;
|
||||
}
|
||||
.legal-links > * {
|
||||
margin: 0 6px;
|
||||
}
|
||||
#language-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#language-icon {
|
||||
padding-top: 10px;
|
||||
}
|
||||
#language-dropdown {
|
||||
padding-left: 22px;
|
||||
--paper-input-container: {
|
||||
width: 156px;
|
||||
};
|
||||
}
|
||||
app-toolbar [main-title] img {
|
||||
height: 16px;
|
||||
margin-top: 8px;
|
||||
|
|
@ -367,15 +384,21 @@ export class AppRoot extends mixinBehaviors
|
|||
</div>
|
||||
|
||||
<!-- Links section -->
|
||||
<paper-listbox on-tap="maybeCloseDrawer">
|
||||
<a href="https://s3.amazonaws.com/outline-vpn/index.html#/en/support/dataCollection">[[localize('nav-data-collection')]]</a>
|
||||
<paper-listbox>
|
||||
<span on-tap="maybeCloseDrawer"><a href="https://s3.amazonaws.com/outline-vpn/index.html#/en/support/dataCollection">[[localize('nav-data-collection')]]</a></span>
|
||||
<span on-tap="submitFeedbackTapped">[[localize('nav-feedback')]]</span>
|
||||
<a href="https://s3.amazonaws.com/outline-vpn/index.html#/en/support/">[[localize('nav-help')]]</a>
|
||||
<span on-tap="maybeCloseDrawer"><a href="https://s3.amazonaws.com/outline-vpn/index.html#/en/support/">[[localize('nav-help')]]</a></span>
|
||||
<span on-tap="aboutTapped">[[localize('nav-about')]]</span>
|
||||
<div class="legal-links">
|
||||
<a href="https://www.google.com/policies/privacy/">[[localize('nav-privacy')]]</a>
|
||||
<a href="https://s3.amazonaws.com/outline-vpn/static_downloads/Outline-Terms-of-Service.html">[[localize('nav-terms')]]</a>
|
||||
<span on-tap="showLicensesTapped">[[localize('nav-licenses')]]</span>
|
||||
<div id="links-footer">
|
||||
<paper-icon-item id="language-row">
|
||||
<iron-icon id="language-icon" icon="language" slot="item-icon"></iron-icon>
|
||||
<outline-language-picker id="language-dropdown" selected-language="{{language}}" languages="{{supportedLanguages}}"></outline-language-picker>
|
||||
</paper-icon-item>
|
||||
<div class="legal-links" on-tap="maybeCloseDrawer">
|
||||
<a href="https://www.google.com/policies/privacy/">[[localize('nav-privacy')]]</a>
|
||||
<a href="https://s3.amazonaws.com/outline-vpn/static_downloads/Outline-Terms-of-Service.html">[[localize('nav-terms')]]</a>
|
||||
<span on-tap="showLicensesTapped">[[localize('nav-licenses')]]</span>
|
||||
</div>
|
||||
</div>
|
||||
</paper-listbox>
|
||||
</app-drawer>
|
||||
|
|
@ -471,6 +494,8 @@ export class AppRoot extends mixinBehaviors
|
|||
return {
|
||||
// Properties language and useKeyIfMissing are used by Polymer.AppLocalizeBehavior.
|
||||
language: {type: String, readonly: true},
|
||||
// An array of {id, name, dir} language objects.
|
||||
supportedLanguages: {type: Array, readonly: true},
|
||||
useKeyIfMissing: {type: Boolean},
|
||||
serverList: {type: Array},
|
||||
selectedServer: {type: Object},
|
||||
|
|
@ -507,6 +532,7 @@ export class AppRoot extends mixinBehaviors
|
|||
/** @type {DisplayServer} */
|
||||
this.selectedServer = undefined;
|
||||
this.language = '';
|
||||
this.supportedLanguages = [];
|
||||
this.useKeyIfMissing = true;
|
||||
/** @type {DisplayServer[]} */
|
||||
this.serverList = [];
|
||||
|
|
@ -515,7 +541,6 @@ export class AppRoot extends mixinBehaviors
|
|||
this.currentPage = 'intro';
|
||||
this.shouldShowSideBar = false;
|
||||
|
||||
|
||||
this.addEventListener('RegionSelected', this.handleRegionSelected);
|
||||
this.addEventListener(
|
||||
'SetUpGenericCloudProviderRequested', this.handleSetUpGenericCloudProviderRequested);
|
||||
|
|
@ -526,17 +551,18 @@ export class AppRoot extends mixinBehaviors
|
|||
|
||||
/**
|
||||
* Sets the language and direction for the application
|
||||
* @param {string} newLanguage
|
||||
* @param {string} langDir
|
||||
* @param {string} language
|
||||
* @param {string} direction
|
||||
*/
|
||||
setLanguage(newLanguage, langDir) {
|
||||
const messagesUrl = `./messages/${newLanguage}.json`;
|
||||
this.loadResources(messagesUrl, newLanguage);
|
||||
if (langDir === 'rtl') {
|
||||
this.$.appDrawer.align = 'right';
|
||||
this.$.sideBar.align = 'right';
|
||||
}
|
||||
this.language = newLanguage;
|
||||
setLanguage(language, direction) {
|
||||
const messagesUrl = `./messages/${language}.json`;
|
||||
this.loadResources(messagesUrl, language);
|
||||
|
||||
const alignDir = direction === 'ltr' ? 'left' : 'right';
|
||||
this.$.appDrawer.align = alignDir;
|
||||
this.$.sideBar.align = alignDir;
|
||||
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
showIntro() {
|
||||
|
|
@ -740,10 +766,12 @@ export class AppRoot extends mixinBehaviors
|
|||
|
||||
submitFeedbackTapped() {
|
||||
this.$.feedbackDialog.open();
|
||||
this.maybeCloseDrawer();
|
||||
}
|
||||
|
||||
aboutTapped() {
|
||||
this.$.aboutDialog.open();
|
||||
this.maybeCloseDrawer();
|
||||
}
|
||||
|
||||
signOutTapped() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Copyright 2018 The Outline Authors
|
||||
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.
|
||||
*/
|
||||
import '@polymer/polymer/polymer-legacy.js';
|
||||
import '@polymer/paper-dropdown-menu/paper-dropdown-menu.js';
|
||||
import '@polymer/paper-listbox/paper-listbox.js';
|
||||
import '@polymer/paper-item/paper-item.js';
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/iron-icons/iron-icons.js';
|
||||
import './cloud-install-styles.js';
|
||||
|
||||
import {html, PolymerElement} from '@polymer/polymer';
|
||||
|
||||
export class OutlineLanguagePicker extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="cloud-install-styles"></style>
|
||||
<style>
|
||||
paper-dropdown-menu {
|
||||
--paper-input-container-input: {
|
||||
color: var(--medium-gray);
|
||||
font-size: 14px;
|
||||
};
|
||||
}
|
||||
.language-item {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding-left: 24px;
|
||||
--paper-item-selected: {
|
||||
color: var(--primary-green);
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
.language-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
<paper-dropdown-menu no-label-float="" vertical-align="bottom">
|
||||
<paper-listbox slot="dropdown-content" selected="{{selectedLanguage}}" attr-for-selected="value" on-selected-changed="_languageChanged">
|
||||
<template is="dom-repeat" items="{{languages}}" as="lang">
|
||||
<paper-item class="language-item" value="{{lang.id}}">
|
||||
<span class="language-name">{{lang.name}}</span>
|
||||
<iron-icon icon="check" hidden$="{{_shouldHideCheckmark(selectedLanguage, lang.id)}}"></iron-icon>
|
||||
</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>`;
|
||||
}
|
||||
|
||||
static get is() {
|
||||
return 'outline-language-picker';
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
selectedLanguage: {type: String},
|
||||
// An array of {id, name, dir} language objects.
|
||||
languages: {type: Array, readonly: true},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selectedLanguage = '';
|
||||
/** @type {Array<{id: string, name: string, dir: "rtl"|"ltr"}>} */
|
||||
this.languages = [];
|
||||
}
|
||||
|
||||
_shouldHideCheckmark(language, languageCode) {
|
||||
return language !== languageCode;
|
||||
}
|
||||
|
||||
_languageChanged(event) {
|
||||
const languageCode = event.detail.value;
|
||||
const languageDir = this.languages.find((lang) => {return lang.id === languageCode}).dir;
|
||||
|
||||
const params = {bubbles: true, composed: true, detail: {languageCode, languageDir}};
|
||||
const customEvent = new CustomEvent('SetLanguageRequested', params);
|
||||
this.dispatchEvent(customEvent);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(OutlineLanguagePicker.is, OutlineLanguagePicker);
|
||||
Loading…
Add table
Add a link
Reference in a new issue