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,
|
ToggleElementMorph, Morph, fontHeight, StageMorph, SyntaxElementMorph,
|
||||||
SnapSerializer, CommentMorph, localize, CSlotMorph, MorphicPreferences,
|
SnapSerializer, CommentMorph, localize, CSlotMorph, MorphicPreferences,
|
||||||
SymbolMorph, isNil, CursorMorph, VariableFrame, WatcherMorph, Variable,
|
SymbolMorph, isNil, CursorMorph, VariableFrame, WatcherMorph, Variable,
|
||||||
BooleanSlotMorph, XML_Serializer*/
|
BooleanSlotMorph, XML_Serializer, SnapTranslator*/
|
||||||
|
|
||||||
// Global stuff ////////////////////////////////////////////////////////
|
// Global stuff ////////////////////////////////////////////////////////
|
||||||
|
|
||||||
modules.byob = '2017-October-09';
|
modules.byob = '2017-December-01';
|
||||||
|
|
||||||
// Declarations
|
// Declarations
|
||||||
|
|
||||||
|
@ -146,11 +146,13 @@ function CustomBlockDefinition(spec, receiver) {
|
||||||
this.comment = null;
|
this.comment = null;
|
||||||
this.codeMapping = null; // experimental, generate text code
|
this.codeMapping = null; // experimental, generate text code
|
||||||
this.codeHeader = 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):
|
// don't serialize (not needed for functionality):
|
||||||
this.receiver = receiver || null; // for serialization only (pointer)
|
this.receiver = receiver || null; // for serialization only (pointer)
|
||||||
this.editorDimensions = null; // a rectangle, last bounds of the editor
|
this.editorDimensions = null; // a rectangle, last bounds of the editor
|
||||||
this.cachedIsRecursive = null; // for automatic yielding
|
this.cachedIsRecursive = null; // for automatic yielding
|
||||||
|
this.cachedTranslation = null; // for localized block specs
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomBlockDefinition instantiating blocks
|
// CustomBlockDefinition instantiating blocks
|
||||||
|
@ -383,6 +385,82 @@ CustomBlockDefinition.prototype.isDirectlyRecursive = function () {
|
||||||
return this.cachedIsRecursive;
|
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 picturing
|
||||||
|
|
||||||
CustomBlockDefinition.prototype.scriptsPicture = function () {
|
CustomBlockDefinition.prototype.scriptsPicture = function () {
|
||||||
|
@ -465,6 +543,7 @@ function CustomCommandBlockMorph(definition, isProto) {
|
||||||
|
|
||||||
CustomCommandBlockMorph.prototype.init = function (definition, isProto) {
|
CustomCommandBlockMorph.prototype.init = function (definition, isProto) {
|
||||||
this.definition = definition; // mandatory
|
this.definition = definition; // mandatory
|
||||||
|
this.semanticSpec = '';
|
||||||
this.isGlobal = definition ? definition.isGlobal : false;
|
this.isGlobal = definition ? definition.isGlobal : false;
|
||||||
this.isPrototype = isProto || false; // optional
|
this.isPrototype = isProto || false; // optional
|
||||||
CustomCommandBlockMorph.uber.init.call(this, true); // silently
|
CustomCommandBlockMorph.uber.init.call(this, true); // silently
|
||||||
|
@ -495,9 +574,11 @@ CustomCommandBlockMorph.prototype.initializeVariables = function (oldVars) {
|
||||||
CustomCommandBlockMorph.prototype.refresh = function (aDefinition, silently) {
|
CustomCommandBlockMorph.prototype.refresh = function (aDefinition, silently) {
|
||||||
var def = aDefinition || this.definition,
|
var def = aDefinition || this.definition,
|
||||||
newSpec = this.isPrototype ?
|
newSpec = this.isPrototype ?
|
||||||
def.spec : def.blockSpec(),
|
def.spec : def.localizedSpec(),
|
||||||
oldInputs;
|
oldInputs;
|
||||||
|
|
||||||
|
this.semanticSpec = def.blockSpec();
|
||||||
|
|
||||||
// make sure local custom blocks don't hold on to a method.
|
// make sure local custom blocks don't hold on to a method.
|
||||||
// future performance optimization plan:
|
// future performance optimization plan:
|
||||||
// null out the definition for local blocks here,
|
// null out the definition for local blocks here,
|
||||||
|
@ -809,7 +890,7 @@ CustomCommandBlockMorph.prototype.edit = function () {
|
||||||
this.duplicateBlockDefinition();
|
this.duplicateBlockDefinition();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
def = rcvr.getMethod(this.blockSpec);
|
def = rcvr.getMethod(this.semanticSpec);
|
||||||
}
|
}
|
||||||
Morph.prototype.trackChanges = false;
|
Morph.prototype.trackChanges = false;
|
||||||
editor = new BlockEditorMorph(def, rcvr);
|
editor = new BlockEditorMorph(def, rcvr);
|
||||||
|
@ -946,6 +1027,13 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
|
||||||
},
|
},
|
||||||
'open a new window\nwith a picture of this script'
|
'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 (this.isGlobal) {
|
||||||
if (hat.inputs().length < 2) {
|
if (hat.inputs().length < 2) {
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
|
@ -1193,6 +1281,7 @@ CustomReporterBlockMorph.prototype.init = function (
|
||||||
isProto
|
isProto
|
||||||
) {
|
) {
|
||||||
this.definition = definition; // mandatory
|
this.definition = definition; // mandatory
|
||||||
|
this.semanticSpec = ''; // used for translations
|
||||||
this.isGlobal = definition ? definition.isGlobal : false;
|
this.isGlobal = definition ? definition.isGlobal : false;
|
||||||
this.isPrototype = isProto || false; // optional
|
this.isPrototype = isProto || false; // optional
|
||||||
CustomReporterBlockMorph.uber.init.call(this, isPredicate, true); // sil.
|
CustomReporterBlockMorph.uber.init.call(this, isPredicate, true); // sil.
|
||||||
|
@ -1942,6 +2031,7 @@ BlockEditorMorph.prototype.init = function (definition, target) {
|
||||||
|
|
||||||
// additional properties:
|
// additional properties:
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
|
this.translations = definition.translationsAsText();
|
||||||
this.handle = null;
|
this.handle = null;
|
||||||
|
|
||||||
// initialize inherited properties:
|
// initialize inherited properties:
|
||||||
|
@ -2161,6 +2251,8 @@ BlockEditorMorph.prototype.updateDefinition = function () {
|
||||||
this.definition.declarations = this.prototypeSlots();
|
this.definition.declarations = this.prototypeSlots();
|
||||||
this.definition.variableNames = this.variableNames();
|
this.definition.variableNames = this.variableNames();
|
||||||
this.definition.scripts = [];
|
this.definition.scripts = [];
|
||||||
|
this.definition.updateTranslations(this.translations);
|
||||||
|
this.definition.cachedTranslation = null;
|
||||||
this.definition.editorDimensions = this.bounds.copy();
|
this.definition.editorDimensions = this.bounds.copy();
|
||||||
this.definition.cachedIsRecursive = null; // flush the cache, don't update
|
this.definition.cachedIsRecursive = null; // flush the cache, don't update
|
||||||
|
|
||||||
|
@ -2247,6 +2339,32 @@ BlockEditorMorph.prototype.variableNames = function () {
|
||||||
).variableNames();
|
).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 layout
|
||||||
|
|
||||||
BlockEditorMorph.prototype.setInitialDimensions = function () {
|
BlockEditorMorph.prototype.setInitialDimensions = function () {
|
||||||
|
|
14
store.js
14
store.js
|
@ -61,7 +61,7 @@ normalizeCanvas, contains*/
|
||||||
|
|
||||||
// Global stuff ////////////////////////////////////////////////////////
|
// Global stuff ////////////////////////////////////////////////////////
|
||||||
|
|
||||||
modules.store = '2017-November-26';
|
modules.store = '2017-December-01';
|
||||||
|
|
||||||
|
|
||||||
// XML_Serializer ///////////////////////////////////////////////////////
|
// XML_Serializer ///////////////////////////////////////////////////////
|
||||||
|
@ -875,7 +875,7 @@ SnapSerializer.prototype.loadCustomBlocks = function (
|
||||||
// private
|
// private
|
||||||
var myself = this;
|
var myself = this;
|
||||||
element.children.forEach(function (child) {
|
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') {
|
if (child.tag !== 'block-definition') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -942,6 +942,11 @@ SnapSerializer.prototype.loadCustomBlocks = function (
|
||||||
definition.codeMapping = code.contents;
|
definition.codeMapping = code.contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trans = child.childNamed('translations');
|
||||||
|
if (trans) {
|
||||||
|
definition.updateTranslations(trans.contents);
|
||||||
|
}
|
||||||
|
|
||||||
comment = child.childNamed('comment');
|
comment = child.childNamed('comment');
|
||||||
if (comment) {
|
if (comment) {
|
||||||
definition.comment = myself.loadComment(comment);
|
definition.comment = myself.loadComment(comment);
|
||||||
|
@ -1928,7 +1933,7 @@ CustomCommandBlockMorph.prototype.toBlockXML = function (serializer) {
|
||||||
var scope = this.isGlobal ? undefined : 'local';
|
var scope = this.isGlobal ? undefined : 'local';
|
||||||
return serializer.format(
|
return serializer.format(
|
||||||
'<custom-block s="@"%>%%%</custom-block>',
|
'<custom-block s="@"%>%%%</custom-block>',
|
||||||
this.blockSpec,
|
this.semanticSpec,
|
||||||
this.isGlobal ?
|
this.isGlobal ?
|
||||||
'' : serializer.format(' scope="@"', scope),
|
'' : serializer.format(' scope="@"', scope),
|
||||||
serializer.store(this.inputs()),
|
serializer.store(this.inputs()),
|
||||||
|
@ -1949,7 +1954,6 @@ CustomReporterBlockMorph.prototype.toBlockXML
|
||||||
CustomBlockDefinition.prototype.toXML = function (serializer) {
|
CustomBlockDefinition.prototype.toXML = function (serializer) {
|
||||||
var myself = this;
|
var myself = this;
|
||||||
|
|
||||||
|
|
||||||
function encodeScripts(array) {
|
function encodeScripts(array) {
|
||||||
return array.reduce(function (xml, element) {
|
return array.reduce(function (xml, element) {
|
||||||
if (element instanceof BlockMorph) {
|
if (element instanceof BlockMorph) {
|
||||||
|
@ -1968,6 +1972,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
|
||||||
(this.variableNames.length ? '<variables>%</variables>' : '@') +
|
(this.variableNames.length ? '<variables>%</variables>' : '@') +
|
||||||
'<header>@</header>' +
|
'<header>@</header>' +
|
||||||
'<code>@</code>' +
|
'<code>@</code>' +
|
||||||
|
'<translations>@</translations>' +
|
||||||
'<inputs>%</inputs>%%' +
|
'<inputs>%</inputs>%%' +
|
||||||
'</block-definition>',
|
'</block-definition>',
|
||||||
this.spec,
|
this.spec,
|
||||||
|
@ -1978,6 +1983,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
|
||||||
serializer.store(new List(this.variableNames)) : ''),
|
serializer.store(new List(this.variableNames)) : ''),
|
||||||
this.codeHeader || '',
|
this.codeHeader || '',
|
||||||
this.codeMapping || '',
|
this.codeMapping || '',
|
||||||
|
this.translationsAsText(),
|
||||||
Object.keys(this.declarations).reduce(function (xml, decl) {
|
Object.keys(this.declarations).reduce(function (xml, decl) {
|
||||||
return xml + serializer.format(
|
return xml + serializer.format(
|
||||||
'<input type="@"$>$%</input>',
|
'<input type="@"$>$%</input>',
|
||||||
|
|
|
@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
|
||||||
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph,
|
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph,
|
||||||
TableFrameMorph, ColorSlotMorph, isSnapObject*/
|
TableFrameMorph, ColorSlotMorph, isSnapObject*/
|
||||||
|
|
||||||
modules.threads = '2017-November-16';
|
modules.threads = '2017-December-01';
|
||||||
|
|
||||||
var ThreadManager;
|
var ThreadManager;
|
||||||
var Process;
|
var Process;
|
||||||
|
@ -1332,7 +1332,7 @@ Process.prototype.evaluateCustomBlock = function () {
|
||||||
var caller = this.context.parentContext,
|
var caller = this.context.parentContext,
|
||||||
block = this.context.expression,
|
block = this.context.expression,
|
||||||
method = block.isGlobal ? block.definition
|
method = block.isGlobal ? block.definition
|
||||||
: this.blockReceiver().getMethod(block.blockSpec),
|
: this.blockReceiver().getMethod(block.semanticSpec),
|
||||||
context = method.body,
|
context = method.body,
|
||||||
declarations = method.declarations,
|
declarations = method.declarations,
|
||||||
args = new List(this.context.inputs),
|
args = new List(this.context.inputs),
|
||||||
|
|
Ładowanie…
Reference in New Issue