diff --git a/config.js b/config.js index ff7ed81..450f19b 100644 --- a/config.js +++ b/config.js @@ -16,6 +16,6 @@ module.exports = { filePath: 'accounts/site.json', sourceLang: 'en', basePath: `${__dirname}/packages/app/i18n`, - minApproved: 80, // Minimal ready percent before translation can be published + minTranslated: 80, // Minimal ready percent before translation can be published }, }; diff --git a/package.json b/package.json index d8c3b8d..b065d8d 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "build:dll": "babel-node --extensions '.ts,.d.ts' ./packages/scripts/build-dll.ts", "build:serve": "http-server --proxy https://dev.account.ely.by ./build", "sb": "APP_ENV=storybook start-storybook -p 9009 --ci", - "sb:build": "APP_ENV=storybook build-storybook" + "sb:build": "APP_ENV=storybook build-storybook", + "postinstall": "node -e \"try{require('./postinstall.js')}catch(e){}\"" }, "lint-staged": { "*.{json,scss,css,md}": [ @@ -144,6 +145,7 @@ "loader-utils": "^2.0.0", "mini-css-extract-plugin": "^0.9.0", "node-sass": "^4.14.1", + "postcss-bidirection": "https://github.com/erickskrauch/postcss-bidirection.git#iss_23", "postcss-import": "^12.0.1", "postcss-loader": "^3.0.0", "postcss-scss": "^2.1.1", diff --git a/packages/app/components/auth/PanelTransition.tsx b/packages/app/components/auth/PanelTransition.tsx index 085a05f..29beac9 100644 --- a/packages/app/components/auth/PanelTransition.tsx +++ b/packages/app/components/auth/PanelTransition.tsx @@ -119,6 +119,7 @@ interface State { formsHeights: Record; } +// TODO: completely broken for RTL languages class PanelTransition extends React.PureComponent { state: State = { contextHeight: 0, diff --git a/packages/app/components/auth/appInfo/appInfo.scss b/packages/app/components/auth/appInfo/appInfo.scss index febbe2c..443525f 100644 --- a/packages/app/components/auth/appInfo/appInfo.scss +++ b/packages/app/components/auth/appInfo/appInfo.scss @@ -16,7 +16,6 @@ display: block; position: absolute; - left: 0; bottom: 0; height: 3px; width: 40px; diff --git a/packages/app/components/auth/chooseAccount/accountSwitcher.scss b/packages/app/components/auth/chooseAccount/accountSwitcher.scss index b93012a..83fbc95 100644 --- a/packages/app/components/auth/chooseAccount/accountSwitcher.scss +++ b/packages/app/components/auth/chooseAccount/accountSwitcher.scss @@ -3,7 +3,7 @@ .accountSwitcher { background: $black; - text-align: left; + text-align: start; } $border: 1px solid lighter($black); @@ -37,12 +37,12 @@ $border: 1px solid lighter($black); .accountAvatar { font-size: 35px; - margin-right: 15px; + margin-inline-end: 15px; } .accountInfo { flex-grow: 1; - margin-right: 15px; + margin-inline-end: 15px; min-width: 0; // Fix for text-overflow. See https://stackoverflow.com/a/40612184 } @@ -76,17 +76,25 @@ $border: 1px solid lighter($black); composes: arrowRight from '~app/components/ui/icons.scss'; position: relative; - left: 0; + inset-inline-start: 0; font-size: 24px; color: #4e4e4e; line-height: 35px; + transition: color 0.25s, inset-inline-start 0.5s; + // TODO: right now transition property doesn't support the bidirectional value. + // See https://github.com/gasolin/postcss-bidirection/issues/25. + // noinspection CssOverwrittenProperties Graceful degradation transition: color 0.25s, left 0.5s; + html[dir='rtl'] & { + transition: color 0.25s, right 0.5s; + } + .item:hover & { color: #aaa; - left: 5px; + inset-inline-start: 5px; } } diff --git a/packages/app/components/auth/forgotPassword/forgotPassword.scss b/packages/app/components/auth/forgotPassword/forgotPassword.scss index de7757a..5f4cdfe 100644 --- a/packages/app/components/auth/forgotPassword/forgotPassword.scss +++ b/packages/app/components/auth/forgotPassword/forgotPassword.scss @@ -16,7 +16,7 @@ position: relative; bottom: 1px; - padding-left: 3px; + padding-inline-start: 3px; color: #666666; font-size: 10px; diff --git a/packages/app/components/auth/permissions/permissions.scss b/packages/app/components/auth/permissions/permissions.scss index c3400d6..a329ca4 100644 --- a/packages/app/components/auth/permissions/permissions.scss +++ b/packages/app/components/auth/permissions/permissions.scss @@ -4,18 +4,18 @@ .authInfo { // Отступы сверху и снизу разные т.к. мы ужимаем высоту линии строки с логином на 2 пикселя и из-за этого теряем отступ снизу padding: 5px 20px 7px; - text-align: left; + text-align: start; } .authInfoAvatar { $size: 30px; - float: left; + float: start; height: $size; width: $size; font-size: $size; line-height: 1; - margin-right: 10px; + margin-inline-end: 10px; margin-top: 2px; color: #aaa; @@ -38,7 +38,7 @@ .permissionsContainer { padding: 15px 12px; - text-align: left; + text-align: start; } .permissionsTitle { @@ -57,7 +57,7 @@ font-size: 14px; line-height: 1.4; padding-bottom: 4px; - padding-left: 17px; + padding-inline-start: 17px; position: relative; &:last-of-type { @@ -71,7 +71,7 @@ line-height: 9px; position: absolute; top: 6px; - left: -4px; + inset-inline-start: -4px; } } } diff --git a/packages/app/components/contact/contactForm.scss b/packages/app/components/contact/contactForm.scss index b98b20b..5efc474 100644 --- a/packages/app/components/contact/contactForm.scss +++ b/packages/app/components/contact/contactForm.scss @@ -37,7 +37,7 @@ width: 50%; &:first-of-type { - margin-right: $popupPadding; + margin-inline-end: $popupPadding; } } diff --git a/packages/app/components/dev/apps/applicationsIndex.scss b/packages/app/components/dev/apps/applicationsIndex.scss index ebfa986..d4770c3 100644 --- a/packages/app/components/dev/apps/applicationsIndex.scss +++ b/packages/app/components/dev/apps/applicationsIndex.scss @@ -160,7 +160,7 @@ composes: arrowRight from '~app/components/ui/icons.scss'; position: relative; - left: 0; + inset-inline-start: 0; font-size: 28px; color: #ebe8e1; @@ -173,7 +173,11 @@ .appExpanded & { color: #777; - transform: rotate(360deg) !important; // Prevent it from hover rotating + transform: rotate(360deg)!important; // Prevent it from hover rotating + + html[dir='rtl'] & { + transform: rotate(0)!important; + } } } @@ -195,7 +199,7 @@ $appDetailsContainerRightLeftPadding: 30px; .editAppLink { position: absolute; top: 4px; - right: 0; + inset-inline-end: 0; font-size: 12px; color: #9a9a9a; @@ -221,17 +225,19 @@ $appDetailsContainerRightLeftPadding: 30px; } .appActionButton { - margin: 0 10px 10px 0; + margin-inline-end: 10px; + margin-bottom: 10px; &:last-of-type { - margin-right: 0; + margin-inline-end: 0; } } .appActionContainer { position: absolute; + width: calc(100% - #{$appDetailsContainerRightLeftPadding * 2}); top: 100%; - left: 0; + inset-inline-start: 0; padding: 0 $appDetailsContainerRightLeftPadding; background: #f5f5f5; } @@ -244,7 +250,7 @@ $appDetailsContainerRightLeftPadding: 30px; .continueActionButtonWrapper { display: inline-block; - margin-left: 10px; + margin-inline-start: 10px; } .continueActionLink { diff --git a/packages/app/components/footerMenu/footerMenu.scss b/packages/app/components/footerMenu/footerMenu.scss index 1be8be0..0d79a6f 100644 --- a/packages/app/components/footerMenu/footerMenu.scss +++ b/packages/app/components/footerMenu/footerMenu.scss @@ -24,5 +24,5 @@ position: relative; bottom: 1px; font-size: 11px; - margin-right: 3px; + margin-inline-end: 3px; } diff --git a/packages/app/components/i18n/IntlProvider.tsx b/packages/app/components/i18n/IntlProvider.tsx index 2340aae..134c75d 100644 --- a/packages/app/components/i18n/IntlProvider.tsx +++ b/packages/app/components/i18n/IntlProvider.tsx @@ -1,5 +1,8 @@ import React, { useState, useEffect, ComponentType } from 'react'; import { RawIntlProvider, IntlShape } from 'react-intl'; +import { Helmet } from 'react-helmet-async'; + +import { getLangDir } from 'rtl-detect'; import i18n from 'app/services/i18n'; import { useReduxSelector } from 'app/functions'; @@ -26,7 +29,12 @@ const IntlProvider: ComponentType = ({ children }) => { return null; } - return {children}; + return ( + + + {children} + + ); }; export default IntlProvider; diff --git a/packages/app/components/i18n/flags/ar.svg b/packages/app/components/i18n/flags/ar.svg new file mode 100644 index 0000000..c6356a1 --- /dev/null +++ b/packages/app/components/i18n/flags/ar.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/app/components/i18n/flags/udm.svg b/packages/app/components/i18n/flags/udm.svg new file mode 100644 index 0000000..704675a --- /dev/null +++ b/packages/app/components/i18n/flags/udm.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/app/components/i18n/localeFlags.ts b/packages/app/components/i18n/localeFlags.ts index c0151af..68f8f08 100644 --- a/packages/app/components/i18n/localeFlags.ts +++ b/packages/app/components/i18n/localeFlags.ts @@ -10,6 +10,8 @@ const localeToCountryCode: Record = { sr: 'rs', zh: 'cn', cs: 'cz', + fil: 'ph', + he: 'il', }; const SUPPORTED_LANGUAGES: ReadonlyArray = Object.keys(supportedLocales); diff --git a/packages/app/components/languageSwitcher/changeLanguageLink/link.scss b/packages/app/components/languageSwitcher/changeLanguageLink/link.scss index 8d38218..3dd30af 100644 --- a/packages/app/components/languageSwitcher/changeLanguageLink/link.scss +++ b/packages/app/components/languageSwitcher/changeLanguageLink/link.scss @@ -26,7 +26,7 @@ position: relative; top: 1px; display: inline-block; - margin-right: 4px; + margin-inline-end: 4px; height: $height; width: $height * 4 / 3; box-shadow: 0 0 1px rgba(#000, 0.2); diff --git a/packages/app/components/languageSwitcher/languageSwitcher.scss b/packages/app/components/languageSwitcher/languageSwitcher.scss index db8351e..3cb5ac3 100644 --- a/packages/app/components/languageSwitcher/languageSwitcher.scss +++ b/packages/app/components/languageSwitcher/languageSwitcher.scss @@ -33,7 +33,7 @@ position: absolute; top: 14px; - right: 12px; + inset-inline-end: 12px; font-size: 22px; color: #edebe5; pointer-events: none; // Иконка чисто декоративная, так что клик должен проходить сквозь неё @@ -94,7 +94,7 @@ $languageListBorderStyle: 1px solid $languageListBorderColor; .languageIco { display: inline-block; - margin-right: 7px; + margin-inline-end: 7px; width: 40px; height: 30px; box-shadow: 0 0 1px rgba(#000, 0.2); @@ -129,7 +129,7 @@ $languageListBorderStyle: 1px solid $languageListBorderColor; box-sizing: border-box; width: 22px; height: 22px; - right: -32px; + inset-inline-end: -32px; font-size: 10px; line-height: 18px; @@ -146,13 +146,13 @@ $languageListBorderStyle: 1px solid $languageListBorderColor; } .languageItem:hover & { - right: 0; + inset-inline-end: 0; } .activeLanguageItem & { border-color: $green; background: $green; - right: 0; + inset-inline-end: 0; &:before { opacity: 1; @@ -209,7 +209,7 @@ $languageListBorderStyle: 1px solid $languageListBorderColor; color: lighter($blue); font-size: 22px; - margin-right: 10px; + margin-inline-end: 10px; } .improveTranslatesContent { diff --git a/packages/app/components/profile/deleteAccount/deleteAccount.scss b/packages/app/components/profile/deleteAccount/deleteAccount.scss index 06d9266..3c070fc 100644 --- a/packages/app/components/profile/deleteAccount/deleteAccount.scss +++ b/packages/app/components/profile/deleteAccount/deleteAccount.scss @@ -19,12 +19,12 @@ li { position: relative; - padding-left: 11px; + padding-inline-start: 11px; &:before { content: '—'; position: absolute; - left: 0; + inset-inline-start: 0; top: 2px; } diff --git a/packages/app/components/profile/multiFactorAuth/instructions/OsInstruction.tsx b/packages/app/components/profile/multiFactorAuth/instructions/OsInstruction.tsx index 7c9994f..7b2ffd7 100644 --- a/packages/app/components/profile/multiFactorAuth/instructions/OsInstruction.tsx +++ b/packages/app/components/profile/multiFactorAuth/instructions/OsInstruction.tsx @@ -75,7 +75,7 @@ export default function OsInstruction({ os }: { os: OS }) {
    {linksByOs[os].featured.map((item) => (
  • - + {item.label}
  • diff --git a/packages/app/components/profile/multiFactorAuth/instructions/instructions.scss b/packages/app/components/profile/multiFactorAuth/instructions/instructions.scss index 50f5605..e83cfbd 100644 --- a/packages/app/components/profile/multiFactorAuth/instructions/instructions.scss +++ b/packages/app/components/profile/multiFactorAuth/instructions/instructions.scss @@ -22,7 +22,7 @@ .otherApps { position: absolute; - right: 0; + inset-inline-end: 0; bottom: 5px; font-size: 10px; @@ -96,7 +96,7 @@ .androidActive & { transform: translateX(0); - left: 0; + inset-inline-start: 0; } .appleActive &, @@ -111,21 +111,33 @@ $translateX: -51%; transform: translateX($translateX) scale(1); - left: 49%; + inset-inline-start: 49%; + + html[dir='rtl'] & { + transform: translateX(-$translateX) scale(1); + } &:hover { transform: translateX($translateX) scale(1.1); + + html[dir='rtl'] & { + transform: translateX(-$translateX) scale(1.1); + } } .appleActive & { - transform: translateX(0); - left: 0; + transform: translateX(0)!important; // override dir='rtl' + inset-inline-start: 0; } .androidActive &, .windowsActive & { transform: translateX($translateX) scale(0); opacity: 0; + + html[dir='rtl'] & { + transform: translateX(-$translateX) scale(0); + } } } @@ -134,21 +146,33 @@ $translateX: -100%; transform: translateX($translateX) scale(1); - left: 100%; + inset-inline-start: 100%; + + html[dir='rtl'] & { + transform: translateX(-$translateX) scale(1); + } &:hover { transform: translateX($translateX) scale(1.1); + + html[dir='rtl'] & { + transform: translateX(-$translateX) scale(1.1); + } } .windowsActive & { - transform: translateX(0); - left: 0; + transform: translateX(0)!important; // override dir='rtl' + inset-inline-start: 0; } .appleActive &, .androidActive & { transform: translateX($translateX) scale(0); opacity: 0; + + html[dir='rtl'] & { + transform: translateX(-$translateX) scale(0); + } } } @@ -166,8 +190,8 @@ position: relative; z-index: 1; margin: 15px; - margin-left: 30%; - padding-left: 15px; + margin-inline-start: 30%; + padding-inline-start: 15px; padding-bottom: 15px; min-height: $boxHeight; } @@ -212,7 +236,7 @@ .osName { font-family: $font-family-title; font-size: 16px; - margin-left: 10px; + margin-inline-start: 10px; } @mixin commonNonActiveTile() { diff --git a/packages/app/components/profile/multiFactorAuth/keyForm/key-form.scss b/packages/app/components/profile/multiFactorAuth/keyForm/key-form.scss index 95dec57..b732229 100644 --- a/packages/app/components/profile/multiFactorAuth/keyForm/key-form.scss +++ b/packages/app/components/profile/multiFactorAuth/keyForm/key-form.scss @@ -16,8 +16,8 @@ $maxQrCodeSize: 242px; } .or { - position: absolute; - left: 0; + position: absolute; // Use absolute positioning to put an element between margin + inset-inline-start: 0; // TODO: doesn't have any effect. Probably can be removed width: 100%; margin-top: -18px; diff --git a/packages/app/components/profile/profile.scss b/packages/app/components/profile/profile.scss index e7651c5..daddd3d 100644 --- a/packages/app/components/profile/profile.scss +++ b/packages/app/components/profile/profile.scss @@ -13,7 +13,8 @@ $formColumnWidth: 416px; } .descriptionColumn { - padding: 12px 20px 0 0; + padding-top: 12px; + padding-inline-end: 20px; box-sizing: border-box; } @@ -97,7 +98,8 @@ $formColumnWidth: 416px; } .paramMessage { - padding: 10px 40px 0 0; + padding-top: 10px; + padding-inline-end: 40px; color: $red; font-size: 11px; @@ -168,7 +170,7 @@ $formColumnWidth: 416px; } .paramEditIcon { - margin-left: 5px; + margin-inline-start: 5px; vertical-align: top; } diff --git a/packages/app/components/profile/profileForm.scss b/packages/app/components/profile/profileForm.scss index 796abf5..ae4568f 100644 --- a/packages/app/components/profile/profileForm.scss +++ b/packages/app/components/profile/profileForm.scss @@ -9,7 +9,7 @@ .backButton { position: absolute; - left: -60px; + inset-inline-start: -60px; top: 15px; width: 25px; height: 25px; @@ -60,7 +60,6 @@ display: block; position: absolute; - left: 0; bottom: 0; height: 3px; width: 86px; @@ -110,7 +109,7 @@ .backButton { top: 29px; - left: 27px; + inset-inline-start: 27px; padding: 0; margin: 0; width: auto; diff --git a/packages/app/components/sourceCode/sourceCodePopup.scss b/packages/app/components/sourceCode/sourceCodePopup.scss index 49b1711..981fa84 100644 --- a/packages/app/components/sourceCode/sourceCodePopup.scss +++ b/packages/app/components/sourceCode/sourceCodePopup.scss @@ -29,7 +29,8 @@ } .iconWrapper { - margin: 4px 12px 0 0; + margin-top: 4px; + margin-inline-end: 12px; } .dbIcon { @@ -68,14 +69,15 @@ font-family: $font-family-title; font-size: 14px; color: #9a9a9a; - margin: 0 0 5px 1px; // Add 1px from the left to make icon look visually aligned with texts + margin-bottom: 5px; + margin-inline-start: 1px; // Add 1px from the left to make icon look visually aligned with texts } .githubIcon { composes: github from '~app/components/ui/icons.scss'; font-size: 12px; - margin-right: 2px; + margin-inline-end: 2px; position: relative; bottom: 1px; } diff --git a/packages/app/components/ui/button-groups.scss b/packages/app/components/ui/button-groups.scss deleted file mode 100644 index 00f88cb..0000000 --- a/packages/app/components/ui/button-groups.scss +++ /dev/null @@ -1,20 +0,0 @@ -.horizontalGroup { - display: flex; -} - -.item { - // TODO: in some cases we do not need overflow hidden - // probably, it is better to create a separate class for children, that will - // enable overflow hidden and ellipsis - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - - $borderConfig: 1px solid rgba(#fff, 0.15); - - border-left: $borderConfig; - - &:last-child { - border-right: $borderConfig; - } -} diff --git a/packages/app/components/ui/form/FormComponent.tsx b/packages/app/components/ui/form/FormComponent.tsx index 112a2c5..92379f3 100644 --- a/packages/app/components/ui/form/FormComponent.tsx +++ b/packages/app/components/ui/form/FormComponent.tsx @@ -1,29 +1,29 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import { MessageDescriptor } from 'react-intl'; + import i18n from 'app/services/i18n'; +function isMessageDescriptor(value: any): value is MessageDescriptor { + return typeof value === 'object' && value.id; +} + export default class FormComponent extends React.Component { /** * Formats message resolving intl translations * * @param {string|object} message - message string, or intl message descriptor with an `id` field * + * @deprecated * @returns {string} */ - formatMessage(message: string | MessageDescriptor): string { - if (!message) { - throw new Error('A message is required'); + formatMessage(message: T): T extends MessageDescriptor ? string : T { + if (isMessageDescriptor(message)) { + // @ts-ignore + return i18n.getIntl().formatMessage(message); } - if (typeof message === 'string') { - return message; - } - - if (!message.id) { - throw new Error(`Invalid message format: ${JSON.stringify(message)}`); - } - - return i18n.getIntl().formatMessage(message); + // @ts-ignore + return message; } /** diff --git a/packages/app/components/ui/form/Input.tsx b/packages/app/components/ui/form/Input.tsx index 0d19c69..358b73a 100644 --- a/packages/app/components/ui/form/Input.tsx +++ b/packages/app/components/ui/form/Input.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import { MessageDescriptor } from 'react-intl'; import clsx from 'clsx'; import { uniqueId, omit } from 'app/functions'; @@ -17,7 +17,7 @@ export default class Input extends FormInputComponent< color: Color; center: boolean; disabled: boolean; - label?: string | MessageDescriptor; + label?: ReactNode | MessageDescriptor; placeholder?: string | MessageDescriptor; icon?: string; copy?: boolean; diff --git a/packages/app/components/ui/form/dropdown.scss b/packages/app/components/ui/form/dropdown.scss index e197fea..07095ab 100644 --- a/packages/app/components/ui/form/dropdown.scss +++ b/packages/app/components/ui/form/dropdown.scss @@ -25,8 +25,9 @@ $dropdownPadding: 15px; display: inline-block; box-sizing: border-box; height: 50px; + padding-inline-start: $dropdownPadding; // 28px - ширина иконки при заданном размере шрифта - padding: 0 ($dropdownPadding * 2 + 28px) 0 $dropdownPadding; + padding-inline-end: $dropdownPadding * 2 + 28px; position: relative; font-family: $font-family-title; @@ -48,6 +49,7 @@ $dropdownPadding: 15px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; + text-align: start; } .opened { @@ -57,18 +59,26 @@ $dropdownPadding: 15px; composes: selecter from '~app/components/ui/icons.scss'; position: absolute; - right: $dropdownPadding; + inset-inline-end: $dropdownPadding; top: 16px; font-size: 17px; - transition: right 0.3s cubic-bezier(0.23, 1, 0.32, 1); // easeOutQuint + transition: inset-inline-end 0.3s cubic-bezier(0.23, 1, 0.32, 1); // easeOutQuint + // TODO: right now transition property doesn't support the bidirectional value. + // See https://github.com/gasolin/postcss-bidirection/issues/25. + // noinspection CssOverwrittenProperties Graceful degradation + transition: right 0.3s cubic-bezier(0.23, 1, 0.32, 1); + + html[dir='rtl'] & { + transition: left 0.3s cubic-bezier(0.23, 1, 0.32, 1); + } .dropdown:hover & { - right: $dropdownPadding - 5px; + inset-inline-end: $dropdownPadding - 5px; } .dropdown:active &, .dropdown.opened & { - right: $dropdownPadding + 5px; + inset-inline-end: $dropdownPadding + 5px; } } @@ -84,7 +94,7 @@ $dropdownPadding: 15px; .menu { position: absolute; top: 60px; - left: 0; + inset-inline-start: 0; z-index: 10; width: 120%; diff --git a/packages/app/components/ui/form/form.scss b/packages/app/components/ui/form/form.scss index f90fdd6..54b9df0 100644 --- a/packages/app/components/ui/form/form.scss +++ b/packages/app/components/ui/form/form.scss @@ -36,7 +36,7 @@ composes: formRow; .textField { - padding-left: 60px; + padding-inline-start: 60px; } } @@ -80,7 +80,7 @@ .textFieldIcon { box-sizing: border-box; position: absolute; - left: 0; + inset-inline-start: 0; top: 0; height: 50px; width: 50px; @@ -95,7 +95,7 @@ .copyIcon { position: absolute; - right: 5px; + inset-inline-end: 5px; top: 10px; padding: 5px; @@ -175,6 +175,7 @@ font-family: $font-family-title; color: #666; font-size: 18px; + text-align: start; } .fieldError { @@ -248,7 +249,7 @@ .markableContainer { display: inline-block; position: relative; - padding-left: 27px; + padding-inline-start: 27px; font-family: $font-family-title; font-size: 16px; @@ -260,7 +261,7 @@ .markPosition { position: absolute; box-sizing: border-box; - left: 0; + inset-inline-start: 0; top: 0; margin: 0; diff --git a/packages/app/components/ui/icons.scss b/packages/app/components/ui/icons.scss index c894b36..7dc748c 100644 --- a/packages/app/components/ui/icons.scss +++ b/packages/app/components/ui/icons.scss @@ -15,10 +15,30 @@ @extend .arrow; transform: rotate(270deg); + + html[dir='rtl'] & { + transform: rotate(90deg); + } } .arrowLeft { @extend .arrow; transform: rotate(90deg); + + html[dir='rtl'] & { + transform: rotate(270deg); + } +} + +html[dir='rtl'] { + .brush, + .exit, + .key, + .pencil, + .search { + &:before { + transform: scaleX(-1); + } + } } diff --git a/packages/app/components/ui/loader/loader.scss b/packages/app/components/ui/loader/loader.scss index 659c907..90ad35f 100644 --- a/packages/app/components/ui/loader/loader.scss +++ b/packages/app/components/ui/loader/loader.scss @@ -1,4 +1,6 @@ .loader-overlay { + direction: ltr; + background: rgba(255, 255, 255, 0.3); position: fixed; top: 0; diff --git a/packages/app/components/ui/motion/SlideMotion.tsx b/packages/app/components/ui/motion/SlideMotion.tsx index 787a6bc..2080385 100644 --- a/packages/app/components/ui/motion/SlideMotion.tsx +++ b/packages/app/components/ui/motion/SlideMotion.tsx @@ -77,8 +77,8 @@ class SlideMotion extends React.PureComponent {
    {React.Children.map(children, (child, index) => ( diff --git a/packages/app/components/ui/motion/slide-motion.scss b/packages/app/components/ui/motion/slide-motion.scss index 147ef7d..622ee48 100644 --- a/packages/app/components/ui/motion/slide-motion.scss +++ b/packages/app/components/ui/motion/slide-motion.scss @@ -1,5 +1,11 @@ .container { white-space: nowrap; + transform: translate(var(--transition-progress)); + + html[dir='ltr'] & { + // noinspection CssInvalidFunction works fine in a browser (: + transform: translate(calc(var(--transition-progress) * -1)); + } } .item { diff --git a/packages/app/components/ui/panel.scss b/packages/app/components/ui/panel.scss index 5fcb5fd..aafb27a 100644 --- a/packages/app/components/ui/panel.scss +++ b/packages/app/components/ui/panel.scss @@ -25,12 +25,12 @@ $headerHeight: 60px; .headerControl { composes: black from '~app/components/ui/buttons.scss'; - float: left; + float: start; overflow: hidden; height: $headerHeight - 1px; width: 49px; padding: 0; - border-right: 1px solid lighter($black); + border-inline-end: 1px solid lighter($black); line-height: 1; text-align: center; @@ -122,7 +122,7 @@ $bodyTopBottomPadding: 15px; composes: close from '~app/components/ui/icons.scss'; position: absolute; - right: 5px; + inset-inline-end: 5px; top: 5px; font-size: 10px; diff --git a/packages/app/components/ui/popup/popup.scss b/packages/app/components/ui/popup/popup.scss index 684f8dc..aea1353 100644 --- a/packages/app/components/ui/popup/popup.scss +++ b/packages/app/components/ui/popup/popup.scss @@ -59,7 +59,7 @@ $popupMargin: 20px; // Outer popup margins .popup { white-space: normal; - text-align: left; + text-align: start; background: #fff; box-shadow: 0 0 10px rgba(#000, 0.2); @@ -95,7 +95,7 @@ $popupMargin: 20px; // Outer popup margins composes: close from '~app/components/ui/icons.scss'; position: absolute; - right: 0; + inset-inline-end: 0; top: 0; padding: 15px; cursor: pointer; @@ -122,6 +122,10 @@ $popupMargin: 20px; // Outer popup margins opacity: 0; transform: translate(100%); transition: 0s; + + html[dir='rtl'] & { + transform: translate(-100%); + } } } diff --git a/packages/app/components/ui/pseudoAvatar.scss b/packages/app/components/ui/pseudoAvatar.scss index 085782a..18c20ed 100644 --- a/packages/app/components/ui/pseudoAvatar.scss +++ b/packages/app/components/ui/pseudoAvatar.scss @@ -47,7 +47,7 @@ position: absolute; top: 0.16em; - left: -0.145em; + inset-inline-start: -0.145em; font-size: 0.7em; color: rgba($red, 0.75); } diff --git a/packages/app/components/ui/stepper/stepper.scss b/packages/app/components/ui/stepper/stepper.scss index f137e83..2ae632c 100644 --- a/packages/app/components/ui/stepper/stepper.scss +++ b/packages/app/components/ui/stepper/stepper.scss @@ -8,7 +8,7 @@ .step { position: relative; - text-align: right; + text-align: end; width: 100%; height: 4px; @@ -24,8 +24,8 @@ position: absolute; height: 4px; - left: 0; - right: 100%; + inset-inline-start: 0; + inset-inline-end: 100%; top: 50%; margin-top: -2px; @@ -53,7 +53,7 @@ .activeStep { &:before { - right: 0; + inset-inline-end: 0; transition-delay: unset; } diff --git a/packages/app/components/userbar/AccountSwitcher.tsx b/packages/app/components/userbar/AccountSwitcher.tsx index cb75ca6..1fa07c8 100644 --- a/packages/app/components/userbar/AccountSwitcher.tsx +++ b/packages/app/components/userbar/AccountSwitcher.tsx @@ -47,7 +47,7 @@ const AccountSwitcher: ComponentType = ({
    -
    +
    {activeAccount.username}
    {activeAccount.email}
    @@ -81,16 +81,16 @@ const AccountSwitcher: ComponentType = ({ > +
    +
    {account.username}
    +
    {account.email}
    +
    +
    - -
    -
    {account.username}
    -
    {account.email}
    -
    ))} diff --git a/packages/app/components/userbar/accountSwitcher.scss b/packages/app/components/userbar/accountSwitcher.scss index 50e40e9..1e9c796 100644 --- a/packages/app/components/userbar/accountSwitcher.scss +++ b/packages/app/components/userbar/accountSwitcher.scss @@ -8,8 +8,6 @@ $bodyLeftRightPadding: 20px; $lightBorderColor: #eee; .accountSwitcher { - text-align: left; - background: #fff; color: #444; min-width: 205px; @@ -20,9 +18,6 @@ $lightBorderColor: #eee; border-bottom: 7px solid darker($green); } -.accountInfo { -} - .accountUsername, .accountEmail { overflow: hidden; @@ -30,6 +25,7 @@ $lightBorderColor: #eee; } .item { + display: flex; padding: 15px; border-bottom: 1px solid $lightBorderColor; } @@ -50,8 +46,7 @@ $lightBorderColor: #eee; .accountIcon { font-size: 27px; width: 20px; - text-align: center; - float: left; + margin-inline-end: 9px; } .activeAccountIcon { @@ -60,10 +55,6 @@ $lightBorderColor: #eee; font-size: 40px; } -.activeAccountInfo { - margin-left: 29px; -} - .activeAccountUsername { font-family: $font-family-title; font-size: 20px; @@ -88,8 +79,7 @@ $lightBorderColor: #eee; } .accountInfo { - margin-left: 29px; - margin-right: 25px; + flex-grow: 1; } .accountUsername { @@ -117,15 +107,16 @@ $lightBorderColor: #eee; color: $green; position: relative; bottom: 1px; - margin-right: 3px; + margin-inline-end: 3px; } .logoutIcon { composes: exit from '~app/components/ui/icons.scss'; + align-self: center; + margin-inline-start: 9px; + color: #cdcdcd; - float: right; - line-height: 27px; transition: 0.25s; &:hover { diff --git a/packages/app/components/userbar/loggedInPanel.scss b/packages/app/components/userbar/loggedInPanel.scss index a0b532f..af5f12d 100644 --- a/packages/app/components/userbar/loggedInPanel.scss +++ b/packages/app/components/userbar/loggedInPanel.scss @@ -27,6 +27,10 @@ .expandIcon { transform: rotate(-180deg); + + html[dir='rtl'] & { + transform: rotate(180deg); // Fix spin direction + } } } @@ -36,13 +40,13 @@ position: relative; bottom: 2px; - padding-right: 5px; + padding-inline-end: 5px; } .expandIcon { composes: caret from '~app/components/ui/icons.scss'; - margin-left: 4px; + margin-inline-start: 4px; font-size: 6px; color: #ccc; transition: 0.2s; @@ -54,7 +58,7 @@ .accountSwitcherContainer { position: absolute; top: 100%; - right: -2px; + inset-inline-end: -2px; cursor: auto; display: none; diff --git a/packages/app/package.json b/packages/app/package.json index a3a13ce..b733761 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -30,6 +30,7 @@ "redux": "^4.0.5", "redux-localstorage": "^0.4.1", "redux-thunk": "^2.0.0", + "rtl-detect": "^1.0.2", "url-search-params-polyfill": "^8.1.0", "webfontloader": "^1.6.26", "whatwg-fetch": "^3.0.0" @@ -45,6 +46,7 @@ "@types/react-helmet": "^6.0.0", "@types/react-motion": "^0.0.29", "@types/react-transition-group": "^4.2.4", + "@types/rtl-detect": "^1.0.0", "@types/webfontloader": "^1.6.30", "@types/webpack-env": "^1.15.2", "utility-types": "^3.10.0" diff --git a/packages/app/pages/404/404.scss b/packages/app/pages/404/404.scss index 36f4058..6cb5898 100644 --- a/packages/app/pages/404/404.scss +++ b/packages/app/pages/404/404.scss @@ -26,7 +26,11 @@ width: 50px; height: 50px; background: white; - animation: cubeRotate 1s ease-out infinite; + animation: cubeRotateLTR 1s ease-out infinite; + + html[dir='rtl'] & { + animation-name: cubeRotateRTL; + } } .road { @@ -34,12 +38,12 @@ width: 100%; height: 1px; background: white; - left: 0; + inset-inline-start: 0; bottom: 0; animation: roadStab 1s ease-out infinite; } -@keyframes cubeRotate { +@keyframes cubeRotateLTR { 0% { transform: rotate(0deg) translate3D(0, 0, 0); } @@ -54,6 +58,21 @@ } } +@keyframes cubeRotateRTL { + 0% { + transform: rotate(0deg) translate3D(0, 0, 0); + } + 65% { + transform: rotate(-45deg) translate3D(0, -13px, 0); + } + 90% { + transform: rotate(-70deg) translate3D(0, -8px, 0); + } + 100% { + transform: rotate(-90deg) translate3D(0, 0, 0); + } +} + @keyframes roadStab { 0% { transform: translate3D(0, 0, 0); @@ -91,7 +110,7 @@ height: 100%; position: absolute; bottom: -50px; - left: 0; + inset-inline-start: 0; overflow: hidden; animation: roadStab 1s ease-out infinite; } @@ -103,7 +122,7 @@ border-right: 2px solid transparent; border-bottom: 4px solid white; bottom: $bottom; - right: -2%; + inset-inline-end: -2%; animation: rockTravelling 10s $delay ease-out infinite; } } @@ -116,37 +135,37 @@ @keyframes rockTravelling { 0% { - right: -2%; + inset-inline-end: -2%; } 10% { - right: 8%; + inset-inline-end: 8%; } 20% { - right: 18%; + inset-inline-end: 18%; } 30% { - right: 29%; + inset-inline-end: 29%; } 40% { - right: 40%; + inset-inline-end: 40%; } 50% { - right: 51%; + inset-inline-end: 51%; } 60% { - right: 62%; + inset-inline-end: 62%; } 70% { - right: 72%; + inset-inline-end: 72%; } 80% { - right: 82%; + inset-inline-end: 82%; } 90% { - right: 92%; + inset-inline-end: 92%; } 100% { - right: 102%; + inset-inline-end: 102%; } } @@ -158,7 +177,7 @@ animation: roadStab 1s ease-out infinite, cloudStab 1s ease-out infinite; position: absolute; bottom: -50px; - left: -50%; + inset-inline-start: -50%; overflow: hidden; } @@ -183,7 +202,7 @@ composes: cloud; top: 65px; - right: -30%; + inset-inline-end: -30%; width: 50px; height: 16px; animation: cloudTravelling 21s 5s linear infinite; @@ -193,7 +212,7 @@ composes: cloud; top: 40px; - right: -30%; + inset-inline-end: -30%; width: 70px; height: 22px; animation: cloudTravelling 26s 11s linear infinite; @@ -201,10 +220,10 @@ @keyframes cloudTravelling { 0% { - right: -30%; + inset-inline-end: -30%; } 100% { - right: 110%; + inset-inline-end: 110%; } } diff --git a/packages/app/pages/auth/auth.scss b/packages/app/pages/auth/auth.scss index 1d2ad9a..0261ac5 100644 --- a/packages/app/pages/auth/auth.scss +++ b/packages/app/pages/auth/auth.scss @@ -34,12 +34,10 @@ $sidebar-width: 320px; @media (min-width: 720px) { .content { padding: 55px 50px; - margin-left: $sidebar-width; + margin-inline-start: $sidebar-width; } .sidebar { - right: auto; - width: $sidebar-width; } diff --git a/packages/app/pages/dev/dev.scss b/packages/app/pages/dev/dev.scss index 52c6ef3..d255c8e 100644 --- a/packages/app/pages/dev/dev.scss +++ b/packages/app/pages/dev/dev.scss @@ -6,7 +6,7 @@ width: 100%; position: absolute; bottom: 10px; - left: 0; + inset-inline-start: 0; // TODO: does nothing. Maybe should be reimplemented? text-align: center; } diff --git a/packages/app/pages/profile/profile.scss b/packages/app/pages/profile/profile.scss index 803bd0a..5ce24a9 100644 --- a/packages/app/pages/profile/profile.scss +++ b/packages/app/pages/profile/profile.scss @@ -6,7 +6,7 @@ width: 100%; position: absolute; bottom: 10px; - left: 0; + inset-inline-start: 0; text-align: center; } diff --git a/packages/app/pages/root/RootPage.tsx b/packages/app/pages/root/RootPage.tsx index f65a667..c9ba816 100644 --- a/packages/app/pages/root/RootPage.tsx +++ b/packages/app/pages/root/RootPage.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Route, Switch } from 'react-router-dom'; -import { Helmet } from 'react-helmet-async'; import clsx from 'clsx'; import { connect } from 'app/functions'; @@ -54,10 +53,6 @@ class RootPage extends React.PureComponent<{ return (
    - - - -
    style } .userBar { - position: absolute; - right: 0; - left: 115px; - top: 0; - text-align: right; + margin-inline-start: auto; } .body { diff --git a/packages/app/pages/rules/rules.scss b/packages/app/pages/rules/rules.scss index ac96765..c1949c1 100644 --- a/packages/app/pages/rules/rules.scss +++ b/packages/app/pages/rules/rules.scss @@ -50,7 +50,7 @@ .rulesList { padding: 0; margin: 0; - padding-left: 20px; + padding-inline-start: 20px; } .rulesItem { @@ -71,7 +71,7 @@ content: ''; position: absolute; top: -10px; - left: -40px; + inset-inline-start: -40px; width: calc(100% + 60px); height: calc(100% + 20px); background: $white; diff --git a/packages/app/services/i18n/overrides/intl/udm.js b/packages/app/services/i18n/overrides/intl/udm.js new file mode 100644 index 0000000..3d298f1 --- /dev/null +++ b/packages/app/services/i18n/overrides/intl/udm.js @@ -0,0 +1,250 @@ +IntlPolyfill.__addLocaleData({ + locale: "udm", + date: { + ca: ["gregory", "generic"], + hourNo0: true, + hour12: false, + formats: { + short: "{1}, {0}", + medium: "{1}, {0}", + full: "{1}, {0}", + long: "{1}, {0}", + availableFormats: { + d: "d", + E: "ccc", + Ed: "ccc, d", + Ehm: "E h:mm a", + EHm: "E HH:mm", + Ehms: "E h:mm:ss a", + EHms: "E HH:mm:ss", + Gy: "G y 'аре'", + GyMMM: "LLL G y", + GyMMMd: "d MMM G y 'аре'", + GyMMMEd: "E, d MMM G y 'аре'", + h: "h a", + H: "H", + hm: "h:mm a", + Hm: "H:mm", + hms: "h:mm:ss a", + Hms: "H:mm:ss", + hmsv: "h:mm:ss a v", + Hmsv: "H:mm:ss v", + hmv: "h:mm a v", + Hmv: "H:mm v", + M: "L", + Md: "dd.MM", + MEd: "E, dd.MM", + MMdd: "dd.MM", + MMM: "LLL", + MMMd: "d MMM", + MMMEd: "ccc, d MMM", + MMMMd: "d MMMM", + ms: "mm:ss", + y: "y", + yM: "MM.y", + yMd: "dd.MM.y", + yMEd: "ccc, d.MM.y 'аре'", + yMM: "MM.y", + yMMM: "LLL y 'аре'", + yMMMd: "d MMM y 'аре'", + yMMMEd: "E, d MMM y 'аре'", + yMMMM: "LLLL y 'аре'", + yQQQ: "QQQ y 'аре'", + yQQQQ: "QQQQ y 'аре'", + }, + dateFormats: { + yMMMMEEEEd: "EEEE, d MMMM y 'аре'", + yMMMMd: "d MMMM y 'аре'", + yMMMd: "d MMM y 'аре'", + yMd: "dd.MM.yy", + }, + timeFormats: { + hmmsszzzz: "H:mm:ss zzzz", + hmsz: "H:mm:ss z", + hms: "H:mm:ss", + hm: "H:mm", + }, + }, + calendars: { + generic: { + months: { + narrow: [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + ], + short: [ + "Т01", + "Т02", + "Т03", + "Т04", + "Т05", + "Т06", + "Т07", + "Т08", + "Т09", + "Т10", + "Т11", + "Т12", + ], + long: [ + "Т01", + "Т02", + "Т03", + "Т04", + "Т05", + "Т06", + "Т07", + "Т08", + "Т09", + "Т10", + "Т11", + "Т12", + ], + }, + days: { + narrow: ["А", "В", "П", "В", "П", "У", "К"], + short: ["ан", "вр", "пк", "вн", "па", "уд", "кн"], + long: [ + "арнянунал", + "вордӥськон", + "пуксён", + "вирнунал", + "покчиарня", + "удмуртарня", + "кӧснунал", + ], + }, + eras: { + narrow: ["ERA0", "ERA1"], + short: ["ERA0", "ERA1"], + long: ["ERA0", "ERA1"], + }, + dayPeriods: { am: "ЛА", pm: "ЛБ" }, + }, + gregory: { + months: { + narrow: ["Т", "Т", "М", "А", "М", "И", "И", "А", "С", "О", "Н", "Д"], + short: [ + "тшт", + "тпт", + "южт", + "ошт", + "крт", + "ивт", + "пст", + "гкт", + "кут", + "квт", + "шкт", + "тст", + ], + long: [ + "толшоре", + "тулыспалэ", + "южтолэзе", + "оштолэзе", + "куартолэзе", + "инвожое", + "пӧсьтолэзе", + "гудырикошконэ", + "куарусёнэ", + "коньывуонэ", + "шуркынмонэ", + "толсуре", + ], + }, + days: { + narrow: ["А", "В", "П", "В", "П", "У", "К"], + short: ["ан", "вр", "пк", "вн", "па", "уд", "кн"], + long: [ + "арнянунал", + "вордӥськон", + "пуксён", + "вирнунал", + "покчиарня", + "удмуртарня", + "кӧснунал", + ], + }, + eras: { + narrow: ["КА", "КБ", "ава", "ав"], + short: ["КА", "КБ", "а.в.а", "а.в."], + long: [ + "Кристослэн вордскем азяз", + "Кристослэн вордскем бераз", + "асьме вакытлэсь азьло", + "асьме вакытэ", + ], + }, + dayPeriods: { am: "ЛА", pm: "ЛБ" }, + }, + }, + }, + number: { + nu: ["latn"], + patterns: { + decimal: { + positivePattern: "{number}", + negativePattern: "{minusSign}{number}", + }, + currency: { + positivePattern: "{number} {currency}", + negativePattern: "{minusSign}{number} {currency}", + }, + percent: { + positivePattern: "{number} {percentSign}", + negativePattern: "{minusSign}{number} {percentSign}", + }, + }, + symbols: { + latn: { + decimal: ",", + group: " ", + nan: "лыд ӧвӧл", + plusSign: "+", + minusSign: "-", + percentSign: "%", + infinity: "∞", + }, + }, + currencies: { + AUD: "A$", + BRL: "R$", + CAD: "CA$", + CNY: "CN¥", + EUR: "€", + GBP: "£", + HKD: "HK$", + ILS: "₪", + INR: "₹", + JPY: "¥", + KRW: "₩", + MXN: "MX$", + NZD: "NZ$", + RUB: "₽", + RUR: "р.", + THB: "฿", + TMT: "ТМТ", + TWD: "NT$", + UAH: "₴", + USD: "$", + VND: "₫", + XAF: "FCFA", + XCD: "EC$", + XOF: "CFA", + XPF: "CFPF", + XXX: "XXXX", + }, + }, +}); + diff --git a/packages/app/services/i18n/overrides/pluralrules/udm.js b/packages/app/services/i18n/overrides/pluralrules/udm.js new file mode 100644 index 0000000..53f3c5f --- /dev/null +++ b/packages/app/services/i18n/overrides/pluralrules/udm.js @@ -0,0 +1,12 @@ +Intl.PluralRules && 'function' == typeof Intl.PluralRules.__addLocaleData && Intl.PluralRules.__addLocaleData({ + data: { + udm: { + categories: { + cardinal: ['other'], + ordinal: ['other'], + }, fn: function(a, l) { + return 'other'; + }, + }, + }, availableLocales: ['udm'], aliases: {}, parentLocales: {}, +}); diff --git a/packages/app/services/i18n/overrides/relativetimeformat/udm.js b/packages/app/services/i18n/overrides/relativetimeformat/udm.js new file mode 100644 index 0000000..ed1f506 --- /dev/null +++ b/packages/app/services/i18n/overrides/relativetimeformat/udm.js @@ -0,0 +1,159 @@ +Intl.RelativeTimeFormat && 'function' == typeof Intl.RelativeTimeFormat.__addLocaleData && Intl.RelativeTimeFormat.__addLocaleData({ + data: { + udm: { + nu: ['latn'], + year: { + 0: 'таяз аре', + 1: 'вуоно аре', + '-1': 'кылем аре', + past: { other: '{0} ар талэсь азьло' }, + future: { other: '{0} ар ортчыса' }, + }, + 'year-short': { + 0: 'туэ', + 1: 'вуоно аре', + '-1': 'кылем аре', + past: { other: '{0} ар талэсь азьло' }, + future: { other: '{0} ар ортчыса' }, + }, + 'year-narrow': { + 0: 'туэ', + 1: 'кайта', + '-1': 'мийым', + past: { other: '{0} ар талэсь азьло' }, + future: { other: '{0} ар ортчыса' }, + }, + quarter: { + 0: 'таяз арньыльмосэ', + 1: 'вуоно арньыльмосэ', + '-1': 'ортчем арньыльмосэ', + past: { other: '{0} арньыльмос талэсь азьло' }, + future: { other: '{0} арныльмос ортчыса' }, + }, + 'quarter-short': { + 0: 'таяз арньыльмосэ', + 1: 'вуоно арньыльмосэ', + '-1': 'ортчем арньыльмосэ', + past: { other: '{0} арньыльмос талэсь азьло' }, + future: { other: '{0} арныльмос ортчыса' }, + }, + 'quarter-narrow': { + 0: 'таяз арньыльмосэ', + 1: 'вуоно арньыльмосэ', + '-1': 'ортчем арньыльмосэ', + past: { other: '{0} арньыльмос талэсь азьло' }, + future: { other: '{0} арныльмос ортчыса' }, + }, + month: { + 0: 'таяз толэзе', + 1: 'вуоно толэзе', + '-1': 'ортчем толэзе', + past: { other: '{0} толэзь талэсь азьло' }, + future: { other: '{0} толэзь ортчыса' }, + }, + 'month-short': { + 0: 'таяз толэзе', + 1: 'вуоно толэзе', + '-1': 'ортчем толэзе', + past: { other: '{0} толэзь талэсь азьло' }, + future: { other: '{0} толэзь ортчыса' }, + }, + 'month-narrow': { + 0: 'таяз толэзе', + 1: 'вуоно толэзе', + '-1': 'ортчем толэзе', + past: { other: '{0} толэзь талэсь азьло' }, + future: { other: '{0} толэзь ортчыса' }, + }, + week: { + 0: 'таяз арняе', + 1: 'вуоно арняе', + '-1': 'ортчем арняе', + past: { other: '{0} арня талэсь азьло' }, + future: { other: '{0} арня ортчыса' }, + }, + 'week-short': { + 0: 'таяз арняе', + 1: 'вуоно арняе', + '-1': 'ортчем арняе', + past: { other: '{0} арня талэсь азьло' }, + future: { other: '{0} арня ортчыса' }, + }, + 'week-narrow': { + 0: 'таяз арняе', + 1: 'вуоно арняе', + '-1': 'ортчем арняе', + past: { other: '{0} арня талэсь азьло' }, + future: { other: '{0} арня ортчыса' }, + }, + day: { + 0: 'туннэ', + 1: 'ӵуказе', + 2: 'ӵуказе улыса', + '-2': 'валлян', + '-1': 'толон', + past: { other: '{0} нунал талэсь азьло' }, + future: { other: '{0} нунал ортчыса' }, + }, + 'day-short': { + 0: 'туннэ', + 1: 'ӵуказе', + 2: 'ӵуказе улыса', + '-2': 'валлян', + '-1': 'толон', + past: { other: '{0} нунал талэсь азьло' }, + future: { other: '{0} нунал ортчыса' }, + }, + 'day-narrow': { + 0: 'туннэ', + 1: 'ӵуказе', + 2: 'усьсэ', + '-2': 'валлян', + '-1': 'толон', + past: { other: '{0} нунал талэсь азьло' }, + future: { other: '{0} нунал ортчыса' }, + }, + hour: { 0: 'таяз часэ', past: { other: '{0} час талэсь азьло' }, future: { other: '{0} час ортчыса' } }, + 'hour-short': { + 0: 'таяз часэ', + past: { other: '{0} час талэсь азьло' }, + future: { other: '{0} час ортчыса' }, + }, + 'hour-narrow': { + 0: 'таяз часэ', + past: { other: '{0} час талэсь азьло' }, + future: { other: '{0} час ортчыса' }, + }, + minute: { + 0: 'таяз минутэ', + past: { other: '{0} минут талэсь азьло' }, + future: { other: '{0} минут ортчыса' }, + }, + 'minute-short': { + 0: 'таяз минутэ', + past: { other: '{0} минут талэсь азьло' }, + future: { other: '{0} минут ортчыса' }, + }, + 'minute-narrow': { + 0: 'таяз минутэ', + past: { other: '{0} минут талэсь азьло' }, + future: { other: '{0} минут ортчыса' }, + }, + second: { + 0: 'таяз секундэ', + past: { other: '{0} секунд талэзь азьло' }, + future: { other: '{0} секунд ортчыса' }, + }, + 'second-short': { + 0: 'таяз секундэ', + past: { other: '{0} секунд талэзь азьло' }, + future: { other: '{0} секунд ортчыса' }, + }, + 'second-narrow': { + 0: 'таяз секундэ', + past: { other: '{0} секунд талэзь азьло' }, + future: { other: '{0} секунд ортчыса' }, + }, + }, + }, availableLocales: ['udm'], aliases: {}, parentLocales: {}, +}); diff --git a/packages/scripts/i18n-crowdin.ts b/packages/scripts/i18n-crowdin.ts index fc2f7ff..7425a3c 100644 --- a/packages/scripts/i18n-crowdin.ts +++ b/packages/scripts/i18n-crowdin.ts @@ -28,7 +28,7 @@ const CROWDIN_FILE_PATH = config.filePath; const SOURCE_LANG = config.sourceLang; const LANG_DIR = config.basePath; const INDEX_FILE_NAME = 'index.js'; -const MIN_RELEASE_PROGRESS = config.minApproved; +const MIN_RELEASE_PROGRESS = config.minTranslated; const crowdin = new Crowdin({ token: config.apiKey, @@ -43,6 +43,7 @@ const releasedLocales: ReadonlyArray = ['be', 'fr', 'id', 'pt', 'ru', 'u * Map Crowdin locales into our internal locales representation */ const LOCALES_MAP: Record = { + 'es-ES': 'es', 'pt-BR': 'pt', 'zh-CN': 'zh', }; @@ -54,6 +55,7 @@ const LOCALES_MAP: Record = { const NATIVE_NAMES_MAP: Record = { be: 'Беларуская', cs: 'Čeština', + fil: 'Wikang Filipino', id: 'Bahasa Indonesia', lt: 'Lietuvių', pl: 'Polski', @@ -67,6 +69,7 @@ const NATIVE_NAMES_MAP: Record = { * This arrays allows us to override Crowdin English languages names */ const ENGLISH_NAMES_MAP: Record = { + fil: 'Filipino', pt: 'Portuguese, Brazilian', sr: 'Serbian', zh: 'Simplified Chinese', @@ -225,7 +228,7 @@ async function pull(): Promise { } console.log('Pulling translation progress...'); - const { data: translationProgress } = await crowdin.translationStatusApi.getFileProgress(PROJECT_ID, fileId, 100); + const { data: fileProgress } = await crowdin.translationStatusApi.getFileProgress(PROJECT_ID, fileId, 100); const localesToPull: Array = []; const indexFileEntries: Record = { @@ -238,16 +241,16 @@ async function pull(): Promise { }, }; - translationProgress.forEach(({ data: { languageId, approvalProgress } }) => { + fileProgress.forEach(({ data: { languageId, translationProgress } }) => { const locale = toInternalLocale(languageId); - if (releasedLocales.includes(locale) || approvalProgress >= MIN_RELEASE_PROGRESS) { + if (releasedLocales.includes(locale) || translationProgress >= MIN_RELEASE_PROGRESS) { localesToPull.push(languageId); indexFileEntries[locale] = { code: locale, name: NATIVE_NAMES_MAP[locale] || iso639.getNativeName(locale), englishName: ENGLISH_NAMES_MAP[locale] || iso639.getName(locale), - progress: approvalProgress, + progress: translationProgress, isReleased: releasedLocales.includes(locale), }; } @@ -268,7 +271,6 @@ async function pull(): Promise { data: { url }, } = await crowdin.translationsApi.buildProjectFileTranslation(PROJECT_ID, fileId, { targetLanguageId: languageId, - exportApprovedOnly: true, }); const { data: fileContents } = await axios.get(url, { diff --git a/postcss.config.js b/postcss.config.js index 163618b..23f13dc 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -47,6 +47,7 @@ module.exports = ({ webpack: loader }) => ({ return defaultLoad(filename, importOptions); })(require('postcss-import/lib/load-content')), }, + 'postcss-bidirection': {}, // TODO: for some reason cssnano strips out @mixin declarations // cssnano: { // /** diff --git a/postinstall.js b/postinstall.js new file mode 100755 index 0000000..17227c7 --- /dev/null +++ b/postinstall.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +/* eslint-disable @typescript-eslint/no-var-requires */ + +const fs = require('fs'); +const path = require('path'); + +const localeOverrides = { + 'packages/app/services/i18n/overrides/intl': 'node_modules/intl/locale-data/jsonp', + 'packages/app/services/i18n/overrides/pluralrules': 'node_modules/@formatjs/intl-pluralrules/dist/locale-data', + // eslint-disable-next-line prettier/prettier + 'packages/app/services/i18n/overrides/relativetimeformat': 'node_modules/@formatjs/intl-relativetimeformat/dist/locale-data', +}; + +Object.entries(localeOverrides).forEach(([sourceDir, targetDir]) => { + fs.readdirSync(sourceDir).forEach((localeFile) => { + fs.copyFileSync(path.join(sourceDir, localeFile), path.join(targetDir, localeFile)); + }); +}); diff --git a/webpack.config.js b/webpack.config.js index acce166..285e44b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -201,19 +201,6 @@ const webpackConfig = { type: 'javascript/auto', use: ['fontgen-loader'], }, - { - // Replace some locales provided by FormatJS with local ones - test: /@formatjs\/intl-\w+\/dist\/locale-data/, - loader: 'file-replace-loader', - options: { - condition: 'if-replacement-exists', - replacement: (resource) => - resource.replace( - /node_modules\/@formatjs\/intl-(\w+)\/dist\/locale-data\/(\w+)\.js/, - 'packages/app/services/i18n/overrides/$1/$2.js', - ), - }, - }, ], }, diff --git a/yarn.lock b/yarn.lock index b4b54a8..1a3c703 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3439,6 +3439,11 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/rtl-detect@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/rtl-detect/-/rtl-detect-1.0.0.tgz#5791e18a111f2b8b5b328160af97f3991a5697a5" + integrity sha512-lyYh44YgrejEK9/5rhASghvRUOxrSJyyyQmqK7L6F/V5qs6PY1RfCi1VbjSkY6kuDt7lzQyhd006slhda4Oypg== + "@types/sax@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.1.tgz#e0248be936ece791a82db1a57f3fb5f7c87e8172" @@ -12535,6 +12540,12 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +"postcss-bidirection@https://github.com/erickskrauch/postcss-bidirection.git#iss_23": + version "2.7.2" + resolved "https://github.com/erickskrauch/postcss-bidirection.git#feabf42c8ab5cf6b0e22f78de04984e22cf42ba4" + dependencies: + postcss "^7.0.13" + postcss-calc@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.1.tgz#36d77bab023b0ecbb9789d84dcb23c4941145436" @@ -12917,6 +12928,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" +postcss@^7.0.13: + version "7.0.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" + integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + postcss@^7.0.26: version "7.0.26" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.26.tgz#5ed615cfcab35ba9bbb82414a4fa88ea10429587" @@ -14246,6 +14266,11 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== +rtl-detect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.2.tgz#8eca316f5c6563d54df4e406171dd7819adda67f" + integrity sha512-5X1422hvphzg2a/bo4tIDbjFjbJUOaPZwqE6dnyyxqwFqfR+tBcvfqapJr0o0VygATVCGKiODEewhZtKF+90AA== + run-async@^2.2.0, run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"