-
Notifications
You must be signed in to change notification settings - Fork 15
/
background-script.js
442 lines (393 loc) · 18.5 KB
/
background-script.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
/* *************************************************************** */
/* TODO ********************************************************** */
// See https://github.com/lingua-libre/SignIt/issues
/* Phases ******************************************************** */
// state: up
// listen to event 1
// loading saved params from localstorage
// state: loading
// loading available sign languages
// loading vidéos urls
// create context menu
// state: ready
// change language asked from popup box
// update global var
// state: loading
// loading vidéos urls
// set local storage
// state: ready
/* *************************************************************** */
/* Sparql endpoints *********************************************** */
const sparqlEndpoints = {
lingualibre: { url: "https://lingualibre.org/bigdata/namespace/wdq/sparql", verb: "POST" },
wikidata: { url: "https://query.wikidata.org/sparql", verb: "GET" },
commons: { url: "https://commons-query.wikimedia.org/sparql", verb: "GET" },
dictionaireFrancophone: { url: "https://www.dictionnairedesfrancophones.org/sparql", verb: "" },
};
/* *************************************************************** */
/* Sparql ******************************************************** */
// Lingualibre: All languages (P4) for which media type (P24) is video (Q88890)
// TODO: NEEDS QUERY HITING ON LLQS+WDQS, FETCHING NATIVE NAME (P1705)
const sparqlSignLanguagesQuery = 'SELECT ?id ?idLabel WHERE { ?id prop:P2 entity:Q4 . ?id prop:P24 entity:Q88890 . SERVICE wikibase:label { bd:serviceParam wikibase:language "fr,en". } }';
// Lingualibre: Given a language (P4) with media video, fetch the list of writen word (P7), url (P3) speakers (P5)
const sparqlSignVideosQuery = 'SELECT ?word ?filename ?speaker WHERE { ?record prop:P2 entity:Q2 . ?record prop:P4 entity:$(lang) . ?record prop:P7 ?word . ?record prop:P3 ?filename . ?record prop:P5 ?speakerItem . ?speakerItem rdfs:label ?speaker filter ( lang( ?speaker ) = "en" ) . }';
// Wikidata: All Sign languages who have a Commons lingualibre category
const sparqlFilesInCategoryQuery = `SELECT ?file ?url ?title
WHERE {
SERVICE wikibase:mwapi {
bd:serviceParam wikibase:api "Generator" ;
wikibase:endpoint "commons.wikimedia.org" ;
mwapi:gcmtitle "Category:Videos Langue des signes française" ;
mwapi:generator "categorymembers" ;
mwapi:gcmtype "file" ;
mwapi:gcmlimit "max" .
?title wikibase:apiOutput mwapi:title .
?pageid wikibase:apiOutput "@pageid" .
}
BIND (URI(CONCAT('https://commons.wikimedia.org/entity/M', ?pageid)) AS ?file)
BIND (URI(CONCAT('https://commons.wikimedia.org/wiki/', ?title)) AS ?url)
}`
/* *************************************************************** */
/* Initial state if no localStorage ********************************* */
var state = 'up', // up, loading, ready, error
records = {},
signLanguages = [],
uiLanguages = [],
// Default values, will be stored in localStorage as well for persistency.
params = {
signLanguage: 'Q99628', // for videos : French Sign language
uiLanguage: 'Q150', // for interface : French
historylimit: 6,
history: ['lapin', 'crabe', 'fraise', 'canard'], // Some fun
wpintegration: true,
twospeed: true,
hinticon: true,
coloredwords: true,
choosepanels: 'both', // issues/36
};
/* *************************************************************** */
/* i18n context ************************************************** */
// List of UI languages with translations on github via translatewiki
var supportedUiLanguages = [
{i18nCode: "anp",labelEN: "Angika",labelNative: "अंगिका",wdQid: "Q28378",wiki: "anp"},
{i18nCode: "ar",labelEN: "Arabic",labelNative: "اللُّغَة العَرَبِيّة",wdQid: "Q13955",wiki: "ar"},
{i18nCode: "bn",labelEN: "Bengali",labelNative: "বাংলা",wdQid: "Q9610",wiki: "bn"},
{i18nCode: "blk",labelEN: "Pa'O",labelNative: "ပအိုဝ်ႏဘာႏသာႏ",wdQid: "",wiki: "Q7121294"},
{i18nCode: "br",labelEN: "Breton",labelNative: "Brezhoneg",wdQid: "Q12107",wiki: "br"},
{i18nCode: "ce",labelEN: "Chechen",labelNative: "Нохчийн мотт",wdQid: "Q33350",wiki: "ce"},
{i18nCode: "de",labelEN: "German",labelNative: "Deutsch",wdQid: "Q188",wiki: "de"},
{i18nCode: "en",labelEN: "English",labelNative: "English",wdQid: "Q1860",wiki: "en"},
{i18nCode: "es",labelEN: "Spanish",labelNative: "Español",wdQid: "Q1321",wiki: "es"},
{i18nCode: "fa",labelEN: "Persian",labelNative: "فارسی",wdQid: "Q9168",wiki: "fa"},
{i18nCode: "fi",labelEN: "Finnish",labelNative: "Suomi",wdQid: "Q1412",wiki: "fi"},
{i18nCode: "fr",labelEN: "French",labelNative: "Français",wdQid: "Q150",wiki: "fr"},
{i18nCode: "gl",labelEN: "Galician",labelNative: "Galego",wdQid: "Q9307",wiki: "gl"},
{i18nCode: "he",labelEN: "Hebrew",labelNative: "עברית",wdQid: "Q9288",wiki: "he"},
{i18nCode: "hi",labelEN: "Hindi",labelNative: "हिन्दी",wdQid: "Q1568",wiki: "hi"},
{i18nCode: "hu",labelEN: "Hungarian",labelNative: "Magyar",wdQid: "Q9067",wiki: "hu"},
{i18nCode: "ia",labelEN: "Interlingua",labelNative: "Interlingua",wdQid: "Q35934",wiki: "ia"},
{i18nCode: "id",labelEN: "Indonesian",labelNative: "Bahasa Indonesia",wdQid: "Q9240",wiki: "id"},
{i18nCode: "it",labelEN: "Italian",labelNative: "Italiano",wdQid: "Q652",wiki: "it"},
{i18nCode: "ja",labelEN: "Japanese",labelNative: "日本語",wdQid: "Q5287",wiki: "ja"},
{i18nCode: "kk-cyrl",labelEN: "Kazakh",labelNative: "Казақша",wdQid: "Q9252",wiki: "kk"},
{i18nCode: "ko",labelEN: "Korean",labelNative: "한국어",wdQid: "Q9176",wiki: "ko"},
{i18nCode: "krc",labelEN: "Karachay-Balkar",labelNative: "Qaraçay-malqar",wdQid: "Q33714",wiki: "krc"},
{i18nCode: "lmo",labelEN: "Lombard",labelNative: "Lengua lombarda",wdQid: "Q33754",wiki: "lmo"},
{i18nCode: "mk",labelEN: "Macedonian",labelNative: "Македонски",wdQid: "Q9296",wiki: "mk"},
{i18nCode: "mnw",labelEN: "Mon",labelNative: "ဘာသာမန်",wdQid: "Q13349",wiki: "mnw"},
{i18nCode: "ms",labelEN: "Malay",labelNative: "Bahasa Melayu",wdQid: "Q9237",wiki: "ms"},
{i18nCode: "nb",labelEN: "Bokmål",labelNative: "Bokmål",wdQid: "Q25167",wiki: "nb"},
{i18nCode: "urdu",labelEN: "Urdu",labelNative: "اردو",wdQid: "Q1389492"},
{i18nCode: "pt",labelEN: "Portuguese",labelNative: "Português (pt)",wdQid: "Q5146",wiki: "pt"},
{i18nCode: "pt-br",labelEN: "Portuguese",labelNative: "Português (br)",wdQid: "Q5146",wiki: "pt"},
{i18nCode: "ru",labelEN: "Russian",labelNative: "Русский язык",wdQid: "Q7737",wiki: "ru"},
{i18nCode: "scn",labelEN: "Sicilian",labelNative: "Sicilianu",wdQid: "Q33973",wiki: "scn"},
{i18nCode: "sl",labelEN: "Slovene",labelNative: "Slovenski jezik",wdQid: "Q9063",wiki: "sl"},
{i18nCode: "sv",labelEN: "Swedish",labelNative: "Svenska",wdQid: "Q9027",wiki: "sv"},
{i18nCode: "sw",labelEN: "Swahili",labelNative: "Kiswahili",wdQid: "Q7838",wiki: "sw"},
{i18nCode: "tl",labelEN: "Tagalog",labelNative: "Wikang Tagalog",wdQid: "Q34057",wiki: "tl"},
{i18nCode: "tr",labelEN: "Turkish",labelNative: "Türkçe",wdQid: "Q256",wiki: "tr"},
{i18nCode: "uk",labelEN: "Ukrainian",labelNative: "Українська мова",wdQid: "Q8798",wiki: "uk"},
// {i18nCode:"zh",labelEN: "Chinese",labelNative: "汉语",wdQid: "Q7850",wiki: "zh"}
{i18nCode: "zh-hant",labelEN: "Traditional Chinese",labelNative: "中文 (繁體)",wdQid: "Q18130932",wiki: "zh"},
{i18nCode: "zh-hans",labelEN: "Modern Chinese",labelNative: "中文 (简体)",wdQid: "Q13414913",wiki: "zh"},
]
// Init internationalisation support with Banana-i18n.js
banana = new Banana('fr'); // use document browser language
loadI18nLocalization(params.uiLanguage);
// Add url support
banana.registerParserPlugin('link', (nodes) => {
return '<a href="' + nodes[0] + '">' + nodes[1] + '</a>';
});
/* ************************************************
async function fetchJS(filepath) {
try {
const response = await fetch(`${filepath}`, {
method: 'GET',
credentials: 'same-origin'
});
const content = await response.json();
return content;
} catch (error) { console.error(error); }
}
messages = await fetchJS(`i18n/${locale}.json`); */
// Loading all UI translations
async function loadI18nLocalization( uiLanguageQid ) {
var localizedPhrases = {};
console.log("uiLanguageQid)",uiLanguageQid);
console.log("supportedUiLanguages",supportedUiLanguages);
state = 'loading';
// Get locale code and corresponding wiktionary
var lang = supportedUiLanguages.filter(item => (item.wdQid==uiLanguageQid) );
locale = lang[0].i18nCode;
console.log("locale",locale)
// Load i18n messages
const res = await fetch(`i18n/${locale}.json`)
localizedPhrases = await res.json();
console.log("messages",localizedPhrases["si-popup-settings-title"])
// Load messages into localisation
banana.load(localizedPhrases, locale); // Load localized messages (chould be conditional to empty)
// Declare localisation
banana.setLocale(locale); // Change to new locale
state = 'ready';
console.log( Object.keys( localizedPhrases ).length + ' i18n messages loaded' );
}
/* *************************************************************** */
/* Settings management : memory, updates ************************* */
// Save parameter and value in localStorage
async function storeParam( name, value ) {
// If value of type array, we make a copy of it to avoid dead references issues
if ( Array.isArray( value ) ) {
value = Array.from( value ); // copy
}
// else, create object { name: value }
console.log('HERE ! Selected option: { ', name+': '+value+' }' );
var tmp = {};
tmp[ name ] = value;
// reset params
params[ name ] = value;
return await browser.storage.local.set( tmp );
}
// Get stored values from init hard coded `params` or from prefered local storage
// Also synchronize both.
async function getStoredParam( name ) {
var tmp = await browser.storage.local.get( name );
params[ name ] = tmp[ name ] || params[ name ] || null;
// If missing from local storage, then save init values in local storage
if(tmp.length == undefined ) { await storeParam(name, params[name]); }
return params[ name ];
}
// Get sign languages covered by Lingualibre
// returns: [{ wdQid: "Q99628", labelNative: "langue des signes française"},{},...]
async function getSignLanguagesWithVideos() {
var i,
signLanguage,
signLanguages = [], // ?? already define in global scopte
response = await $.post(
sparqlEndpoints.lingualibre.url,
{ format: 'json', query: sparqlSignLanguagesQuery }
);
// create signLanguages objects
for ( i = 0; i < response.results.bindings.length; i++ ) {
var signLanguageRaw = response.results.bindings[ i ];
console.log("#149",signLanguageRaw)
signLanguage = { wdQid: signLanguageRaw.id.value.split( '/' ).pop(), labelNative: signLanguageRaw.idLabel.value }
signLanguages[i] = signLanguage;
}
// TEMPORARY, WHEN ONLY LSF HAS VIDEOS
signLanguages = filterArrayBy(signLanguages,"wdQid", "Q99628");
console.log(signLanguages)
return signLanguages;
}
// Loading all vidéos of a given sign language. Format:
// returns format: { word: { filename: url, speaker: name }, ... };
async function getAllRecords( signLanguage ) {
var i, record, word, response,
records = {};
state = 'loading';
response = await $.post( sparqlEndpoints.lingualibre.url, { format: 'json', query: sparqlSignVideosQuery.replace( '$(lang)', signLanguage ) } );
for ( i = 0; i < response.results.bindings.length; i++ ) {
record = response.results.bindings[ i ];
word = record.word.value.toLowerCase();
if ( records.hasOwnProperty( word ) === false ) {
records[ word ] = [];
}
records[ word ].push( { filename: record.filename.value.replace( 'http://', 'https://' ), speaker: record.speaker.value } );
}
state = 'ready';
console.log( Object.keys( records ).length + ' records loaded' );
return records;
}
// Given language's Qid, reload list of available videos and records/words data
async function changeLanguage( newLang ) {
records = await getAllRecords( newLang );
await storeParam( 'signLanguage', newLang ); // localStorage save
}
// Given language's Qid, reload available translations
async function changeUiLanguage( newLang ) {
console.log('changeUiLanguage newLang', newLang); // => 'Q150' for french
await loadI18nLocalization( newLang );
await storeParam( 'uiLanguage', newLang ); // localStorage save
}
/* *************************************************************** */
/* Toolbox functions ********************************************* */
var filterArrayBy = function (arr, key, value){
return arr.filter(item => (item[key]==value) )
};
function normalize( selection ) { // this could do more
return selection.trim();
}
// Check a string with multiple words and return the word whose record is available
function getAvailableWord( word ) {
// while this makes for an ungly regex , I couldn't think of anything else.
// Using metacharcters like /W or /w meant removing accented letters as well, which would have broken
// many french words . . .so I retorted to this;
const regex = /[!"#$%&()*+,-./:;<=>?@[\\\]^_{|}~]/;
const stringWithoutSpecialChars = word.replace(regex,"");
const wordArray = stringWithoutSpecialChars.split(" ");
console.log(wordArray);
for ( let newWord of wordArray ) {
if( records.hasOwnProperty(newWord.toLowerCase()) ){
return newWord;
}
}
}
// Given a word string, check if exist in available records data, if so return data on that word
// returns format: { filename: url, speaker: name }
function wordToFiles( word ) {
var fileData =
records.hasOwnProperty( word ) ? records[ word ]
:records.hasOwnProperty( word.toLowerCase() )? records[ word.toLowerCase() ]
:null;
return fileData;
}
var normalizeMessage = function(msg){
var text = msg.selectionText || msg.iconText || msg.wpTitle;
delete msg.selectionText; // when from background-script.js via right-click menu
delete msg.iconText; // when from signit.js icon click
delete msg.wpTitle; // when from wpintegration.js auto-injection
msg.text = text.trim();
// msg.list = getAllRecords() <--------- how to do
return msg
}
/* *************************************************************** */
/* Dependencies, CSP ********************************************* */
async function getActiveTabId () {
await browser.tabs.query({active: true, currentWindow: true});
return tabs[ 0 ].id;
}
// Ping tab, if fails, then CSS, JS dependencies loaded and executed
async function checkActiveTabInjections( tab ) {
try {
await browser.tabs.sendMessage( tab, { command: "ping" } );
} catch ( error ) {
var i,
dependencies = browser.runtime.getManifest().content_scripts[ 0 ];
scripts = dependencies.js,
stylesheets = dependencies.css;
for( i = 0; i < scripts.length; i++ ) {
await browser.tabs.executeScript( tab, { file: scripts[ i ] } );
}
for( i = 0; i < stylesheets.length; i++ ) {
await browser.tabs.insertCSS( tab, { file: stylesheets[ i ] } );
}
}
}
/* *************************************************************** */
/* Browser interactions ****************************************** */
var callModal = async function(msg){
// Tab
console.log("Call modal > msg",{ msg });
var tabs = await browser.tabs.query({active: true, currentWindow: true});
await checkActiveTabInjections( tabs[ 0 ].id );
console.log("Call modal > #282 > tab id", tabs[0].id)
// Data
var word = msg.text
if(word.split(" ").length > 1){
word = getAvailableWord(word)
}
var videosFiles = msg.files || wordToFiles( word ) || [];
// Send message which opens the modal
browser.tabs.sendMessage( tabs[ 0 ].id, {
command: "signit.sign",
text: word,
files: videosFiles,
supportedWords: Object.keys(records), // <------ array for coloredwords feature
banana : banana
} );
storeParam( 'history', [ word, ...params.history ] );
}
// Create a context menu item (right-click on text to see)
browser.contextMenus.create({
id: "signit",
title: 'Lingua Libre SignIt',
contexts: ["selection"]
}, function() {return;});
// Listen for right-click menu's signals
browser.contextMenus.onClicked.addListener( async function( menuMessage, __tab ) { // var tab not used ? Can remove ?
message = normalizeMessage(menuMessage)
callModal(message);
});
// Listen for other signals
browser.runtime.onMessage.addListener( async function ( message ) {
console.log("Message heard in background-script.js: ", message, "---------------------" )
// Passing messages instead of browser type checking since its not scalable
// inside both chrome and firefox
if (message.command === "checkActiveTabInjections") {
await checkActiveTabInjections(message.argument);
return;
} else if (message.command === "normalizeWordAndReturnFiles") {
const word = normalize(message.argument);
const files = wordToFiles(word);
return [word, files];
}
else if (message.command === "changeUiLanguage") {
await changeUiLanguage(message.argument);
return;
}
else if (message.command === "storeParam") {
const [name,value] = message.argument;
storeParam(name,value);
return;
}
else if (message.command === 'bananai18n') {
let [msg,placeholderValue] = message.arg;
const i18nMessage = banana.i18n(msg,...placeholderValue);
return i18nMessage;
}
message = normalizeMessage(message);
// When message 'signit.getfiles' is heard, returns relevant extract of records[]
if ( message.command === 'signit.getfiles' ) {
console.log('bg>signit.getfiles')
return records[ message.text ] || records[ message.text.toLowerCase() ] || [];
}
// Start modal
// When right click's menu "Lingua Libre SignIt" clicked, send message 'signit.sign' to the content script => opens Signit modal
else if ( message.command === 'signit.hinticon' ) {
callModal(message);
}
});
/* *************************************************************** */
/* Main ********************************************************** */
async function main() {
state = 'loading';
// Get local storage value if exist, else get default values
// promise.all
await getStoredParam( 'history' );
await getStoredParam( 'historylimit' );
await getStoredParam( 'wpintegration' );
await getStoredParam( 'twospeed' );
// storeParam( 'twospeed', params.twospeed ); //
await getStoredParam( 'hinticon' );
await getStoredParam( 'coloredwords' );
await getStoredParam( 'choosepanels' );
signLanguage = await getStoredParam( 'signLanguage' );
signLanguages = await getSignLanguagesWithVideos();
uiLanguage = await getStoredParam( 'uiLanguage' );
console.log("supportedUiLanguages",supportedUiLanguages)
uiLanguages = supportedUiLanguages;
records = await getAllRecords( signLanguage );
state = 'ready';
}
// Run it
main();