translation pipeline: working.

This commit is contained in:
Hans Fast 2026-03-23 15:41:15 +01:00
parent 3025b6d50a
commit 2ecb2ab1fd
3 changed files with 189 additions and 19 deletions

View File

@ -1,5 +1,134 @@
:root {
/* size variables */
/* more pronounced, perfect fifth on narrow screens */
/* @link https://utopia.fyi/type/calculator?c=360,16,1.5,1240,20,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
--step--2: clamp(0.4444rem, 0.299rem + 0.6465vw, 0.8rem);
--step--1: clamp(0.6667rem, 0.5303rem + 0.6061vw, 1rem);
--step-0: clamp(1rem, 0.8977rem + 0.4545vw, 1.25rem);
--step-1: clamp(1.5rem, 1.4744rem + 0.1136vw, 1.5625rem);
--step-2: clamp(1.9531rem, 2.3714rem + -0.5398vw, 2.25rem);
--step-3: clamp(2.4414rem, 3.7569rem + -1.6974vw, 3.375rem);
--step-4: clamp(3.0518rem, 5.8851rem + -3.6559vw, 5.0625rem);
--step-5: clamp(3.8147rem, 9.1397rem + -6.871vw, 7.5938rem);
/* @link https://utopia.fyi/space/calculator?c=360,18,1.2,1240,20,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
--space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
--space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
--space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
--space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
--space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
--space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
--space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
--space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
--space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
/* One-up pairs */
--space-3xs-2xs: clamp(0.3125rem, 0.1847rem + 0.5682vw, 0.625rem);
--space-2xs-xs: clamp(0.5625rem, 0.4091rem + 0.6818vw, 0.9375rem);
--space-xs-s: clamp(0.875rem, 0.7216rem + 0.6818vw, 1.25rem);
--space-s-m: clamp(1.125rem, 0.8182rem + 1.3636vw, 1.875rem);
--space-m-l: clamp(1.6875rem, 1.3551rem + 1.4773vw, 2.5rem);
--space-l-xl: clamp(2.25rem, 1.6364rem + 2.7273vw, 3.75rem);
--space-xl-2xl: clamp(3.375rem, 2.7102rem + 2.9545vw, 5rem);
--space-2xl-3xl: clamp(4.5rem, 3.2727rem + 5.4545vw, 7.5rem);
/* Custom pairs */
--space-s-l: clamp(1.125rem, 0.5625rem + 2.5vw, 2.5rem);
/*COLORS*/
--blue: #d7f0e1;
--darkblue: #2c6a88;
--oc-gray-3: #dee2e6;
--oc-gray-5: #adb5bd;
--oc-gray-6: #868e96;
--oc-gray-7: #495057;
--oc-gray-8: #343a40;
--oc-cyan-6: #15aabf;
--oc-cyan-9: #0b7285;
--oc-blue-1: #d0ebff;
--oc-blue-3: #74c0fc;
--oc-blue-4: #4dabf7;
--oc-blue-7: #1c7ed6;
--oc-blue-9: #1864ab;
--oc-grape-9: #862e9c;
--oc-violet-4: #9775fa;
--oc-violet-9: #5f3dc4;
}
body {
font-family: "sans-serif";
margin: 0 auto;
max-width: 66ch;
}
@layer translations {
body > trl-selector:first-child {display: none;} /* was adding a grid row! */
trl-selector {
display: flex;
flex-direction: row;
gap: var(--space-3xs);
flex-wrap: wrap;
justify-content: end;
justify-self: end;
align-items: start;
padding-inline: var(--space-3xs);
margin-inline-end: var(--space-3xs);
grid-column: grid-start/content-start;
align-self: start;
margin-top: var(--space-l);
background-color: var(--oc-blue-1);
max-width: max-content;
border-radius: 3px;
label {
cursor: pointer;
}
input {
display: none;
}
/* assumes labels follow their inputs immediately */
input:checked + label {
font-weight: bold;
}
}
/* Do we use trl-selector ~ or [trl] descendants? */
/* need one of these rules for each language.*/
trl-selector:has(> input[value="nl"]:checked) {
& ~ trl-alt:lang('nl') {
display: block;
}
& ~ trl-alt:not(:lang('nl')) {
display: none;
}
}
trl-selector:has( > input[value="en"]:checked) {
& ~ trl-alt:lang('en') {
display: block;
}
& ~ trl-alt:not(:lang('en')) {
display: none;
}
}
body:has(> trl-selector input[value="nl"]:checked) {
& [trl-ui]:lang('nl') {
display: block;
}
& [trl-ui]:not(:lang('nl')) {
display: none;
}
}
body:has(> trl-selector input[value="en"]:checked) {
& [trl-ui]:lang('en') {
display: block;
}
& [trl-ui]:not(:lang('en')) {
display: none;
}
}
}

