Добрый день! В своих проектах как раз использую такой "кустарный" подход, разделяя его на два этапа:
1) подгрузка глобальных констант в движок nunjucks до компиляции страниц (хранятся в json);
2) добавление фильтра на выдачу локалей (тоже хранятся в json, но в отдельной папке).
Грубо говоря, это два способа реализации,
первый - выгрузка глобальной константы в nunjucks в контекст (
gulp-nunjucks-render,
render api );
второй - это использование отдельной от nunjucks сущности (в моем случае экземпляр
i18next без бекенда), в которой происходит синхронный процесс выдачи данных по запросу через кастомный фильтр nunjucks.
Сейчас, проведя с такой сборкой уже около 8-9 месяцев, могу сказать, что там много условностей, багов и проблем с производительностью при сборке. И в целом она не умещается в рамки хоть какой-нибудь глобальной концепции по организации кода (что просто усложнит поддержку в будущем). Наверное, более правильное решение - это настроить какой-то готовый генератор статических сайтов, чем писать свою собственную сборку. Но если вам интересно, с чего начать, то структура и код:
├── datasets
│ └── meta.json
├── locales
│ ├── en
│ │ └── translation.json
│ └── ru
├── tasks
...
└── gulpfile.js
# Это упрощенная структура,
# но если вам будет что-то непонятно, я напишу подробнее.
код html таска на сборку страниц
import gulp from 'gulp';
import fs from 'fs';
import path from 'path';
import i18next from 'i18next';
import config from '../gulpfile.config';
import engine from 'gulp-nunjucks-render';
import minify from 'gulp-htmlmin';
import Backend from 'i18next-sync-fs-backend';
import rename from 'gulp-rename';
i18next.use(Backend).init({
debug: true,
fallbackLng: ['en'],
initImmediate: false,
backend: {
loadPath: config.paths.locales + '/{{lng}}/{{ns}}.json' // локали собираются по названиям из папки locales
},
ns: ['translation'],
defaultNS: 'translation'
});
export function html(done) {
const envHooks = [
env => env.addFilter('__', function(key, ns) { // фильтр на поиск и выдачу информации по ключу в i18next
if(!i18next.exists(key)) return 0;
return i18next.t(key);
}),
]
const data = fs.readdirSync( config.paths.datasets ).reduce( (acc, filename) => {
return { ...acc, [ path.basename( filename, '.json') ] : require('../' + config.paths.datasets + '/' + filename) };
}, {});
data.get = function(name) {
return this[name];
}
const [ def ] = i18next.options.fallbackLng;
const rec = (arr) => {
const [lng, ...rest] = arr;
return i18next.changeLanguage(lng, (err) => {
if(err) throw new Error(err);
return gulp.src(config.paths.pages)
.pipe(engine({
data: {
datasets: data,
},
path: ['src/pages/templates'],
manageEnv: function(env) {
return envHooks.forEach(fn => fn(env));
},
}))
.pipe(minify({ collapseWhitespace: true }))
.pipe(rename(function(path) {
path.basename = lng === def ? path.basename : lng + '.' + path.basename;
}))
.pipe(gulp.dest(config.server.dest))
.on('end', () => {
return rest.length ? rec(rest) : done();
});
});
}
rec(['ru', 'en']); // если язык дефолтный, то страницы компилируются обычным образом, если нет - у них будет префикс с названием языка
return;
};
В коде демонстрируется сразу 2 подхода. В темплейтах вызывается по-разному:
1)
<!--
datasets/features.json
[
{
"name" : "independent",
"_descr" : "features.indep.descr"
},
{
"name" : "secure",
"_descr" : "features.secure.descr",
},
]
-->
{% set features = datasets.get('features') %}
<ul class="page-home__features">
{% for f in features %}
<li>
{{ feature.make(f) }}
</li>
{% endfor %}
</ul>
2)
<!--
locales/en/translation.json
"pages" : {
"home" : {
"headings" : {
"qualities": "a perfect solution for projects"
}
}
}
-->
<code lang="html">
<h2 class="text-center font-weight-bold mb-4 mb-lg-6">
{{ 'pages.home.headings.qualities' | __ }}
<!-- фильтр зарегистрирован под названием __ -->
</h2>
</code>