1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | 'use strict';
|
9 |
|
10 | const fs = require('fs');
|
11 | const path = require('path');
|
12 | const walkSync = require('walk-sync');
|
13 | const mergeTrees = require('broccoli-merge-trees');
|
14 | const stringify = require('json-stable-stringify');
|
15 | const extract = require('@ember-intl/broccoli-cldr-data');
|
16 | const calculateCacheKeyForTree = require('calculate-cache-key-for-tree');
|
17 |
|
18 | const buildTranslationTree = require('./lib/broccoli/build-translation-tree');
|
19 | const TranslationReducer = require('./lib/broccoli/translation-reducer');
|
20 | const isValidLocaleFormat = require('./lib/utils/is-valid-locale-format');
|
21 | const findEngine = require('./lib/utils/find-engine');
|
22 |
|
23 | const 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() {
|
35 | return true;
|
36 | }
|
37 | };
|
38 |
|
39 | module.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 |
|
193 |
|
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 | };
|