Skip to content
This repository has been archived by the owner on Sep 3, 2019. It is now read-only.

Commit

Permalink
Merge pull request #495 from LLK/translate-variables
Browse files Browse the repository at this point in the history
Translate variables
  • Loading branch information
sclements committed Sep 22, 2014
2 parents 3cb6cf9 + a986ca4 commit 9b0f2d7
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 95 deletions.
2 changes: 1 addition & 1 deletion src/Scratch.as
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ public class Scratch extends Sprite {
m.addLine();
m.addItem('Import experimental extension', function():void {
function loadJSExtension(dialog:DialogBox):void {
var url:String = dialog.fields['URL'].text.replace(/^\s+|\s+$/g, '');
var url:String = dialog.getField('URL').replace(/^\s+|\s+$/g, '');
if (url.length == 0) return;
externalCall('ScratchExtensions.loadExternalJS', null, url);
}
Expand Down
4 changes: 2 additions & 2 deletions src/scratch/BlockMenus.as
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ public class BlockMenus implements DragClient {

private function renameVar():void {
function doVarRename(dialog:DialogBox):void {
var newName:String = dialog.fields['New name'].text.replace(/^\s+|\s+$/g, '');
var newName:String = dialog.getField('New name').replace(/^\s+|\s+$/g, '');
if (newName.length == 0 || app.viewedObj().lookupVar(newName)) return;
if (block.op != Specs.GET_VAR) return;
var oldName:String = blockVarOrListName();
Expand Down Expand Up @@ -759,7 +759,7 @@ public class BlockMenus implements DragClient {

private function newBroadcast():void {
function changeBroadcast(dialog:DialogBox):void {
var newName:String = dialog.fields['Message Name'].text;
var newName:String = dialog.getField('Message Name');
if (newName.length == 0) return;
setBlockArg(newName);
}
Expand Down
4 changes: 2 additions & 2 deletions src/scratch/PaletteBuilder.as
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public class PaletteBuilder {

private function makeVariable():void {
function makeVar2():void {
var n:String = d.fields['Variable name'].text.replace(/^\s+|\s+$/g, '');
var n:String = d.getField('Variable name').replace(/^\s+|\s+$/g, '');
if (n.length == 0) return;

createVar(n, varSettings);
Expand All @@ -209,7 +209,7 @@ public class PaletteBuilder {

private function makeList():void {
function makeList2(d:DialogBox):void {
var n:String = d.fields['List name'].text.replace(/^\s+|\s+$/g, '');
var n:String = d.getField('List name').replace(/^\s+|\s+$/g, '');
if (n.length == 0) return;

createVar(n, varSettings);
Expand Down
86 changes: 22 additions & 64 deletions src/translation/Translator.as
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@
*/

package translation {
import flash.events.Event;
import flash.net.*;
import flash.utils.ByteArray;
import blocks.Block;
import uiwidgets.Menu;
import util.*;
import flash.events.Event;
import flash.net.*;
import flash.utils.ByteArray;
import flash.utils.Dictionary;
import blocks.Block;

import mx.utils.StringUtil;

import uiwidgets.Menu;
import util.*;

public class Translator {

Expand All @@ -36,8 +40,7 @@ public class Translator {
private static const font12:Array = ['fa', 'he','ja','ja_HIRA', 'zh_CN'];
private static const font13:Array = ['ar'];

private static var dictionary:Object = new Object();
private static var isEnglish:Boolean = true;
private static var dictionary:Object = {};

public static function initializeLanguageList():void {
// Get a list of language names for the languages menu from the server.
Expand All @@ -46,7 +49,7 @@ public class Translator {
for each (var line:String in data.split('\n')) {
var fields:Array = line.split(',');
if (fields.length >= 2) {
languages.push([trimWhitespace(fields[0]), trimWhitespace(fields[1])]);
languages.push([StringUtil.trim(fields[0]), StringUtil.trim(fields[1])]);
}
}
}
Expand All @@ -66,8 +69,7 @@ public class Translator {
if ('import translation file' == lang) { importTranslationFromFile(); return; }
if ('set font size' == lang) { fontSizeMenu(); return; }

dictionary = new Object(); // default to English (empty dictionary) if there's no .po file
isEnglish = true;
dictionary = {}; // default to English (empty dictionary) if there's no .po file
setFontsFor('en');
if ('en' == lang) Scratch.app.translationChanged(); // there is no .po file English
else Scratch.app.server.getPOFile(lang, gotPOFile);
Expand All @@ -82,7 +84,6 @@ public class Translator {
var langName:String = file.name.slice(0, i);
var data:ByteArray = file.data;
if (data) {
dictionary = new Object(); // default to English
dictionary = parsePOData(data);
setFontsFor(langName);
checkBlockTranslations();
Expand All @@ -109,7 +110,6 @@ public class Translator {
// Set the rightToLeft flag and font sizes the given language.

currentLang = lang;
isEnglish = (lang == 'en');

const rtlLanguages:Array = ['ar', 'fa', 'he'];
rightToLeft = rtlLanguages.indexOf(lang) > -1;
Expand All @@ -119,12 +119,10 @@ public class Translator {
if (font13.indexOf(lang) > -1) Block.setFonts(13, 12, false, 0);
}

public static function map(s:String):String {
//return TranslatableStrings.has(s) ? s.toUpperCase() : s;
//return (s.indexOf('%') > -1) ? s : s.toUpperCase(); // xxx testing only
public static function map(s:String, context:Dictionary=null):String {
var result:* = dictionary[s];
//if (!isEnglish && (s.indexOf('%') == -1)) return s.toUpperCase(); // xxx for testing only; comment out before pushing!
if ((result == null) || (result.length == 0)) return s;
if ((result == null) || (result.length == 0)) result = s;
if (context) result = StringUtils.substitute(result, context);
return result;
}

Expand All @@ -133,7 +131,7 @@ public class Translator {
skipBOM(bytes);
var lines:Array = [];
while (bytes.bytesAvailable > 0) {
var s:String = trimWhitespace(nextLine(bytes));
var s:String = StringUtil.trim(nextLine(bytes));
if ((s.length > 0) && (s.charAt(0) != '#')) lines.push(s);
}
return makeDictionary(lines);
Expand All @@ -150,37 +148,26 @@ public class Translator {
bytes.position = bytes.position - 3; // BOM not found; back up
}

private static function trimWhitespace(s:String):String {
// Remove leading and trailing whitespace characters.
if (s.length == 0) return ''; // empty
var i:int = 0;
while ((i < s.length) && (s.charCodeAt(i) <= 32)) i++;
if (i == s.length) return ''; // all whitespace
var j:int = s.length - 1;
while ((j > i) && (s.charCodeAt(j) <= 32)) j--;
return s.slice(i, j + 1);
}

private static function nextLine(bytes:ByteArray):String {
// Read the next line from the given ByteArray. A line ends with CR, LF, or CR-LF.
var buf:ByteArray = new ByteArray();
while (bytes.bytesAvailable > 0) {
var byte:int = bytes.readUnsignedByte();
if (byte == 13) { // CR
var nextByte:int = bytes.readUnsignedByte();
if (nextByte == 13) { // CR
// line could end in CR or CR-LF
if (bytes.readUnsignedByte() != 10) bytes.position--; // try to read LF, but backup if not LF
break;
}
if (byte == 10) break; // LF
buf.writeByte(byte); // append anything else
if (nextByte == 10) break; // LF
buf.writeByte(nextByte); // append anything else
}
buf.position = 0;
return buf.readUTFBytes(buf.length);
}

private static function makeDictionary(lines:Array):Object {
// Return a dictionary mapping original strings to their translations.
var dict:Object = new Object();
var dict:Object = {};
var mode:String = 'none'; // none, key, val
var key:String = '';
var val:String = '';
Expand Down Expand Up @@ -219,34 +206,6 @@ public class Translator {
return result;
}

private static function recordPairIn(key:String, val:String, dict:Object):void {
// Handle some special cases where block specs changed for Scratch 2.0.
// Note: No longer needed now that translators are starting with current block specs.
switch (key) {
case '%a of %m':
val = val.replace('%a', '%m.attribute');
val = val.replace('%m', '%m.sprite');
dict['%m.attribute of %m.sprite'] = val;
break;
case 'stop all':
dict['@stop stop all'] = '@stop ' + val;
break;
case 'touching %m?':
dict['touching %m.touching?'] = val.replace('%m', '%m.touching');
break;
case 'turn %n degrees':
dict['turn @turnRight %n degrees'] = val.replace('%n', '@turnRight %n');
dict['turn @turnLeft %n degrees'] = val.replace('%n', '@turnLeft %n');
break;
case 'when %m clicked':
dict['when @greenFlag clicked'] = val.replace('%m', '@greenFlag');
dict['when I am clicked'] = val.replace('%m', 'I am');
break;
default:
dict[key] = val;
}
}

private static function checkBlockTranslations():void {
for each (var entry:Array in Specs.commands) checkBlockSpec(entry[0]);
for each (var spec:String in Specs.extensionSpecs) checkBlockSpec(spec);
Expand All @@ -255,7 +214,6 @@ public class Translator {
private static function checkBlockSpec(spec:String):void {
var translatedSpec:String = map(spec);
if (translatedSpec == spec) return; // not translated
var origArgs:Array = extractArgs(spec);
if (!argsMatch(extractArgs(spec), extractArgs(translatedSpec))) {
Scratch.app.log('Block argument mismatch:');
Scratch.app.log(' ' + spec);
Expand Down
68 changes: 44 additions & 24 deletions src/uiwidgets/DialogBox.as
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ package uiwidgets {

public class DialogBox extends Sprite {

public var fields:Dictionary = new Dictionary();
public var booleanFields:Dictionary = new Dictionary();
private var fields:Dictionary = new Dictionary();
private var booleanFields:Dictionary = new Dictionary();
public var widget:DisplayObject;
public var w:int, h:int;
private var w:int, h:int;
public var leftJustify:Boolean;

private var context:Dictionary;
private var title:TextField;
protected var buttons:Array = [];
private var labelsAndFields:Array = [];
Expand All @@ -58,31 +59,50 @@ public class DialogBox extends Sprite {
addEventListener(FocusEvent.KEY_FOCUS_CHANGE, focusChange);
}

public static function ask(question:String, defaultAnswer:String, stage:Stage = null, resultFunction:Function = null):void {
public static function ask(question:String, defaultAnswer:String, stage:Stage = null, resultFunction:Function = null, context:Dictionary = null):void {
function done():void { if (resultFunction != null) resultFunction(d.fields['answer'].text) }
var d:DialogBox = new DialogBox(done);
d.addTitle(question);
d.addField('answer', 120, defaultAnswer, false);
d.addButton('OK', d.accept);
if (context) d.updateContext(context);
d.showOnStage(stage ? stage : Scratch.app.stage);
}

public static function confirm(question:String, stage:Stage = null, okFunction:Function = null, cancelFunction:Function = null):void {
public static function confirm(question:String, stage:Stage = null, okFunction:Function = null, cancelFunction:Function = null, context:Dictionary = null):void {
var d:DialogBox = new DialogBox(okFunction, cancelFunction);
d.addTitle(question);
d.addAcceptCancelButtons('OK');
if (context) d.updateContext(context);
d.showOnStage(stage ? stage : Scratch.app.stage);
}

public static function notify(title:String, msg:String, stage:Stage = null, leftJustify:Boolean = false, okFunction:Function = null, cancelFunction:Function = null):void {
public static function notify(title:String, msg:String, stage:Stage = null, leftJustify:Boolean = false, okFunction:Function = null, cancelFunction:Function = null, context:Dictionary = null):void {
var d:DialogBox = new DialogBox(okFunction, cancelFunction);
d.leftJustify = leftJustify;
d.addTitle(title);
d.addText(msg);
d.addButton('OK', d.accept);
if (context) d.updateContext(context);
d.showOnStage(stage ? stage : Scratch.app.stage);
}

// Updates the context for variable substitution in the dialog's text, or sets it if there was none before.
// Make sure any text values in the context are already translated: they will not be translated here.
// Calling this will update the text of the dialog immediately.
public function updateContext(c:Dictionary):void {
if (!context) context = new Dictionary();
for (var key:String in c) {
context[key] = c[key];
}
for (var i:int = 0; i < numChildren; ++i) {
var f:VariableTextField = getChildAt(i) as VariableTextField;
if (f) {
f.applyContext(context);
}
}
}

public function addTitle(s:String):void {
title = makeLabel(Translator.map(s), true);
addChild(title);
Expand Down Expand Up @@ -126,23 +146,23 @@ public class DialogBox extends Sprite {
booleanLabelsAndFields.push([l, f]);
}

private function getCheckMark(b:Boolean):Sprite{
var spr:Sprite = new Sprite();
var g:Graphics = spr.graphics;
g.clear();
g.beginFill(0xFFFFFF);
g.lineStyle(1, 0x929497, 1, true);
g.drawRoundRect(0, 0, 17, 17, 3, 3);
g.endFill();
if (b) {
g.lineStyle(2, 0x4c4d4f, 1, true);
g.moveTo(3,7);
g.lineTo(5,7);
g.lineTo(8,13);
g.lineTo(14,3);
private function getCheckMark(b:Boolean):Sprite{
var spr:Sprite = new Sprite();
var g:Graphics = spr.graphics;
g.clear();
g.beginFill(0xFFFFFF);
g.lineStyle(1, 0x929497, 1, true);
g.drawRoundRect(0, 0, 17, 17, 3, 3);
g.endFill();
if (b) {
g.lineStyle(2, 0x4c4d4f, 1, true);
g.moveTo(3,7);
g.lineTo(5,7);
g.lineTo(8,13);
g.lineTo(14,3);
}
return spr;
}
return spr;
}

public function addAcceptCancelButtons(acceptLabel:String = null):void {
// Add a cancel button and an optional accept button with the given label.
Expand Down Expand Up @@ -225,11 +245,11 @@ private function getCheckMark(b:Boolean):Sprite{

private function makeLabel(s:String, forTitle:Boolean = false):TextField {
const normalFormat:TextFormat = new TextFormat(CSS.font, 14, CSS.textColor);
var result:TextField = new TextField();
var result:VariableTextField = new VariableTextField();
result.autoSize = TextFieldAutoSize.LEFT;
result.selectable = false;
result.background = false;
result.text = s;
result.setText(s, context);
result.setTextFormat(forTitle ? CSS.titleFormat : normalFormat);
return result;
}
Expand Down
47 changes: 47 additions & 0 deletions src/uiwidgets/VariableTextField.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Scratch Project Editor and Player
* Copyright (C) 2014 Massachusetts Institute of Technology
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package uiwidgets {
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.Dictionary;

import util.StringUtils;

public class VariableTextField extends TextField {
private var originalText:String;

override public function set text(value:String):void {
throw Error('Call setText() instead');
}

public function setText(t: String, context:Dictionary = null):void {
originalText = t;
applyContext(context);
}

// Re-substitutes values from this new context into the original text.
// This context must be a complete context, not just the fields that have changed.
public function applyContext(context: Dictionary):void {
// Assume that the whole text field uses the same format since there's no guarantee how indices will map.
var oldFormat:TextFormat = this.getTextFormat();
super.text = context ? StringUtils.substitute(originalText, context) : originalText;
setTextFormat(oldFormat);
}
}
}
Loading

0 comments on commit 9b0f2d7

Please sign in to comment.