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:
mpmcroy 2020-06-23 14:24:18 -04:00 • committed by GitHub
parent 2e8b2ba51e
commit fb1255e6f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 210 additions and 290 deletions

View file

@ -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}"
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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() {

View file

@ -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);