From 3af3af0b25b8e275b246601757b398ad08d240af Mon Sep 17 00:00:00 2001 From: Claudio Costa Date: Thu, 14 Nov 2024 15:02:00 -0600 Subject: [PATCH] [MM-60792] Add support for loading experimental (e.g., in progress) languages (#29009) * Add support for loading experimental (e.g., in progress) languages * Fix e2e config * Improve language labels * Improvements --- .../support/server/default_config.ts | 1 + server/config/client.go | 2 + server/public/model/config.go | 11 +- .../admin_console/admin_definition.tsx | 7 + webapp/channels/src/i18n/en.json | 2 + webapp/channels/src/i18n/i18n.jsx | 90 +- webapp/channels/src/i18n/i18n.test.jsx | 94 ++ webapp/channels/src/i18n/imports.ts | 75 ++ webapp/channels/src/i18n/langmap.js | 897 ++++++++++++++++++ webapp/package.json | 3 +- webapp/platform/types/src/config.ts | 2 + webapp/scripts/gen_lang_imports.mjs | 62 ++ 12 files changed, 1194 insertions(+), 52 deletions(-) create mode 100644 webapp/channels/src/i18n/i18n.test.jsx create mode 100644 webapp/channels/src/i18n/imports.ts create mode 100644 webapp/channels/src/i18n/langmap.js create mode 100644 webapp/scripts/gen_lang_imports.mjs diff --git a/e2e-tests/playwright/support/server/default_config.ts b/e2e-tests/playwright/support/server/default_config.ts index 79b788817b..060f36a2af 100644 --- a/e2e-tests/playwright/support/server/default_config.ts +++ b/e2e-tests/playwright/support/server/default_config.ts @@ -509,6 +509,7 @@ const defaultServerConfig: AdminConfig = { DefaultServerLocale: 'en', DefaultClientLocale: 'en', AvailableLocales: '', + EnableExperimentalLocales: false, }, SamlSettings: { Enable: false, diff --git a/server/config/client.go b/server/config/client.go index 3f2dd4545a..f1b543870c 100644 --- a/server/config/client.go +++ b/server/config/client.go @@ -75,6 +75,8 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["EnablePublicLink"] = strconv.FormatBool(*c.FileSettings.EnablePublicLink) props["AvailableLocales"] = *c.LocalizationSettings.AvailableLocales + props["EnableExperimentalLocales"] = strconv.FormatBool(*c.LocalizationSettings.EnableExperimentalLocales) + props["SQLDriverName"] = *c.SqlSettings.DriverName props["EnableEmojiPicker"] = strconv.FormatBool(*c.ServiceSettings.EnableEmojiPicker) diff --git a/server/public/model/config.go b/server/public/model/config.go index 3ccce20062..d47a8a2ed9 100644 --- a/server/public/model/config.go +++ b/server/public/model/config.go @@ -2597,9 +2597,10 @@ func (s *ComplianceSettings) SetDefaults() { } type LocalizationSettings struct { - DefaultServerLocale *string `access:"site_localization"` - DefaultClientLocale *string `access:"site_localization"` - AvailableLocales *string `access:"site_localization"` + DefaultServerLocale *string `access:"site_localization"` + DefaultClientLocale *string `access:"site_localization"` + AvailableLocales *string `access:"site_localization"` + EnableExperimentalLocales *bool `access:"site_localization"` } func (s *LocalizationSettings) SetDefaults() { @@ -2614,6 +2615,10 @@ func (s *LocalizationSettings) SetDefaults() { if s.AvailableLocales == nil { s.AvailableLocales = NewPointer("") } + + if s.EnableExperimentalLocales == nil { + s.EnableExperimentalLocales = NewPointer(false) + } } type SamlSettings struct { diff --git a/webapp/channels/src/components/admin_console/admin_definition.tsx b/webapp/channels/src/components/admin_console/admin_definition.tsx index f74b8848bf..083cc8e2c4 100644 --- a/webapp/channels/src/components/admin_console/admin_definition.tsx +++ b/webapp/channels/src/components/admin_console/admin_definition.tsx @@ -2102,6 +2102,13 @@ const AdminDefinition: AdminDefinitionType = { no_result: defineMessage({id: 'admin.general.localization.availableLocalesNoResults', defaultMessage: 'No results found'}), isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.LOCALIZATION)), }, + { + type: 'bool', + key: 'LocalizationSettings.EnableExperimentalLocales', + label: defineMessage({id: 'admin.general.localization.enableExperimentalLocalesTitle', defaultMessage: 'Enable Experimental Locales:'}), + help_text: defineMessage({id: 'admin.general.localization.enableExperimentalLocalesDescription', defaultMessage: 'When true, it allows users to select experimental (e.g., in progress) languages.'}), + isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.LOCALIZATION)), + }, ], }, }, diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 3053633982..504057bc4f 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -1021,6 +1021,8 @@ "admin.general.localization.availableLocalesTitle": "Available Languages:", "admin.general.localization.clientLocaleDescription": "Default language for newly created users and pages where the user hasn't logged in.", "admin.general.localization.clientLocaleTitle": "Default Client Language:", + "admin.general.localization.enableExperimentalLocalesDescription": "When true, it allows users to select experimental (e.g. in progress) languages", + "admin.general.localization.enableExperimentalLocalesTitle": "Enable Experimental Locales", "admin.general.localization.serverLocaleDescription": "Default language for system messages.", "admin.general.localization.serverLocaleTitle": "Default Server Language:", "admin.general.log": "Logging", diff --git a/webapp/channels/src/i18n/i18n.jsx b/webapp/channels/src/i18n/i18n.jsx index 843bfde8ab..55107453a3 100644 --- a/webapp/channels/src/i18n/i18n.jsx +++ b/webapp/channels/src/i18n/i18n.jsx @@ -5,38 +5,17 @@ * @typedef {} Language */ -/* eslint-disable import/order */ -import bg from './bg.json'; -import de from './de.json'; -import enAU from './en-AU.json'; -import es from './es.json'; -import fa from './fa.json'; -import fr from './fr.json'; -import hu from './hu.json'; -import it from './it.json'; -import ja from './ja.json'; -import ko from './ko.json'; -import nl from './nl.json'; -import pl from './pl.json'; -import ptBR from './pt-BR.json'; -import ro from './ro.json'; -import ru from './ru.json'; -import sv from './sv.json'; -import vi from './vi.json'; -import tr from './tr.json'; -import uk from './uk.json'; -import zhTW from './zh-TW.json'; -import zhCN from './zh-CN.json'; - import {getConfig} from 'mattermost-redux/selectors/entities/general'; +import {langFiles, langIDs, langLabels} from './imports'; + // should match the values in server/public/shared/i18n/i18n.go -const languages = { +export const languages = { de: { value: 'de', name: 'Deutsch', order: 0, - url: de, + url: langFiles.de, }, en: { value: 'en', @@ -48,125 +27,140 @@ const languages = { value: 'en-AU', name: 'English (Australia)', order: 2, - url: enAU, + url: langFiles['en-AU'], }, es: { value: 'es', name: 'Español (Beta)', order: 3, - url: es, + url: langFiles.es, }, fr: { value: 'fr', name: 'Français (Beta)', order: 4, - url: fr, + url: langFiles.fr, }, it: { value: 'it', name: 'Italiano (Alpha)', order: 5, - url: it, + url: langFiles.it, }, hu: { value: 'hu', name: 'Magyar (Beta)', order: 6, - url: hu, + url: langFiles.hu, }, nl: { value: 'nl', name: 'Nederlands', order: 7, - url: nl, + url: langFiles.nl, }, pl: { value: 'pl', name: 'Polski', order: 8, - url: pl, + url: langFiles.pl, }, 'pt-BR': { value: 'pt-BR', name: 'Português (Brasil) (Alpha)', order: 9, - url: ptBR, + url: langFiles['pt-BR'], }, ro: { value: 'ro', name: 'Română (Alpha)', order: 10, - url: ro, + url: langFiles.ro, }, sv: { value: 'sv', name: 'Svenska', order: 11, - url: sv, + url: langFiles.sv, }, vi: { value: 'vi', name: 'Tiếng Việt (Beta)', order: 12, - url: vi, + url: langFiles.vi, }, tr: { value: 'tr', name: 'Türkçe', order: 13, - url: tr, + url: langFiles.tr, }, bg: { value: 'bg', name: 'Български (Alpha)', order: 14, - url: bg, + url: langFiles.bg, }, ru: { value: 'ru', name: 'Pусский', order: 15, - url: ru, + url: langFiles.ru, }, uk: { value: 'uk', name: 'Yкраїнська (Alpha)', order: 16, - url: uk, + url: langFiles.uk, }, fa: { value: 'fa', name: 'فارسی (Alpha)', order: 17, - url: fa, + url: langFiles.fa, }, ko: { value: 'ko', name: '한국어 (Alpha)', order: 18, - url: ko, + url: langFiles.ko, }, 'zh-CN': { value: 'zh-CN', name: '中文 (简体) (Beta)', order: 19, - url: zhCN, + url: langFiles['zh-CN'], }, 'zh-TW': { value: 'zh-TW', name: '中文 (繁體) (Beta)', order: 20, - url: zhTW, + url: langFiles['zh-TW'], }, ja: { value: 'ja', name: '日本語', order: 21, - url: ja, + url: langFiles.ja, }, }; -export function getAllLanguages() { +export function getAllLanguages(includeExperimental) { + if (includeExperimental) { + let order = Object.keys(languages).length; + return { + ...langIDs.reduce((out, id) => { + out[id] = { + value: id, + name: langLabels[id], + url: langFiles[id], + order: order++, + }; + return out; + }, {}), + ...languages, + }; + } return languages; } @@ -177,7 +171,7 @@ export function getAllLanguages() { export function getLanguages(state) { const config = getConfig(state); if (!config.AvailableLocales) { - return getAllLanguages(); + return getAllLanguages(config.EnableExperimentalLocales === 'true'); } return config.AvailableLocales.split(',').reduce((result, l) => { if (languages[l]) { @@ -188,7 +182,7 @@ export function getLanguages(state) { } export function getLanguageInfo(locale) { - return getAllLanguages()[locale]; + return getAllLanguages(true)[locale]; } /** diff --git a/webapp/channels/src/i18n/i18n.test.jsx b/webapp/channels/src/i18n/i18n.test.jsx new file mode 100644 index 0000000000..8187af59f0 --- /dev/null +++ b/webapp/channels/src/i18n/i18n.test.jsx @@ -0,0 +1,94 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {getAllLanguages, getLanguageInfo, getLanguages, isLanguageAvailable, languages} from './i18n'; + +jest.mock('./imports', () => ({ + langIDs: ['cc'], + langFiles: {cc: 'cc.json'}, + langLabels: {cc: 'CC Language'}, +})); + +describe('i18n', () => { + test('getAllLanguages', () => { + // no experimental languages + expect(getAllLanguages()).toBe(languages); + expect(getAllLanguages(false)).toBe(languages); + + // with experimental languages + expect(getAllLanguages(true)).toStrictEqual({ + cc: { + name: 'CC Language', + value: 'cc', + order: 22, + url: 'cc.json', + }, + ...languages, + }); + }); + + test('getLanguages', () => { + const state = { + entities: { + general: { + config: { + }, + }, + }, + }; + + // no experimental languages + expect(getLanguages(state)).toBe(languages); + + // with experimental languages + state.entities.general.config.EnableExperimentalLocales = 'true'; + expect(getLanguages(state)).toStrictEqual({ + cc: { + name: 'CC Language', + value: 'cc', + order: 22, + url: 'cc.json', + }, + ...languages, + }); + }); + + test('getLanguageInfo', () => { + // supported language + expect(getLanguageInfo('en')).toStrictEqual({ + name: 'English (US)', + order: 1, + url: '', + value: 'en', + }); + + // experimental language (e.g. in progress) + expect(getLanguageInfo('cc')).toStrictEqual({ + name: 'CC Language', + value: 'cc', + order: 22, + url: 'cc.json', + }); + + // non existant + expect(getLanguageInfo('invalid')).not.toBeDefined(); + }); + + test('isLanguageAvailable', () => { + const state = { + entities: { + general: { + config: { + }, + }, + }, + }; + + // no experimental languages + expect(isLanguageAvailable(state, 'cc')).toBe(false); + + // with experimental languages + state.entities.general.config.EnableExperimentalLocales = 'true'; + expect(isLanguageAvailable(state, 'cc')).toBe(true); + }); +}); diff --git a/webapp/channels/src/i18n/imports.ts b/webapp/channels/src/i18n/imports.ts new file mode 100644 index 0000000000..6b526137ee --- /dev/null +++ b/webapp/channels/src/i18n/imports.ts @@ -0,0 +1,75 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// DO NOT EDIT - This file is generated by gen_lang_imports through the gen-lang-imports script + +/* eslint-disable */ + +import am from './am.json'; +import ar from './ar.json'; +import be from './be.json'; +import bg from './bg.json'; +import bn from './bn.json'; +import br from './br.json'; +import ca from './ca.json'; +import cs from './cs.json'; +import da from './da.json'; +import de from './de.json'; +import el from './el.json'; +import enAU from './en-AU.json'; +import es from './es.json'; +import et from './et.json'; +import eu from './eu.json'; +import fa from './fa.json'; +import fi from './fi.json'; +import fil from './fil.json'; +import fr from './fr.json'; +import fy from './fy.json'; +import gl from './gl.json'; +import gu from './gu.json'; +import he from './he.json'; +import hi from './hi.json'; +import hr from './hr.json'; +import hu from './hu.json'; +import id from './id.json'; +import is from './is.json'; +import it from './it.json'; +import ja from './ja.json'; +import ka from './ka.json'; +import kkLatn from './kk-Latn.json'; +import kk from './kk.json'; +import km from './km.json'; +import ko from './ko.json'; +import la from './la.json'; +import lo from './lo.json'; +import lt from './lt.json'; +import lv from './lv.json'; +import mk from './mk.json'; +import ml from './ml.json'; +import mn from './mn.json'; +import nbNO from './nb-NO.json'; +import ne from './ne.json'; +import nl from './nl.json'; +import pl from './pl.json'; +import pr from './pr.json'; +import ptBR from './pt-BR.json'; +import pt from './pt.json'; +import ro from './ro.json'; +import ru from './ru.json'; +import si from './si.json'; +import sl from './sl.json'; +import sq from './sq.json'; +import sr from './sr.json'; +import sv from './sv.json'; +import th from './th.json'; +import tr from './tr.json'; +import uk from './uk.json'; +import vi from './vi.json'; +import zhCN from './zh-CN.json'; +import zhTW from './zh-TW.json'; + +export const langIDs = ["am","ar","be","bg","bn","br","ca","cs","da","de","el","en-AU","es","et","eu","fa","fi","fil","fr","fy","gl","gu","he","hi","hr","hu","id","is","it","ja","ka","kk-Latn","kk","km","ko","la","lo","lt","lv","mk","ml","mn","nb-NO","ne","nl","pl","pr","pt-BR","pt","ro","ru","si","sl","sq","sr","sv","th","tr","uk","vi","zh-CN","zh-TW"]; + +export const langLabels = {"am":"አማርኛ","ar":"العربية","be":"Беларуская","bg":"Български","bn":"বাংলা","br":"Brezhoneg","ca":"Català","cs":"Čeština","da":"Dansk","de":"Deutsch","el":"Ελληνικά","en-AU":"English (Australia)","es":"Español","et":"eesti keel","eu":"Euskara","fa":"فارسی","fi":"Suomi","fil":"Filipino","fr":"Français","fy":"Frysk","gl":"Galego","gu":"ગુજરાતી","he":"עברית‏","hi":"हिन्दी","hr":"Hrvatski","hu":"Magyar","id":"Bahasa Indonesia","is":"Íslenska","it":"Italiano","ja":"日本語","ka":"ქართული","kk-Latn":"Қазақша","kk":"Қазақша","km":"ភាសាខ្មែរ","ko":"한국어","la":"Latin","lo":"ພາສາລາວ","lt":"Lietuvių","lv":"Latviešu","mk":"Македонски","ml":"മലയാളം","mn":"Монгол","nb-NO":"Norsk (bokmål)","ne":"नेपाली","nl":"Nederlands","pl":"Polski","pr":"pr","pt-BR":"Português (Brasil)","pt":"Português","ro":"Română","ru":"Русский","si":"සිංහල","sl":"Slovenščina","sq":"Shqip","sr":"Српски","sv":"Svenska","th":"ภาษาไทย","tr":"Türkçe","uk":"Українська","vi":"Tiếng Việt","zh-CN":"中文(中国大陆)","zh-TW":"中文(台灣)"}; + +export const langFiles = {am,ar,be,bg,bn,br,ca,cs,da,de,el,'en-AU':enAU,es,et,eu,fa,fi,fil,fr,fy,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,'kk-Latn':kkLatn,kk,km,ko,la,lo,lt,lv,mk,ml,mn,'nb-NO':nbNO,ne,nl,pl,pr,'pt-BR':ptBR,pt,ro,ru,si,sl,sq,sr,sv,th,tr,uk,vi,'zh-CN':zhCN,'zh-TW':zhTW}; \ No newline at end of file diff --git a/webapp/channels/src/i18n/langmap.js b/webapp/channels/src/i18n/langmap.js new file mode 100644 index 0000000000..eab86241ce --- /dev/null +++ b/webapp/channels/src/i18n/langmap.js @@ -0,0 +1,897 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +module.exports = { + ach: { + nativeName: 'Lwo', + englishName: 'Acholi', + }, + ady: { + nativeName: 'Адыгэбзэ', + englishName: 'Adyghe', + }, + af: { + nativeName: 'Afrikaans', + englishName: 'Afrikaans', + }, + 'af-NA': { + nativeName: 'Afrikaans (Namibia)', + englishName: 'Afrikaans (Namibia)', + }, + 'af-ZA': { + nativeName: 'Afrikaans (South Africa)', + englishName: 'Afrikaans (South Africa)', + }, + ak: { + nativeName: 'Tɕɥi', + englishName: 'Akan', + }, + am: { + name: 'Amharic', + nativeName: 'አማርኛ', + }, + ar: { + nativeName: 'العربية', + englishName: 'Arabic', + }, + 'ar-AR': { + nativeName: 'العربية', + englishName: 'Arabic', + }, + 'ar-MA': { + nativeName: 'العربية', + englishName: 'Arabic (Morocco)', + }, + 'ar-SA': { + nativeName: 'العربية (السعودية)', + englishName: 'Arabic (Saudi Arabia)', + }, + 'ay-BO': { + nativeName: 'Aymar aru', + englishName: 'Aymara', + }, + az: { + nativeName: 'Azərbaycan dili', + englishName: 'Azerbaijani', + }, + 'az-AZ': { + nativeName: 'Azərbaycan dili', + englishName: 'Azerbaijani', + }, + 'be-BY': { + nativeName: 'Беларуская', + englishName: 'Belarusian', + }, + bg: { + nativeName: 'Български', + englishName: 'Bulgarian', + }, + 'bg-BG': { + nativeName: 'Български', + englishName: 'Bulgarian', + }, + bn: { + nativeName: 'বাংলা', + englishName: 'Bengali', + }, + 'bn-IN': { + nativeName: 'বাংলা (ভারত)', + englishName: 'Bengali (India)', + }, + 'bn-BD': { + nativeName: 'বাংলা(বাংলাদেশ)', + englishName: 'Bengali (Bangladesh)', + }, + br: { + nativeName: 'Brezhoneg', + englishName: 'Breton', + }, + 'bs-BA': { + nativeName: 'Bosanski', + englishName: 'Bosnian', + }, + ca: { + nativeName: 'Català', + englishName: 'Catalan', + }, + 'ca-ES': { + nativeName: 'Català', + englishName: 'Catalan', + }, + cak: { + nativeName: 'Maya Kaqchikel', + englishName: 'Kaqchikel', + }, + 'ck-US': { + nativeName: 'ᏣᎳᎩ (tsalagi)', + englishName: 'Cherokee', + }, + cs: { + nativeName: 'Čeština', + englishName: 'Czech', + }, + 'cs-CZ': { + nativeName: 'Čeština', + englishName: 'Czech', + }, + cy: { + nativeName: 'Cymraeg', + englishName: 'Welsh', + }, + 'cy-GB': { + nativeName: 'Cymraeg', + englishName: 'Welsh', + }, + da: { + nativeName: 'Dansk', + englishName: 'Danish', + }, + 'da-DK': { + nativeName: 'Dansk', + englishName: 'Danish', + }, + de: { + nativeName: 'Deutsch', + englishName: 'German', + }, + 'de-AT': { + nativeName: 'Deutsch (Österreich)', + englishName: 'German (Austria)', + }, + 'de-DE': { + nativeName: 'Deutsch (Deutschland)', + englishName: 'German (Germany)', + }, + 'de-CH': { + nativeName: 'Deutsch (Schweiz)', + englishName: 'German (Switzerland)', + }, + dsb: { + nativeName: 'Dolnoserbšćina', + englishName: 'Lower Sorbian', + }, + el: { + nativeName: 'Ελληνικά', + englishName: 'Greek', + }, + 'el-GR': { + nativeName: 'Ελληνικά', + englishName: 'Greek (Greece)', + }, + en: { + nativeName: 'English', + englishName: 'English', + }, + 'en-GB': { + nativeName: 'English (UK)', + englishName: 'English (UK)', + }, + 'en-AU': { + nativeName: 'English (Australia)', + englishName: 'English (Australia)', + }, + 'en-CA': { + nativeName: 'English (Canada)', + englishName: 'English (Canada)', + }, + 'en-IE': { + nativeName: 'English (Ireland)', + englishName: 'English (Ireland)', + }, + 'en-IN': { + nativeName: 'English (India)', + englishName: 'English (India)', + }, + 'en-PI': { + nativeName: 'English (Pirate)', + englishName: 'English (Pirate)', + }, + 'en-SG': { + nativeName: 'English (Singapore)', + englishName: 'English (Singapore)', + }, + 'en-UD': { + nativeName: 'English (Upside Down)', + englishName: 'English (Upside Down)', + }, + 'en-US': { + nativeName: 'English (US)', + englishName: 'English (US)', + }, + 'en-ZA': { + nativeName: 'English (South Africa)', + englishName: 'English (South Africa)', + }, + 'en@pirate': { + nativeName: 'English (Pirate)', + englishName: 'English (Pirate)', + }, + eo: { + nativeName: 'Esperanto', + englishName: 'Esperanto', + }, + 'eo-EO': { + nativeName: 'Esperanto', + englishName: 'Esperanto', + }, + es: { + nativeName: 'Español', + englishName: 'Spanish', + }, + 'es-AR': { + nativeName: 'Español (Argentine)', + englishName: 'Spanish (Argentina)', + }, + 'es-419': { + nativeName: 'Español (Latinoamérica)', + englishName: 'Spanish (Latin America)', + }, + 'es-CL': { + nativeName: 'Español (Chile)', + englishName: 'Spanish (Chile)', + }, + 'es-CO': { + nativeName: 'Español (Colombia)', + englishName: 'Spanish (Colombia)', + }, + 'es-EC': { + nativeName: 'Español (Ecuador)', + englishName: 'Spanish (Ecuador)', + }, + 'es-ES': { + nativeName: 'Español (España)', + englishName: 'Spanish (Spain)', + }, + 'es-LA': { + nativeName: 'Español (Latinoamérica)', + englishName: 'Spanish (Latin America)', + }, + 'es-NI': { + nativeName: 'Español (Nicaragua)', + englishName: 'Spanish (Nicaragua)', + }, + 'es-MX': { + nativeName: 'Español (México)', + englishName: 'Spanish (Mexico)', + }, + 'es-US': { + nativeName: 'Español (Estados Unidos)', + englishName: 'Spanish (United States)', + }, + 'es-VE': { + nativeName: 'Español (Venezuela)', + englishName: 'Spanish (Venezuela)', + }, + et: { + nativeName: 'eesti keel', + englishName: 'Estonian', + }, + 'et-EE': { + nativeName: 'Eesti (Estonia)', + englishName: 'Estonian (Estonia)', + }, + eu: { + nativeName: 'Euskara', + englishName: 'Basque', + }, + 'eu-ES': { + nativeName: 'Euskara', + englishName: 'Basque', + }, + fa: { + nativeName: 'فارسی', + englishName: 'Persian', + }, + 'fa-IR': { + nativeName: 'فارسی', + englishName: 'Persian', + }, + 'fb-LT': { + nativeName: 'Leet Speak', + englishName: 'Leet', + }, + ff: { + nativeName: 'Fulah', + englishName: 'Fulah', + }, + fi: { + nativeName: 'Suomi', + englishName: 'Finnish', + }, + 'fi-FI': { + nativeName: 'Suomi', + englishName: 'Finnish', + }, + fo: { + nativeName: 'Føroyskt', + englishName: 'Faroese', + }, + 'fo-FO': { + nativeName: 'Føroyskt (Færeyjar)', + englishName: 'Faroese (Faroe Islands)', + }, + fr: { + nativeName: 'Français', + englishName: 'French', + }, + 'fr-CA': { + nativeName: 'Français (Canada)', + englishName: 'French (Canada)', + }, + 'fr-FR': { + nativeName: 'Français (France)', + englishName: 'French (France)', + }, + 'fr-BE': { + nativeName: 'Français (Belgique)', + englishName: 'French (Belgium)', + }, + 'fr-CH': { + nativeName: 'Français (Suisse)', + englishName: 'French (Switzerland)', + }, + 'fy-NL': { + nativeName: 'Frysk', + englishName: 'Frisian (West)', + }, + ga: { + nativeName: 'Gaeilge', + englishName: 'Irish', + }, + 'ga-IE': { + nativeName: 'Gaeilge', + englishName: 'Irish', + }, + gd: { + nativeName: 'Gàidhlig', + englishName: 'Gaelic', + }, + gl: { + nativeName: 'Galego', + englishName: 'Galician', + }, + 'gl-ES': { + nativeName: 'Galego', + englishName: 'Galician', + }, + 'gn-PY': { + nativeName: "Avañe'ẽ", + englishName: 'Guarani', + }, + 'gu-IN': { + nativeName: 'ગુજરાતી', + englishName: 'Gujarati', + }, + gv: { + nativeName: 'Gaelg', + englishName: 'Manx', + }, + 'gx-GR': { + nativeName: 'Ἑλληνική ἀρχαία', + englishName: 'Classical Greek', + }, + he: { + nativeName: 'עברית‏', + englishName: 'Hebrew', + }, + 'he-IL': { + nativeName: 'עברית‏', + englishName: 'Hebrew', + }, + hi: { + nativeName: 'हिन्दी', + englishName: 'Hindi', + }, + 'hi-IN': { + nativeName: 'हिन्दी', + englishName: 'Hindi', + }, + hr: { + nativeName: 'Hrvatski', + englishName: 'Croatian', + }, + 'hr-HR': { + nativeName: 'Hrvatski', + englishName: 'Croatian', + }, + hsb: { + nativeName: 'Hornjoserbšćina', + englishName: 'Upper Sorbian', + }, + ht: { + nativeName: 'Kreyòl', + englishName: 'Haitian Creole', + }, + hu: { + nativeName: 'Magyar', + englishName: 'Hungarian', + }, + 'hu-HU': { + nativeName: 'Magyar', + englishName: 'Hungarian', + }, + hy: { + nativeName: 'Հայերեն', + englishName: 'Armenian', + }, + 'hy-AM': { + nativeName: 'Հայերեն (Հայաստան)', + englishName: 'Armenian (Armenia)', + }, + id: { + nativeName: 'Bahasa Indonesia', + englishName: 'Indonesian', + }, + 'id-ID': { + nativeName: 'Bahasa Indonesia', + englishName: 'Indonesian', + }, + is: { + nativeName: 'Íslenska', + englishName: 'Icelandic', + }, + 'is-IS': { + nativeName: 'Íslenska (Iceland)', + englishName: 'Icelandic (Iceland)', + }, + it: { + nativeName: 'Italiano', + englishName: 'Italian', + }, + 'it-IT': { + nativeName: 'Italiano', + englishName: 'Italian', + }, + ja: { + nativeName: '日本語', + englishName: 'Japanese', + }, + 'ja-JP': { + nativeName: '日本語 (日本)', + englishName: 'Japanese (Japan)', + }, + 'jv-ID': { + nativeName: 'Basa Jawa', + englishName: 'Javanese', + }, + 'ka-GE': { + nativeName: 'ქართული', + englishName: 'Georgian', + }, + 'kk-KZ': { + nativeName: 'Қазақша', + englishName: 'Kazakh', + }, + km: { + nativeName: 'ភាសាខ្មែរ', + englishName: 'Khmer', + }, + kl: { + nativeName: 'kalaallisut', + englishName: 'Greenlandic', + }, + 'km-KH': { + nativeName: 'ភាសាខ្មែរ', + englishName: 'Khmer', + }, + kab: { + nativeName: 'Taqbaylit', + englishName: 'Kabyle', + }, + kn: { + nativeName: 'ಕನ್ನಡ', + englishName: 'Kannada', + }, + 'kn-IN': { + nativeName: 'ಕನ್ನಡ (India)', + englishName: 'Kannada (India)', + }, + ko: { + nativeName: '한국어', + englishName: 'Korean', + }, + 'ko-KR': { + nativeName: '한국어 (한국)', + englishName: 'Korean (Korea)', + }, + 'ku-TR': { + nativeName: 'Kurdî', + englishName: 'Kurdish', + }, + kw: { + nativeName: 'Kernewek', + englishName: 'Cornish', + }, + la: { + nativeName: 'Latin', + englishName: 'Latin', + }, + 'la-VA': { + nativeName: 'Latin', + englishName: 'Latin', + }, + lb: { + nativeName: 'Lëtzebuergesch', + englishName: 'Luxembourgish', + }, + 'li-NL': { + nativeName: 'Lèmbörgs', + englishName: 'Limburgish', + }, + lo: { + name: 'Lao', + nativeName: 'ພາສາລາວ', + }, + lt: { + nativeName: 'Lietuvių', + englishName: 'Lithuanian', + }, + 'lt-LT': { + nativeName: 'Lietuvių', + englishName: 'Lithuanian', + }, + lv: { + nativeName: 'Latviešu', + englishName: 'Latvian', + }, + 'lv-LV': { + nativeName: 'Latviešu', + englishName: 'Latvian', + }, + mai: { + nativeName: 'मैथिली, মৈথিলী', + englishName: 'Maithili', + }, + 'mg-MG': { + nativeName: 'Malagasy', + englishName: 'Malagasy', + }, + mk: { + nativeName: 'Македонски', + englishName: 'Macedonian', + }, + 'mk-MK': { + nativeName: 'Македонски (Македонски)', + englishName: 'Macedonian (Macedonian)', + }, + ml: { + nativeName: 'മലയാളം', + englishName: 'Malayalam', + }, + 'ml-IN': { + nativeName: 'മലയാളം', + englishName: 'Malayalam', + }, + 'mn-MN': { + nativeName: 'Монгол', + englishName: 'Mongolian', + }, + mr: { + nativeName: 'मराठी', + englishName: 'Marathi', + }, + 'mr-IN': { + nativeName: 'मराठी', + englishName: 'Marathi', + }, + ms: { + nativeName: 'Bahasa Melayu', + englishName: 'Malay', + }, + 'ms-MY': { + nativeName: 'Bahasa Melayu', + englishName: 'Malay', + }, + mt: { + nativeName: 'Malti', + englishName: 'Maltese', + }, + 'mt-MT': { + nativeName: 'Malti', + englishName: 'Maltese', + }, + my: { + nativeName: 'ဗမာစကာ', + englishName: 'Burmese', + }, + no: { + nativeName: 'Norsk', + englishName: 'Norwegian', + }, + nb: { + nativeName: 'Norsk (bokmål)', + englishName: 'Norwegian (bokmal)', + }, + 'nb-NO': { + nativeName: 'Norsk (bokmål)', + englishName: 'Norwegian (bokmal)', + }, + ne: { + nativeName: 'नेपाली', + englishName: 'Nepali', + }, + 'ne-NP': { + nativeName: 'नेपाली', + englishName: 'Nepali', + }, + nl: { + nativeName: 'Nederlands', + englishName: 'Dutch', + }, + 'nl-BE': { + nativeName: 'Nederlands (België)', + englishName: 'Dutch (Belgium)', + }, + 'nl-NL': { + nativeName: 'Nederlands (Nederland)', + englishName: 'Dutch (Netherlands)', + }, + 'nn-NO': { + nativeName: 'Norsk (nynorsk)', + englishName: 'Norwegian (nynorsk)', + }, + oc: { + nativeName: 'Occitan', + englishName: 'Occitan', + }, + 'or-IN': { + nativeName: 'ଓଡ଼ିଆ', + englishName: 'Oriya', + }, + pa: { + nativeName: 'ਪੰਜਾਬੀ', + englishName: 'Punjabi', + }, + 'pa-IN': { + nativeName: 'ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)', + englishName: 'Punjabi (India)', + }, + pl: { + nativeName: 'Polski', + englishName: 'Polish', + }, + 'pl-PL': { + nativeName: 'Polski', + englishName: 'Polish', + }, + 'ps-AF': { + nativeName: 'پښتو', + englishName: 'Pashto', + }, + pt: { + nativeName: 'Português', + englishName: 'Portuguese', + }, + 'pt-BR': { + nativeName: 'Português (Brasil)', + englishName: 'Portuguese (Brazil)', + }, + 'pt-PT': { + nativeName: 'Português (Portugal)', + englishName: 'Portuguese (Portugal)', + }, + 'qu-PE': { + nativeName: 'Qhichwa', + englishName: 'Quechua', + }, + 'rm-CH': { + nativeName: 'Rumantsch', + englishName: 'Romansh', + }, + ro: { + nativeName: 'Română', + englishName: 'Romanian', + }, + 'ro-RO': { + nativeName: 'Română', + englishName: 'Romanian', + }, + ru: { + nativeName: 'Русский', + englishName: 'Russian', + }, + 'ru-RU': { + nativeName: 'Русский', + englishName: 'Russian', + }, + 'sa-IN': { + nativeName: 'संस्कृतम्', + englishName: 'Sanskrit', + }, + 'se-NO': { + nativeName: 'Davvisámegiella', + englishName: 'Northern Sámi', + }, + sh: { + nativeName: 'српскохрватски', + englishName: 'Serbo-Croatian', + }, + 'si-LK': { + nativeName: 'සිංහල', + englishName: 'Sinhala (Sri Lanka)', + }, + sk: { + nativeName: 'Slovenčina', + englishName: 'Slovak', + }, + 'sk-SK': { + nativeName: 'Slovenčina (Slovakia)', + englishName: 'Slovak (Slovakia)', + }, + sl: { + nativeName: 'Slovenščina', + englishName: 'Slovenian', + }, + 'sl-SI': { + nativeName: 'Slovenščina', + englishName: 'Slovenian', + }, + 'so-SO': { + nativeName: 'Soomaaliga', + englishName: 'Somali', + }, + sq: { + nativeName: 'Shqip', + englishName: 'Albanian', + }, + 'sq-AL': { + nativeName: 'Shqip', + englishName: 'Albanian', + }, + sr: { + nativeName: 'Српски', + englishName: 'Serbian', + }, + 'sr-RS': { + nativeName: 'Српски (Serbia)', + englishName: 'Serbian (Serbia)', + }, + su: { + nativeName: 'Basa Sunda', + englishName: 'Sundanese', + }, + sv: { + nativeName: 'Svenska', + englishName: 'Swedish', + }, + 'sv-SE': { + nativeName: 'Svenska', + englishName: 'Swedish', + }, + sw: { + nativeName: 'Kiswahili', + englishName: 'Swahili', + }, + 'sw-KE': { + nativeName: 'Kiswahili', + englishName: 'Swahili (Kenya)', + }, + ta: { + nativeName: 'தமிழ்', + englishName: 'Tamil', + }, + 'ta-IN': { + nativeName: 'தமிழ்', + englishName: 'Tamil', + }, + te: { + nativeName: 'తెలుగు', + englishName: 'Telugu', + }, + 'te-IN': { + nativeName: 'తెలుగు', + englishName: 'Telugu', + }, + tg: { + nativeName: 'забо́ни тоҷикӣ́', + englishName: 'Tajik', + }, + 'tg-TJ': { + nativeName: 'тоҷикӣ', + englishName: 'Tajik', + }, + th: { + nativeName: 'ภาษาไทย', + englishName: 'Thai', + }, + 'th-TH': { + nativeName: 'ภาษาไทย (ประเทศไทย)', + englishName: 'Thai (Thailand)', + }, + fil: { + nativeName: 'Filipino', + englishName: 'Filipino', + }, + tlh: { + nativeName: 'tlhIngan-Hol', + englishName: 'Klingon', + }, + tr: { + nativeName: 'Türkçe', + englishName: 'Turkish', + }, + 'tr-TR': { + nativeName: 'Türkçe', + englishName: 'Turkish', + }, + 'tt-RU': { + nativeName: 'татарча', + englishName: 'Tatar', + }, + uk: { + nativeName: 'Українська', + englishName: 'Ukrainian', + }, + 'uk-UA': { + nativeName: 'Українська', + englishName: 'Ukrainian', + }, + ur: { + nativeName: 'اردو', + englishName: 'Urdu', + }, + 'ur-PK': { + nativeName: 'اردو', + englishName: 'Urdu', + }, + uz: { + nativeName: "O'zbek", + englishName: 'Uzbek', + }, + 'uz-UZ': { + nativeName: "O'zbek", + englishName: 'Uzbek', + }, + vi: { + nativeName: 'Tiếng Việt', + englishName: 'Vietnamese', + }, + 'vi-VN': { + nativeName: 'Tiếng Việt', + englishName: 'Vietnamese', + }, + 'xh-ZA': { + nativeName: 'isiXhosa', + englishName: 'Xhosa', + }, + yi: { + nativeName: 'ייִדיש', + englishName: 'Yiddish', + }, + 'yi-DE': { + nativeName: 'ייִדיש (German)', + englishName: 'Yiddish (German)', + }, + zh: { + nativeName: '中文', + englishName: 'Chinese', + }, + 'zh-Hans': { + nativeName: '中文简体', + englishName: 'Chinese Simplified', + }, + 'zh-Hant': { + nativeName: '中文繁體', + englishName: 'Chinese Traditional', + }, + 'zh-CN': { + nativeName: '中文(中国大陆)', + englishName: 'Chinese Simplified (China)', + }, + 'zh-HK': { + nativeName: '中文(香港)', + englishName: 'Chinese Traditional (Hong Kong)', + }, + 'zh-SG': { + nativeName: '中文(新加坡)', + englishName: 'Chinese Simplified (Singapore)', + }, + 'zh-TW': { + nativeName: '中文(台灣)', + englishName: 'Chinese Traditional (Taiwan)', + }, + 'zu-ZA': { + nativeName: 'isiZulu', + englishName: 'Zulu', + }, +}; diff --git a/webapp/package.json b/webapp/package.json index 8a4b16852c..9e8d30c8e8 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -16,7 +16,8 @@ "check": "npm run check --workspaces --if-present", "fix": "npm run fix --workspaces --if-present", "check-types": "npm run check-types --workspaces --if-present", - "clean": "npm run clean --workspaces --if-present" + "clean": "npm run clean --workspaces --if-present", + "gen-lang-imports": "node scripts/gen_lang_imports.mjs" }, "dependencies": { "react-intl": "6.6.2", diff --git a/webapp/platform/types/src/config.ts b/webapp/platform/types/src/config.ts index 8374f7ff3b..0c6a78481e 100644 --- a/webapp/platform/types/src/config.ts +++ b/webapp/platform/types/src/config.ts @@ -58,6 +58,7 @@ export type ClientConfig = { EnableCustomEmoji: string; EnableCustomGroups: string; EnableCustomUserStatuses: string; + EnableExperimentalLocales: string; EnableUserStatuses: string; EnableLastActiveTime: string; EnableTimedDND: string; @@ -720,6 +721,7 @@ export type LocalizationSettings = { DefaultServerLocale: string; DefaultClientLocale: string; AvailableLocales: string; + EnableExperimentalLocales: boolean; }; export type SamlSettings = { diff --git a/webapp/scripts/gen_lang_imports.mjs b/webapp/scripts/gen_lang_imports.mjs new file mode 100644 index 0000000000..50242ff86a --- /dev/null +++ b/webapp/scripts/gen_lang_imports.mjs @@ -0,0 +1,62 @@ +#!/bin/node + +import * as fs from 'fs'; +import langmap from '../channels/src/i18n/langmap.js'; + +let lines = ''; +const langIDs = []; +const langFiles = {}; +const langLabels = {}; + +fs.readdirSync('./channels/src/i18n').forEach(file => { + if (file.endsWith('.json')) { + const langID = file.substr(0, file.indexOf('.json')); + + // Skip default language + if (langID === 'en') { + return + } + + langIDs.push(langID); + lines += `import ${langID.replace('-', '')} from './${file}';\n`; + langFiles[langID] = langID.replace('-', ''); + + let m = langmap[langID] + if (!m) { + // We fallback to the language code prefix if we can't find a map. + const id = langID.includes('-') ? langID.substr(0, langID.indexOf('-')) : langID; + for (const k of Object.keys(langmap)) { + if (k.startsWith(id)) { + m = langmap[k]; + break; + } + } + } + + langLabels[langID] = m ? m["nativeName"] : langID; + } +}); + +lines += `\nexport const langIDs = ${JSON.stringify(langIDs)};\n` +lines += `\nexport const langLabels = ${JSON.stringify(langLabels)};\n` + +// To generate the file exports we need to do a bit more work to handle ids with dashes and also output a map of literals rather than strings. +lines += '\nexport const langFiles = {' + Object.keys(langFiles).reduce((out, id, idx) => { + if (id.includes('-')) { + out += `'${id}':${langFiles[id]}`; + } else { + out += `${langFiles[id]}`; + } + + if (idx !== (langIDs.length - 1)) { + out += ','; + } + + return out; +}, '') + '};'; + +const header = `// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information.\n\n// DO NOT EDIT - This file is generated by gen_lang_imports through the gen-lang-imports script\n\n/* eslint-disable */\n\n`; + +fs.writeFileSync('./channels/src/i18n/imports.ts', header+lines); +