1 /*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
2 regexp:true, undef:true, strict:true, trailing:true, white:true */
3 /*global X:true, Backbone:true, _:true, XM:true, XT:true */
5 if (typeof XT === 'undefined') {
12 var _ = require("underscore"),
13 async = require("async"),
15 locale = require("../../lib/tools/source/locale"),
16 path = require("path"),
17 createQuery = function (strings, context, language) {
18 return "select xt.set_dictionary($$%@$$, '%@', '%@');"
19 .f(JSON.stringify(strings),
25 Looks (by convention) in en/strings.js of the extension for the
26 English strings and asyncronously returns the sql command to put
27 that hash into the database.
29 exports.getDictionarySql = function (extension, callback) {
30 var isLibOrm = extension.indexOf("lib/orm") >= 0,
31 isApplicationCore = extension.indexOf("enyo-client") >= 0 &&
32 extension.indexOf("extension") < 0,
38 XT.getLanguage = locale.getLanguage;
39 XT.stringsFor = locale.stringsFor;
42 // smash the tools and enyo-x strings together into one query
43 clientHash = _.extend(
44 require(path.join(extension, "../enyo-x/source/en/strings.js")).language.strings,
45 require(path.join(extension, "../tools/source/en/strings.js")).language.strings
47 callback(null, createQuery(clientHash, "_framework_"));
49 } else if (isApplicationCore) {
50 // put the client strings into one query
51 // put the database strings into another query
52 clientHash = require(path.join(extension, "application/source/en/strings.js")).language.strings;
53 databaseHash = require(path.join(extension, "database/source/en/strings.js")).language.strings;
54 callback(null, createQuery(clientHash) + createQuery(databaseHash, "_database_"));
57 // return the extension strings if they exist
58 filename = path.join(extension, "client/en/strings.js");
59 fs.exists(filename, function (exists) {
61 callback(null, createQuery(require(filename).language.strings,
62 path.basename(extension).replace("/", "")));
64 // no problem. Maybe there is just no strings file
73 // The below code supports importing and exporting of dictionaries.
74 // This functionality can be accessed through the command line via
75 // the ./scripts/export_database.js and ./scripts/import_database.js
79 var dataSource = require('../../node-datasource/lib/ext/datasource').dataSource;
80 var querystring = require("querystring");
81 var request = require("request");
84 // note that if we haven't been given an API key then the control flow
85 // will still come through here, but we'll just return the empty string
87 var autoTranslate = function (text, apiKey, destinationLang, callback) {
88 if (!apiKey || !destinationLang || !text) {
89 // the user doesn't want to autotranslate
94 if (destinationLang.indexOf("zh") === 0) {
95 // Google uses the country code for chinese, but uses dashes instead of our underscores
96 destinationLang = destinationLang.replace("_", "-");
98 } else if (destinationLang.indexOf("_") >= 0) {
99 // strip off the locale for google
100 destinationLang = destinationLang.substring(0, destinationLang.indexOf("_"));
105 target: destinationLang,
109 url = "https://www.googleapis.com/language/translate/v2?" + querystring.stringify(query);
111 request.get(url, function (err, resp, body) {
116 var response = JSON.parse(body);
117 if (response.error) {
118 callback(response.error);
121 var translations = response.data.translations;
122 if (translations.length !== 1 || !translations[0].translatedText) {
123 console.log("could not parse translations", JSON.stringify(translations));
127 var translation = translations[0].translatedText;
128 callback(null, translation);
134 // Group similar english and foreign rows together
135 // This will help us generate a dictionary file if there's
136 // already a partway- or fully- implemented translation already
137 // sitting in the database.
139 var marryLists = function (list) {
140 var englishList = _.filter(list, function (row) {
141 return row.dict_language_name === "en_US";
143 var foreignList = _.difference(list, englishList);
144 var marriedList = _.map(englishList, function (englishRow) {
145 var foreignRow = _.find(foreignList, function (foreignRow) {
146 return foreignRow.ext_name === englishRow.ext_name &&
147 foreignRow.dict_is_database === englishRow.dict_is_database &&
148 foreignRow.dict_is_framework === englishRow.dict_is_framework;
159 @param {String} database. The database name, such as "dev"
160 @param {String} apiKey. Your Google Translate API key. Leave blank for no autotranslation
161 @param {String} destinationLang. In form "es_MX".
162 @param {Function} masterCallback
164 exports.exportEnglish = function (options, masterCallback) {
165 var creds = require("../../node-datasource/config").databaseServer,
166 sql = "select dict_strings, dict_is_database, dict_is_framework, " +
167 "dict_language_name, ext_name from xt.dict " +
168 "left join xt.ext on dict_ext_id = ext_id " +
169 "where dict_language_name = 'en_US'",
170 database = options.database,
171 apiKey = options.apiKey,
172 destinationDir = options.directory,
173 destinationLang = options.language;
175 if (destinationLang) {
176 sql = sql + " or dict_language_name = $1";
177 creds.parameters = [destinationLang];
178 } // else the user wants a blank template, so no need to search for pre-existing translations
181 creds.database = database;
182 dataSource.query(sql, creds, function (err, res) {
183 var processExtension = function (rowMap, extensionCallback) {
184 var row = rowMap.source;
185 var foreignStrings = rowMap.target ? JSON.parse(rowMap.target.dict_strings) : [];
186 var stringsArray = _.map(JSON.parse(row.dict_strings), function (value, key) {
187 return {value: value, key: key};
189 var processString = function (stringObj, stringCallback) {
191 // If this translation has already been made into the target language, put that
192 // translation into the dictionary file and do not bother autotranslating.
194 var preExistingTranslation = _.find(foreignStrings, function (foreignString, foreignKey) {
195 return foreignString && foreignKey === stringObj.key;
197 if (preExistingTranslation) {
198 // this has already been translated. No need to talk to Google etc.
199 stringCallback(null, {
201 source: stringObj.value,
202 target: preExistingTranslation
204 } else if ( destinationLang.indexOf('en') === 0 ) {
205 // if locale is en_AU en_GB copy the en_US source: strings to target:
206 stringCallback(null, {
208 source: stringObj.value,
209 target: stringObj.value
212 // ask google (or not)
213 autoTranslate(stringObj.value, apiKey, destinationLang, function (err, target) {
214 stringCallback(null, {
216 source: stringObj.value,
222 async.map(stringsArray, processString, function (err, strings) {
223 extensionCallback(null, {
224 extension: row.dict_is_database ? "_database_" :
225 row.dict_is_framework ? "_framework_" :
226 row.ext_name || "_core_",
232 // group together english and foreign strings of the same extension
233 var marriedRows = marryLists(res.rows);
234 async.map(marriedRows, processExtension, function (err, extensions) {
235 // sort alpha so as to keep diffs under control
236 _.each(extensions, function (extension) {
237 extension.strings = _.sortBy(extension.strings, function (stringObj) {
238 return stringObj.key.toLowerCase();
241 extensions = _.sortBy(extensions, function (extObj) {
242 return extObj.extension;
246 language: destinationLang || "",
247 extensions: extensions
249 // filename convention is ./scripts/output/es_MX_dictionary.js
250 destinationDir = destinationDir || path.join(__dirname, "../output");
252 var exportFilename = path.join(destinationDir,
253 (destinationLang || "blank") + "_dictionary.js");
254 console.log("Exporting to", exportFilename);
255 fs.writeFile(exportFilename, JSON.stringify(output, undefined, 2), function (err, result) {
256 masterCallback(err, result);
263 Takes a dictionary definition file and inserts the data into the database
265 var importDictionary = exports.importDictionary = function (database, filename, masterCallback) {
266 var creds = require("../../node-datasource/config").databaseServer;
267 creds.database = database;
269 filename = path.resolve(process.cwd(), filename);
270 if (path.extname(filename) !== '.js') {
271 console.log("Skipping non-dictionary file", filename);
275 fs.readFile(filename, "utf8", function (err, contents) {
280 var dictionary = JSON.parse(contents);
281 var processExtension = function (extension, extensionCallback) {
282 var context = extension.extension;
283 var strings = _.reduce(extension.strings, function (memo, trans) {
284 memo[trans.key] = trans.target;
287 var sql = createQuery(strings, context, dictionary.language);
289 dataSource.query(sql, creds, function (err, res) {
290 extensionCallback(err, res);
293 async.each(dictionary.extensions, processExtension, function (err, results) {
294 masterCallback(err, results);
299 exports.importAllDictionaries = function (database, callback) {
300 var translationsDir = path.join(__dirname, "../../node_modules/xtuple-linguist/translations");
301 if (!fs.existsSync(translationsDir)) {
302 console.log("No translations directory found. Ignoring linguist.");
305 var importOne = function (dictionary, next) {
306 importDictionary(database, dictionary, next);
308 var allDictionaries = _.map(fs.readdirSync(translationsDir), function (filename) {
309 return path.join(translationsDir, filename);
311 async.map(allDictionaries, importOne, callback);