@videinfra/static-website-builder 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/init/default/config/config.js +8 -6
- package/init/test/config/config.js +9 -0
- package/init/test/src/html/de/translation.twig +1 -0
- package/init/test/src/html/translation.twig +15 -0
- package/init/test/src/translations/test-2.en.yml +1 -0
- package/init/test/src/translations/test.de.yml +3 -0
- package/init/test/src/translations/test.en.yml +3 -0
- package/lib/get-file-content.js +0 -0
- package/package.json +2 -1
- package/plugins/twig/symfony-filters/filters.js +35 -0
- package/plugins/twig/symfony-functions/functions.js +20 -1
- package/tasks/data/config.js +2 -0
- package/tasks/data/data-loader-yml.js +6 -0
- package/tasks/data/get-data.js +86 -5
- package/tasks/translations/config.js +29 -0
- package/tasks/translations/get-translations.js +48 -0
- package/tasks/translations/preprocess-config.js +19 -0
- package/tests/build/translations.test.js +57 -0
- package/vendor/gulp-sass/index.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [2.3.0] - 2026-04-09
|
|
8
|
+
### Addded
|
|
9
|
+
- Translation support for TWIG
|
|
10
|
+
|
|
11
|
+
## [2.2.2] - 2026-04-08
|
|
12
|
+
### Fixed
|
|
13
|
+
- Fixed gulp-sass missing error message
|
|
14
|
+
|
|
7
15
|
## [2.2.1] - 2026-03-12
|
|
8
16
|
### Fixed
|
|
9
17
|
- Fixed env variables not being set to empty string when not found
|
|
@@ -6,9 +6,6 @@
|
|
|
6
6
|
* in each tasks config.js file
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import * as sassPlugin from '@videinfra/static-website-builder/plugins/sass';
|
|
10
|
-
import * as twigPlugin from '@videinfra/static-website-builder/plugins/twig';
|
|
11
|
-
|
|
12
9
|
export const clean = {};
|
|
13
10
|
export const staticFiles = {};
|
|
14
11
|
export const html = {};
|
|
@@ -20,15 +17,20 @@ export const javascripts = {};
|
|
|
20
17
|
export const stylesheets = {};
|
|
21
18
|
export const browserSync = {};
|
|
22
19
|
export const sizereport = {};
|
|
20
|
+
export const translations = {};
|
|
23
21
|
|
|
24
22
|
export const plugins = [
|
|
25
23
|
// Enables SASS engine and .sass and .scss file compilation
|
|
26
|
-
|
|
24
|
+
(await import('@videinfra/static-website-builder/plugins/sass.js')),
|
|
27
25
|
|
|
28
26
|
// Enables TwigJS engine .twig file compilation
|
|
29
|
-
|
|
30
|
-
];
|
|
27
|
+
(await import('@videinfra/static-website-builder/plugins/twig.js')),
|
|
31
28
|
|
|
29
|
+
// Additional twig plugins
|
|
30
|
+
(await import('@videinfra/static-website-builder/plugins/twig/symfony-filters.js')),
|
|
31
|
+
(await import('@videinfra/static-website-builder/plugins/twig/lodash-filters.js')),
|
|
32
|
+
(await import('@videinfra/static-website-builder/plugins/twig/symfony-functions.js')),
|
|
33
|
+
];
|
|
32
34
|
|
|
33
35
|
/*
|
|
34
36
|
* Path configuration
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import * as sassPlugin from '../../../plugins/sass.js';
|
|
6
6
|
import * as twigPlugin from '../../../plugins/twig.js';
|
|
7
7
|
import * as symfonyFiltersPlugin from '../../../plugins/twig/symfony-filters.js';
|
|
8
|
+
import * as symfonyFunctionsPlugin from '../../../plugins/twig/symfony-functions.js';
|
|
8
9
|
|
|
9
10
|
export const clean = {};
|
|
10
11
|
export const staticFiles = {};
|
|
@@ -46,6 +47,9 @@ export const plugins = [
|
|
|
46
47
|
|
|
47
48
|
// Enables TWIG Symfony filters
|
|
48
49
|
symfonyFiltersPlugin,
|
|
50
|
+
|
|
51
|
+
// Enables TWIG Symfony functions
|
|
52
|
+
symfonyFunctionsPlugin,
|
|
49
53
|
];
|
|
50
54
|
|
|
51
55
|
export const env = {
|
|
@@ -65,6 +69,11 @@ export const env = {
|
|
|
65
69
|
},
|
|
66
70
|
};
|
|
67
71
|
|
|
72
|
+
export const translations = {
|
|
73
|
+
locales: ['en', 'de'],
|
|
74
|
+
defaultLocale: 'en',
|
|
75
|
+
};
|
|
76
|
+
|
|
68
77
|
/*
|
|
69
78
|
* Path configuration
|
|
70
79
|
* All options will be merged with defaults, but not replaces whole configuration object
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{% include '../translation.twig' %}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<p>ROUTE: {{ app.request.attributes.get('_route') }}</p>
|
|
2
|
+
<p>PATH: {{ path(app.request.attributes.get('_route')) }}</p>
|
|
3
|
+
<p>DE_PATH: {{ path(app.request.attributes.get('_route'), { _locale: 'de' }) }}</p>
|
|
4
|
+
<p>EN_PATH: {{ path(app.request.attributes.get('_route'), { _locale: 'en' }) }}</p>
|
|
5
|
+
<p>HOMEPAGE PATH: {{ path('app.homepage') }}</p>
|
|
6
|
+
<p>HOMEPAGE DE_PATH: {{ path('app.homepage', { _locale: 'de' }) }}</p>
|
|
7
|
+
<p>HOMEPAGE EN_PATH: {{ path('app.homepage', { _locale: 'en' }) }}</p>
|
|
8
|
+
|
|
9
|
+
<p>TITLE: {{ 'title'|trans({}, 'test') }}</p>
|
|
10
|
+
<p>CATEGORY: {{ 'category.name'|trans({}, 'test') }}</p>
|
|
11
|
+
<p>MISSING: {{ 'missing'|trans({}, 'test') }}</p>
|
|
12
|
+
<p>INVALID_GROUP: {{ 'invalid_group'|trans({}, 'invalid_group') }}</p>
|
|
13
|
+
<p>MISSING_ATTRIBUTES: {{ 'missing_attributes'|trans }}</p>
|
|
14
|
+
|
|
15
|
+
<p>TITLE 2: {{ 'title'|trans({}, 'test-2') }}</p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
title: Test 2
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@videinfra/static-website-builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Customizable static site project builder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"gulp-sizereport": "^1.2.1",
|
|
51
51
|
"gulp-sourcemaps": "^3.0.0",
|
|
52
52
|
"gulp-svgstore": "^9.0.0",
|
|
53
|
+
"js-yaml": "^4.1.1",
|
|
53
54
|
"lodash.clone": "^4.3.2",
|
|
54
55
|
"lodash.some": "^4.2.2",
|
|
55
56
|
"minimist": "^1.2.8",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getTaskConfig } from '../../../lib/get-config.js';
|
|
2
2
|
import { loadEnvData } from '../../../tasks/env/get-env.js';
|
|
3
|
+
import getTranslations from '../../../tasks/translations/get-translations.js';
|
|
3
4
|
import preposition_nbsp from './preposition_nbsp.js';
|
|
4
5
|
|
|
5
6
|
const exports = [];
|
|
@@ -99,4 +100,38 @@ exports.push({
|
|
|
99
100
|
}
|
|
100
101
|
});
|
|
101
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Translation filter
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* {{ 'hello world' | trans }}
|
|
108
|
+
*/
|
|
109
|
+
exports.push({
|
|
110
|
+
name: 'trans',
|
|
111
|
+
func: function (data, args) {
|
|
112
|
+
const group = args[1];
|
|
113
|
+
const currentPagePath = this.context.currentPagePath;
|
|
114
|
+
const locales = this.context.app.request.languages;
|
|
115
|
+
let locale = currentPagePath.split('/')[1];
|
|
116
|
+
|
|
117
|
+
if (!locales.includes(locale)) {
|
|
118
|
+
locale = this.context.app.request.defaultLocale;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Find translation
|
|
122
|
+
let translations = getTranslations(locale, group);
|
|
123
|
+
const dataParts = data.split('.');
|
|
124
|
+
|
|
125
|
+
for (let i = 0; i < dataParts.length; i++) {
|
|
126
|
+
if (translations && dataParts[i] in translations) {
|
|
127
|
+
translations = translations[dataParts[i]];
|
|
128
|
+
} else {
|
|
129
|
+
return data;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return translations;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
102
137
|
export default exports;
|
|
@@ -27,5 +27,24 @@ export default [
|
|
|
27
27
|
const normalizedPath = (path || path === 0 ? String(path) : '');
|
|
28
28
|
return applyFilter('version', applyFilter('cdnify', normalizedPath));
|
|
29
29
|
}
|
|
30
|
-
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Fake "path" filter, it tries to replicate Symfony's path() function
|
|
33
|
+
{
|
|
34
|
+
name: 'path',
|
|
35
|
+
func: function (path, args) {
|
|
36
|
+
// Find locale information
|
|
37
|
+
const locale = args?._locale || this.context.app.request.locale;
|
|
38
|
+
const defaultLocale = this.context.app.request.defaultLocale;
|
|
39
|
+
const pathPrefix = locale === defaultLocale ? '' : `/${locale}`;
|
|
40
|
+
|
|
41
|
+
if (path === 'app.homepage') {
|
|
42
|
+
return `${pathPrefix}/`;
|
|
43
|
+
} else if (path.startsWith('app.')) {
|
|
44
|
+
return `${pathPrefix}/${path.slice(4).replace(/_/g, '-')}`;
|
|
45
|
+
} else {
|
|
46
|
+
return `${pathPrefix}${path}`;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
},
|
|
31
50
|
];
|
package/tasks/data/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import dataLoaderJs from './data-loader-js.js';
|
|
2
2
|
import dataLoaderJson from './data-loader-json.js';
|
|
3
|
+
import dataLoaderYml from './data-loader-yml.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Data loading for HTML task
|
|
@@ -13,6 +14,7 @@ export const data = {
|
|
|
13
14
|
loaders: {
|
|
14
15
|
js: dataLoaderJs,
|
|
15
16
|
json: dataLoaderJson,
|
|
17
|
+
yml: dataLoaderYml,
|
|
16
18
|
},
|
|
17
19
|
|
|
18
20
|
// Glob list of files, which to ignore, relative to the data source folder
|
package/tasks/data/get-data.js
CHANGED
|
@@ -80,6 +80,29 @@ export default function (options) {
|
|
|
80
80
|
const build = options && !!options.build;
|
|
81
81
|
const htmlSourceFolders = getSourcePaths('html');
|
|
82
82
|
|
|
83
|
+
// Find locale information
|
|
84
|
+
const translationConfig = getTaskConfig('translations');
|
|
85
|
+
const locales = translationConfig.locales;
|
|
86
|
+
const defaultLocale = translationConfig.defaultLocale;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create symfony request parameter
|
|
90
|
+
* @param {object} values
|
|
91
|
+
* @returns {object}
|
|
92
|
+
*/
|
|
93
|
+
function symfonyRequestProperty(values) {
|
|
94
|
+
values = values || {};
|
|
95
|
+
return {
|
|
96
|
+
all: values,
|
|
97
|
+
get: function (name) {
|
|
98
|
+
return name in values ? values[name] : null;
|
|
99
|
+
},
|
|
100
|
+
has: function (name) {
|
|
101
|
+
return !!(name in values);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
83
106
|
/**
|
|
84
107
|
* Calculate current page path based on file path which is being processed
|
|
85
108
|
* @param {*} file
|
|
@@ -109,27 +132,85 @@ export default function (options) {
|
|
|
109
132
|
return currentPagePath;
|
|
110
133
|
}
|
|
111
134
|
|
|
135
|
+
const getRouteFromPath = function (path) {
|
|
136
|
+
if (!path || path === '/') {
|
|
137
|
+
return 'app.homepage';
|
|
138
|
+
} else {
|
|
139
|
+
return path;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
112
143
|
return function (file) {
|
|
113
144
|
// We expose `currentPagePath` to Twig templates
|
|
114
145
|
const currentPagePath = getCurrentPagePath(file);
|
|
115
146
|
|
|
147
|
+
// Resolve locales
|
|
148
|
+
let currentPagePathWithoutLocale = currentPagePath;
|
|
149
|
+
let locale = currentPagePath.split('/')[1];
|
|
150
|
+
|
|
151
|
+
if (!locales.includes(locale)) {
|
|
152
|
+
locale = defaultLocale;
|
|
153
|
+
} else {
|
|
154
|
+
currentPagePathWithoutLocale = currentPagePath.replace(`/${locale}`, '') || '/';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const symonyAppData = {
|
|
158
|
+
app: {
|
|
159
|
+
environment: global.production ? 'prod' : 'dev',
|
|
160
|
+
debug: false,
|
|
161
|
+
|
|
162
|
+
request: {
|
|
163
|
+
content: null,
|
|
164
|
+
languages: locales,
|
|
165
|
+
charsets: null,
|
|
166
|
+
encodings: null,
|
|
167
|
+
acceptableContentTypes: null,
|
|
168
|
+
pathInfo: currentPagePath,
|
|
169
|
+
requestUri: currentPagePath,
|
|
170
|
+
baseUrl: '',
|
|
171
|
+
basePath: null,
|
|
172
|
+
method: 'GET',
|
|
173
|
+
format: null,
|
|
174
|
+
locale: locale,
|
|
175
|
+
defaultLocale: defaultLocale,
|
|
176
|
+
|
|
177
|
+
attributes: symfonyRequestProperty({
|
|
178
|
+
_locale: locale,
|
|
179
|
+
_route: getRouteFromPath(currentPagePathWithoutLocale),
|
|
180
|
+
_route_params: {
|
|
181
|
+
_locale: locale,
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
query: symfonyRequestProperty(),
|
|
185
|
+
server: symfonyRequestProperty(),
|
|
186
|
+
files: symfonyRequestProperty(),
|
|
187
|
+
cookies: symfonyRequestProperty(),
|
|
188
|
+
headers: symfonyRequestProperty(),
|
|
189
|
+
|
|
190
|
+
get: function () {
|
|
191
|
+
return null;
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
116
197
|
if (build) {
|
|
117
198
|
// Cache during full build
|
|
118
199
|
if (!cache) {
|
|
119
200
|
cache = getData();
|
|
120
201
|
}
|
|
121
202
|
|
|
122
|
-
return {
|
|
203
|
+
return merge({
|
|
123
204
|
currentPagePath,
|
|
124
|
-
...cache
|
|
125
|
-
};
|
|
205
|
+
...cache,
|
|
206
|
+
}, symonyAppData);
|
|
126
207
|
} else {
|
|
127
208
|
// Don't cache during watch build
|
|
128
209
|
cache = null;
|
|
129
|
-
return {
|
|
210
|
+
return merge({
|
|
130
211
|
currentPagePath,
|
|
131
212
|
...getData(),
|
|
132
|
-
};
|
|
213
|
+
}, symonyAppData);
|
|
133
214
|
}
|
|
134
215
|
};
|
|
135
216
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import preprocessTranslationsConfig from './preprocess-config.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Translations loading for HTML task
|
|
5
|
+
* This task doesn't have actual task, instead it's used by 'html' task
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const translations = {
|
|
9
|
+
// List of locales, must be set only if there is more than 1 locale
|
|
10
|
+
locales: [],
|
|
11
|
+
|
|
12
|
+
// Default locale
|
|
13
|
+
defaultLocale: '',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Paths relative to the global src and dest folders
|
|
18
|
+
*/
|
|
19
|
+
export const paths = {
|
|
20
|
+
translations: {
|
|
21
|
+
src: 'translations',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const preprocess = {
|
|
26
|
+
translations: [
|
|
27
|
+
preprocessTranslationsConfig,
|
|
28
|
+
]
|
|
29
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import merge from '../../lib/merge.js';
|
|
5
|
+
import { getSourcePaths, getPathConfig } from '../../lib/get-path.js';
|
|
6
|
+
import logError from '../../lib/log-error.js';
|
|
7
|
+
import dataLoaderYml from '../data/data-loader-yml.js';
|
|
8
|
+
|
|
9
|
+
let cache = {};
|
|
10
|
+
let cacheTime = null;
|
|
11
|
+
|
|
12
|
+
export default function getTranslations(locale, group) {
|
|
13
|
+
const build = global.production;
|
|
14
|
+
|
|
15
|
+
// Invalidate cache after 1 second
|
|
16
|
+
if (!build && cacheTime && Date.now() - cacheTime > 1000) {
|
|
17
|
+
cache = {};
|
|
18
|
+
cacheTime = Date.now();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (cache[locale] && cache[locale][group]) {
|
|
22
|
+
return cache[locale][group];
|
|
23
|
+
} else {
|
|
24
|
+
const folders = getSourcePaths('translations');
|
|
25
|
+
|
|
26
|
+
folders.forEach((folder) => {
|
|
27
|
+
const fullFilePath = path.resolve(folder, `${group}.${locale}.yml`);
|
|
28
|
+
|
|
29
|
+
if (fs.existsSync(fullFilePath)) {
|
|
30
|
+
try {
|
|
31
|
+
const fileData = dataLoaderYml(fullFilePath);
|
|
32
|
+
cache = merge(cache, { [locale]: { [group]: fileData }});
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logError({
|
|
35
|
+
message: `Failed to parse "${path.join(getPathConfig().src, getPathConfig().data.src, fileName)}"`,
|
|
36
|
+
plugin: 'data',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (cache[locale] && cache[locale][group]) {
|
|
43
|
+
return cache[locale][group];
|
|
44
|
+
} else {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modify configuration
|
|
3
|
+
*
|
|
4
|
+
* @param {object} config Translations configuration
|
|
5
|
+
* @returns {object} Transformed Translations configuration
|
|
6
|
+
*/
|
|
7
|
+
export default function preprocessTranslationsConfig (config = {}) {
|
|
8
|
+
if ((!config.locales || !config.locales.length) && config.defaultLocale) {
|
|
9
|
+
config.locales = [config.defaultLocale];
|
|
10
|
+
} else if (config.locales && config.locales.length && !config.defaultLocale) {
|
|
11
|
+
config.defaultLocale = config.locales[0];
|
|
12
|
+
} else if ((!config.locales || !config.locales.length) && !config.defaultLocale) {
|
|
13
|
+
// Assume "en" as default
|
|
14
|
+
config.locales = ['en'];
|
|
15
|
+
config.defaultLocale = 'en';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import fsPromises from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const publicPath = path.resolve(__dirname, 'public');
|
|
7
|
+
|
|
8
|
+
test('Translation EN paths', () => {
|
|
9
|
+
return fsPromises.readFile(path.resolve(publicPath, 'translation.html'), { encoding: 'utf8' }).then((html) => {
|
|
10
|
+
expect(html.indexOf('<p>ROUTE: /translation</p>')).not.toBe(-1);
|
|
11
|
+
expect(html.indexOf('<p>PATH: /translation</p>')).not.toBe(-1);
|
|
12
|
+
expect(html.indexOf('<p>DE_PATH: /de/translation</p>')).not.toBe(-1);
|
|
13
|
+
expect(html.indexOf('<p>EN_PATH: /translation</p>')).not.toBe(-1);
|
|
14
|
+
|
|
15
|
+
expect(html.indexOf('<p>HOMEPAGE PATH: /</p>')).not.toBe(-1);
|
|
16
|
+
expect(html.indexOf('<p>HOMEPAGE DE_PATH: /de/</p>')).not.toBe(-1);
|
|
17
|
+
expect(html.indexOf('<p>HOMEPAGE EN_PATH: /</p>')).not.toBe(-1);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('Translation DE paths', () => {
|
|
22
|
+
return fsPromises.readFile(path.resolve(publicPath, 'de/translation.html'), { encoding: 'utf8' }).then((html) => {
|
|
23
|
+
expect(html.indexOf('<p>ROUTE: /translation</p>')).not.toBe(-1);
|
|
24
|
+
expect(html.indexOf('<p>PATH: /de/translation</p>')).not.toBe(-1);
|
|
25
|
+
expect(html.indexOf('<p>DE_PATH: /de/translation</p>')).not.toBe(-1);
|
|
26
|
+
expect(html.indexOf('<p>EN_PATH: /translation</p>')).not.toBe(-1);
|
|
27
|
+
|
|
28
|
+
expect(html.indexOf('<p>HOMEPAGE PATH: /de/</p>')).not.toBe(-1);
|
|
29
|
+
expect(html.indexOf('<p>HOMEPAGE DE_PATH: /de/</p>')).not.toBe(-1);
|
|
30
|
+
expect(html.indexOf('<p>HOMEPAGE EN_PATH: /</p>')).not.toBe(-1);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Translation EN', () => {
|
|
35
|
+
return fsPromises.readFile(path.resolve(publicPath, 'translation.html'), { encoding: 'utf8' }).then((html) => {
|
|
36
|
+
expect(html.indexOf('<p>TITLE: Title</p>')).not.toBe(-1);
|
|
37
|
+
expect(html.indexOf('<p>CATEGORY: Category</p>')).not.toBe(-1);
|
|
38
|
+
|
|
39
|
+
// Missing translation file
|
|
40
|
+
expect(html.indexOf('<p>TITLE 2: Test 2</p>')).not.toBe(-1);
|
|
41
|
+
|
|
42
|
+
// Missing translations just produce same string
|
|
43
|
+
expect(html.indexOf('<p>MISSING: missing</p>')).not.toBe(-1);
|
|
44
|
+
expect(html.indexOf('<p>INVALID_GROUP: invalid_group</p>')).not.toBe(-1);
|
|
45
|
+
expect(html.indexOf('<p>MISSING_ATTRIBUTES: missing_attributes</p>')).not.toBe(-1);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('Translation DE', () => {
|
|
50
|
+
return fsPromises.readFile(path.resolve(publicPath, 'de/translation.html'), { encoding: 'utf8' }).then((html) => {
|
|
51
|
+
expect(html.indexOf('<p>TITLE: Titel</p>')).not.toBe(-1);
|
|
52
|
+
expect(html.indexOf('<p>CATEGORY: Kategorie</p>')).not.toBe(-1);
|
|
53
|
+
|
|
54
|
+
// Missing translation file
|
|
55
|
+
expect(html.indexOf('<p>TITLE 2: title</p>')).not.toBe(-1);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -36,7 +36,7 @@ const filePush = (file, css, callback) => {
|
|
|
36
36
|
const handleError = (error, file, callback) => {
|
|
37
37
|
const filePath = (error.file === 'stdin' ? file.path : error.file) || file.path;
|
|
38
38
|
const relativePath = path.relative(process.cwd(), filePath);
|
|
39
|
-
const message = `${chalk.underline(relativePath)}\n${error.formatted}`;
|
|
39
|
+
const message = `${chalk.underline(relativePath)}\n${error.formatted || error.message}`;
|
|
40
40
|
|
|
41
41
|
error.messageFormatted = message;
|
|
42
42
|
error.messageOriginal = error.message;
|