diff --git a/.twosky.json b/.twosky.json
index 8ad2a205..c1dcf2f9 100644
--- a/.twosky.json
+++ b/.twosky.json
@@ -7,29 +7,29 @@
"da": "Dansk",
"de": "Deutsch",
"nl": "Dutch",
- "no": "Norsk",
"en": "English",
"es": "Español",
"fr": "Français",
+ "hr": "Hrvatski",
"id": "Indonesian",
"it": "Italiano",
+ "no": "Norsk",
"pl": "Polski",
"pt-br": "Portuguese (BR)",
"pt-pt": "Portuguese (PT)",
"sk": "Slovenčina",
"sl": "Slovenščina",
+ "sr-cs": "Srpski",
"sv": "Svenska",
"vi": "Tiếng Việt",
"tr": "Türkçe",
"cs": "Český",
"bg": "Български",
"ru": "Русский",
+ "fa": "فارسی",
"ja": "日本語",
"zh-tw": "正體中文",
"zh-cn": "简体中文",
- "sr-cs": "Srpski",
- "hr": "Hrvatski",
- "fa": "فارسی",
"ko": "한국어"
}
}
diff --git a/client/.eslintrc b/client/.eslintrc
index d5d5955b..56b5a7a6 100644
--- a/client/.eslintrc
+++ b/client/.eslintrc
@@ -17,6 +17,11 @@
"react": {
"pragma": "React",
"version": "16.4"
+ },
+ "import/resolver": {
+ "webpack": {
+ "config": "webpack.common.js"
+ }
}
},
diff --git a/client/package-lock.json b/client/package-lock.json
index 81899b0c..d0ec7a06 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -925,6 +925,12 @@
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true
},
+ "array-find": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz",
+ "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=",
+ "dev": true
+ },
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@@ -4548,6 +4554,79 @@
}
}
},
+ "eslint-import-resolver-webpack": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.12.1.tgz",
+ "integrity": "sha512-O/sUAXk6GWrICiN8JUkkjdt9uZpqZHP+FVnTxtEILL6EZMaPSrnP4lGPSFwcKsv7O211maqq4Nz60+dh236hVg==",
+ "dev": true,
+ "requires": {
+ "array-find": "^1.0.0",
+ "debug": "^2.6.9",
+ "enhanced-resolve": "^0.9.1",
+ "find-root": "^1.1.0",
+ "has": "^1.0.3",
+ "interpret": "^1.2.0",
+ "lodash": "^4.17.15",
+ "node-libs-browser": "^1.0.0 || ^2.0.0",
+ "resolve": "^1.13.1",
+ "semver": "^5.7.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "enhanced-resolve": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz",
+ "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "memory-fs": "^0.2.0",
+ "tapable": "^0.1.8"
+ }
+ },
+ "interpret": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
+ "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
+ "dev": true
+ },
+ "memory-fs": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz",
+ "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
+ "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "tapable": {
+ "version": "0.1.10",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz",
+ "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=",
+ "dev": true
+ }
+ }
+ },
"eslint-loader": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-1.9.0.tgz",
@@ -5458,7 +5537,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -5479,12 +5559,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5499,17 +5581,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5626,7 +5711,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -5638,6 +5724,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5652,6 +5739,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5659,12 +5747,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -5683,6 +5773,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5763,7 +5854,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -5775,6 +5867,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -5860,7 +5953,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -5896,6 +5990,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -5915,6 +6010,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -5958,12 +6054,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -6778,6 +6876,29 @@
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.3.tgz",
"integrity": "sha512-sJZ2n9Vgax0vGer23hJMwyO3FRO7P0dq2DXZPXWE329g3snfJUcw+S24Mp3lqJaxL/0McDu4BD75ds6pzIfhhw=="
},
+ "i18next-xhr-backend": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/i18next-xhr-backend/-/i18next-xhr-backend-3.2.2.tgz",
+ "integrity": "sha512-OtRf2Vo3IqAxsttQbpjYnmMML12IMB5e0fc5B7qKJFLScitYaXa1OhMX0n0X/3vrfFlpHL9Ro/H+ps4Ej2j7QQ==",
+ "requires": {
+ "@babel/runtime": "^7.5.5"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz",
+ "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.3",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+ "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
+ }
+ }
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
diff --git a/client/package.json b/client/package.json
index b2be9b5b..39136778 100644
--- a/client/package.json
+++ b/client/package.json
@@ -15,6 +15,7 @@
"date-fns": "^1.29.0",
"i18next": "^12.0.0",
"i18next-browser-languagedetector": "^2.2.3",
+ "i18next-xhr-backend": "^3.2.2",
"lodash": "^4.17.15",
"nanoid": "^1.2.3",
"prop-types": "^15.7.2",
@@ -54,6 +55,7 @@
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-react-app": "^2.1.0",
+ "eslint-import-resolver-webpack": "^0.12.1",
"eslint-loader": "1.9.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-jsx-a11y": "5.1.1",
diff --git a/client/src/components/ui/Footer.js b/client/src/components/ui/Footer.js
index b61058d4..8174ba2d 100644
--- a/client/src/components/ui/Footer.js
+++ b/client/src/components/ui/Footer.js
@@ -1,7 +1,9 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
-import { REPOSITORY, LANGUAGES, PRIVACY_POLICY_LINK } from '../../helpers/constants';
+
+import { REPOSITORY, PRIVACY_POLICY_LINK } from '../../helpers/constants';
+import { LANGUAGES } from '../../helpers/twosky';
import i18n from '../../i18n';
import Version from './Version';
@@ -68,9 +70,9 @@ class Footer extends Component {
value={i18n.language}
onChange={this.changeLanguage}
>
- {LANGUAGES.map(language => (
-
))}
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 5920552c..2c457736 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -31,117 +31,6 @@ export const REPOSITORY = {
export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
-export const LANGUAGES = [
- {
- key: 'da',
- name: 'Dansk',
- },
- {
- key: 'de',
- name: 'Deutsch',
- },
- {
- key: 'nl',
- name: 'Dutch',
- },
- {
- key: 'en',
- name: 'English',
- },
- {
- key: 'es',
- name: 'Español',
- },
- {
- key: 'fr',
- name: 'Français',
- },
- {
- key: 'id',
- name: 'Indonesian',
- },
- {
- key: 'it',
- name: 'Italiano',
- },
- {
- key: 'pl',
- name: 'Polski',
- },
- {
- key: 'pt-br',
- name: 'Portuguese (BR)',
- },
- {
- key: 'pt-pt',
- name: 'Portuguese (PT)',
- },
- {
- key: 'sk',
- name: 'Slovenčina',
- },
- {
- key: 'sl',
- name: 'Slovenščina',
- },
- {
- key: 'sv',
- name: 'Svenska',
- },
- {
- key: 'vi',
- name: 'Tiếng Việt',
- },
- {
- key: 'tr',
- name: 'Türkçe',
- },
- {
- key: 'cs',
- name: 'Český',
- },
- {
- key: 'bg',
- name: 'Български',
- },
- {
- key: 'ru',
- name: 'Русский',
- },
- {
- key: 'ja',
- name: '日本語',
- },
- {
- key: 'zh-tw',
- name: '正體中文',
- },
- {
- key: 'zh-cn',
- name: '简体中文',
- },
- {
- key: 'no',
- name: 'Norsk',
- },
- {
- key: 'sr-cs',
- name: 'Srpski',
- },
- {
- key: 'hr',
- name: 'Hrvatski',
- },
- {
- key: 'fa',
- name: 'فارسی',
- },
- {
- key: 'ko',
- name: '한국어',
- },
-];
-
export const INSTALL_FIRST_STEP = 1;
export const INSTALL_TOTAL_STEPS = 5;
diff --git a/client/src/helpers/twosky.js b/client/src/helpers/twosky.js
new file mode 100644
index 00000000..d9534ba6
--- /dev/null
+++ b/client/src/helpers/twosky.js
@@ -0,0 +1,6 @@
+import twosky from 'MainRoot/.twosky.json';
+
+export const {
+ languages: LANGUAGES,
+ base_locale: BASE_LOCALE,
+} = twosky[0];
diff --git a/client/src/i18n.js b/client/src/i18n.js
index 6f13536a..afc3b65b 100644
--- a/client/src/i18n.js
+++ b/client/src/i18n.js
@@ -1,132 +1,21 @@
import i18n from 'i18next';
+import XHR from 'i18next-xhr-backend';
import { reactI18nextModule } from 'react-i18next';
import { initReactI18n } from 'react-i18next/hooks';
import langDetect from 'i18next-browser-languagedetector';
-import { DEFAULT_LANGUAGE } from './helpers/constants';
+import { LANGUAGES, BASE_LOCALE } from './helpers/twosky';
-import vi from './__locales/vi.json';
-import en from './__locales/en.json';
-import ru from './__locales/ru.json';
-import es from './__locales/es.json';
-import fr from './__locales/fr.json';
-import ja from './__locales/ja.json';
-import sv from './__locales/sv.json';
-import ptBR from './__locales/pt-br.json';
-import zhTW from './__locales/zh-tw.json';
-import bg from './__locales/bg.json';
-import zhCN from './__locales/zh-cn.json';
-import cs from './__locales/cs.json';
-import da from './__locales/da.json';
-import de from './__locales/de.json';
-import id from './__locales/id.json';
-import it from './__locales/it.json';
-import ko from './__locales/ko.json';
-import no from './__locales/no.json';
-import nl from './__locales/nl.json';
-import pl from './__locales/pl.json';
-import ptPT from './__locales/pt-pt.json';
-import sk from './__locales/sk.json';
-import sl from './__locales/sl.json';
-import tr from './__locales/tr.json';
-import srCS from './__locales/sr-cs.json';
-import hr from './__locales/hr.json';
-import fa from './__locales/fa.json';
-
-const resources = {
- en: {
- translation: en,
- },
- vi: {
- translation: vi,
- },
- ru: {
- translation: ru,
- },
- es: {
- translation: es,
- },
- fr: {
- translation: fr,
- },
- ja: {
- translation: ja,
- },
- sv: {
- translation: sv,
- },
- 'pt-br': {
- translation: ptBR,
- },
- 'zh-tw': {
- translation: zhTW,
- },
- bg: {
- translation: bg,
- },
- 'zh-cn': {
- translation: zhCN,
- },
- cs: {
- translation: cs,
- },
- da: {
- translation: da,
- },
- de: {
- translation: de,
- },
- id: {
- translation: id,
- },
- it: {
- translation: it,
- },
- ko: {
- translation: ko,
- },
- no: {
- translation: no,
- },
- nl: {
- translation: nl,
- },
- pl: {
- translation: pl,
- },
- 'pt-pt': {
- translation: ptPT,
- },
- sk: {
- translation: sk,
- },
- sl: {
- translation: sl,
- },
- tr: {
- translation: tr,
- },
- 'sr-cs': {
- translation: srCS,
- },
- hr: {
- translation: hr,
- },
- fa: {
- translation: fa,
- },
-};
-
-const availableLanguages = Object.keys(resources);
+const availableLanguages = Object.keys(LANGUAGES);
i18n
.use(langDetect)
+ .use(XHR)
.use(initReactI18n)
.use(reactI18nextModule)
.init({
- resources,
lowerCaseLng: true,
- fallbackLng: DEFAULT_LANGUAGE,
+ fallbackLng: BASE_LOCALE,
keySeparator: false,
nsSeparator: false,
returnEmptyString: false,
@@ -136,9 +25,13 @@ i18n
react: {
wait: true,
},
+ whitelist: availableLanguages,
+ backend: {
+ loadPath: '/__locales/{{lng}}.json',
+ },
}, () => {
if (!availableLanguages.includes(i18n.language)) {
- i18n.changeLanguage(DEFAULT_LANGUAGE);
+ i18n.changeLanguage(BASE_LOCALE);
}
});
diff --git a/client/webpack.common.js b/client/webpack.common.js
index 32248b14..db61ea17 100644
--- a/client/webpack.common.js
+++ b/client/webpack.common.js
@@ -15,6 +15,7 @@ const HTML_PATH = path.resolve(RESOURCES_PATH, 'public/index.html');
const HTML_INSTALL_PATH = path.resolve(RESOURCES_PATH, 'public/install.html');
const HTML_LOGIN_PATH = path.resolve(RESOURCES_PATH, 'public/login.html');
const FAVICON_PATH = path.resolve(RESOURCES_PATH, 'public/favicon.png');
+const LOCALES_PATH = path.resolve(RESOURCES_PATH, 'src/__locales/*.json');
const PUBLIC_PATH = path.resolve(__dirname, '../build/static');
@@ -32,6 +33,10 @@ const config = {
},
resolve: {
modules: ['node_modules'],
+ alias: {
+ MainRoot: path.resolve(__dirname, '../'),
+ ClientRoot: path.resolve(__dirname, './src'),
+ },
},
module: {
rules: [
@@ -101,7 +106,7 @@ const config = {
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}),
- new CleanWebpackPlugin(['*.*'], {
+ new CleanWebpackPlugin(['**/*.*'], {
root: PUBLIC_PATH,
verbose: false,
dry: false,
@@ -132,6 +137,13 @@ const config = {
new CopyPlugin([
{ from: FAVICON_PATH, to: PUBLIC_PATH },
]),
+ new CopyPlugin([
+ {
+ from: LOCALES_PATH,
+ to: PUBLIC_PATH,
+ context: 'src/',
+ },
+ ]),
],
};
diff --git a/home/auth.go b/home/auth.go
index aae40f33..9afe2c87 100644
--- a/home/auth.go
+++ b/home/auth.go
@@ -379,7 +379,8 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
}
} else if r.URL.Path == "/favicon.png" ||
- strings.HasPrefix(r.URL.Path, "/login.") {
+ strings.HasPrefix(r.URL.Path, "/login.") ||
+ strings.HasPrefix(r.URL.Path, "/__locales/") {
// process as usual
} else if config.auth != nil && config.auth.AuthRequired() {
diff --git a/home/helpers.go b/home/helpers.go
index c00fcbc8..ec95aa67 100644
--- a/home/helpers.go
+++ b/home/helpers.go
@@ -111,8 +111,9 @@ func preInstallHandler(handler http.Handler) http.Handler {
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if config.firstRun &&
- !strings.HasPrefix(r.URL.Path, "/install.") &&
- r.URL.Path != "/favicon.png" {
+ !(strings.HasPrefix(r.URL.Path, "/install.") ||
+ strings.HasPrefix(r.URL.Path, "/__locales/") ||
+ r.URL.Path == "/favicon.png") {
http.Redirect(w, r, "/install.html", http.StatusSeeOther) // should not be cacheable
return
}