Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Added localization support to name/description elements
+Hijacked data2mxl node module in order to fix a bug in their code.

Thanks to @sleroux for a large portion of this work.

Fixes blackberry#122
  • Loading branch information
James Keshavarzi committed Nov 16, 2012
1 parent 087249b commit a66c0cb
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 19 deletions.
48 changes: 44 additions & 4 deletions lib/config-parser.js
Expand Up @@ -418,9 +418,14 @@ function validateConfig(widgetConfig) {
check(widgetConfig.version, localize.translate("EXCEPTION_INVALID_VERSION"))
.notNull()
.regex("^[0-9]{1,3}([.][0-9]{1,3}){2,3}$");
check(widgetConfig.name, localize.translate("EXCEPTION_INVALID_NAME")).notEmpty();
check(widgetConfig.author, localize.translate("EXCEPTION_INVALID_AUTHOR")).notNull();

for (var prop in widgetConfig.name) {
if (widgetConfig.name.hasOwnProperty(prop)) {
check(widgetConfig.name[prop], localize.translate("EXCEPTION_INVALID_NAME")).notEmpty();
}
}

check(widgetConfig.author, localize.translate("EXCEPTION_INVALID_AUTHOR")).notNull();
check(widgetConfig.content, localize.translate("EXCEPTION_INVALID_CONTENT"))
.notNull()
.notEmpty();
Expand Down Expand Up @@ -492,6 +497,42 @@ function validateConfig(widgetConfig) {
}
}

function processLocalizedText(tag, data, widgetConfig) {
var tagData = data[tag];

function processLanguage(tagElement) {
var attribs = tagElement['@'],
language;

if (attribs) {
language = attribs['xml:lang'] || 'default';
widgetConfig[tag][language] = tagElement['#'];
} else {
widgetConfig[tag]['default'] = tagElement;
}
}

if (tagData instanceof Array) {
//i.e. <element xml:lang="en">english value</element>
// <element xml:lang="fr">french value</element>
tagData.forEach(processLanguage);
} else if (tagData instanceof Object) {
//i.e. <element xml:lang="en">english value</element>
processLanguage(tagData);
} else {
//i.e <element>value</element>
widgetConfig[tag]['default'] = tagData;
}
}

function processNameAndDescription(data, widgetConfig) {
widgetConfig.name = {};
widgetConfig.description = {};

processLocalizedText('name', data, widgetConfig);
processLocalizedText('description', data, widgetConfig);
}

