From e089388ab53d8e015f0cc997b3cee0fb1d62bca6 Mon Sep 17 00:00:00 2001 From: ache Date: Sat, 22 Apr 2023 00:08:23 +0200 Subject: Support for multilang --- src/build/article.mjs | 31 ++++++ src/build/i18n.mjs | 56 ++++++++++ src/build/index.mjs | 242 ++++++++++++++++---------------------------- src/build/list-articles.mjs | 7 -- src/build/loadMD.mjs | 75 ++++++++++++++ src/build/utils.mjs | 0 src/css/_contenu.scss | 11 ++ src/templates/article.tmpl | 2 +- src/templates/header.tmpl | 2 +- src/templates/hid.tmpl | 2 +- src/templates/index.tmpl | 2 +- src/templates/left.tmpl | 18 ++-- src/templates/tag.tmpl | 2 +- 13 files changed, 274 insertions(+), 176 deletions(-) create mode 100644 src/build/article.mjs create mode 100644 src/build/i18n.mjs delete mode 100644 src/build/list-articles.mjs create mode 100644 src/build/loadMD.mjs create mode 100644 src/build/utils.mjs (limited to 'src') diff --git a/src/build/article.mjs b/src/build/article.mjs new file mode 100644 index 0000000..1a7b09b --- /dev/null +++ b/src/build/article.mjs @@ -0,0 +1,31 @@ +// Set of functions to handle articles + +function getArticleYear(article) { + if (article.metaData.pubDate.getFullYear) { + return article.metaData.pubDate.getFullYear(); + } + + if (article.metaData.pubDate.getUTCFullYear) { + return article.metaData.pubDate.getUTCFullYear(); + } + + return 0; +} + +function getArticleDate(article) { + if (article.metaData.pubDate.getDate) { + return article.metaData.pubDate.getFullYear() * 100 + article.metaData.pubDate.getDate(); + } + + if (article.metaData.pubDate.getUTCDate) { + return article.metaData.pubDate.getUTCFullYear() * 100 + article.metaData.pubDate.getDate(); + } + + return 0; +} + +function cmpArticles(a, b) { + return getArticleDate(b) - getArticleDate(a); +} + +export {getArticleDate, getArticleYear, cmpArticles}; diff --git a/src/build/i18n.mjs b/src/build/i18n.mjs new file mode 100644 index 0000000..f81f413 --- /dev/null +++ b/src/build/i18n.mjs @@ -0,0 +1,56 @@ + +const i18n = { + fr: { + intro: { + 'description': 'Éternel étudiant en Math-Info.', + 'about': 'Autodidacte passionné,
désormais ingénieur.', + 'about_tags': 'GNU\Linux, C, C++, Python, Math, autohébergement, décentralisation, P2P, commun, ...
', + }, + title: { + 'main': 'ache: Blog personnel', + 'home': 'L\'accueil', + 'git': 'Dépôt git personnel', + 'mastodon': 'Mon mastodon', + }, + articles: [ + 'bizarreries-du-langage-c.md', + 'retour-sur-laoc-2021-semaine-1.md', + '2FA-discord-sur-pc.md', + 'duckduckgo-google-en-mieux.md', + ] + }, + en: { + intro: { + 'description': 'Everlast CS student.', + 'about': 'Passionated self-taught,
now engineer.', + 'about_tags': 'GNU\Linux, C, C++, Python, Math, self-hosted, dececntralisation, P2P, ...
', + }, + title: { + 'main': 'ache: Personnel blog', + 'home': 'Home', + 'git': 'Personnel git repository', + 'mastodon': 'Mastodon account', + }, + articles: [ + 'c-language-quirks.md', + ] + } +} + +const lang_desc = { + fr: "Version Française", + en: "English version", +} + +export function addDescription(alt_lang) { + if (alt_lang) { + alt_lang.forEach(e => { + e.description = lang_desc[e.lang] + }); + } + + return alt_lang; +} + + +export default i18n; diff --git a/src/build/index.mjs b/src/build/index.mjs index c2701d9..7a47c8e 100644 --- a/src/build/index.mjs +++ b/src/build/index.mjs @@ -1,106 +1,11 @@ import fs from 'node:fs'; import mustache from 'mustache'; -import {u} from 'unist-builder'; -import {h} from 'hastscript'; -import {select} from 'hast-util-select'; -import {toString as hastToString} from 'mdast-util-to-string'; -import cssesc from 'cssesc'; -import toml from '@ltd/j-toml'; - -import {toHtmlRaw, toString, toMdRaw, mdToHtmlRaw} from './to-html.mjs'; import loadSVG from './load-svg.mjs'; -import listArticles from './list-articles.mjs'; import getRSS from './rss.mjs'; - -function getArticleYear(article) { - if (article.metaData.pubDate.getFullYear) { - return article.metaData.pubDate.getFullYear(); - } - - if (article.metaData.pubDate.getUTCFullYear) { - return article.metaData.pubDate.getUTCFullYear(); - } - - return 0; -} - -function getArticleDate(article) { - if (article.metaData.pubDate.getDate) { - return article.metaData.pubDate.getFullYear() * 100 + article.metaData.pubDate.getDate(); - } - - if (article.metaData.pubDate.getUTCDate) { - return article.metaData.pubDate.getUTCFullYear() * 100 + article.metaData.pubDate.getDate(); - } - - return 0; -} - -function cmpArticles(a, b) { - return getArticleDate(b) - getArticleDate(a); -} - -const loadMD = (listFile, suffix) => { - const listContent = []; - for (const file of listFile) { - console.log(`Working on ${file}`); - const content = fs.readFileSync(`${suffix}/${file}`, 'utf8'); - const mdRaw = toMdRaw(content); - const tomlStringValue = mdRaw.children[0].value; - const metaData = toml.parse(tomlStringValue); - const newHTML = mdToHtmlRaw(mdRaw); - - const htmlContent = newHTML; - const htmlRender = toString(htmlContent); - - const titleHtml = select('h1', htmlContent); - const intro = select('p', htmlContent); - intro.children = intro.children.filter(child => child.tagName !== 'br'); - - const logo = select('img', intro); - logo.properties.src = `${suffix}/${logo.properties.src}`; - logo.properties.height = '150'; - logo.properties.width = '150'; - - const logoP = select('source', intro); - if (logoP !== null) { - logoP.properties.srcSet = `${suffix}/${logoP.properties.srcSet}`; - } - - titleHtml.children[0].properties.href = `${suffix}/${file.slice(0, -3)}`; - - const title = hastToString(titleHtml); - const domTitle = cssesc(title.replace(/\s+/g, '-').replace(/['"#@]/, '').toLowerCase()); // Maybe encodeURI - - const readMore = h('a', 'Lire la suite …'); - - readMore.properties.href = `${suffix}/${file.slice(0, -3)}`; - const pubYear = getArticleYear({metaData}); - - if (metaData.pubDate) { - try { - metaData.pubDateISO = metaData.pubDate.toISOString(); - } catch (error) { - console.error(`Error on file ${file} with pubDate (${metaData.pubDate}): ${error}`); - } - } - - listContent.push({ - name: file.slice(0, -3), - content: htmlRender, - intro: toString(u('root', [titleHtml, intro, readMore])), - introDesc: hastToString(intro), - imageUrl: logo.properties.src, - metaData, - pubYear, - title, - domTitle, - url: `/${suffix}/${file.slice(0, -3)}`, - }); - } - - return listContent; -}; +import loadMD from './loadMD.mjs'; +import {cmpArticles} from './article.mjs'; +import i18n from './i18n.mjs'; +import {addDescription} from './i18n.mjs'; const leftPanelTmpl = fs.readFileSync('src/templates/left.tmpl', 'utf8'); const likesTmpl = fs.readFileSync('src/templates/likes.tmpl', 'utf8'); @@ -118,68 +23,95 @@ const partials = { hid: hidTmpl, }; +// Load global variables const svg = loadSVG(); -const articles = loadMD(listArticles, 'articles'); -const tagsArticle = new Map(); - -for (const article of articles) { - const context = { - svg, - title: `${article.title} - ache`, - canonical: `${baseUrl}${article.url.slice(1)}`, - content: article.content, - domTitle: article.domTitle, - metaData: article.metaData, - }; - const output = mustache.render(articleTmpl, context, partials); - console.log(`Create : ${article.title}`); - fs.writeFileSync(`articles/${article.name}.html`, output); - - for (const tag of article.metaData.tags) { - if (tagsArticle.has(tag)) { - tagsArticle.get(tag).push(article); - } else { - tagsArticle.set(tag, [article]); +for (const lang in i18n) { + const tagsArticle = new Map(); + const articles = loadMD(i18n[lang].articles, 'articles', lang); + + for (const article of articles) { + const context = { + svg, + page_title: `${article.title} - ache`, + title: i18n[lang].title, + intro: i18n[lang].intro, + lang, + canonical: `${baseUrl}${article.url.slice(1)}`, + content: article.content, + title: i18n[lang].title, + metaData: article.metaData, + alt_lang: addDescription(article.metaData.alt_lang), + }; + const output = mustache.render(articleTmpl, context, partials); + + console.log(`Create : ${article.title}`); + fs.writeFileSync(`articles/${article.name}.html`, output); + + for (const tag of article.metaData.tags) { + if (tagsArticle.has(tag)) { + tagsArticle.get(tag).push(article); + } else { + tagsArticle.set(tag, [article]); + } } } -} - -try { - fs.mkdirSync('tag'); -} catch { - fs.rmSync('tag', {force: true, recursive: true}); - fs.mkdirSync('tag'); -} - -for (const [tag, articles] of tagsArticle.entries()) { - console.log(`Create tag page : ${tag}.xml`); - articles.sort(cmpArticles); - const context = { - svg, - title: `ache - Tag: ${tag}`, - tag, - articles, - }; +// Set of pages language dependant - const output = mustache.render(tagTmpl, context, partials); - fs.writeFileSync(`tag/${tag}.html`, output); -} - -console.log('Create RSS Flux: rss.xml'); -const xmlFeed = getRSS(articles, baseUrl); -fs.writeFileSync('rss.xml', xmlFeed); + try { + fs.mkdirSync(`${lang}/tag`, {recursive: true}); + } catch { + fs.rmSync(`${lang}/tag`, {force: true, recursive: true}); + fs.mkdirSync(`${lang}/tag`, {recursive: true}); + } -{ - const context = { - title: 'ache: Blog personnel', - canonical: baseUrl, - svg, - articles, - }; + for (const [tag, articles] of tagsArticle.entries()) { + console.log(`Create tag page : ${lang}/${tag}.html`); + articles.sort(cmpArticles); + + const context = { + svg, + page_title: `ache - Tag: ${tag}`, + title: i18n[lang].title, + intro: i18n[lang].intro, + lang, + tag, + articles, + }; + + const output = mustache.render(tagTmpl, context, partials); + fs.writeFileSync(`${lang}/tag/${tag}.html`, output); + } - console.log('Create : Home page'); - const output = mustache.render(indexTmpl, context, partials); - fs.writeFileSync('index.html', output); + console.log(`Create RSS Flux: ${lang}/rss.xml`); + const xmlFeed = getRSS(articles, baseUrl); + fs.writeFileSync(`${lang}/rss.xml`, xmlFeed); + + { + const alt_lang = { + fr: { + lang: 'en', + url: '/en/', + }, + en: { + lang: 'fr', + url: '/fr/', + }, + }; + const context = { + page_title: i18n[lang].title.main, + title: i18n[lang].title, + intro: i18n[lang].intro, + lang, + canonical: baseUrl, + svg, + articles, + alt_lang: [alt_lang[lang]], + }; + + console.log(`Create : Home page ${lang}`); + const output = mustache.render(indexTmpl, context, partials); + fs.writeFileSync(`${lang}/index.html`, output); + } } diff --git a/src/build/list-articles.mjs b/src/build/list-articles.mjs deleted file mode 100644 index d327573..0000000 --- a/src/build/list-articles.mjs +++ /dev/null @@ -1,7 +0,0 @@ -const listArticles = [ - 'bizarreries-du-langage-c.md', - 'retour-sur-laoc-2021-semaine-1.md', - '2FA-discord-sur-pc.md', - 'duckduckgo-google-en-mieux.md', -]; -export default listArticles; diff --git a/src/build/loadMD.mjs b/src/build/loadMD.mjs new file mode 100644 index 0000000..6d513ef --- /dev/null +++ b/src/build/loadMD.mjs @@ -0,0 +1,75 @@ +import fs from 'node:fs'; +import {u} from 'unist-builder'; +import {h} from 'hastscript'; +import {select} from 'hast-util-select'; +import {toString as hastToString} from 'mdast-util-to-string'; +import cssesc from 'cssesc'; +import toml from '@ltd/j-toml'; + +import {toString, toMdRaw, mdToHtmlRaw} from './to-html.mjs'; +import {getArticleYear} from './article.mjs'; +import i18n from './i18n.mjs'; + +const loadMD = (listFile, suffix, lang) => { + const listContent = []; + for (const file of listFile) { + console.log(`Working on ${file}`); + const content = fs.readFileSync(`${suffix}/${file}`, 'utf8'); + const mdRaw = toMdRaw(content); + const tomlStringValue = mdRaw.children[0].value; + const metaData = toml.parse(tomlStringValue); + const newHTML = mdToHtmlRaw(mdRaw); + + const htmlContent = newHTML; + const htmlRender = toString(htmlContent); + + const titleHtml = select('h1', htmlContent); + const intro = select('p', htmlContent); + intro.children = intro.children.filter(child => child.tagName !== 'br'); + + const logo = select('img', intro); + logo.properties.src = `/${suffix}/${logo.properties.src}`; + logo.properties.height = '150'; + logo.properties.width = '150'; + + const logoP = select('source', intro); + if (logoP !== null) { + logoP.properties.srcSet = `/${suffix}/${logoP.properties.srcSet}`; + } + + titleHtml.children[0].properties.href = `/${suffix}/${file.slice(0, -3)}`; + + const title = hastToString(titleHtml); + const domTitle = cssesc(title.replace(/\s+/g, '-').replace(/['"#@]/, '').toLowerCase()); // Maybe encodeURI + + const readMore = h('a', i18n[lang]['read_more']); + + readMore.properties.href = `/${suffix}/${file.slice(0, -3)}`; + const pubYear = getArticleYear({metaData}); + + if (metaData.pubDate) { + try { + metaData.pubDateISO = metaData.pubDate.toISOString(); + } catch (error) { + console.error(`Error on file ${file} with pubDate (${metaData.pubDate}): ${error}`); + } + } + + listContent.push({ + name: file.slice(0, -3), + content: htmlRender, + intro: toString(u('root', [titleHtml, intro, readMore])), + introDesc: hastToString(intro), + imageUrl: logo.properties.src, + metaData, + pubYear, + title, + domTitle, + url: `/${suffix}/${file.slice(0, -3)}`, + }); + } + + return listContent; +}; + +export default loadMD; diff --git a/src/build/utils.mjs b/src/build/utils.mjs new file mode 100644 index 0000000..e69de29 diff --git a/src/css/_contenu.scss b/src/css/_contenu.scss index c2b3058..9d2a9dd 100755 --- a/src/css/_contenu.scss +++ b/src/css/_contenu.scss @@ -408,6 +408,17 @@ section.likes { } } } +.alt-lang { + a { + padding-right: 20px; + } + font-size: 1.15rem; + text-transform: lowercase; + font-weight: bolder; + position: relative; + top: -3px; + font-family: Source Sans Pro,Segoe UI,Trebuchet MS,Helvetica,Helvetica Neue,Arial,sans-serif; +} .tags { display: flex; diff --git a/src/templates/article.tmpl b/src/templates/article.tmpl index 6b1b628..5419628 100644 --- a/src/templates/article.tmpl +++ b/src/templates/article.tmpl @@ -5,7 +5,7 @@
-
{{# metaData.tags }}{{{ . }}}{{/ metaData.tags }}
+
{{# metaData.tags }}{{{ . }}}{{/ metaData.tags }}
{{ metaData.pubDateISO }}
{{{ content }}} {{> likesButton }} diff --git a/src/templates/header.tmpl b/src/templates/header.tmpl index 433b130..6085192 100644 --- a/src/templates/header.tmpl +++ b/src/templates/header.tmpl @@ -1,6 +1,6 @@ - {{ title }} + {{ page_title }} diff --git a/src/templates/hid.tmpl b/src/templates/hid.tmpl index 242ed5f..7f0ab78 100644 --- a/src/templates/hid.tmpl +++ b/src/templates/hid.tmpl @@ -1 +1 @@ -
{{{svg.sun}}}
{{{svg.moon}}}
+
{{# alt_lang }}{{ lang }}{{/ alt_lang }}
{{{svg.sun}}}
{{{svg.moon}}}
diff --git a/src/templates/index.tmpl b/src/templates/index.tmpl index 61d1221..5ed2ed4 100644 --- a/src/templates/index.tmpl +++ b/src/templates/index.tmpl @@ -1,5 +1,5 @@ - + {{>header }} {{#articles}} diff --git a/src/templates/left.tmpl b/src/templates/left.tmpl index ef4715b..c8a4bb7 100644 --- a/src/templates/left.tmpl +++ b/src/templates/left.tmpl @@ -2,27 +2,27 @@ {{{ svg.lt }}}