feat(i18n): Translate attributes

This commit is contained in:
Tibo De Peuter 2025-11-02 17:04:44 +01:00
parent e2dc68e7ed
commit 1a20d1ae31
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
6 changed files with 102 additions and 19 deletions

View file

@ -341,7 +341,6 @@
<!-- TODO Keep study place and year on the same line where possible -->
<p>
<span class="degree save-link">
<!-- TODO Also change URLs based on language -->
<a href="https://studiekiezer.ugent.be/2024/master-of-science-in-computer-science"
data-i18n="cv.msc_computer_science.title">
M.Sc. Computer Science
@ -357,7 +356,6 @@
<li>
<p>
<span class="degree save-link">
<!-- TODO Also change URLs based on language -->
<a href="https://studiekiezer.ugent.be/2021/bachelor-of-science-in-computer-science"
data-i18n="cv.bsc_computer_science.title">
B.Sc. Computer Science
@ -400,7 +398,9 @@
- <span class="place" data-i18n="names.slc">Sint-Lievenscollege Secondary School Ghent</span>
<time class="time" datetime="2021-06">2021</time>
<br>
<span class="description">CLIL English: 4th year geography, 6th year biology</span>
<span class="description" data-i18n="cv.hi_science_maths.description">
CLIL English: 4th year geography, 6th year biology
</span>
</p>
</li>
</ul>

View file

