UNPKG

7.26 kBJavaScriptView Raw
1/* eslint-env node */
2
3/**
4 * Copyright 2015, Yahoo! Inc.
5 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
6 */
7
8'use strict';
9
10const fs = require('fs');
11const path = require('path');
12const walkSync = require('walk-sync');
13const mergeTrees = require('broccoli-merge-trees');
14const stringify = require('json-stable-stringify');
15const extract = require('@ember-intl/broccoli-cldr-data');
16const calculateCacheKeyForTree = require('calculate-cache-key-for-tree');
17
18const buildTranslationTree = require('./lib/broccoli/build-translation-tree');
19const TranslationReducer = require('./lib/broccoli/translation-reducer');
20const isValidLocaleFormat = require('./lib/utils/is-valid-locale-format');
21const findEngine = require('./lib/utils/find-engine');
22
23const DEFAULT_CONFIG = {
24 locales: null,
25 publicOnly: false,
26 fallbackLocale: null,
27 autoPolyfill: false,
28 disablePolyfill: false,
29 inputPath: 'translations',
30 outputPath: 'translations',
31 errorOnMissingTranslations: false,
32 errorOnNamedArgumentMismatch: false,
33 wrapTranslationsWithNamespace: false,
34 requiresTranslation(/* key, locale */) {
35 return true;
36 }
37};
38
39module.exports = {
40 name: 'ember-intl',
41 opts: null,
42 isLocalizationFramework: true,
43
44 cacheKeyForTree(treeType) {
45 const paths = this.package.paths || this.package.treePaths;
46
47 return calculateCacheKeyForTree(treeType, this, paths ? paths[treeType] : this.package.root);
48 },
49
50 included(parent) {
51 this._super.included.apply(this, arguments);
52
53 const engine = findEngine(parent);
54
55 this.app = this._findHost();
56 this.package = engine || this.project;
57 this.opts = this.createOptions(this.app.env, this.app.project);
58 this.isSilent = this.app.options.intl && this.app.options.intl.silent;
59 this.locales = this.findAvailableLocales();
60 },
61
62 generateTranslationTree(bundlerOptions) {
63 const [translationTree, addonsWithTranslations] = buildTranslationTree(
64 this.project,
65 this.opts.inputPath,
66 this.treeGenerator
67 );
68
69 const _bundlerOptions = bundlerOptions || {};
70 const addon = this;
71
72 if ('throwMissingTranslations' in this.opts) {
73 this.log('DEPRECIATION: `throwMissingTranslations` has been renamed to `errorOnMissingTranslations`', {
74 warning: true
75 });
76 }
77
78 return new TranslationReducer([translationTree], {
79 verbose: !this.isSilent,
80 outputPath: this.opts.outputPath,
81 fallbackLocale: this.opts.fallbackLocale,
82 filename: _bundlerOptions.filename,
83 wrapEntry: _bundlerOptions.wrapEntry,
84 requiresTranslation: this.opts.requiresTranslation,
85 errorOnMissingTranslations: this.opts.throwMissingTranslations || this.opts.errorOnMissingTranslations,
86 errorOnNamedArgumentMismatch: this.opts.errorOnNamedArgumentMismatch,
87 stripEmptyTranslations: this.opts.stripEmptyTranslations,
88 wrapTranslationsWithNamespace: this.opts.wrapTranslationsWithNamespace,
89 addonsWithTranslations: addonsWithTranslations,
90 log() {
91 return addon.log.apply(addon, arguments);
92 }
93 });
94 },
95
96 findAssetPath(appOptions) {
97 if (appOptions.app && appOptions.app.intl) {
98 return appOptions.app.intl;
99 }
100
101 return 'assets/intl';
102 },
103
104 contentFor(name, config) {
105 if (name === 'head' && !this.opts.disablePolyfill && this.opts.autoPolyfill) {
106 let assetPath = this.findAssetPath(this.app.options);
107 let locales = this.locales;
108 let prefix = '';
109
110 if (config.rootURL) {
111 prefix += config.rootURL;
112 }
113 if (assetPath) {
114 prefix += assetPath;
115 }
116
117 let localeScripts = locales.map(function(locale) {
118 return `<script src="${prefix}/locales/${locale}.js"></script>`;
119 });
120
121 return [`<script src="${prefix}/intl.min.js"></script>`].concat(localeScripts).join('\n');
122 }
123 },
124
125 treeForApp(tree) {
126 let trees = [tree];
127
128 if (!this.opts.publicOnly) {
129 trees.push(
130 this.generateTranslationTree({
131 outputPath: 'translations',
132 filename(key) {
133 return `${key}.js`;
134 },
135 wrapEntry(obj) {
136 return `export default ${stringify(obj)};`;
137 }
138 })
139 );
140 }
141
142 if (tree && this.locales.length) {
143 let cldrTree = extract(tree, {
144 locales: this.locales,
145 relativeFields: true,
146 numberFields: true,
147 destDir: 'cldrs',
148 prelude: '/*jslint eqeq: true*/\n',
149 moduleType: 'es6'
150 });
151
152 trees.push(cldrTree);
153 }
154
155 return mergeTrees(trees, { overwrite: true });
156 },
157
158 treeForPublic() {
159 let trees = [];
160
161 if (!this.opts.disablePolyfill) {
162 let appOptions = this.app.options || {};
163
164 trees.push(
165 require('./lib/broccoli/intl-polyfill')({
166 locales: this.locales,
167 destDir: (appOptions.app && appOptions.app.intl) || 'assets/intl'
168 })
169 );
170 }
171
172 if (this.opts.publicOnly) {
173 trees.push(this.generateTranslationTree());
174 }
175
176 return mergeTrees(trees, { overwrite: true });
177 },
178
179 log(msg, options) {
180 if (this.isSilent) {
181 return;
182 }
183
184 if (options && options.warning && this.ui.writeWarnLine) {
185 this.ui.writeWarnLine(`[ember-intl] ${msg}`);
186 } else {
187 this.ui.writeLine(`[ember-intl] ${msg}`);
188 }
189 },
190
191 readConfig(environment, project) {
192 // NOTE: For ember-cli >= 2.6.0-beta.3, project.configPath() returns absolute path
193 // while older ember-cli versions return path relative to project root
194 let configPath = path.dirname(project.configPath());
195 let config = path.join(configPath, 'ember-intl.js');
196
197 if (!path.isAbsolute(config)) {
198 config = path.join(project.root, config);
199 }
200
201 if (fs.existsSync(config)) {
202 return require(config)(environment);
203 }
204
205 return {};
206 },
207
208 createOptions(environment, project) {
209 let config = Object.assign({}, DEFAULT_CONFIG, this.readConfig(environment, project));
210
211 if (typeof config.requiresTranslation !== 'function') {
212 this.log('Configured `requiresTranslation` is not a function. Using default implementation.');
213 config.requiresTranslation = DEFAULT_CONFIG.requiresTranslation;
214 }
215
216 if (config.locales && !Array.isArray(config.locales)) {
217 config.locales = [config.locales];
218 }
219
220 return config;
221 },
222
223 findAvailableLocales() {
224 let locales = [];
225 let projectInputPath = path.join(this.app.project.root, this.opts.inputPath);
226
227 if (fs.existsSync(projectInputPath)) {
228 locales.push(
229 ...walkSync(projectInputPath, {
230 globs: ['**/*.{yml,yaml,json}'],
231 directories: false
232 }).map(function(filename) {
233 return path.basename(filename, path.extname(filename));
234 })
235 );
236 }
237
238 if (Array.isArray(this.opts.locales)) {
239 locales.push(...this.opts.locales);
240 }
241
242 let normalizedLocales = locales.map(locale => {
243 return locale
244 .trim()
245 .toLowerCase()
246 .replace(/_/g, '-');
247 });
248
249 return [...new Set(normalizedLocales).values()].filter(locale => {
250 if (isValidLocaleFormat(locale)) {
251 return true;
252 }
253
254 this.log(`'${locale}' is not a valid locale name`);
255
256 return false;
257 });
258 }
259};