i18n: mirror UI for RTL languages, detect user locale (#463)

This commit is contained in:
alalamav 2019-08-28 11:11:13 -04:00 committed by GitHub
parent 1ba390b85f
commit e956c94df4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 2206 additions and 66 deletions

View file

@ -0,0 +1,35 @@
// Copyright 2019 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.
const gulp = require('gulp');
const posthtml = require('gulp-posthtml');
const posthtmlcss = require('posthtml-postcss');
const rtl = require('postcss-rtl');
const replace = require('gulp-replace');
// Generates inline CSS RTL mirroring rules for Polymer components.
module.exports = function(src, dest) {
console.log('Generating RTL CSS');
const plugins = [rtl()];
const options = {from: undefined, sync: true};
const filterType = /\/css$/;
return gulp.src([src])
.pipe(posthtml([posthtmlcss(plugins, options, filterType)]))
// Replace the generated selectors with Shadow DOM selectors for Polymer compatibility.
.pipe(replace('[dir=rtl]', ':host(:dir(rtl))'))
.pipe(replace('[dir=ltr]', ':host(:dir(ltr))'))
// rtlcss generates [dir] selectors for rules unaffected by directionality; ignore them.
.pipe(replace('[dir]', ''))
.pipe(gulp.dest(dest));
}

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>back</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Manager-007b" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-35.000000, -24.000000)" opacity="0.86">
<g id="ic/arrow_back/white" transform="translate(31.000000, 20.000000)">
<g id="ic_arrow_back_24px">
<polygon id="Shape" points="0 0 24 0 24 24 0 24"></polygon>
<polygon id="Shape" fill="#FFFFFF" fill-rule="nonzero" points="20 11 7.83 11 13.42 5.41 12 4 4 12 12 20 13.41 18.59 7.83 13 20 13"></polygon>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 897 B

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 18 18" width="18" height="18"><defs><path d="M0 0C0 0 0 0 0 0C0 10.8 0 16.8 0 18C0 18 0 18 0 18C10.8 18 16.8 18 18 18C18 18 18 18 18 18C18 7.2 18 1.2 18 0C18 0 18 0 18 0C7.2 0 1.2 0 0 0Z" id="e5SCyvaqZI"></path><path d="M14.41 9.06L10.45 13.28L3.59 5.95L5.05 4.59L10.45 10.36L12.95 7.69L14.41 9.06Z" id="a4YtBrc4C"></path></defs><g><g><g><use xlink:href="#e5SCyvaqZI" opacity="1" fill="#4285F4" fill-opacity="0"></use><g><use xlink:href="#e5SCyvaqZI" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a4YtBrc4C" opacity="1" fill="#4285F4" fill-opacity="1"></use><g><use xlink:href="#a4YtBrc4C" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="10px" viewBox="0 0 12 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop" transform="translate(-534.000000, -1020.000000)" fill="#FF9900">
<g id="check-copy-2" transform="translate(531.000000, 1016.000000)">
<rect id="Rectangle" fill-opacity="0" x="0" y="0" width="18" height="18"></rect>
<polygon id="Path-8-Copy-10" fill-rule="nonzero" points="5.04574404 7.68776621 3.58652645 9.05549572 7.54659883 13.2804593 14.4134736 5.95425596 12.954256 4.58652645 7.54659883 10.3559044"></polygon>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 815 B

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 18 18" width="18" height="18"><defs><path d="M0 0C0 0 0 0 0 0C0 10.8 0 16.8 0 18C0 18 0 18 0 18C10.8 18 16.8 18 18 18C18 18 18 18 18 18C18 7.2 18 1.2 18 0C18 0 18 0 18 0C7.2 0 1.2 0 0 0Z" id="e5SCyvaqZI"></path><path d="M14.41 9.06L10.45 13.28L3.59 5.95L5.05 4.59L10.45 10.36L12.95 7.69L14.41 9.06Z" id="a4YtBrc4C"></path></defs><g><g><g><use xlink:href="#e5SCyvaqZI" opacity="1" fill="#00bfa5" fill-opacity="0"></use><g><use xlink:href="#e5SCyvaqZI" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a4YtBrc4C" opacity="1" fill="#00bfa5" fill-opacity="1"></use><g><use xlink:href="#a4YtBrc4C" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 18 18" width="18" height="18"><defs><path d="M0 0C0 0 0 0 0 0C0 10.8 0 16.8 0 18C0 18 0 18 0 18C10.8 18 16.8 18 18 18C18 18 18 18 18 18C18 7.2 18 1.2 18 0C18 0 18 0 18 0C7.2 0 1.2 0 0 0Z" id="e5SCyvaqZI"></path><path d="M14.41 9.06L10.45 13.28L3.59 5.95L5.05 4.59L10.45 10.36L12.95 7.69L14.41 9.06Z" id="a4YtBrc4C"></path></defs><g><g><g><use xlink:href="#e5SCyvaqZI" opacity="1" fill="#FF9900" fill-opacity="0"></use><g><use xlink:href="#e5SCyvaqZI" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a4YtBrc4C" opacity="1" fill="#FF9900" fill-opacity="1"></use><g><use xlink:href="#a4YtBrc4C" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 18 18" width="18" height="18"><defs><path d="M0 0C0 0 0 0 0 0C0 10.8 0 16.8 0 18C0 18 0 18 0 18C10.8 18 16.8 18 18 18C18 18 18 18 18 18C18 7.2 18 1.2 18 0C18 0 18 0 18 0C7.2 0 1.2 0 0 0Z" id="e5SCyvaqZI"></path><path d="M14.41 9.06L10.45 13.28L3.59 5.95L5.05 4.59L10.45 10.36L12.95 7.69L14.41 9.06Z" id="a4YtBrc4C"></path></defs><g><g><g><use xlink:href="#e5SCyvaqZI" opacity="1" fill="#ffffff" fill-opacity="0"></use><g><use xlink:href="#e5SCyvaqZI" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a4YtBrc4C" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#a4YtBrc4C" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

@ -14,13 +14,12 @@
limitations under the License.
-->
<!DOCTYPE html>
<html>
<!-- Default LTR direction will be changed dynamically for RTL locales. -->
<html dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
<link rel="shortcut icon" href="/ui_components/images/favicon.png">
<link rel="stylesheet" href="/ui_components/style.css">
<script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="/bower_components/app-layout/app-toolbar/app-toolbar.html">
@ -47,7 +46,7 @@
<title>Outline Manager</title>
</head>
<body dir="ltr">
<body>
<app-root id="appRoot"></app-root>
</body>

View file

@ -30,7 +30,12 @@
"browserify": "^14.5.0",
"electron": "^2.0.16",
"electron-builder": "20.34.0",
"electron-icon-maker": "^0.0.4"
"electron-icon-maker": "^0.0.4",
"gulp": "^4.0.0",
"gulp-posthtml": "^3.0.4",
"gulp-replace": "^1.0.0",
"postcss-rtl": "^1.4.0",
"posthtml-postcss": "^0.2.6"
},
"scripts": {
"postinstall": "bower install"

View file

@ -75,6 +75,7 @@
height: 20px;
width: 20px;
}
/* rtl:begin:ignore */
#appDrawer {
--app-drawer-content-container: {
color: var(--medium-gray);
@ -85,6 +86,7 @@
align-items: right;
};
}
/* rtl:end:ignore */
#appDrawer > * {
width: 100%;
}
@ -110,6 +112,9 @@
padding-left: 24px;
line-height: 39px;
}
.servers-header > span {
flex: 1;
}
.do-overflow-menu {
padding: 24px;
color: var(--dark-gray);
@ -201,7 +206,7 @@
#appDrawer > paper-listbox > * {
display: block;
cursor: pointer;
padding: 0 24px;
padding-left: 24px;
font-size: 14px;
line-height: 40px;
outline: none;
@ -225,12 +230,14 @@
.side-bar-margin {
margin-left: var(--side-bar-width);
}
/* rtl:begin:ignore */
#sideBar {
--app-drawer-width: var(--side-bar-width);
--app-drawer-content-container: {
background-color: var(--background-contrast-color);
};
}
/* rtl:end:ignore */
.side-bar-container {
height: 100%;
text-align: center;
@ -269,7 +276,7 @@
flex: 1 0 24px;
border-bottom: none;
}
.side-bar-section .server-icon {
.side-bar-section > .server-icon {
margin: 0;
}
#getConnectedDialog {
@ -450,6 +457,19 @@
Polymer.AppLocalizeBehavior
],
properties: {
LANGUAGES_AVAILABLE: {
type: Object,
readonly: true,
value: {
en: {id: 'en', dir: 'ltr'},
km: {id: 'km', dir: 'ltr'},
}
},
DEFAULT_LANGUAGE: {
type: String,
readonly: true,
value: 'en'
},
useKeyIfMissing: {
type: Boolean,
value: true
@ -457,7 +477,7 @@
language: {
type: String,
readonly: true,
value: 'en'
computed: '_computeLanguage(LANGUAGES_AVAILABLE, DEFAULT_LANGUAGE)'
},
serverList: {
type: Array,
@ -511,9 +531,14 @@
'ManualServerEntryCancelled': 'handleManualCancelled'
},
ready: function() {
// TODO(alalama): detect language
const messagesUrl = `/messages/${this.language}.json`;
this.loadResources(messagesUrl, this.language);
const languageProperties = this.LANGUAGES_AVAILABLE[this.language];
if (languageProperties && languageProperties.dir === 'rtl') {
document.documentElement.setAttribute('dir', 'rtl');
this.$.appDrawer.align = 'right';
this.$.sideBar.align = 'right';
}
},
showIntro: function () {
this.maybeCloseDrawer();
@ -776,6 +801,10 @@
this.fire('ShowServerRequested', {displayServerId: server.id });
this.maybeCloseDrawer();
},
_computeLanguage: function (LANGUAGES_AVAILABLE, DEFAULT_LANGUAGE) {
return OutlineI18n.getBestMatchingLanguage(
Object.keys(LANGUAGES_AVAILABLE)) || DEFAULT_LANGUAGE;
},
// Wrapper to encode a string in base64. This is necessary to set the server view IDs to
// the display server IDs, which are URLs, so they can be used with selector methods. The IDs
// are never decoded.

View file

@ -220,6 +220,11 @@ https://www.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-modules
background: var(--background-contrast-color);
border-radius: 2px;
}
/* Mirror progress indicators for RTL languages */
:host(:dir(rtl)) paper-progress {
transform: scaleX(-1);
}
</style>
</template>

View file

@ -56,6 +56,8 @@
}
.card p {
color: var(--light-gray);
width: 100%;
text-align: center;
}
.card paper-button {
color: var(--light-gray);
@ -67,6 +69,10 @@
color: var(--medium-gray);
background: transparent;
}
/* Mirror images */
:host(:dir(rtl)) .mirror {
transform: scaleX(-1);
}
</style>
<iron-pages id='pages' attr-for-selected='id' selected='{{ currentPage }}'>
@ -87,7 +93,7 @@
<span slot="step-description">[[localize('oauth-verify')]]</span>
<paper-card class="card">
<div class="container">
<img src="images/do_oauth_email.svg" />
<img class="mirror" src="images/do_oauth_email.svg" />
<p>[[localize('oauth-verify-tag')]]</p>
</div>
<paper-button on-tap="_cancelTapped">[[localize('oauth-sign-out')]]</paper-button>
@ -99,7 +105,7 @@
<span slot="step-description">[[localize('oauth-billing')]]</span>
<paper-card class="card">
<div class="container">
<img src="images/do_oauth_billing.svg" />
<img class="mirror" src="images/do_oauth_billing.svg" />
<p>[[localize('oauth-billing-tag')]]</p>
</div>
<paper-button on-tap="_cancelTapped">[[localize('oauth-sign-out')]]</paper-button>
@ -111,7 +117,7 @@
<span slot="step-description">[[localize('oauth-account-active')]]</span>
<paper-card class="card">
<div class="container">
<img src="images/do_oauth_done.svg" />
<img class="mirror" src="images/do_oauth_done.svg" />
<p>[[localize('oauth-account-active-tag')]]</p>
</div>
<paper-button disabled>[[localize('oauth-sign-out')]]</paper-button>