@ -1,6 +1,15 @@
{
"alternative_link": "alternative link",
"contact": {
"email": {
"title": "Mail me"
},
"github": {
"title": "My GitHub account"
},
"linkedin": {
"title": "My LinkedIn account"
},
"mobile": "Mobile",
"reveal": "Show contact information",
"rijbewijs_b": "Category B driving license"
@ -20,7 +29,10 @@
"4_ai": "Understanding of ethical and sustainable Artificial Intelligence, <abbr title=\"Explainable AI\">XAI</abbr>, neural networks, <abbr title=\"Constraint Satisfaction Problem\">CSPs</abbr>, <abbr title=\"Hidden Markov Models\">HMMs</abbr> and computer vision.",
"5_learning": "Lifelong learning, quickly and independently adopting new technologies and methodologies.",
"description": "Minor in security and parallel systems",
"title": "B.Sc. Computer Science"
"title": {
"href": "https://studiekiezer.ugent.be/2021/bachelor-of-science-in-computer-science",
"innerHTML": "B.Sc. Computer Science"
}
},
"certificates": "Certificates",
"codefever": {
@ -40,12 +52,16 @@
"title": "Head youth leader"
},
"hi_science_maths": {
"description": "CLIL English: 4th year geography, 6th year biology",
"title": "Science and mathematics"
},
"languages": "Languages",
"msc_computer_science": {
"description": "Electives with a focus on high-performance systems and cybersecurity",
"title": "M.Sc. Computer Science"
"title": {
"href": "https://studiekiezer.ugent.be/2024/master-of-science-in-computer-science",
"innerHTML": "M.Sc. Computer Science"
}
},
"operating_systems": "Operating systems",
"other_skills": "Microsoft Office and equivalents, Adobe Creative Cloud",
@ -67,6 +83,9 @@
},
"kind_regards": "Kind regards",
"landing": {
"git": {
"title": "Visit my personal git server"
},
"subtitle": "I like to work with computers.",
"title": "My name is Tibo De Peuter."
},
@ -79,6 +98,11 @@
"mother_tongue": "Mother tongue",
"nl": "Dutch"
},
"languageToggle": {
"change": {
"alt": "Change language"
}
},
"names": {
"slc": "Saint Livinuscollege Secondary School Ghent",
"ugent": "Ghent University"

View file

@ -1,6 +1,15 @@
{
"alternative_link": "alternatieve link",
"contact": {
"email": {
"title": "Stuur mij een e-mail"
},
"github": {
"title": "Mijn GitHub account"
},
"linkedin": {
"title": "Mijn LinkedIn account"
},
"mobile": "Mobiel",
"reveal": "Toon contactinformatie",
"rijbewijs_b": "Rijbewijs B"
@ -20,7 +29,10 @@
"4_ai": "Inzicht in ethische en duurzame Artificiële Intelligentie, <abbr title=\"Explainable AI\">XAI</abbr>, neurale netwerken, <abbr title=\"Constraint Satisfaction Problem\">CSP</abbr>'s, <abbr title=\"Hidden Markov Model\">HMM</abbr>'s en computer visie.",
"5_learning": "Levenslang leren en snel zelfstandig nieuwe technologieën en methodologieën eigen maken.",
"description": "Minor beveiliging & parallele systemen",
"title": "BSc informatica"
"title": {
"href": "https://studiekiezer.ugent.be/2021/bachelor-of-science-in-de-informatica",
"innerHTML": "BSc informatica"
}
},
"certificates": "Attesten",
"codefever": {
@ -40,12 +52,16 @@
"title": "Hoofdanimator in het Jeugdwerk"
},
"hi_science_maths": {
"description": "CLIL Engels: 4de jaar aardrijkskunde, 6de jaar biologie",
"title": "Wetenschappen-wiskunde"
},
"languages": "Talenkennis",
"msc_computer_science": {
"description": "Keuzevakken met een focus op performante systemen en beveiliging",
"title": "MSc informatica"
"title": {
"href": "https://studiekiezer.ugent.be/2024/master-of-science-in-de-informatica",
"innerHTML": "MSc informatica"
}
},
"operating_systems": "Besturingssystemen",
"other_skills": "Microsoft Office en varianten, Adobe Creative Cloud",
@ -67,6 +83,9 @@
},
"kind_regards": "Met vriendelijke groeten",
"landing": {
"git": {
"title": "Bezoek mijn persoonlijke git server"
},
"subtitle": "Ik werk graag met computers.",
"title": "Mijn naam is Tibo De Peuter."
},
@ -79,6 +98,11 @@
"mother_tongue": "Moedertaal",
"nl": "Nederlands"
},
"languageToggle": {
"change": {
"alt": "Verander taal"
}
},
"names": {
"slc": "Sint-Lievenscollege Humaniora Gent",
"ugent": "Universiteit Gent"

View file

@ -42,22 +42,22 @@
<ul class="links">
<li>
<a href="mailto:tibo@depeuter.dev" title="My mail address">
<img src="assets/images/icons/mail.svg" alt="Mail me" class="icon"/>
<a href="mailto:tibo@depeuter.dev" title="Mail me" data-i18n="contact.email">
<img src="assets/images/icons/mail.svg" alt="" class="icon"/>
</a>
</li>
<li>
<a href="https://git.depeuter.dev/tdpeuter" title="My personal git">
<img src="assets/images/icons/git.svg" alt="Gitea" class="icon"/>
<a href="https://git.depeuter.dev/tdpeuter" title="Visit my personal git" data-i18n="landing.git">
<img src="assets/images/icons/git.svg" alt="" class="icon"/>
</a>
</li>
<li>
<a href="https://github.com/tdpeuter" title="My GitHub account">
<img src="assets/images/icons/github.svg" alt="GitHub" class="icon"/>
<a href="https://github.com/tdpeuter" title="My GitHub account" data-i18n="contact.github">
<img src="assets/images/icons/github.svg" alt="" class="icon"/>
</a>
</li>
<li>
<a href="https://www.linkedin.com/in/tdpeuter/" title="My LinkedIn account">
<a href="https://www.linkedin.com/in/tdpeuter/" title="My LinkedIn account" data-i18n="contact.linkedin">
<img src="assets/images/icons/linkedin.svg" alt="LinkedIn" class="icon"/>
</a>
</li>

View file

@ -25,7 +25,7 @@ function getLanguageToggle() {
</style>
<label for="language-dropdown"></label>
<img class="icon" src="/assets/images/icons/language.svg" alt="language"/>
<img class="icon" src="/assets/images/icons/language.svg" alt="Change language" data-i18n="languageToggle.change"/>
<select id="language-dropdown">
<option value="en" data-i18n="lang.en">English</option>
<option value="nl" data-i18n="lang.nl">Dutch</option>

View file

@ -1,5 +1,22 @@
import { I18nLoadedEvent } from "./i18nLoadedEvent.js";
/**
* @class Translator - Manages loading and applying translations for the application.
* @property {string} prefferedLanguage - The preferred language code.
*
* Expects translation files to be structured as JSON files for each language, more specifically:
* <key>: <value>,
* <key.attribute>: <value>,
* <category>: {
* <subkey>: <subvalue>
* }
* to be referenced in HTML as:
* <element data-i18n="key"></element>
* <element data-i18n="category.subkey"></element>
*
* (Attributes behave as though they are subkeys.)
*/
class Translator {
constructor() {
this.prefferedLanguage = this.determinePreferredLanguage();
@ -61,12 +78,30 @@ class Translator {
translateElement(element, translations) {
const keys = element.dataset.i18n.split('.');
const text = keys.reduce((obj, key) => obj[key], translations);
const elementTranslations = keys.reduce((obj, key) => obj[key], translations);
debugLog(`Translating '${element.dataset.i18n}': '${JSON.stringify(elementTranslations)}'`);
if (text) {
element.innerHTML = text;
} else {
// No translation found
if (!elementTranslations) {
element.innerHTML = `[i18n:${element.dataset.i18n}]`;
return;
}
// Simple string translation
if (typeof elementTranslations === 'string') {
element.innerHTML = elementTranslations;
return;
}
// Attribute translations
for (const [attribute, value] of Object.entries(elementTranslations)) {
if (attribute === 'innerHTML') {
element.innerHTML = value;
continue;
}
debugLog(`Translating attribute '${attribute}': '${value}'`);
element.setAttribute(attribute, value);
}
}