View File

@ -1,24 +1,38 @@
import {fromHtml} from 'hast-util-from-html';
import {toHtml} from 'hast-util-to-html';
import {visit} from 'unist-util-visit';
import {JSDOM} from 'jsdom';
/*
* The idea: need to create another subdir `title/nl/index.html`
*/
/*
* addTranslationInterface adds the <trl-selector> with radio buttons and labels to each <trl-group>.
* It sets the checked property on the indicated language.
*/
/*
* first, index.html is the original page, with the page language (from markdown frontmatter) active.
* This allows switching between languages for each translated fragment in the page.
* then, for each language in the frontmatter `trls`, another folder-with-index.html is generated,
* with <trl-selector> added,
* where the language corresponding to the given language is active.
*
* So note that page `trls: []` is independent of the languages of the `<trl-alt>`s in each `<trl-group>`.
*/
export default async (page) => {
const html = await page.html;
return {
"index.html": page.html,
"index.html": addTranslationInterface(html, page?.lang),
...handleTranslations(html, page.trls)
}
}
//this is going to create a folder for each 'translation' specified in frontmatter.
function handleTranslations(html, trls) {
if (trls) {
console.log(trls);
const outputs = {};
trls.forEach(trl => {
outputs[trl] = {
"index.html": reorderTranslations(html, trl)
"index.html": addTranslationInterface(html, trl)
}
})
return outputs;
@ -27,11 +41,43 @@ function handleTranslations(html, trls) {
}
}
//reorders all '.translation-group's so the element with given lang is first, and 'open' is set appropriately.
//also, handles latest/laatst. Is that a special case, or should that be translated in the page too?
function reorderTranslations(body, lang){
//handles both text and dom object as input
const hast = fromHtml(body);
//visit: trlselector>radio, and set active on current lang and inactive on other langs.
return toHtml(hast);
//"reorders" all `trl-group`s so the element with given lang is displayed.
//EN is default language ...
//also adds a (hidden by css) trl-selector at the top of `body`.
//TODO: add a link to translated pages ...
//that's complicated, because what about all the links in the page?
function addTranslationInterface(body, activelang="en"){
const dom = new JSDOM(body);
const doc = dom.window.document;
let seq = 1; //serial number for identifying translation group inputs and labels
const trlGroups = Array.from(doc.querySelectorAll('trl-group'));
trlGroups.forEach((trlGroup) => {
//for each trlGroup.
const trlAltElems = Array.from(trlGroup.querySelectorAll('trl-alt'));
const langsInGroup = trlAltElems.map(t => t.getAttribute('lang'));
const trlSel = doc.createElement('trl-selector');
langsInGroup.forEach(lang => {
console.log(lang);
let grname = `trlg-${seq}`;
let inputId = `${grname}-${lang}`;
const input = doc.createElement('input');
input.type = 'radio';
input.setAttribute('id', inputId);
input.setAttribute('name', grname);
input.setAttribute('lang', lang);
input.setAttribute('value', lang);
if (lang === activelang) {
input.setAttribute('checked', '');
}
const label = doc.createElement('label');
label.setAttribute('for', inputId);
label.textContent = lang;
trlSel.append(input);
trlSel.append(label);
})
trlGroup.prepend(trlSel);
seq ++; //increment id serial number
})
return dom.serialize();
}

View File

@ -64,14 +64,9 @@
Then, we can use that html and the trls property to add the translations.
*/
//NOTE! two steps at once. Could separate the withTranslations part: add a html_LANG property to the document object, then later create the folder structure.
pagesInFolders: Tree.map(renderedPages, pagesInFoldersWithTranslations.js)
/*
pagesInFolders: Tree.map(renderedPages, {
value: (value) => {
index.html: value
}
})
*/
//bug in 0.6.14?
linksByFile: Tree.map(renderedPages,getLinkedFilesFromHtml.js)