blob: e5a816155f26001f6b12b4b5b0f9319ef71099e4 [file] [log] [blame]
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +02001import {convertLanguages, isoLangs} from './consts';
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +02002
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +02003export type TabOptionValue = ''|'yep'|'popup';
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +02004type DeprecatedTabOptionValue = 'panel';
5type TabOptionValueIncludingDeprecated =
6 TabOptionValue|DeprecatedTabOptionValue;
7
8interface TabOption {
9 value: TabOptionValue;
10 labelMsg: string;
11 deprecatedValues: TabOptionValueIncludingDeprecated[];
12}
13
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +020014export interface TargetLangs {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020015 [key: string]: string; // Here the key is a string with a number.
16}
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +020017export interface OptionsV0 {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020018 translateinto: TargetLangs;
19 uniquetab: TabOptionValue;
20}
21
22interface LegacyLanguages {
23 [key: string]: string; // Here the key is a string with the language code.
24}
25/**
26 * Backwards-compatible interface for the information available in the sync
27 * storage area.
28 */
29interface LegacyOptions {
30 translateinto: TargetLangs;
31 languages: LegacyLanguages;
32 uniquetab: TabOptionValueIncludingDeprecated;
33}
34
35interface OptionsWrapper {
36 options: OptionsV0;
37 isFirstRun: boolean;
38}
39
40export const TAB_OPTIONS: TabOption[] = [
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020041 // Open in new tab for each translation
42 {
43 value: '',
44 labelMsg: 'options_tabsoption_1',
45 deprecatedValues: [],
46 },
47 // Open in a unique tab
48 {
49 value: 'yep',
50 labelMsg: 'options_tabsoption_2',
51 deprecatedValues: [],
52 },
53 // Open in a popup
54 {
55 value: 'popup',
56 labelMsg: 'options_tabsoption_3',
57 deprecatedValues: ['panel'],
58 },
59];
60
61// Class which can be used to retrieve the user options in order to act
62// accordingly.
63export default class Options {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020064 _options: OptionsV0;
65 isFirstRun: boolean;
66
67 constructor(options: OptionsV0, isFirstRun: boolean) {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020068 this._options = options;
69 this.isFirstRun = isFirstRun;
70 }
71
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020072 get uniqueTab(): TabOptionValue {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020073 return this._options.uniquetab;
74 }
75
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020076 get targetLangs(): TargetLangs {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020077 return this._options.translateinto;
78 }
79
80 // Returns a promise that resolves in an instance of the Object class with the
81 // current options.
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +020082 static getOptions(readOnly = true): Promise<Options> {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020083 return Options.getOptionsRaw(readOnly).then(res => {
84 return new Options(res.options, res.isFirstRun);
85 });
86 }
87
88 // Returns a promise that resolves to an object containing:
89 // - |options|: normalized options object which can be used to initialize the
90 // Options class, and which contains the current options set up by the user.
91 // - |isFirstRun|: whether the extension is running for the first time and
92 // needs to be set up.
93 //
94 // If the options needed to be normalized/created, they are also saved in the
95 // sync storage area.
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020096 static getOptionsRaw(readOnly: boolean): Promise<OptionsWrapper> {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020097 return new Promise((res, rej) => {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020098 chrome.storage.sync.get(null, itemsAny => {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020099 if (chrome.runtime.lastError) {
100 return rej(chrome.runtime.lastError);
101 }
102
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +0200103 const items = <LegacyOptions>itemsAny;
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200104 let didTranslateintoChange = false;
105 let didUniquetabChange = false;
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200106
107 // If the extension sync storage area is blank, set this as being the
108 // first run.
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +0200109 const isFirstRun = Object.keys(items).length === 0;
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200110
111 // Create |translateinto| property if it doesn't exist.
112 if (items.translateinto === undefined) {
113 didTranslateintoChange = true;
114
115 // Upgrade from a version previous to v0.7 if applicable, otherwise
116 // create the property with the default values.
117 if (items.languages !== undefined) {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200118 let newTranslateinto: TargetLangs;
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200119 items.translateinto =
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200120 Object.assign(newTranslateinto, Object.values(items.languages));
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200121 } else {
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +0200122 const uiLocale = chrome.i18n.getMessage('@@ui_locale');
123 const defaultLang1 = uiLocale.replace('_', '-');
124 const defaultLang2 = uiLocale.split('_')[0];
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200125
126 items.translateinto = {};
Adrià Vilanova Martínez28f2aa02022-05-31 11:25:06 +0200127 if (isoLangs[defaultLang1] != undefined)
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200128 items.translateinto['0'] = defaultLang1;
Adrià Vilanova Martínez28f2aa02022-05-31 11:25:06 +0200129 else if (isoLangs[defaultLang2] != undefined)
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200130 items.translateinto['0'] = defaultLang2;
131 }
132 }
133
134 // Normalize |translateinto| property: remove non-existent languages or
135 // change them with the correct language code.
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +0200136 for (const [index, language] of Object.entries(items.translateinto)) {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200137 if (isoLangs[language] === undefined) {
138 didTranslateintoChange = true;
139 if (convertLanguages[language] === undefined) {
140 // The language doesn't exist
141 console.log(
142 'Deleting ' + language +
143 ' from items.translateinto because it doesn\'t exist.');
144 delete items.translateinto[index];
145 } else {
146 // The language doesn't exist but a known replacement is known
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +0200147 const newLanguage = convertLanguages[language];
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200148 console.log('Replacing ' + language + ' with ' + newLanguage);
149
150 // If the converted language is already on the list, just remove
151 // the wrong language, otherwise convert the language
152 if (Object.values(items.translateinto).includes(newLanguage))
153 delete items.translateinto[index];
154 else
155 items.translateinto[index] = newLanguage;
156 }
157 }
158 }
159
160 // Normalize |uniquetab| property:
161 // - If it is set to a valid value, leave it alone.
162 // - If it is set to a deprecated value, change it to the corresponding
163 // value we use now.
164 // - If it is set to an incorrect value or it isn't set, change it to
165 // the default value.
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200166 let uniquetabNewValue: TabOptionValue;
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200167 let foundValue = false;
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +0200168 for (const opt of TAB_OPTIONS) {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200169 if (opt.value == items?.uniquetab) {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200170 uniquetabNewValue = opt.value;
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200171 foundValue = true;
172 break;
173 }
174 if (opt.deprecatedValues.includes(items?.uniquetab)) {
175 foundValue = true;
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200176 uniquetabNewValue = opt.value;
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200177 break;
178 }
179 }
180 if (!foundValue) {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200181 uniquetabNewValue = 'popup';
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200182 didUniquetabChange = true;
183 }
184
185 // Clean up deprecated properties
186 if (items.languages !== undefined) {
187 delete items.languages;
188 chrome.storage.sync.remove('languages');
189 }
190
Adrià Vilanova Martínez2b50e912022-06-01 00:05:40 +0200191 const returnObject: OptionsWrapper = {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200192 isFirstRun,
193 options: {
194 translateinto: items.translateinto,
195 uniquetab: uniquetabNewValue,
196 }
197 };
198
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200199 // Save properties that have changed if we're not in read-only mode
200 if (!readOnly) {
201 if (didTranslateintoChange || didUniquetabChange) {
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200202 chrome.storage.sync.set(returnObject.options);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200203 }
204 }
205
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200206 res(returnObject);
207 });
208 });
209 }
210}