function processResult(data, session, extManager) {
var widgetConfig = {};

Expand All @@ -503,9 +544,8 @@ function processResult(data, session, extManager) {
processPermissionsData(data, widgetConfig);
processInvokeTargetsData(data, widgetConfig);
processSplashScreenData(data, widgetConfig);
processNameAndDescription(data, widgetConfig);

widgetConfig.name = data.name;
widgetConfig.description = data.description;
widgetConfig.configXML = "config.xml";

//validate the widgetConfig
Expand Down
30 changes: 28 additions & 2 deletions lib/i18n-manager.js
Expand Up @@ -87,6 +87,31 @@ function generateLocalizedMetadataForSplashScreenIcon(config, configKey, xmlObje
}
}

function generateLocalizedText(session, config, xmlObject, key) {
var localizedText = config[key],
textElements = [],
locale;

for (locale in localizedText) {
if (localizedText.hasOwnProperty(locale)) {
//Don't add default locale and don't add locale if it already exists
if (locale !== 'default' && textElements && textElements.indexOf(locale) === -1) {
textElements.push({
_attr: {
"xml:lang": locale
},
_value: localizedText[locale]
});
}
}
}

xmlObject[key] = {
_value: localizedText['default'],
text: textElements
};
}

function generateLocalizedMetadata(session, config, xmlObject, key) {
if (config.icon || config["rim:splash"]) {
var localeFiles,
Expand All @@ -111,5 +136,6 @@ function generateLocalizedMetadata(session, config, xmlObject, key) {

module.exports = {
LOCALES_DIR: LOCALES_DIR,
generateLocalizedMetadata: generateLocalizedMetadata
};
generateLocalizedMetadata: generateLocalizedMetadata,
generateLocalizedText: generateLocalizedText
};
13 changes: 6 additions & 7 deletions lib/native-packager.js
Expand Up @@ -18,7 +18,7 @@ var childProcess = require("child_process"),
fs = require("fs"),
path = require("path"),
util = require("util"),
data2xml = require("data2xml"),
data2xml = require("../third_party/data2xml/data2xml"),
wrench = require("wrench"),
conf = require("./conf"),
logger = require("./logger"),
Expand All @@ -31,7 +31,6 @@ function generateTabletXMLFile(session, config) {
var files = wrench.readdirSyncRecursive(session.sourceDir),
xmlObject = {
id : config.id,
name : config.name,
versionNumber : config.version,
author : config.author,
asset : [{
Expand Down Expand Up @@ -83,6 +82,11 @@ function generateTabletXMLFile(session, config) {
});
}

i18nMgr.generateLocalizedText(session, config, xmlObject, "name");

if (config.description) {
i18nMgr.generateLocalizedText(session, config, xmlObject, "description");
}

i18nMgr.generateLocalizedMetadata(session, config, xmlObject, "icon");
i18nMgr.generateLocalizedMetadata(session, config, xmlObject, "rim:splash");
Expand Down Expand Up @@ -157,11 +161,6 @@ function generateTabletXMLFile(session, config) {
});
}

//Add description element if specifed
if (config.description) {
xmlObject.description = config.description;
}

//Add orientation mode
if (config.orientation) {
xmlObject.initialWindow.aspectRatio = config.orientation;
Expand Down
126 changes: 124 additions & 2 deletions test/unit/lib/config-parser.js
Expand Up @@ -46,8 +46,8 @@ describe("config parser", function () {
expect(configObj.authorURL).toEqual("http://www.rim.com/");
expect(configObj.copyright).toEqual("No Copyright");
expect(configObj.authorEmail).toEqual("author@rim.com");
expect(configObj.name).toEqual("Demo");
expect(configObj.description).toEqual("This app does everything.");
expect(configObj.name).toEqual({ default : 'Demo' });
expect(configObj.description).toEqual({ default : 'This app does everything.' });
expect(configObj.permissions).toContain('access_shared');
expect(configObj.permissions).toContain('read_geolocation');
expect(configObj.permissions).toContain('use_camera');
Expand Down Expand Up @@ -127,6 +127,128 @@ describe("config parser", function () {
});
});

it("Fails when no name was provided - single element", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.name = "";

mockParsing(data);

expect(function () {
configParser.parse(configPath, session, extManager, {});
}).toThrow(localize.translate("EXCEPTION_INVALID_NAME"));
});

it("Fails when no name was provided - multiple elements", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.name = ["",
{ '#': 'API Smoke Test-FR', '@': { 'xml:lang': 'FR' } },
];

mockParsing(data);

expect(function () {
configParser.parse(configPath, session, extManager, {});
}).toThrow(localize.translate("EXCEPTION_INVALID_NAME"));
});

it("Fails when localized name was provided but empty", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.name = ["API Smoke Test",
{ '#': '', '@': { 'xml:lang': 'FR' } },
];

mockParsing(data);

expect(function () {
configParser.parse(configPath, session, extManager, {});
}).toThrow(localize.translate("EXCEPTION_INVALID_NAME"));
});

it("Parses a name element - single element", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.name = "API Smoke Test";

mockParsing(data);

configParser.parse(configPath, session, extManager, function (configObj) {
expect(configObj.name).toEqual({"default": "API Smoke Test"});
});
});

it("Parses a name element with xml:lang - single element", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.name = { '#': 'EN VALUE', '@': { 'xml:lang': 'EN' } };

mockParsing(data);

configParser.parse(configPath, session, extManager, function (configObj) {
expect(configObj.name).toEqual({"EN": "EN VALUE"});
});
});

it("Parses a name element with xml:lang - multi element", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.name = ['API Smoke Test',
{ '#': 'EN VALUE', '@': { 'xml:lang': 'EN' } },
{ '#': 'FR VALUE', '@': { 'xml:lang': 'FR' } }

];
mockParsing(data);

configParser.parse(configPath, session, extManager, function (configObj) {
expect(configObj.name).toEqual({"default": "API Smoke Test", "EN": "EN VALUE", "FR": "FR VALUE"});
});
});

it("Fails when localized name was provided but empty", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.name = ['API Smoke Test',
{ '#': '', '@': { 'xml:lang': 'FR' } },
];

mockParsing(data);

expect(function () {
configParser.parse(configPath, session, extManager, {});
}).toThrow(localize.translate("EXCEPTION_INVALID_NAME"));
});

it("Parses a description element - single element", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.description = "This is my app";

mockParsing(data);

configParser.parse(configPath, session, extManager, function (configObj) {
expect(configObj.description).toEqual({"default": "This is my app"});
});
});

it("Parses a description element with xml:lang - single element", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.description = { '#': 'EN VALUE', '@': { 'xml:lang': 'EN' } };

mockParsing(data);

configParser.parse(configPath, session, extManager, function (configObj) {
expect(configObj.description).toEqual({"EN": "EN VALUE"});
});
});

