blob: e5a816155f26001f6b12b4b5b0f9319ef71099e4 [file] [log] [blame]
import {convertLanguages, isoLangs} from './consts';
export type TabOptionValue = ''|'yep'|'popup';
type DeprecatedTabOptionValue = 'panel';
type TabOptionValueIncludingDeprecated =
TabOptionValue|DeprecatedTabOptionValue;
interface TabOption {
value: TabOptionValue;
labelMsg: string;
deprecatedValues: TabOptionValueIncludingDeprecated[];
}
export interface TargetLangs {
[key: string]: string; // Here the key is a string with a number.
}
export interface OptionsV0 {
translateinto: TargetLangs;
uniquetab: TabOptionValue;
}
interface LegacyLanguages {
[key: string]: string; // Here the key is a string with the language code.
}
/**
* Backwards-compatible interface for the information available in the sync
* storage area.
*/
interface LegacyOptions {
translateinto: TargetLangs;
languages: LegacyLanguages;
uniquetab: TabOptionValueIncludingDeprecated;
}
interface OptionsWrapper {
options: OptionsV0;
isFirstRun: boolean;
}
export const TAB_OPTIONS: TabOption[] = [
// Open in new tab for each translation
{
value: '',
labelMsg: 'options_tabsoption_1',
deprecatedValues: [],
},
// Open in a unique tab
{
value: 'yep',
labelMsg: 'options_tabsoption_2',
deprecatedValues: [],
},
// Open in a popup
{
value: 'popup',
labelMsg: 'options_tabsoption_3',
deprecatedValues: ['panel'],
},
];
// Class which can be used to retrieve the user options in order to act
// accordingly.
export default class Options {
_options: OptionsV0;
isFirstRun: boolean;
constructor(options: OptionsV0, isFirstRun: boolean) {
this._options = options;
this.isFirstRun = isFirstRun;
}
get uniqueTab(): TabOptionValue {
return this._options.uniquetab;
}
get targetLangs(): TargetLangs {
return this._options.translateinto;
}
// Returns a promise that resolves in an instance of the Object class with the
// current options.
static getOptions(readOnly = true): Promise<Options> {
return Options.getOptionsRaw(readOnly).then(res => {
return new Options(res.options, res.isFirstRun);
});
}
// Returns a promise that resolves to an object containing:
// - |options|: normalized options object which can be used to initialize the
// Options class, and which contains the current options set up by the user.
// - |isFirstRun|: whether the extension is running for the first time and
// needs to be set up.
//
// If the options needed to be normalized/created, they are also saved in the
// sync storage area.
static getOptionsRaw(readOnly: boolean): Promise<OptionsWrapper> {
return new Promise((res, rej) => {
chrome.storage.sync.get(null, itemsAny => {
if (chrome.runtime.lastError) {
return rej(chrome.runtime.lastError);
}
const items = <LegacyOptions>itemsAny;
let didTranslateintoChange = false;
let didUniquetabChange = false;
// If the extension sync storage area is blank, set this as being the
// first run.
const isFirstRun = Object.keys(items).length === 0;
// Create |translateinto| property if it doesn't exist.
if (items.translateinto === undefined) {
didTranslateintoChange = true;
// Upgrade from a version previous to v0.7 if applicable, otherwise
// create the property with the default values.
if (items.languages !== undefined) {
let newTranslateinto: TargetLangs;
items.translateinto =
Object.assign(newTranslateinto, Object.values(items.languages));
} else {
const uiLocale = chrome.i18n.getMessage('@@ui_locale');
const defaultLang1 = uiLocale.replace('_', '-');
const defaultLang2 = uiLocale.split('_')[0];
items.translateinto = {};
if (isoLangs[defaultLang1] != undefined)
items.translateinto['0'] = defaultLang1;
else if (isoLangs[defaultLang2] != undefined)
items.translateinto['0'] = defaultLang2;
}
}
// Normalize |translateinto| property: remove non-existent languages or
// change them with the correct language code.
for (const [index, language] of Object.entries(items.translateinto)) {
if (isoLangs[language] === undefined) {
didTranslateintoChange = true;
if (convertLanguages[language] === undefined) {
// The language doesn't exist
console.log(
'Deleting ' + language +
' from items.translateinto because it doesn\'t exist.');
delete items.translateinto[index];
} else {
// The language doesn't exist but a known replacement is known
const newLanguage = convertLanguages[language];
console.log('Replacing ' + language + ' with ' + newLanguage);
// If the converted language is already on the list, just remove
// the wrong language, otherwise convert the language
if (Object.values(items.translateinto).includes(newLanguage))
delete items.translateinto[index];
else
items.translateinto[index] = newLanguage;
}
}
}
// Normalize |uniquetab| property:
// - If it is set to a valid value, leave it alone.
// - If it is set to a deprecated value, change it to the corresponding
// value we use now.
// - If it is set to an incorrect value or it isn't set, change it to
// the default value.
let uniquetabNewValue: TabOptionValue;
let foundValue = false;
for (const opt of TAB_OPTIONS) {
if (opt.value == items?.uniquetab) {
uniquetabNewValue = opt.value;
foundValue = true;
break;
}
if (opt.deprecatedValues.includes(items?.uniquetab)) {
foundValue = true;
uniquetabNewValue = opt.value;
break;
}
}
if (!foundValue) {
uniquetabNewValue = 'popup';
didUniquetabChange = true;
}
// Clean up deprecated properties
if (items.languages !== undefined) {
delete items.languages;
chrome.storage.sync.remove('languages');
}
const returnObject: OptionsWrapper = {
isFirstRun,
options: {
translateinto: items.translateinto,
uniquetab: uniquetabNewValue,
}
};
// Save properties that have changed if we're not in read-only mode
if (!readOnly) {
if (didTranslateintoChange || didUniquetabChange) {
chrome.storage.sync.set(returnObject.options);
}
}
res(returnObject);
});
});
}
}