Extension L10N - VM part (#133)
Sandboxed extensions will use navigator.language Unsandboxed extensions will use the language set by setLocale
This commit is contained in:
1 parent
8a7ae3f774
commit
398892c6f2
6 files changed
+165
-1
No files matched your search
@@ -4,6 +4,9 @@ const ScratchCommon = require('./tw-extension-api-common');
|
||||
const dispatch = require('../dispatch/worker-dispatch');
|
||||
const log = require('../util/log');
|
||||
const {isWorker} = require('./tw-extension-worker-context');
|
||||
const createTranslate = require('./tw-l10n');
|
||||
|
||||
const translate = createTranslate(null);
|
||||
|
||||
const loadScripts = url => {
|
||||
if (isWorker) {
|
||||
@@ -75,7 +78,8 @@ Object.assign(global.Scratch, ScratchCommon, {
|
||||
canOpenWindow: () => Promise.resolve(false),
|
||||
openWindow: () => Promise.reject(new Error('Scratch.openWindow not supported in sandboxed extensions')),
|
||||
canRedirect: () => Promise.resolve(false),
|
||||
redirect: () => Promise.reject(new Error('Scratch.redirect not supported in sandboxed extensions'))
|
||||
redirect: () => Promise.reject(new Error('Scratch.redirect not supported in sandboxed extensions')),
|
||||
translate
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
const formatMessage = require('format-message');
|
||||
|
||||
/**
|
||||
* @param {VM|null} vm
|
||||
* @returns {object}
|
||||
*/
|
||||
const createTranslate = vm => {
|
||||
const namespace = formatMessage.namespace();
|
||||
|
||||
const translate = (message, args) => {
|
||||
if (message && typeof message === 'object') {
|
||||
// already in the expected format
|
||||
} else if (typeof message === 'string') {
|
||||
message = {
|
||||
default: message
|
||||
};
|
||||
} else {
|
||||
throw new Error('unsupported data type in translate()');
|
||||
}
|
||||
return namespace(message, args);
|
||||
};
|
||||
|
||||
const generateId = defaultMessage => `_${defaultMessage}`;
|
||||
|
||||
const getLocale = () => {
|
||||
if (vm) return vm.getLocale();
|
||||
if (typeof navigator !== 'undefined') return navigator.language;
|
||||
return 'en';
|
||||
};
|
||||
|
||||
translate.setup = translations => {
|
||||
namespace.setup({
|
||||
locale: getLocale(),
|
||||
missingTranslation: 'ignore',
|
||||
generateId,
|
||||
translations
|
||||
});
|
||||
};
|
||||
|
||||
translate.setup({});
|
||||
|
||||
if (vm) {
|
||||
vm.on('LOCALE_CHANGED', () => {
|
||||
translate.setup({});
|
||||
});
|
||||
}
|
||||
|
||||
return translate;
|
||||
};
|
||||
|
||||
module.exports = createTranslate;
|
||||
@@ -1,5 +1,6 @@
|
||||
const ScratchCommon = require('./tw-extension-api-common');
|
||||
const AsyncLimiter = require('../util/async-limiter');
|
||||
const createTranslate = require('./tw-l10n');
|
||||
|
||||
/**
|
||||
* Parse a URL object or return null.
|
||||
@@ -98,6 +99,8 @@ const setupUnsandboxedExtensionAPI = vm => new Promise(resolve => {
|
||||
location.href = url;
|
||||
};
|
||||
|
||||
Scratch.translate = createTranslate(vm);
|
||||
|
||||
global.Scratch = Scratch;
|
||||
global.ScratchExtensions = require('./tw-scratchx-compatibility-layer');
|
||||
});
|
||||
|
||||
@@ -1354,6 +1354,7 @@ class VirtualMachine extends EventEmitter {
|
||||
if (locale !== formatMessage.setup().locale) {
|
||||
formatMessage.setup({locale: locale, translations: {[locale]: messages}});
|
||||
}
|
||||
this.emit('LOCALE_CHANGED', locale);
|
||||
return this.extensionManager.refreshBlocks();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,3 +56,53 @@ test('redirect', async t => {
|
||||
await t.rejects(global.Scratch.redirect('https://example.com/'), /^Scratch\.redirect not supported in sandboxed extensions$/);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('translate', t => {
|
||||
t.equal(global.Scratch.translate({
|
||||
id: 'test1',
|
||||
default: 'Message 1: {var}',
|
||||
description: 'Description'
|
||||
}, {
|
||||
var: 'test'
|
||||
}), 'Message 1: test');
|
||||
t.equal(global.Scratch.translate('test1'), 'test1');
|
||||
t.equal(global.Scratch.translate('test1 {VAR}', {
|
||||
VAR: '3'
|
||||
}), 'test1 3');
|
||||
|
||||
const messages = {
|
||||
en: {
|
||||
test1: 'EN Message 1: {var}'
|
||||
},
|
||||
es: {
|
||||
test1: 'ES Message 1: {var}'
|
||||
}
|
||||
};
|
||||
|
||||
// Should default to English when no navigator object
|
||||
global.Scratch.translate.setup(messages);
|
||||
t.equal(global.Scratch.translate({
|
||||
id: 'test1',
|
||||
default: 'Message 1',
|
||||
description: 'Description'
|
||||
}, {
|
||||
var: 'ok'
|
||||
}), 'EN Message 1: ok');
|
||||
|
||||
// But if there is a navigator object, it should use its language
|
||||
// This is slightly contrived because setup() should only be run once in real extensions,
|
||||
// but this should still be useful as a test.
|
||||
global.navigator = {
|
||||
language: 'es'
|
||||
};
|
||||
global.Scratch.translate.setup(messages);
|
||||
t.equal(global.Scratch.translate({
|
||||
id: 'test1',
|
||||
default: 'Message 1',
|
||||
description: 'Description'
|
||||
}, {
|
||||
var: 'ok'
|
||||
}), 'ES Message 1: ok');
|
||||
|
||||
t.end();
|
||||
});
|
||||
@@ -243,3 +243,58 @@ test('redirect', async t => {
|
||||
t.equal(global.location.href, 'https://example.com/2');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('translate', async t => {
|
||||
const vm = new VirtualMachine();
|
||||
UnsandboxedExtensionRunner.setupUnsandboxedExtensionAPI(vm);
|
||||
|
||||
t.equal(global.Scratch.translate({
|
||||
id: 'test1',
|
||||
default: 'Message 1: {var}',
|
||||
description: 'Description'
|
||||
}, {
|
||||
var: 'test'
|
||||
}), 'Message 1: test');
|
||||
t.equal(global.Scratch.translate('test1 {var}', {
|
||||
var: 'ok'
|
||||
}), 'test1 ok');
|
||||
|
||||
global.Scratch.translate.setup({
|
||||
en: {
|
||||
test1: 'EN Message 1: {var}'
|
||||
},
|
||||
es: {
|
||||
test1: 'ES Message 1: {var}'
|
||||
}
|
||||
});
|
||||
t.equal(global.Scratch.translate({
|
||||
id: 'test1',
|
||||
default: 'Message 1: {var}',
|
||||
description: 'Description'
|
||||
}, {
|
||||
var: 'test'
|
||||
}), 'EN Message 1: test');
|
||||
t.equal(global.Scratch.translate('test1 {var}', {
|
||||
var: 'ok'
|
||||
}), 'test1 ok');
|
||||
|
||||
await vm.setLocale('es');
|
||||
|
||||
global.Scratch.translate.setup({
|
||||
en: {
|
||||
test1: 'EN Message 1: {var}'
|
||||
},
|
||||
es: {
|
||||
test1: 'ES Message 1: {var}'
|
||||
}
|
||||
});
|
||||
t.equal(global.Scratch.translate({
|
||||
id: 'test1',
|
||||
default: 'Message 1: {var}',
|
||||
description: 'Description'
|
||||
}, {
|
||||
var: 'test'
|
||||
}), 'ES Message 1: test');
|
||||
|
||||
t.end();
|
||||
});
|
||||
Reference in new issue
Block a user