kopia lustrzana https://github.com/backface/turtlestitch
Localization support for custom blocks (experimental)
rodzic
bf3eedab75
commit
3584c13258
126
byob.js
126
byob.js
|
@ -104,11 +104,11 @@ contains, InputSlotMorph, ToggleButtonMorph, IDE_Morph, MenuMorph, copy,
|
|||
ToggleElementMorph, Morph, fontHeight, StageMorph, SyntaxElementMorph,
|
||||
SnapSerializer, CommentMorph, localize, CSlotMorph, MorphicPreferences,
|
||||
SymbolMorph, isNil, CursorMorph, VariableFrame, WatcherMorph, Variable,
|
||||
BooleanSlotMorph, XML_Serializer*/
|
||||
BooleanSlotMorph, XML_Serializer, SnapTranslator*/
|
||||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.byob = '2017-October-09';
|
||||
modules.byob = '2017-December-01';
|
||||
|
||||
// Declarations
|
||||
|
||||
|
@ -146,11 +146,13 @@ function CustomBlockDefinition(spec, receiver) {
|
|||
this.comment = null;
|
||||
this.codeMapping = null; // experimental, generate text code
|
||||
this.codeHeader = null; // experimental, generate text code
|
||||
this.translations = {}; // experimental, format: {lang : spec}
|
||||
|
||||
// don't serialize (not needed for functionality):
|
||||
this.receiver = receiver || null; // for serialization only (pointer)
|
||||
this.editorDimensions = null; // a rectangle, last bounds of the editor
|
||||
this.cachedIsRecursive = null; // for automatic yielding
|
||||
this.cachedTranslation = null; // for localized block specs
|
||||
}
|
||||
|
||||
// CustomBlockDefinition instantiating blocks
|
||||
|
@ -383,6 +385,82 @@ CustomBlockDefinition.prototype.isDirectlyRecursive = function () {
|
|||
return this.cachedIsRecursive;
|
||||
};
|
||||
|
||||
// CustomBlockDefinition localizing, highly experimental
|
||||
|
||||
CustomBlockDefinition.prototype.localizedSpec = function () {
|
||||
if (this.cachedTranslation) {return this.cachedTranslation; }
|
||||
|
||||
var loc = this.translations[SnapTranslator.language],
|
||||
sem = this.blockSpec(),
|
||||
locParts,
|
||||
inputs,
|
||||
i = -1;
|
||||
|
||||
function isInput(str) {
|
||||
return (str.length > 1) && (str[0] === '%');
|
||||
}
|
||||
|
||||
if (isNil(loc)) {return sem; }
|
||||
inputs = BlockMorph.prototype.parseSpec(sem).filter(function (str) {
|
||||
return (isInput(str));
|
||||
});
|
||||
locParts = BlockMorph.prototype.parseSpec(loc);
|
||||
|
||||
// perform a bunch of sanity checks on the localized spec
|
||||
if (locParts.some(function (str) {return isInput(str); }) ||
|
||||
(locParts.filter(function (str) {return str === '_'; }).length !==
|
||||
inputs.length)
|
||||
) {
|
||||
this.cachedTranslation = sem;
|
||||
} else {
|
||||
// substitute each input place holder with its semantic spec part
|
||||
locParts = locParts.map(function (str) {
|
||||
if (str === '_') {
|
||||
i += 1;
|
||||
return inputs[i];
|
||||
}
|
||||
return str;
|
||||
});
|
||||
this.cachedTranslation = locParts.join(' ');
|
||||
}
|
||||
return this.cachedTranslation;
|
||||
};
|
||||
|
||||
CustomBlockDefinition.prototype.abstractBlockSpec = function () {
|
||||
// answer the semantic block spec substituting each input
|
||||
// with an underscore
|
||||
return BlockMorph.prototype.parseSpec(this.blockSpec()).map(
|
||||
function (str) {
|
||||
return (str.length > 1 && (str[0]) === '%') ? '_' : str;
|
||||
}
|
||||
).join(' ');
|
||||
};
|
||||
|
||||
CustomBlockDefinition.prototype.translationsAsText = function () {
|
||||
var myself = this,
|
||||
txt = '';
|
||||
Object.keys(this.translations).forEach(function (lang) {
|
||||
txt += (lang + ':' + myself.translations[lang] + '\n');
|
||||
});
|
||||
return txt;
|
||||
};
|
||||
|
||||
CustomBlockDefinition.prototype.updateTranslations = function (text) {
|
||||
var myself = this,
|
||||
lines = text.split('\n').filter(function (txt) {
|
||||
return txt.length;
|
||||
});
|
||||
this.translations = {};
|
||||
lines.forEach(function (txt) {
|
||||
var idx = txt.indexOf(':'),
|
||||
key = txt.slice(0, idx).trim(),
|
||||
val = txt.slice(idx + 1).trim();
|
||||
if (idx) {
|
||||
myself.translations[key] = val;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// CustomBlockDefinition picturing
|
||||
|
||||
CustomBlockDefinition.prototype.scriptsPicture = function () {
|
||||
|
@ -465,6 +543,7 @@ function CustomCommandBlockMorph(definition, isProto) {
|
|||
|
||||
CustomCommandBlockMorph.prototype.init = function (definition, isProto) {
|
||||
this.definition = definition; // mandatory
|
||||
this.semanticSpec = '';
|
||||
this.isGlobal = definition ? definition.isGlobal : false;
|
||||
this.isPrototype = isProto || false; // optional
|
||||
CustomCommandBlockMorph.uber.init.call(this, true); // silently
|
||||
|
@ -495,9 +574,11 @@ CustomCommandBlockMorph.prototype.initializeVariables = function (oldVars) {
|
|||
CustomCommandBlockMorph.prototype.refresh = function (aDefinition, silently) {
|
||||
var def = aDefinition || this.definition,
|
||||
newSpec = this.isPrototype ?
|
||||
def.spec : def.blockSpec(),
|
||||
def.spec : def.localizedSpec(),
|
||||
oldInputs;
|
||||
|
||||
this.semanticSpec = def.blockSpec();
|
||||
|
||||
// make sure local custom blocks don't hold on to a method.
|
||||
// future performance optimization plan:
|
||||
// null out the definition for local blocks here,
|
||||
|
@ -809,7 +890,7 @@ CustomCommandBlockMorph.prototype.edit = function () {
|
|||
this.duplicateBlockDefinition();
|
||||
return;
|
||||
}
|
||||
def = rcvr.getMethod(this.blockSpec);
|
||||
def = rcvr.getMethod(this.semanticSpec);
|
||||
}
|
||||
Morph.prototype.trackChanges = false;
|
||||
editor = new BlockEditorMorph(def, rcvr);
|
||||
|
@ -946,6 +1027,13 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
|
|||
},
|
||||
'open a new window\nwith a picture of this script'
|
||||
);
|
||||
menu.addItem(
|
||||
"translations...",
|
||||
function () {
|
||||
hat.parentThatIsA(BlockEditorMorph).editTranslations();
|
||||
},
|
||||
'experimental -\nunder construction'
|
||||
);
|
||||
if (this.isGlobal) {
|
||||
if (hat.inputs().length < 2) {
|
||||
menu.addItem(
|
||||
|
@ -1193,6 +1281,7 @@ CustomReporterBlockMorph.prototype.init = function (
|
|||
isProto
|
||||
) {
|
||||
this.definition = definition; // mandatory
|
||||
this.semanticSpec = ''; // used for translations
|
||||
this.isGlobal = definition ? definition.isGlobal : false;
|
||||
this.isPrototype = isProto || false; // optional
|
||||
CustomReporterBlockMorph.uber.init.call(this, isPredicate, true); // sil.
|
||||
|
@ -1942,6 +2031,7 @@ BlockEditorMorph.prototype.init = function (definition, target) {
|
|||
|
||||
// additional properties:
|
||||
this.definition = definition;
|
||||
this.translations = definition.translationsAsText();
|
||||
this.handle = null;
|
||||
|
||||
// initialize inherited properties:
|
||||
|
@ -2161,6 +2251,8 @@ BlockEditorMorph.prototype.updateDefinition = function () {
|
|||
this.definition.declarations = this.prototypeSlots();
|
||||
this.definition.variableNames = this.variableNames();
|
||||
this.definition.scripts = [];
|
||||
this.definition.updateTranslations(this.translations);
|
||||
this.definition.cachedTranslation = null;
|
||||
this.definition.editorDimensions = this.bounds.copy();
|
||||
this.definition.cachedIsRecursive = null; // flush the cache, don't update
|
||||
|
||||
|
@ -2247,6 +2339,32 @@ BlockEditorMorph.prototype.variableNames = function () {
|
|||
).variableNames();
|
||||
};
|
||||
|
||||
// BlockEditorMorph translation
|
||||
|
||||
BlockEditorMorph.prototype.editTranslations = function () {
|
||||
var myself = this,
|
||||
block = this.definition.blockInstance();
|
||||
block.addShadow(new Point(3, 3));
|
||||
new DialogBoxMorph(
|
||||
myself,
|
||||
function (text) {
|
||||
myself.translations = text;
|
||||
},
|
||||
myself
|
||||
).promptCode(
|
||||
'Custom Block Translations',
|
||||
myself.translations,
|
||||
myself.world(),
|
||||
block.fullImage(),
|
||||
myself.definition.abstractBlockSpec() +
|
||||
'\n\n' +
|
||||
localize('Enter one translation per line. ' +
|
||||
'use colon (":") as lang/spec delimiter\n' +
|
||||
'and underscore ("_") as placeholder for an input, ' +
|
||||
'e.g.:\n\nen:say _ for _ secs')
|
||||
);
|
||||
};
|
||||
|
||||
// BlockEditorMorph layout
|
||||
|
||||
BlockEditorMorph.prototype.setInitialDimensions = function () {
|
||||
|
|
14
store.js
14
store.js
|
@ -61,7 +61,7 @@ normalizeCanvas, contains*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.store = '2017-November-26';
|
||||
modules.store = '2017-December-01';
|
||||
|
||||
|
||||
// XML_Serializer ///////////////////////////////////////////////////////
|
||||
|
@ -875,7 +875,7 @@ SnapSerializer.prototype.loadCustomBlocks = function (
|
|||
// private
|
||||
var myself = this;
|
||||
element.children.forEach(function (child) {
|
||||
var definition, names, inputs, vars, header, code, comment, i;
|
||||
var definition, names, inputs, vars, header, code, trans, comment, i;
|
||||
if (child.tag !== 'block-definition') {
|
||||
return;
|
||||
}
|
||||
|
@ -942,6 +942,11 @@ SnapSerializer.prototype.loadCustomBlocks = function (
|
|||
definition.codeMapping = code.contents;
|
||||
}
|
||||
|
||||
trans = child.childNamed('translations');
|
||||
if (trans) {
|
||||
definition.updateTranslations(trans.contents);
|
||||
}
|
||||
|
||||
comment = child.childNamed('comment');
|
||||
if (comment) {
|
||||
definition.comment = myself.loadComment(comment);
|
||||
|
@ -1928,7 +1933,7 @@ CustomCommandBlockMorph.prototype.toBlockXML = function (serializer) {
|
|||
var scope = this.isGlobal ? undefined : 'local';
|
||||
return serializer.format(
|
||||
'<custom-block s="@"%>%%%</custom-block>',
|
||||
this.blockSpec,
|
||||
this.semanticSpec,
|
||||
this.isGlobal ?
|
||||
'' : serializer.format(' scope="@"', scope),
|
||||
serializer.store(this.inputs()),
|
||||
|
@ -1949,7 +1954,6 @@ CustomReporterBlockMorph.prototype.toBlockXML
|
|||
CustomBlockDefinition.prototype.toXML = function (serializer) {
|
||||
var myself = this;
|
||||
|
||||
|
||||
function encodeScripts(array) {
|
||||
return array.reduce(function (xml, element) {
|
||||
if (element instanceof BlockMorph) {
|
||||
|
@ -1968,6 +1972,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
|
|||
(this.variableNames.length ? '<variables>%</variables>' : '@') +
|
||||
'<header>@</header>' +
|
||||
'<code>@</code>' +
|
||||
'<translations>@</translations>' +
|
||||
'<inputs>%</inputs>%%' +
|
||||
'</block-definition>',
|
||||
this.spec,
|
||||
|
@ -1978,6 +1983,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
|
|||
serializer.store(new List(this.variableNames)) : ''),
|
||||
this.codeHeader || '',
|
||||
this.codeMapping || '',
|
||||
this.translationsAsText(),
|
||||
Object.keys(this.declarations).reduce(function (xml, decl) {
|
||||
return xml + serializer.format(
|
||||
'<input type="@"$>$%</input>',
|
||||
|
|
|
@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
|
|||
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph,
|
||||
TableFrameMorph, ColorSlotMorph, isSnapObject*/
|
||||
|
||||
modules.threads = '2017-November-16';
|
||||
modules.threads = '2017-December-01';
|
||||
|
||||
var ThreadManager;
|
||||
var Process;
|
||||
|
@ -1332,7 +1332,7 @@ Process.prototype.evaluateCustomBlock = function () {
|
|||
var caller = this.context.parentContext,
|
||||
block = this.context.expression,
|
||||
method = block.isGlobal ? block.definition
|
||||
: this.blockReceiver().getMethod(block.blockSpec),
|
||||
: this.blockReceiver().getMethod(block.semanticSpec),
|
||||
context = method.body,
|
||||
declarations = method.declarations,
|
||||
args = new List(this.context.inputs),
|
||||
|
|
Ładowanie…
Reference in New Issue