View file

@ -153,6 +153,19 @@
#gcp .description ul {
list-style-image: url('../images/check_blue.svg');
}
/* Reverse check icon for RTL languages */
:host(:dir(rtl)) #digital-ocean .description ul {
list-style-image: url('../images/check_white_rtl.svg');
}
:host(:dir(rtl)) #manual-server .description ul {
list-style-image: url('../images/check_green_rtl.svg');
}
:host(:dir(rtl)) #aws .description ul {
list-style-image: url('../images/check_orange_rtl.svg');
}
:host(:dir(rtl)) #gcp .description ul {
list-style-image: url('../images/check_blue_rtl.svg');
}
</style>
<outline-step-view>

View file

@ -122,7 +122,10 @@
display: none;
}
}
/* rtl:ignore */
.code, #command, paper-textarea {
direction: ltr;
text-align: left;
@apply --code-mixin;
}
iron-icon {

View file

@ -48,6 +48,7 @@
padding: 0;
color: #fff;
font-size: 16px;
width: 100%;
}
.setting p {
margin-bottom: 12px;
@ -122,7 +123,7 @@
<div>
<h3>DigitalOcean</h3>
<paper-input readonly value="[[serverLocation]]" label="[[localize('settings-server-location')]]" hidden$="[[!serverLocation]]" always-float-label maxlength="100"></paper-input>
<paper-input readonly value="[[serverMonthlyCost]] USD/month" label="[[localize('settings-server-cost')]]" hidden$="[[!serverMonthlyCost]]" always-float-label maxlength="100"></paper-input>
<paper-input readonly value="[[serverMonthlyCost]] USD" label="[[localize('settings-server-cost')]]" hidden$="[[!serverMonthlyCost]]" always-float-label maxlength="100"></paper-input>
<paper-input readonly value="[[serverMonthlyTransferLimit]]" label="[[localize('settings-transfer-limit')]]" hidden$="[[!serverMonthlyTransferLimit]]" always-float-label maxlength="100"></paper-input>
</div>
</div>

View file

@ -331,6 +331,11 @@
.flex-1 {
flex: 1;
}
/* Mirror icons */
:host(:dir(rtl)) iron-icon, :host(:dir(rtl)) .share-button,
:host(:dir(rtl)) .access-key-icon {
transform: scaleX(-1);
}
</style>
<div class="container">
@ -720,6 +725,9 @@
if (monthlyLimitBytes && numBytes) {
utilizationPercentage = Math.round(numBytes / monthlyLimitBytes * 100);
}
if (document.documentElement.dir === 'rtl') {
return `%${utilizationPercentage}`;
}
return `${utilizationPercentage}%`
},
_accessKeysAddedOrRemoved: function(changeRecord) {

View file

@ -757,9 +757,8 @@ export class App {
view.serverHostname = selectedServer.getHostname();
view.serverManagementApiUrl = selectedServer.getManagementApiUrl();
view.serverPortForNewAccessKeys = selectedServer.getPortForNewAccessKeys();
// TODO(alalama): use actual locale.
view.serverCreationDate = selectedServer.getCreatedDate().toLocaleString(
'en-US', {year: 'numeric', month: 'long', day: 'numeric'});
this.appRoot.language, {year: 'numeric', month: 'long', day: 'numeric'});
if (isManagedServer(selectedServer)) {
view.isServerManaged = true;

View file

@ -56,3 +56,5 @@ cp -r $OUT_DIR/browserified/* $STATIC_DIR/
# Copy static resources
cp -r $ROOT_DIR/src/server_manager/{bower_components,ui_components,index.html,images,messages} $STATIC_DIR
# Generate CSS rules to mirror the UI in RTL languages.
node -e "require('./scripts/generate_rtl_css.js')('$STATIC_DIR/ui_components/*.html', '$STATIC_DIR/ui_components')"

2097
yarn.lock

File diff suppressed because it is too large Load diff