mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-17 21:50:56 +08:00
149b389508
* Switch to using Typescript instead of Babel * Switch all es6 / jsx file extensions to ts / tsx * Convert Utils to a TS module from module.exports style module * Move everything from module.exports to typescript exports * Define .d.ts files for mailspring-exports and component kit… Yes it seems this is the best option :( * Load up on those @types * Synthesize TS types from PropTypes for standard components * Add types to Model classes and move constructor constants to instance vars * 9800 => 7700 TS errors * 7700 => 5600 TS errors * 5600 => 5330 TS errors * 5330 => 4866 TS errors * 4866 => 4426 TS errors * 4426 => 2411 TS errors * 2411 > 1598 TS errors * 1598 > 769 TS errors * 769 > 129 TS errors * 129 > 22 TS errors * Fix runtime errors * More runtime error fixes * Remove support for custom .es6 file extension * Remove a few odd remaining references to Nylas * Don’t ship Typescript support in the compiled app for now * Fix issues in compiled app - module resolution in TS is case sensitive? * README updates * Fix a few more TS errors * Make “No Signature” option clickable + selectable * Remove flicker when saving file and reloading keymaps * Fix mail rule item height in preferences * Fix missing spacing in thread sharing popover * Fix scrollbar ticks being nested incorrectly * Add Japanese as a manually reviewed language * Prevent the thread list from “sticking” * Re-use Sheet when switching root tabs, prevent sidebar from resetting * Ensure specs run * Update package configuration to avoid shpping types * Turn eslint back on - we will opt-in to the TS rules one by one
265 lines
7 KiB
TypeScript
265 lines
7 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
const React = process.type === 'renderer' ? require('react') : null;
|
|
|
|
const RTL_LANGS = [
|
|
'ae' /* Avestan */,
|
|
'ar' /* 'العربية', Arabic */,
|
|
'arc' /* Aramaic */,
|
|
'bcc' /* 'بلوچی مکرانی', Southern Balochi */,
|
|
'bqi' /* 'بختياري', Bakthiari */,
|
|
'ckb' /* 'Soranî / کوردی', Sorani */,
|
|
'dv' /* Dhivehi */,
|
|
'fa' /* 'فارسی', Persian */,
|
|
'glk' /* 'گیلکی', Gilaki */,
|
|
'he' /* 'עברית', Hebrew */,
|
|
'ku' /* 'Kurdî / كوردی', Kurdish */,
|
|
'mzn' /* 'مازِرونی', Mazanderani */,
|
|
'nqo' /* N'Ko */,
|
|
'pnb' /* 'پنجابی', Western Punjabi */,
|
|
'ps' /* 'پښتو', Pashto, */,
|
|
'sd' /* 'سنڌي', Sindhi */,
|
|
'ug' /* 'Uyghurche / ئۇيغۇرچە', Uyghur */,
|
|
'ur' /* 'اردو', Urdu */,
|
|
'yi' /* 'ייִדיש', Yiddish */,
|
|
];
|
|
|
|
// For now, we only default to a localized version of Mailspring if the translations
|
|
// have been manually reviewed by a contributor. We have Google translations in /tons/
|
|
// of languages but in many languages the translations are poor.
|
|
const VERIFIED_LANGS = ['en', 'de', 'es', 'fr', 'ko', 'pl', 'ja', 'zh-TW', 'pt-BR'];
|
|
|
|
const LANG_NAMES = {
|
|
af: 'Afrikaans',
|
|
am: 'Amharic',
|
|
an: 'Aragonese',
|
|
ar: 'Arabic',
|
|
ast: 'Asturian',
|
|
az: 'Azerbaijani',
|
|
be: 'Belarusian',
|
|
bg: 'Bulgarian',
|
|
bn: 'Bengali',
|
|
br: 'Breton',
|
|
bs: 'Bosnian',
|
|
ca: 'Catalan',
|
|
cak: 'Kaqchikel',
|
|
ceb: 'Cebuano',
|
|
co: 'Corsican',
|
|
cs: 'Czech',
|
|
cy: 'Welsh',
|
|
da: 'Danish',
|
|
de: 'German',
|
|
dsb: 'Lower Sorbian',
|
|
el: 'Greek',
|
|
en: 'English',
|
|
eo: 'Esperanto',
|
|
es: 'Spanish',
|
|
es_419: 'Spanish - Latin America',
|
|
et: 'Estonian',
|
|
eu: 'Basque',
|
|
fa: 'Persian',
|
|
fi: 'Finnish',
|
|
fr: 'French',
|
|
fy: 'Western Frisian',
|
|
ga: 'Irish',
|
|
gd: 'Gaelic',
|
|
gl: 'Galician',
|
|
gu: 'Gujarati',
|
|
ha: 'Hausa',
|
|
haw: 'Hawaiian',
|
|
he: 'Hebrew',
|
|
hi: 'Hindi',
|
|
hmn: 'Hmong',
|
|
hr: 'Croatian',
|
|
hsb: 'Upper Sorbian',
|
|
ht: 'Haitian',
|
|
hu: 'Hungarian',
|
|
hy: 'Armenian',
|
|
id: 'Indonesian',
|
|
ig: 'Igbo',
|
|
is: 'Icelandic',
|
|
it: 'Italian',
|
|
ja: 'Japanese',
|
|
ka: 'Georgian',
|
|
kab: 'Kabyle',
|
|
kk: 'Kazakh',
|
|
km: 'Central Khmer',
|
|
kn: 'Kannada',
|
|
ko: 'Korean',
|
|
ku: 'Kurdish',
|
|
ky: 'Kirghiz',
|
|
lb: 'Luxembourgish',
|
|
lo: 'Lao',
|
|
lt: 'Lithuanian',
|
|
lv: 'Latvian',
|
|
mg: 'Malagasy',
|
|
mi: 'Maori',
|
|
mk: 'Macedonian',
|
|
mn: 'Mongolian',
|
|
ms: 'Malay',
|
|
mt: 'Maltese',
|
|
my: 'Burmese',
|
|
nb: 'Norwegian Bokmål',
|
|
ne: 'Nepali',
|
|
nl: 'Dutch',
|
|
nr: 'Ndebele',
|
|
nso: 'Northern Sotho',
|
|
oc: 'Occitan',
|
|
pa: 'Punjabi',
|
|
pl: 'Polish',
|
|
pt: 'Portuguese',
|
|
'pt-BR': 'Brazilian Portuguese',
|
|
rm: 'Romansh',
|
|
ro: 'Romanian',
|
|
ru: 'Russian',
|
|
si: 'Sinhala',
|
|
sk: 'Slovak',
|
|
sl: 'Slovenian',
|
|
sm: 'Samoan',
|
|
so: 'Somali',
|
|
sq: 'Albanian',
|
|
sr: 'Serbian',
|
|
ss: 'Swati',
|
|
st: 'Southern Sotho',
|
|
sv: 'Swedish',
|
|
ta: 'Tamil',
|
|
th: 'Thai',
|
|
tn: 'Tswana',
|
|
tr: 'Turkish',
|
|
ts: 'Tsonga',
|
|
uk: 'Ukrainian',
|
|
uz: 'Uzbek',
|
|
ve: 'Venda',
|
|
vi: 'Vietnamese',
|
|
xh: 'Xhosa',
|
|
'yue-CN': 'Cantonese',
|
|
'zh-CN': 'Chinese - Simplified',
|
|
'zh-HK': 'Chinese - Hong Kong',
|
|
'zh-TW': 'Chinese - Taiwan',
|
|
zh: 'Chinese',
|
|
zu: 'Zulu',
|
|
};
|
|
|
|
// The locale string "en-US" or "fr-FR", etc. provided by the system
|
|
const systemLocale =
|
|
process.type === 'renderer' ? window.navigator.language : require('electron').app.getLocale();
|
|
|
|
// The locale Mailspring will default to. We do not default to unverified translations
|
|
const automaticLocale =
|
|
VERIFIED_LANGS.includes(systemLocale) || VERIFIED_LANGS.includes(systemLocale.split('-').shift())
|
|
? systemLocale
|
|
: 'en-US';
|
|
|
|
const langsDir = path.join(__dirname, '..', 'lang');
|
|
const langsFiles = fs
|
|
.readdirSync(langsDir)
|
|
.filter(f => f.endsWith('.json'))
|
|
.map(f => f.replace('.json', ''));
|
|
|
|
let localizations = {};
|
|
let locale = null;
|
|
|
|
export let isRTL = false;
|
|
|
|
export function initializeLocalization({ configDirPath }) {
|
|
locale = automaticLocale;
|
|
|
|
// Load the user's language choice if they've explicitly set one in settings
|
|
// Note we can't use our nice Config class becuase the config-schema itself
|
|
// contains localized strings...
|
|
try {
|
|
const config = JSON.parse(fs.readFileSync(path.join(configDirPath, 'config.json')).toString());
|
|
const choice = config['*'].core.intl.language;
|
|
if (choice && choice !== '') {
|
|
// '' represents "use automatic"
|
|
locale = choice;
|
|
}
|
|
} catch (err) {
|
|
// may be first run
|
|
}
|
|
|
|
const lang = locale.split('-')[0] || 'en';
|
|
|
|
localizations = {};
|
|
isRTL = false;
|
|
|
|
// Load localizations for the base language (eg: `zh`)
|
|
if (langsFiles.includes(lang)) {
|
|
localizations = require(path.join(langsDir, `${lang}.json`));
|
|
isRTL = RTL_LANGS.includes(lang);
|
|
}
|
|
|
|
// Load localizations for the full locale if present (eg: `zh-CN`)
|
|
// and override base localizations with the more specific regional ones.
|
|
if (langsFiles.includes(locale)) {
|
|
localizations = Object.assign(localizations, require(path.join(langsDir, `${locale}.json`)));
|
|
}
|
|
}
|
|
|
|
export function localized(en, ...subs) {
|
|
let i = 0;
|
|
let translated = localizations[en] || en;
|
|
if (subs.length) {
|
|
// Support "%@ and %@" OR "%1$@ and %2$@" which allows for param reordering.
|
|
// The translation may contain this format even if the original string does not.
|
|
if (translated.includes('%1$@')) {
|
|
subs.forEach((sub, idx) => {
|
|
translated = translated.replace(`%${idx + 1}$@`, sub);
|
|
});
|
|
} else {
|
|
translated = translated.replace(/%@/g, () => subs[i++]);
|
|
}
|
|
}
|
|
return translated;
|
|
}
|
|
|
|
export function localizedReactFragment(en, ...subs) {
|
|
let translated = localizations[en] || en;
|
|
if (!subs.length) {
|
|
return translated;
|
|
}
|
|
|
|
// Support "%@ and %@" OR "%1$@ and %2$@" which allows for param reordering.
|
|
// The translation may contain this format even if the original string does not.
|
|
|
|
let parts = [];
|
|
let match = null;
|
|
let used = 0;
|
|
while ((match = /%(?:(\d)+\$)?@/g.exec(translated))) {
|
|
if (match.index > 0) {
|
|
parts.push(translated.substr(0, match.index));
|
|
}
|
|
if (match[1]) {
|
|
parts.push(subs[match[1] / 1]);
|
|
} else {
|
|
parts.push(subs[used++]);
|
|
}
|
|
translated = translated.substr(match.index + match[0].length);
|
|
}
|
|
if (translated.length > 0) {
|
|
parts.push(translated);
|
|
}
|
|
|
|
return React.createElement(React.Fragment, [], ...parts);
|
|
}
|
|
|
|
// For Preferences UI:
|
|
|
|
export function getAvailableLanguages() {
|
|
const localeToItem = f => ({ key: f, name: LANG_NAMES[f] || f });
|
|
|
|
// The list we expose is mostly just languages, but also a few lang-locale combos like zh-CN.
|
|
// If our current/system locale in a known combo expose that, otherwis cut it so it's always
|
|
// a member of the available list.
|
|
const currentLang = LANG_NAMES[locale] ? locale : locale.split('-').shift();
|
|
const automaticLang = LANG_NAMES[automaticLocale]
|
|
? automaticLocale
|
|
: automaticLocale.split('-').shift();
|
|
|
|
return {
|
|
current: localeToItem(currentLang),
|
|
automatic: localeToItem(automaticLang),
|
|
verified: VERIFIED_LANGS.map(localeToItem),
|
|
experimental: langsFiles.filter(f => !VERIFIED_LANGS.includes(f)).map(localeToItem),
|
|
};
|
|
}
|