it("Parses a description element with xml:lang - multi element", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.description = ['This is my app',
{ '#': 'EN VALUE', '@': { 'xml:lang': 'EN' } },
{ '#': 'FR VALUE', '@': { 'xml:lang': 'FR' } }

];
mockParsing(data);

configParser.parse(configPath, session, extManager, function (configObj) {
expect(configObj.description).toEqual({"default": "This is my app", "EN": "EN VALUE", "FR": "FR VALUE"});
});
});

it("Fails when missing content error is not shown", function () {
var data = testUtilities.cloneObj(testData.xml2jsConfig);
data.content = "";
Expand Down
80 changes: 78 additions & 2 deletions test/unit/lib/native-packager.js
Expand Up @@ -116,7 +116,6 @@ describe("Native packager", function () {
it("exec blackberry-nativepackager", function () {
var bbTabletXML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<qnx><id>" + config.id + "</id>" +
"<name>" + config.name + "</name>" +
"<versionNumber>" + config.version + "</versionNumber>" +
"<author>" + config.author + "</author>" +
"<asset entry=\"true\" type=\"qnx/elf\">wwe</asset>" +
Expand All @@ -127,7 +126,8 @@ describe("Native packager", function () {
"<env value=\"8\" var=\"WEBKIT_NUMBER_OF_BACKINGSTORE_TILES\"></env>" +
"<permission system=\"true\">run_native</permission>" +
"<permission system=\"false\">access_internet</permission>" +
"<description>" + config.description + "</description></qnx>",
"<name>" + config.name['default'] + "</name>" +
"<description>" + config.description['default'] + "</description></qnx>",
cmd = path.normalize(session.conf.DEPENDENCIES_TOOLS + "/bin/blackberry-nativepackager" + (pkgrUtils.isWindows() ? ".bat" : ""));

spyOn(pkgrUtils, "writeFile");
Expand All @@ -139,6 +139,82 @@ describe("Native packager", function () {
expect(callback).toHaveBeenCalledWith(0);
});

it("can process application name", function () {
var config = testUtils.cloneObj(testData.config);
config.name = {"default": "API Smoke Test"};

spyOn(pkgrUtils, "writeFile").andCallFake(function (fileLocation, fileName, fileData) {
expect(fileData).toContain("<name>API Smoke Test</name>");
});

nativePkgr.exec(session, target, config, callback);

});

it("can process localized application name", function () {
var config = testUtils.cloneObj(testData.config);
config.name = {"FR": "API Smoke Test - FR"};

spyOn(pkgrUtils, "writeFile").andCallFake(function (fileLocation, fileName, fileData) {
expect(fileData).toContain('<name><text xml:lang="FR">API Smoke Test - FR</text></name>');
});

nativePkgr.exec(session, target, config, callback);
});

it("can process mutiple application names", function () {
var config = testUtils.cloneObj(testData.config);
config.name = {
"default": "API Smoke Test",
"EN": "API Smoke Test - EN",
"FR": "API Smoke Test - FR"
};

spyOn(pkgrUtils, "writeFile").andCallFake(function (fileLocation, fileName, fileData) {
expect(fileData).toContain('<name>API Smoke Test<text xml:lang="EN">API Smoke Test - EN</text><text xml:lang="FR">API Smoke Test - FR</text></name>');
});

nativePkgr.exec(session, target, config, callback);
});

it("can process application description", function () {
var config = testUtils.cloneObj(testData.config);
config.description = {"default": "My app description"};

spyOn(pkgrUtils, "writeFile").andCallFake(function (fileLocation, fileName, fileData) {
expect(fileData).toContain("<description>My app description</description>");
});

nativePkgr.exec(session, target, config, callback);

});

it("can process localized application description", function () {
var config = testUtils.cloneObj(testData.config);
config.description = {"FR": "My app description - FR"};

spyOn(pkgrUtils, "writeFile").andCallFake(function (fileLocation, fileName, fileData) {
expect(fileData).toContain('<description><text xml:lang="FR">My app description - FR</text></description>');
});

nativePkgr.exec(session, target, config, callback);
});

it("can process mutiple application descriptions", function () {
var config = testUtils.cloneObj(testData.config);
config.description = {
"default": "My app description",
"EN": "My app description - EN",
"FR": "My app description - FR"
};

spyOn(pkgrUtils, "writeFile").andCallFake(function (fileLocation, fileName, fileData) {
expect(fileData).toContain('<description>My app description<text xml:lang="EN">My app description - EN</text><text xml:lang="FR">My app description - FR</text></description>');
});

nativePkgr.exec(session, target, config, callback);
});

it("can process permissions with no attributes", function () {
var config = testUtils.cloneObj(testData.config);
config.permissions = ['read_device_identifying_information'];
Expand Down

0 comments on commit a66c0cb

Please sign in to comment.