├── .gitignore ├── LICENSE ├── README.md ├── docs └── img │ ├── appjsbust-2013.png │ ├── fixappjs.gif │ ├── react-titanium.png │ ├── ticalabash.png │ └── titanium-instruments.png ├── package.json ├── plugin └── fixappjs │ └── 1.4 │ └── hooks │ └── postcompile.js ├── run └── utils └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Andrew McElroy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FixAppJS 2 | 3 | FixAppJS on Titanium iOS projects corrects the "Could not find the file app.js" issue. 4 | 5 | ## Why is this needed? 6 | Appcelerator has a history of regressing to "Could not find the file app.js" when you try to open Xcode and use their generated Xcode project and I have a history of fixing and/or reporting it. 7 | 8 | In 2013 I had this to say: 9 | 10 | [![app.js not found from 2013](docs/img/appjsbust-2013.png)](https://vimeo.com/75159222 11 | ) 12 | 13 | Fast forward to 2016 and Appcelerator has [this problem](https://jira.appcelerator.org/browse/AC-623) [yet again](https://jira.appcelerator.org/browse/TIMOB-20253). Their stance is [We do not support opening the generated Xcode project since Titanium 4.0.0](https://jira.appcelerator.org/browse/AC-4549). It's time for a Titanium CLI hook that just fixes the problem for good already. **Enter FixAppJS.** 14 | 15 | ![fixappjs](docs/img/fixappjs.gif) 16 | 17 | This Titanium CLI Hook works for both Titanium Classic and Alloy. At this time I am ignoring hyperloop compatibility, although that would not be difficult to add. 18 | 19 | 20 | ## Install 21 | 22 | As global Titanium CLI hook: 23 | 24 | $ npm install -g fixappjs 25 | 26 | ## Usage 27 | 28 | This only works for iOS builds. Simply append --fixappjs to the end of your appc or ti build command: 29 | ``` 30 | $ ti build --platform=iphone --fixappjs 31 | ``` 32 | 33 | If you attempt to use --fixappjs on android, this plugin will stop the build and issue the following error: 34 | 35 | ``` 36 | [DEBUG] No project level plugins to load 37 | [ERROR] You can't run --fixappjs for android Projects! 38 | It's for iOS projects only!!! 39 | ``` 40 | 41 | Let's make this the last time we have to handle this issue. Once you have Xcode back, all kinds of possibilities open up. 42 | 43 | ## What's Possible Now? 44 | 45 | So now that we have back our generated Xcode project, so what. What can I do now that I couldn't do before? 46 | 47 | Here's three ideas, with many more in the works. 48 | 49 | [![Titanium and Instruments](docs/img/titanium-instruments.png)](https://codexcasts.com/episodes/titanium-and-xcode-instruments-6-3 50 | ) 51 | 52 | [![TiCalabash](docs/img/ticalabash.png)](https://codexcasts.com/episodes/ticalabash-getting-started 53 | ) 54 | 55 | [![Titanium React](docs/img/react-titanium.png)](https://codexcasts.com/episodes/getting-started-with-react-titanium 56 | ) 57 | 58 | To be fair, you don't strictly need this plugin to make this last one work. 59 | 60 | Keep an eye on both https://CodexCasts.com and http://shockoe.com/blog/ for more content. 61 | 62 | 63 | ## TODO/ ways you can help 64 | 65 | If you find a bug please report it. 66 | Pull Requests are welcome. 67 | 68 | ## Final Notes 69 | 70 | This project is not affiliated with Appcelerator in any way. As stated earlier, they no longer support using their generated Xcode project. If you run into issues, please file a bug with this project. 71 | 72 | ## Misc Troubleshooting Notes 73 | 74 | Please note that you should not ever install a npm package using sudo. 75 | If you are here is how you can resolve that issue. 76 | First, get your npm prefix path. 77 | 78 | $ npm config get prefix 79 | You will likely see: 80 | ``` 81 | /usr/local 82 | ``` 83 | If you don't see anything, then consider reinstalling npm. 84 | 85 | Next you will want to run: 86 | ``` 87 | $ sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share} 88 | ``` 89 | 90 | If you ran 91 | ``` 92 | $ npm install -g fixappjs 93 | ``` 94 | and saw an error that looked like this: 95 | 96 | ``` 97 | Unable to write config file /User/your_username/.titanium/config.json 98 | Please ensure the Titanium CLI has access to modify this file 99 | ``` 100 | Then you need to also run: 101 | ``` 102 | $ sudo chown -R $(whoami) .titanium 103 | ``` 104 | 105 | ##### DerivedData 106 | 107 | DerivedData deserves is own special place in ~~hell~~ this README. It will likely be the biggest (technical) reason over time that this cli hook could fail. At this time there are no issues being caused by DerivedData that I am aware of. 108 | 109 | 110 | ## License 111 | 112 | Copyright 2016 Andrew McElroy 113 | 114 | Licensed under the Apache License, Version 2.0 (the "License"); 115 | you may not use this file except in compliance with the License. 116 | You may obtain a copy of the License at 117 | 118 | http://www.apache.org/licenses/LICENSE-2.0 119 | 120 | Unless required by applicable law or agreed to in writing, software 121 | distributed under the License is distributed on an "AS IS" BASIS, 122 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 123 | See the License for the specific language governing permissions and 124 | limitations under the License. 125 | 126 | -------------------------------------------------------------------------------- /docs/img/appjsbust-2013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/fixAppJS/3c6cca0633f2d609250fe0266a9b66a792becad8/docs/img/appjsbust-2013.png -------------------------------------------------------------------------------- /docs/img/fixappjs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/fixAppJS/3c6cca0633f2d609250fe0266a9b66a792becad8/docs/img/fixappjs.gif -------------------------------------------------------------------------------- /docs/img/react-titanium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/fixAppJS/3c6cca0633f2d609250fe0266a9b66a792becad8/docs/img/react-titanium.png -------------------------------------------------------------------------------- /docs/img/ticalabash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/fixAppJS/3c6cca0633f2d609250fe0266a9b66a792becad8/docs/img/ticalabash.png -------------------------------------------------------------------------------- /docs/img/titanium-instruments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sophrinix/fixAppJS/3c6cca0633f2d609250fe0266a9b66a792becad8/docs/img/titanium-instruments.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fixappjs", 3 | "version": "1.5.2", 4 | "description": "A Titanium plugin to fix app.js in xcodeproj", 5 | "scripts": { 6 | "postinstall": "node run install", 7 | "preuninstall": "node run uninstall" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Sophrinix/fixappjs.git" 12 | }, 13 | "dependencies": { 14 | "ioslib": "0.15.4", 15 | "node-appc": "0.2.39", 16 | "xcode": "0.8.9", 17 | "tiapp.xml": "^0.2.2", 18 | "fs-extra":"1.0.0", 19 | "xmldom": "0.1.22" 20 | }, 21 | "keywords": [ 22 | "appcelerator", 23 | "titanium", 24 | "alloy" 25 | ], 26 | "engines": { 27 | "node": ">=0.10" 28 | }, 29 | "author": "Andrew McElroy", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/Sophrinix/fixappjs/issues" 33 | }, 34 | "homepage": "https://github.com/Sophrinix/fixappjs#readme" 35 | } 36 | -------------------------------------------------------------------------------- /plugin/fixappjs/1.4/hooks/postcompile.js: -------------------------------------------------------------------------------- 1 | var xcode = require('xcode'), 2 | fs = require('fs-extra'), 3 | path = require('path'), 4 | spawn = require('child_process').spawn, 5 | utils = require('../../../../utils'), 6 | tiapp = require('tiapp.xml').load('./tiapp.xml'), 7 | appc = require('node-appc'), 8 | i18n = appc.i18n(__dirname); 9 | 10 | /* Needed paths for the plugin */ 11 | var paths = {} 12 | exports.cliVersion = ">=3.x" 13 | exports.version = "1.0.3" 14 | 15 | exports.init = function(logger, config, cli) { 16 | 17 | /* Handle brutal stops */ 18 | process.on('SIGINT', function() { 19 | process.exit(2) 20 | }) 21 | process.on('exit', function() {}) 22 | 23 | cli.on('build.pre.construct', executeSeq(logger, [ 24 | prepare 25 | ])) 26 | cli.on('build.post.compile', executeSeq(logger, [ 27 | copyCompiledResources 28 | ])) 29 | } 30 | 31 | function executeSeq(logger, tasks) { 32 | var current = 0, 33 | errored = false, 34 | fixappjs = null; 35 | 36 | return function task(data, terminate) { 37 | if (fixappjs === null && data.cli) { 38 | var propFixAppJS = data.cli.tiapp.properties.fixappjs && data.cli.tiapp.properties.fixappjs.value 39 | var optiFixAppJS = data.cli.argv.$_.indexOf('--fixappjs') !== -1 40 | fixappjs = propFixAppJS || optiFixAppJS 41 | } 42 | if (!fixappjs) { 43 | return terminate() 44 | } 45 | tasks[current](logger, data, function next(err, type) { 46 | if (err) { 47 | if (errored) { 48 | return 49 | } 50 | errored = true 51 | logger[type || 'error'](err) 52 | return terminate(type && type !== 'error' ? undefined : "Unable to fix app.js issue") 53 | } 54 | if (++current >= tasks.length) { 55 | return terminate() 56 | } 57 | task(data, terminate) 58 | }) 59 | } 60 | } 61 | 62 | function failWithAngryMessage(data) { 63 | console.log("[ERROR] You can't run --fixappjs for " + data.cli.argv["platform"] + " Projects!\n It's for iOS projects only!!! "); 64 | process.exit(2) 65 | } 66 | 67 | function prepare(logger, data, next) { 68 | 69 | data.cli.argv["platform"] === ('android' || 'mobileweb' || 'windows') ? failWithAngryMessage(data) : console.log('Preparing to fix app.js issue for your iOS project'); 70 | utils.clean('', next) 71 | } 72 | 73 | // I think we can back this code out 74 | function symlinkResources(logger, data, next) { 75 | logger.info("Symlinking resources") 76 | } 77 | 78 | function cleanProject(logger, data, next) { 79 | logger.info("Another function that I think I can give the axe to") 80 | } 81 | 82 | function copyCompiledResources(logger, data, next) { 83 | logger.info("Copying compiled resources") 84 | utils.clean('', next) 85 | var exec = require('child_process').exec, 86 | path = require('path'), 87 | parentDir = path.resolve(process.cwd(), '.'); 88 | 89 | exec('NowWeFixAppJS', { 90 | cwd: parentDir 91 | }, function(error, stdout, stderr) { 92 | 93 | // read for hyperloop..we are going to skip over this for the first release. 94 | 95 | 96 | function alloyResourcesPath(parentDir) { 97 | return parentDir + '/Resources/iphone/'; 98 | console.log('[INFO] Alloy resources represent') 99 | } 100 | 101 | function classicResourcesPath(parentDir) { 102 | return parentDir + '/Resources/'; 103 | console.log('[INFO] classic resources represent') 104 | } 105 | 106 | var projectPath = parentDir + '/build/iphone/' + tiapp.name + '.xcodeproj/project.pbxproj', 107 | myProj = xcode.project(projectPath), 108 | isAlloy = false, 109 | projectFailure = false, 110 | resourcesPath = parentDir + '/Resources/'; 111 | 112 | myProj.parse(function(err) { 113 | var plugins = tiapp.getPlugins(); 114 | plugins.forEach(function(plugin) { 115 | if (plugin.id === 'ti.alloy') { 116 | console.log('[INFO] fixappjs has detected that this is an alloy project!') 117 | return isAlloy = true; 118 | } 119 | }); 120 | var android = new RegExp('android', 'i'), 121 | mobileweb = new RegExp('mobileweb', 'i'), 122 | ios = new RegExp('ios', 'i'), 123 | iphone = new RegExp('iphone', 'i'); 124 | 125 | isAlloy ? resourcesPath = alloyResourcesPath(parentDir) : classicResourcesPath(parentDir); 126 | 127 | // we are copying {parentDir (where tiapp.xml is)}/Resources/alloy folder into 128 | // {parentDir}/Resources/iphone/alloy if this is an alloy project. 129 | if(isAlloy){ 130 | fs.copy(parentDir+'/Resources/alloy/', resourcesPath+'/alloy/', function (err) { 131 | if (err) { 132 | console.error(err); 133 | projectFailure = true; 134 | } 135 | }); 136 | } 137 | 138 | // straight up stolen from _build.js Thanks Chris. 139 | // good artists copy, great artist steal, and pretentious artists 140 | // quote steve jobs quoting other people. 141 | 142 | function writeI18NFiles(parentDir,resourcesPath) { 143 | var data = utils.load(parentDir, this.logger), 144 | header = '/**\n' + 145 | ' * Appcelerator Titanium\n' + 146 | ' * this is a generated file - DO NOT EDIT\n' + 147 | ' */\n\n'; 148 | function add(obj, dest, map) { 149 | if (obj) { 150 | var rel = dest.replace(parentDir + '/', ''), 151 | contents = header + Object.keys(obj).map(function (name) { 152 | return '"' + (map && map[name] || name).replace(/\\"/g, '"').replace(/"/g, '\\"') + 153 | '" = "' + (''+obj[name]).replace(/%s/g, '%@').replace(/\\"/g, '"').replace(/"/g, '\\"') + '";'; 154 | }).join('\n'); 155 | 156 | if (!fs.existsSync(dest) || contents !== fs.readFileSync(dest).toString()) { 157 | if (!this.forceRebuild && /device|dist\-appstore|dist\-adhoc/.test(this.target)) { 158 | this.forceRebuild = true; 159 | } 160 | fs.writeFileSync(dest, contents); 161 | } else { 162 | //this.logger.trace(__('No change, skipping %s', dest.cyan)); 163 | } 164 | } 165 | } 166 | 167 | var keys = Object.keys(data); 168 | if (keys.length) { 169 | keys.forEach(function (lang) { 170 | logger.info("our lang is: " + JSON.stringify(lang)); 171 | var dir = path.join(resourcesPath, lang + '.lproj'); 172 | fs.existsSync(dir) || fs.mkdirsSync(dir); 173 | add.call(this, data[lang].app, path.join(dir, 'InfoPlist.strings'), { appname: 'CFBundleDisplayName' }); 174 | add.call(this, data[lang].strings, path.join(dir, 'Localizable.strings')); 175 | }, this); 176 | } else { 177 | logger.debug('No i18n files to process'); 178 | } 179 | }; 180 | writeI18NFiles(parentDir, resourcesPath); 181 | var tiSourceAddToResources =fs.readdirSync(resourcesPath); 182 | for (var i in tiSourceAddToResources) { 183 | if (String(tiSourceAddToResources[i]).match(android) || String(tiSourceAddToResources[i]).match(mobileweb)) { 184 | logger.trace((tiSourceAddToResources[i] + ' ignored').grey); 185 | }else { 186 | logger.info((tiSourceAddToResources[i] + ' added to Xcode Project').grey); 187 | myProj.addResourceFile( resourcesPath + tiSourceAddToResources[i]); 188 | } 189 | fs.writeFileSync(projectPath, myProj.writeSync()); 190 | } 191 | //we should make this a conditionally true line. 192 | logger.info('Congrats, you now have a corrected xcodeproj!'); 193 | }); 194 | }); 195 | } 196 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var CONF = require('./package.json') 4 | var spawn = require('child_process').spawn 5 | var path = require('path') 6 | var version = CONF.version.split('.').slice(0, 2).join('.') 7 | var plugin = path.join(__dirname, 'plugin', CONF.name, version, 'hooks') 8 | 9 | function info (i) { process.stdout.write('\033[1;32m' + i + '\033[0m\n') } 10 | function run (opt, msg, cmd) { 11 | var opts = ['config', 'paths.hooks', opt, plugin] 12 | opts = cmd === "appc" ? ['ti'].concat(opts) : opts 13 | var installation = spawn(cmd, opts) 14 | installation.stderr.pipe(process.stderr) 15 | installation.on("exit", function (code) { 16 | if (code === 0) { 17 | info(CONF.name + ' v' + CONF.version + ' successfully ' + msg + '.') 18 | } 19 | process.exit(code) 20 | }) 21 | installation.on("error", function (err) { 22 | if (err.code === 'ENOENT' && err.syscall === 'spawn ti') { 23 | console.log("titanium cli isn't installed. Trying with appc cli...") 24 | run(opt, msg, 'appc') 25 | return 26 | } 27 | console.log("Error: ", err) 28 | process.exit(1) 29 | }) 30 | } 31 | 32 | run.apply(null, process.argv[2] === 'install' ? ['-a', 'installed', 'ti'] : ['-r', 'uninstalled', 'ti']) 33 | -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | appc = require('node-appc'), 4 | xml = appc.xml, 5 | DOMParser = require('xmldom').DOMParser; 6 | 7 | /* 8 | * If an .app folder exists, it will remove the replace the app/ folder by the .app folder 9 | * and then, remove the .app folder. It assumes that the .app folder is the sources backup 10 | */ 11 | exports.clean = function clean(folder, next) { 12 | if (arguments.length <= 1) { 13 | return exports.cleanSync(folder) 14 | } 15 | fs.stat(folder, function(e, stats) { 16 | if (e || !stats.isDirectory()) { 17 | return next() 18 | } 19 | exports.rm(folder, next) 20 | }) 21 | } 22 | 23 | exports.cleanSync = function cleanSync(folder) { 24 | try { 25 | var stats = fs.statSync(folder) 26 | if (!stats.isDirectory()) { 27 | throw ("Nothing to clean") 28 | } 29 | } catch (e) { 30 | return 31 | } 32 | 33 | try { 34 | exports.rmSync(folder) 35 | } catch (e) { 36 | return e 37 | } 38 | } 39 | 40 | /* 41 | * Copy a bunch of file recursively from a folder into another one. Synchronous 42 | */ 43 | exports.cpSync = function cpSync(src, dest) { 44 | try { 45 | var stats = fs.statSync(src) 46 | if (!stats.isDirectory()) { 47 | fs.writeFileSync(dest, fs.readFileSync(src)); 48 | return 49 | } 50 | } catch (e) { 51 | return e 52 | } 53 | 54 | try { 55 | fs.mkdirSync(dest) 56 | } catch (e) { 57 | if (e.code !== 'EEXIST') { 58 | return e 59 | } 60 | } 61 | 62 | try { 63 | fs.readdirSync(src).forEach(function(file) { 64 | cpSync(path.join(src, file), path.join(dest, file)) 65 | }) 66 | } catch (e) { 67 | return e 68 | } 69 | } 70 | 71 | /* 72 | * Recursively delete files or folder. WARNING DANGEROUS. Synchronous 73 | */ 74 | exports.rmSync = function rmSync(src) { 75 | try { 76 | var stats = fs.statSync(src) 77 | if (!stats.isDirectory()) { 78 | fs.unlinkSync(src); 79 | return 80 | } 81 | fs.readdirSync(src).forEach(function(file) { 82 | rmSync(path.join(src, file)) 83 | }) 84 | fs.rmdirSync(src) 85 | } catch (e) { 86 | return e 87 | } 88 | } 89 | 90 | 91 | /* 92 | * Copy a bunch of file recursively from a folder into another one. 93 | */ 94 | exports.cp = function cp(src, dest, next) { 95 | fs.stat(src, function(e, stats) { 96 | if (e) { 97 | return next(e) 98 | } 99 | if (!stats.isDirectory()) { 100 | var read = fs.createReadStream(src), 101 | write = fs.createWriteStream(dest) 102 | var after = (function() { 103 | var called = false 104 | return function(e) { 105 | if (!called) { 106 | called = true; 107 | next(e) 108 | } 109 | } 110 | }()) 111 | read.on('error', after) 112 | write.on('error', after) 113 | write.on('finish', after) 114 | return read.pipe(write) 115 | } 116 | fs.mkdir(dest, function(e) { 117 | if (e && e.code !== 'EEXIST') { 118 | return next(e) 119 | } 120 | fs.readdir(src, function(e, files) { 121 | if (e) { 122 | return next(e) 123 | } 124 | var n = files.length 125 | if (n === 0) { 126 | return next() 127 | } 128 | files.forEach(function(file) { 129 | cp(path.join(src, file), path.join(dest, file), function(e) { 130 | if (e) { 131 | return next(e) 132 | } 133 | if (--n === 0) { 134 | next() 135 | } 136 | }) 137 | }) 138 | }) 139 | }) 140 | }) 141 | } 142 | 143 | /* 144 | * Recursively delete files or folder. WARNING DANGEROUS. 145 | */ 146 | exports.rm = function rm(src, next) { 147 | fs.stat(src, function(e, stats) { 148 | if (e) { 149 | return next(e) 150 | } 151 | if (!stats.isDirectory()) { 152 | return fs.unlink(src, next) 153 | } 154 | fs.readdir(src, function(e, files) { 155 | if (e) { 156 | return next(e) 157 | } 158 | var n = files.length 159 | if (n === 0) { 160 | return fs.rmdir(src, next) 161 | } 162 | files.forEach(function(file) { 163 | rm(path.join(src, file), function(e) { 164 | if (e) { 165 | return next(e) 166 | } 167 | if (--n === 0) { 168 | fs.rmdir(src, next) 169 | } 170 | }) 171 | }) 172 | }) 173 | }) 174 | } 175 | 176 | 177 | 178 | exports.load = function load(projectDir, logger, opts) { 179 | if (process.argv.indexOf('--i18n-dir') !== -1) { 180 | // Enable developers to specify i18n directory location with build flag 181 | var customI18n = process.argv[process.argv.indexOf('--i18n-dir') + 1]; 182 | if (customI18n && fs.existsSync(path.join(path.resolve(projectDir), customI18n))) { 183 | projectDir = path.join(projectDir, customI18n); 184 | } 185 | } 186 | var i18nDir = path.join(projectDir, 'i18n'), 187 | data = {}, 188 | ignoreDirs = opts && opts.ignoreDirs, 189 | ignoreFiles = opts && opts.ignoreFiles; 190 | 191 | if (fs.existsSync(i18nDir)) { 192 | console.log('Compiling localization files'); 193 | fs.readdirSync(i18nDir).forEach(function(lang) { 194 | var langDir = path.join(i18nDir, lang), 195 | isDir = fs.statSync(langDir).isDirectory(); 196 | if (fs.existsSync(langDir) && isDir && (!ignoreDirs || !ignoreDirs.test(lang))) { 197 | var s = data[lang] = {}; 198 | fs.readdirSync(langDir).forEach(function(name) { 199 | var file = path.join(langDir, name); 200 | if (/.+\.xml$/.test(name) && (!ignoreFiles || !ignoreFiles.test(name)) && fs.existsSync(file) && fs.statSync(file).isFile()) { 201 | var dest = name == 'app.xml' ? 'app' : 'strings', 202 | obj = s[dest] = s[dest] || {}, 203 | dom = new DOMParser().parseFromString(fs.readFileSync(file).toString(), 'text/xml'); 204 | xml.forEachElement(dom.documentElement, function(elem) { 205 | if (elem.nodeType == 1 && elem.tagName == 'string') { 206 | var name = xml.getAttr(elem, 'name'); 207 | name && (obj[name] = elem && elem.firstChild && elem.firstChild.data || ''); 208 | } 209 | }); 210 | } 211 | }); 212 | } 213 | }); 214 | } 215 | 216 | return data; 217 | } 218 | --------------------------------------------------------------------------------