diff --git a/public/assets/images/flag-en.svg b/public/assets/images/flag-en.svg
new file mode 100644
index 0000000000000000000000000000000000000000..be642b356a8353c5c91a2f03518bffc011d0faaf
--- /dev/null
+++ b/public/assets/images/flag-en.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:cc="http://creativecommons.org/ns#"
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    id="svg1"
+    inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
+    viewBox="0 0 20 15"
+    sodipodi:version="0.32"
+    inkscape:output_extension="org.inkscape.output.svg.inkscape"
+    sodipodi:docname="en.svg"
+    version="1.1"
+    width="20"
+    height="15">
+    <metadata id="metadata14">
+        <rdf:RDF>
+            <cc:Work rdf:about="">
+                <dc:format>image/svg+xml</dc:format>
+                <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+                <dc:title></dc:title>
+            </cc:Work>
+        </rdf:RDF>
+    </metadata>
+    <defs id="defs12" />
+    <sodipodi:namedview pagecolor="#ffffff"
+        bordercolor="#666666"
+        borderopacity="1"
+        objecttolerance="10"
+        gridtolerance="10"
+        guidetolerance="10"
+        inkscape:pageopacity="0"
+        inkscape:pageshadow="2"
+        inkscape:window-width="1920"
+        inkscape:window-height="1010"
+        id="namedview10"
+        showgrid="false"
+        fit-margin-top="0"
+        fit-margin-left="0"
+        fit-margin-right="0"
+        fit-margin-bottom="0"
+        inkscape:zoom="32"
+        inkscape:cx="2.0440539"
+        inkscape:cy="2.5393561"
+        inkscape:window-x="2224"
+        inkscape:window-y="0"
+        inkscape:window-maximized="1"
+        inkscape:current-layer="svg1"
+        inkscape:document-rotation="0" />
+    <g id="g578"
+        transform="matrix(0.33348664,0,0,0.49980131,0.00398355,0.00382895)">
+        <rect id="rect124"
+            style="fill:#000066;stroke-width:1pt"
+            height="30"
+            width="60"
+            y="0"
+            x="0" />
+        <g id="g584">
+            <path id="path146"
+                style="fill:#ffffff;stroke-width:1pt"
+                d="m 0,0 v 3.3541 l 53.292,26.646 H 60 v -3.354 L 6.708,1e-4 H 0 Z M 60,0 V 3.354 L 6.708,30 H 0 V 26.646 L 53.292,0 Z" />
+            <path id="path136"
+                style="fill:#ffffff;stroke-width:1pt"
+                d="M 25,0 V 30 H 35 V 0 Z M 0,10 V 20 H 60 V 10 Z" />
+            <path id="path141"
+                style="fill:#cc0000;stroke-width:1pt"
+                d="m 0,12 v 6 H 60 V 12 Z M 27,0 v 30 h 6 V 0 Z" />
+            <path id="path150"
+                style="fill:#cc0000;stroke-width:1pt"
+                d="M 0,30 20,20 h 4.472 l -20,10 z M 0,0 20,10 H 15.528 L 0,2.2361 Z m 35.528,10 20,-10 H 60 L 40,10 Z M 60,30 40,20 h 4.472 L 60,27.764 Z" />
+        </g>
+    </g>
+</svg>
diff --git a/public/assets/images/flag-fr.svg b/public/assets/images/flag-fr.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e8c6f9f6f37ed22f9ce49cc951c242cd3ff7c7f1
--- /dev/null
+++ b/public/assets/images/flag-fr.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:cc="http://creativecommons.org/ns#"
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    width="20"
+    height="15"
+    viewBox="0 0 5.2916665 3.96875"
+    version="1.1"
+    id="svg865"
+    inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
+    sodipodi:docname="fr.svg">
+    <defs id="defs859" />
+    <sodipodi:namedview id="base"
+        pagecolor="#ffffff"
+        bordercolor="#666666"
+        borderopacity="1.0"
+        inkscape:pageopacity="0.0"
+        inkscape:pageshadow="2"
+        inkscape:zoom="256"
+        inkscape:cx="20.884772"
+        inkscape:cy="15.722232"
+        inkscape:document-units="mm"
+        inkscape:current-layer="layer1"
+        inkscape:document-rotation="0"
+        showgrid="false"
+        fit-margin-top="0"
+        fit-margin-left="0"
+        fit-margin-right="0"
+        fit-margin-bottom="0"
+        inkscape:window-width="1638"
+        inkscape:window-height="1010"
+        inkscape:window-x="2224"
+        inkscape:window-y="0"
+        inkscape:window-maximized="0"
+        units="px" />
+    <metadata id="metadata862">
+        <rdf:RDF>
+            <cc:Work rdf:about="">
+                <dc:format>image/svg+xml</dc:format>
+                <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+                <dc:title></dc:title>
+            </cc:Work>
+        </rdf:RDF>
+    </metadata>
+    <g inkscape:label="Calque 1"
+        inkscape:groupmode="layer"
+        id="layer1"
+        transform="translate(-170.91496,-192.29281)">
+        <path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#0000c2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00492183;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000"
+            d="m 170.91496,192.29281 h 1.75965 v 3.96087 h -1.75965 z"
+            id="rect10" />
+        <path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.00492183;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000"
+            d="m 172.67461,192.29281 h 1.75964 v 3.96087 h -1.75964 z"
+            id="rect10-3" />
+        <path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#be0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0049432;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000"
+            d="m 174.43425,192.29281 h 1.77188 v 3.96773 h -1.77188 z"
+            id="rect10-3-6" />
+    </g>
+</svg>
diff --git a/public/assets/images/tech_logos/apache.png b/public/assets/images/tech_logos/apache.png
deleted file mode 100644
index 24c10f44733ae7c98647f08d031dfa383be38c13..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/apache.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/c.png b/public/assets/images/tech_logos/c.png
deleted file mode 100644
index fc00d068b1255276a15503c4f055fc24f6f233a9..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/c.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/css.png b/public/assets/images/tech_logos/css.png
deleted file mode 100644
index 499afaaab62a968d7ab1baf35b5e04154c92daba..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/css.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/docker.png b/public/assets/images/tech_logos/docker.png
deleted file mode 100644
index 61228d658ea6d01bb2a99ca1b24cffe6d5bddcd1..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/docker.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/html.png b/public/assets/images/tech_logos/html.png
deleted file mode 100644
index 5f78f2d438d0fd7e69d52834e0fa6e791838fd72..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/html.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/js.png b/public/assets/images/tech_logos/js.png
deleted file mode 100644
index f768218684cdb1eec27793b14d49cbac1a1c4dba..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/js.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/linux.png b/public/assets/images/tech_logos/linux.png
deleted file mode 100644
index 6226359e12db783f46ff21b7bf61fd76d9b42555..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/linux.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/mongodb.png b/public/assets/images/tech_logos/mongodb.png
deleted file mode 100644
index b5f846655bfa31a5e5b54a352efb0055131e95c8..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/mongodb.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/mysql.png b/public/assets/images/tech_logos/mysql.png
deleted file mode 100644
index 4371026f51f3c40bb1d72739f3c1bb230477418f..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/mysql.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/nginx.png b/public/assets/images/tech_logos/nginx.png
deleted file mode 100644
index b3bf0f889fb07ecc2a31e5e0d2063c66fc418fc8..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/nginx.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/node.png b/public/assets/images/tech_logos/node.png
deleted file mode 100644
index 854d7ed021bbce7b68682714bb3608e578d0b1ac..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/node.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/postgre.png b/public/assets/images/tech_logos/postgre.png
deleted file mode 100644
index f0d08ae50cc49a56e3d111ccd5ed99f32ad9eb9a..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/postgre.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/python.png b/public/assets/images/tech_logos/python.png
deleted file mode 100644
index 3d9e0c290514c05c2115a3d13748b48f500f1625..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/python.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/react.png b/public/assets/images/tech_logos/react.png
deleted file mode 100644
index bdbb5f043f0875e0311c1134f65ac703b41402b7..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/react.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/rust.png b/public/assets/images/tech_logos/rust.png
deleted file mode 100644
index 41a01113e7637228c9034fa4572f56f2eb681bcc..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/rust.png and /dev/null differ
diff --git a/public/assets/images/tech_logos/sass.png b/public/assets/images/tech_logos/sass.png
deleted file mode 100644
index 88d9104a9101d1c2a44349c4b2df038eef277bc6..0000000000000000000000000000000000000000
Binary files a/public/assets/images/tech_logos/sass.png and /dev/null differ
diff --git a/public/assets/translations/en.json b/public/assets/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..7dbe0401ad090f13686d140bc20ce64410f3a334
--- /dev/null
+++ b/public/assets/translations/en.json
@@ -0,0 +1,33 @@
+{
+    "Simplicité": "Simplicity",
+    "Légèreté": "Lightness",
+    "Écologie": "Ecology",
+    "kuadrado-home-description": "Video game creation studio based in France - Ardèche<br />Digital art creation | Development of free and open source software tools | Education",
+    "Site en construction ...": "Website in construction...",
+    "Sur les réseaux": "On the networks",
+    "kuadrado-footer-copyleft": "All images on this website were made by me and may be reused for personal usage.",
+    "Ce site web est": "This website is",
+    "Jeux": "Games",
+    "Pédagogie": "Education",
+    "games-description": "Video game creations, web games and PC games, work in progress.",
+    "education-description": "Taking ownership of technology through knowledge sharing.",
+    "software-description": "R&D, experimental projects, web and software tools",
+    "games-page-intro": "Creation of independent video games - web games, PC games and projects in development",
+    "Jouer": "Play",
+    "edu-page-intro": "Workshops, courses, workshops and private lessons accessible to all. Programming, 2D graphics, video games, computer popularisation, etc.",
+    "Programmation": "Programming",
+    "edu-learn-coding": "<b>Break the code wall!</b><br />Learn to program with different languages (Python, Javascript, ...), for web, software, video game or other.",
+    "Dessin numérique et animation 2D": "Digital drawing and 2D animation",
+    "edu-learn-2d": "Learn how to use open source 2D graphics and animation software to create characters and backgrounds, and run your own cartoon, illustration or video game project.",
+    "Maths et physiques": "Math and physics",
+    "edu-learn-math": "Tackle the fundamental concepts in a relaxed way for the pleasure of understanding based on the application areas of video games.",
+    "Aide informatique générale": "General IT support",
+    "edu-learn-computer": "Lost with your computer or smartphone, software, internet? Get to grips with the basics and learn step by step how to use technology serenely.",
+    "Stage GNU/Linux": "GNU/Linux course",
+    "edu-learn-gnu": "<b>Take the free route! </b><br/>Learn how to install Linux, take your first steps with free software and gain enough autonomy for basic use.",
+    "Créer un jeu avec Mentalo": "Create a game with Mentalo",
+    "edu-learn-mentalo": "Create a game in a few sessions with the Mentalo application. Handle logical, narrative and artistic concepts with maximum simplicity.",
+    "Me contacter": "Contact me",
+    "Pour s'inscrire ou en savoir plus": "To register or find out more",
+    "software-page-intro": "R&D, experimental projects, software tools for game development or for the web."
+}
\ No newline at end of file
diff --git a/public/assets/translations/fr.json b/public/assets/translations/fr.json
new file mode 100644
index 0000000000000000000000000000000000000000..fdcaa31909af1067f2f13dbe733496b97e59ed30
--- /dev/null
+++ b/public/assets/translations/fr.json
@@ -0,0 +1,16 @@
+{
+    "kuadrado-home-description": "Studio de création de jeux vidéo basé en Ardèche, Vernoux en Vivarais.<br />Création artisitique numérique | Développement d'outillage logiciel libre et open source | Pédagogie.",
+    "kuadrado-footer-copyleft": "Toutes les images du site ont été réalisées par mes soins et peuvent être réutilisées pour un usage personnel.",
+    "games-description": "Créations vidéoludiques, jeux web et jeux PC, projets en cours.",
+    "education-description": "S'approprier la technologie par le partage de connaissances.",
+    "software-description": "R&D, projets expérimentaux, web et outillage logiciel",
+    "games-page-intro": "Création de jeux vidéos indépendants.<br/>Jeux web, PC et projets en cours de développement",
+    "edu-page-intro": "Ateliers, stages, workshops et cours particuliers accessibles à tous. Programmation, graphisme 2D, jeux vidéo, vulgarisation informatique, etc.",
+    "edu-learn-coding": "<b>Franchissez le mur du code !</b><br />Apprenez à programmer avec différents langages (Python, Javascript, ...), pour du web, du logiciel, du jeu vidéo ou autre.",
+    "edu-learn-2d": "Apprenez à utiliser des logiciels libres de création graphique 2D et d'animation.<br />Créez des personnages et des décors, menez votre projet de dessin animé, d'illustration ou de jeu vidéo.",
+    "edu-learn-math": "Abordez les notions fondamentales de façon décontractée pour le plaisir de comprendre en s'appuyant sur les domaines d'application du jeu vidéo.",
+    "edu-learn-computer": "Perdu avec votre ordinateur ou votre smartphone, les logiciels, internet ? Prenez en main les fondamentaux apprenez pas à pas à utiliser sereinement la technologie.",
+    "edu-learn-gnu": "<b>Passez le cap du libre ! </b><br/>Apprenez à installer Linux, faites vos premiers pas avec les logiciels libres et acquérez une autonomie suffisante pour une utilisation basique.",
+    "edu-learn-mentalo": "Créez un jeu en quelques séances avec l'application Mentalo. Manipulez des concepts logiques, narratifs et artistiques avec le maximum de simplicité.",
+    "software-page-intro": "R&D, projets expérimentaux, outillage logiciel pour le développement de jeu ou pour le web."
+}
\ No newline at end of file
diff --git a/public/education/education.js b/public/education/education.js
index f6ccf79699a659c430e4878c7de49bfee751109e..8207c7e067fad0161f321ccd291908f1a0f00a0d 100644
--- a/public/education/education.js
+++ b/public/education/education.js
@@ -10,10 +10,199 @@ module.exports = {
 module.exports = {
     images_url: `/assets/images`,
     data_url: `/assets/data`,
+    translations_url: "/assets/translations"
 };
 
 },{}],3:[function(require,module,exports){
 "use strict";
+/**
+ * A tiny library to handle text translation.
+ */
+
+/**
+ * init parameter object type
+ * @typedef TranslatorParam
+ * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes
+ * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en"
+ *
+ * @property {Boolean} use_url_locale_fragment - DEFAULT: true -
+ * If true, the 1st fragment of the window.location url will be parsed as a language code
+ * Example:
+ * window.location
+ * > https://example.com/en/some-page...
+ * then the /en/ part of the url will be taken in priority and locale will be set to "en".
+ * window.location
+ * > https://example.com/some-page...
+ * No locale fragment will be found so locale will not be set from url fragment.
+ * window.location
+ * > https://example.com/some-page/en
+ * Doesn't work, locale fragment must be the first framgment
+ *
+ * @property {String} local_storage_key  - DEFAULT: "translator-prefered-language"
+ *  The key used to saved the current locale into local storage
+ *
+ * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations
+ * Translations files are expected to be named with their corresponding locale code.
+ * Example:
+ * if supported_languages is set to ["en", "fr", "it"]
+ * and translations_url is set to "https://example.com/translations/""
+ * Then the expected translations files are
+ * https://example.com/translations/en.json
+ * https://example.com/translations/fr.json
+ * https://example.com/translations/it.json
+ * The json resources must simple key value maps, value being the translated text.
+ */
+
+module.exports = {
+    locale: "en", // ISO 639 lowercase language code
+    supported_languages: ["en"],
+    translations: {},
+    translations_url: "",
+    use_url_locale_fragment: true,
+    local_storage_key: "translator-prefered-language",
+
+    /**
+     * Initialize the lib with params
+     * @param {TranslatorParam} params 
+     * @returns {Promise}
+     */
+    init(params) {
+        Object.entries(params).forEach(k_v => {
+            const [key, value] = k_v;
+            if ([
+                "supported_languages",
+                "use_url_locale_fragment",
+                "local_storage_key",
+                "translations_url"
+            ].includes(key)) {
+                this[key] = value;
+            }
+        });
+
+        this.translations_url = this.format_translations_url(this.translations_url);
+        this.supported_languages = this.format_supported_languages(this.supported_languages);
+
+        return new Promise((resolve, reject) => {
+            const loc =
+                (() => {// Locale from url priority 1
+                    if (this.use_url_locale_fragment) {
+                        const first_url_fragment = window.location.pathname.substring(1).split("/")[0];
+                        const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
+                        return fragment_is_locale ? first_url_fragment : "";
+                    } else {
+                        return "";
+                    }
+                })()
+                || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2
+                || (() => { // locale from navigator priority 3
+                    const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase();
+                    return this.supported_languages.includes(navigator_locale)
+                        ? navigator_locale
+                        : this.supported_languages[0] // Default if navigator locale is not supported
+                })();
+
+            fetch(`${this.translations_url}${loc}.json`)
+                .then(response => response.json())
+                .then(response => {
+                    this.locale = loc;
+                    this.translations = response;
+                    resolve();
+                })
+                .catch(err => {
+                    this.locale = "en";
+                    reject(err);
+                });
+        });
+    },
+
+    /**
+     * Return a lowercase string without dash.
+     * If given locale is en-EN, then "en" will be returned.
+     * @param {String} locale 
+     * @returns A lowercase string
+     */
+    format_locale(locale) {
+        return locale.split("-")[0].toLowerCase();
+    },
+
+    /**
+     * Appends a slash at the end of the given string if missing, and returns the url.
+     * @param {String} url 
+     * @returns {String}
+     */
+    format_translations_url(url) {
+        if (url.charAt(url.length - 1) !== "/") {
+            url += "/"
+        }
+        return url;
+    },
+
+    /**
+     * Return the array of language codes formatted as lowsercase language code.
+     * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned.
+     * @param {String[]} languages_codes 
+     * @returns {String[]}
+     */
+    format_supported_languages(languages_codes) {
+        return languages_codes.map(lc => this.format_locale(lc));
+    },
+
+    /**
+     * Fetches a new set of translations in case the wanted language changes
+     * @param {String} locale A lowercase language code
+     * @returns {Promise}
+     */
+    update_translations(locale) {
+        locale = this.format_locale(locale);
+        return new Promise((resolve, reject) => {
+            fetch(`${this.translations_url}${locale}.json`)
+                .then(response => response.json())
+                .then(response => {
+                    this.translations = response;
+                    this.locale = locale;
+
+                    localStorage.setItem(this.local_storage_key, locale);
+
+                    const split_path = window.location.pathname.substring(1).split("/");
+                    const first_url_fragment = split_path[0];
+                    const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
+
+                    if (fragment_is_locale) {
+                        split_path.splice(0, 1, locale);
+                        const updated_path = split_path.join("/");
+                        window.history.replaceState(null, "", "/" + updated_path);
+                    }
+                    resolve();
+                })
+                .catch(err => {
+                    reject(err);
+                });
+        });
+    },
+
+    /**
+     * Tries to get the translation of the source string, or return the string as it.
+     * @param {String} text The source text to translate
+     * @param {Object} params Some dynamic element to insert in the text
+     * Params can be used if the translated text provide placeholder like {%some_word%}
+     * Example:
+     * translator.trad("Some trad key", {some_word: "a dynamic parameter"})
+     * -> translation for "Some trad key": "A translated text with {%some_word%}"
+     * -> will be return as : "A translated text with a dynamic parameter"
+     * @returns {String}
+     */
+    trad: function (text, params = {}) {
+        text = this.translations[text] || text;
+
+        Object.keys(params).forEach(k => {
+            text = text.replace(`{%${k}%}`, params[k]);
+        });
+
+        return text;
+    }
+};
+},{}],4:[function(require,module,exports){
+"use strict";
 
 module.exports = {
     register_key: "objectToHtmlRender",
@@ -229,70 +418,72 @@ module.exports = {
         window.dispatchEvent(event);
     },
 };
-},{}],4:[function(require,module,exports){
+},{}],5:[function(require,module,exports){
 "use strict";
+const translator = require("ks-cheap-translator");
+const { translations_url } = require("../../constants");
 
 class WebPage {
     constructor(args) {
         Object.assign(this, args);
+
+        if (!this.id) {
+            this.id = "webpage-" + performance.now();
+        }
+
+        translator.init({
+            translations_url,
+            supported_languages: ["fr", "en"],
+        }).then(this.refresh_all.bind(this));
+    }
+
+    refresh() {
+        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
+    }
+
+    refresh_all() {
+        obj2htm.renderCycle()
     }
 }
 
 module.exports = WebPage;
-},{}],5:[function(require,module,exports){
+},{"../../constants":2,"ks-cheap-translator":3}],6:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../constants");
 const WebPage = require("../../lib/web-page");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 const EDU_THEMES = [
-    // {
-    //     title: "Création de jeux vidéo",
-    //     description: "Conception, graphisme et animation, programmation, je vous accompagne dans la découverte des techniques pour créer un jeu vidéo de A à Z",
-    //     image: "learning_theme_conception.png",
-    //     pageUrl: "gamedev",
-    // },
     {
         title: "Programmation",
-        description: `<b>Franchissez le mur du code !</b><br />
-        Apprenez à programmer avec différents langages (Python, Javascript, C ...), pour du web, du logiciel, du jeu vidéo ou autre.`,
+        description: "edu-learn-coding",
         image: "learning_theme_coding.png",
-        // pageUrl: "coding",
     },
     {
         title: "Dessin numérique et animation 2D",
-        description: `Apprenez à utiliser des logiciels libres de création graphique 2D et d'animation.<br />
-        Créez des personnages et des décors, menez votre projet de dessin animé, d'illustration ou de jeu vidéo.`,
+        description: "edu-learn-2d",
         image: "learning_theme_2d.png",
-        // pageUrl: "2d",
     },
     {
         title: "Maths et physique",
-        description: "Abordez les notions fondamentales de façon décontractée pour le plaisir de comprendre en s'appuyant sur les domaines d'application du jeu vidéo.",
+        description: "edu-learn-math",
         image: "learning_theme_math.png",
-        // pageUrl: "math",
     },
-    // {
-    //     title: "Musique et sons électroniques",
-    //     description: "Découvrez des logiciels libres de composition musicales, de synthèse sonore et de prise de son.",
-    //     image: "learning_theme_sound.png",
-    //     pageUrl: "sound",
-    // },
     {
         title: "Aide informatique générale",
-        description: "Perdu avec votre ordinateur ou votre smartphone, les logiciels, internet ? Prenez en main les fondamentaux apprenez pas à pas à utiliser sereinement la technologie.",
+        description: "edu-learn-computer",
         image: "learning_theme_pc.png",
-        // pageUrl: "popularization"
     },
     {
         title: "Stage GNU/Linux",
-        description: `<b>Passez le cap du libre ! </b><br/>
-        Apprenez à installer Linux, faites vos premiers pas avec les logiciels libres et acquérez une autonomie suffisante pour une utilisation basique.`,
+        description: "edu-learn-gnu",
         image: "learning_theme_linux.png"
     },
     {
         title: "Créer un jeu avec Mentalo",
-        description: "Créez un jeu en quelques séances avec l'application Mentalo. Manipulez des concepts logiques, narratifs et artistiques avec le maximum de simplicité.",
+        description: "edu-learn-mentalo",
         image: "learning_theme_mentalo.png",
     }
 ];
@@ -323,11 +514,10 @@ class EducationPage extends WebPage {
                                         },
                                     ],
                                 },
-                                { tag: "h1", contents: "Pédagogie" },
+                                { tag: "h1", contents: t("Pédagogie") },
                                 {
                                     tag: "p",
-                                    contents: `Ateliers, stages, workshops et cours particuliers accessibles à tous. 
-                                    Programmation, graphisme 2D, jeux vidéo, vulgarisation informatique, etc.`,
+                                    contents: t("edu-page-intro"),
                                 },
                             ],
                         },
@@ -354,8 +544,8 @@ class EducationPage extends WebPage {
                                             class: "edu-theme",
                                             contents: [
                                                 { tag: "img", width: 250, height: 140, class: "pixelated", src: `${images_url}/${theme.image}` },
-                                                { tag: "h3", contents: theme.title },
-                                                { tag: "p", contents: theme.description },
+                                                { tag: "h3", contents: t(theme.title) },
+                                                { tag: "p", contents: t(theme.description) },
                                             ]
                                         }
                                     })
@@ -372,131 +562,11 @@ class EducationPage extends WebPage {
                             tag: "div",
                             class: "page-contents-center",
                             contents: [
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Pour qui ?" },
-                                //         {
-                                //             tag: "p",
-                                //             class: "info-body",
-                                //             contents: `Les ateliers sont accessibles aux adultes comme aux enfants, plutôt à partir de 12 ans.<br/>
-                                //             Les séances ont lieu en groupes mixtes. Capacité limitée à 5 personnes.
-                                //             `
-                                //         }
-                                //     ]
-                                // },
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Où ça ?" },
-                                //         {
-                                //             tag: "p",
-                                //             class: "info-body",
-                                //             contents: "Dans mon local professionnel : <br /><blue>32 rue Simon Vialet, passage du Cheminou, 07240 Vernoux en Vivarais.</blue>"
-                                //         }
-                                //     ]
-                                // },
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Quel matériel ?" },
-                                //         {
-                                //             tag: "p",
-                                //             class: "info-body",
-                                //             contents: `Le matériel informatique est fourni sur place (ordinateurs et tablettes graphique) 
-                                //             mais il est possible d'amener le sien. 
-                                //             <br />Il est recommandé d'apporter au moins une clé USB pour faire ses sauvegardes.`
-                                //         }
-                                //     ]
-                                // },
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Quand ?" },
-                                //         {
-                                //             tag: "ul",
-                                //             class: "info-body tabled",
-                                //             contents: [
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Mardi" },
-                                //                         { tag: "span", contents: "16h - 18h" },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Mercredi" },
-                                //                         { tag: "span", contents: "14h - 16h" },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Jeudi" },
-                                //                         { tag: "span", contents: "16h - 18h" },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     class: "fullwidth",
-                                //                     contents: "<em><blue>Ouvert de Septembre à Juin, sauf vacances scolaires ou fermetures exceptionnelles</blue></em>"
-                                //                 }
-                                //             ]
-                                //         },
-                                //     ]
-                                // },
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Combien ça coûte ?" },
-                                //         {
-                                //             tag: "ul",
-                                //             class: "info-body tabled",
-                                //             contents: [
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Inscription au mois" },
-                                //                         { tag: "span", contents: "50€, accès à toutes les plages horaires." },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Inscription à la séance" },
-                                //                         { tag: "span", contents: "15€" },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Cours particuliers" },
-                                //                         { tag: "span", contents: "30€/h, sur place ou en visio. Horaires à définir." },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Stage 4 séances de 2h" },
-                                //                         { tag: "span", contents: "40€ par personne, horaires et dates à définir selon la demande." },
-                                //                     ]
-                                //                 }
-                                //             ]
-                                //         }
-                                //     ]
-                                // },
                                 {
                                     tag: "div",
                                     class: "info-block",
                                     contents: [
-                                        { tag: "h3", class: "info-title", contents: "Pour s'inscrire ou en savoir plus <em>(programme 2021 2022 à définir, plus d'infos bientôt)</em>" },
+                                        { tag: "h3", class: "info-title", contents: `${t("Pour s'inscrire ou en savoir plus")} <em>(programme 2022 à définir, plus d'infos bientôt)</em>` },
                                         {
                                             tag: "ul",
                                             class: "info-body",
@@ -504,7 +574,7 @@ class EducationPage extends WebPage {
                                                 {
                                                     tag: "li",
                                                     contents: [
-                                                        { tag: "span", contents: "Me contacter" },
+                                                        { tag: "span", contents: t("Me contacter") },
                                                         {
                                                             tag: "a",
                                                             href: "mailto:contact@kuadrado-software.fr",
@@ -524,12 +594,6 @@ class EducationPage extends WebPage {
                                                         },
                                                     ]
                                                 },
-                                                // {
-                                                //     tag: "li",
-                                                //     contents: [
-                                                //         { tag: "span", contents: "ou passer directement me voir au local !" }
-                                                //     ]
-                                                // }
                                             ]
                                         }
                                     ]
@@ -546,13 +610,13 @@ class EducationPage extends WebPage {
 
 module.exports = EducationPage;
 
-},{"../../../constants":2,"../../lib/web-page":4}],6:[function(require,module,exports){
+},{"../../../constants":2,"../../lib/web-page":5,"ks-cheap-translator":3}],7:[function(require,module,exports){
 "use strict";
 const runPage = require("../../run-page");
 const EducationPage = require("./education");
 runPage(EducationPage);
 
-},{"../../run-page":7,"./education":5}],7:[function(require,module,exports){
+},{"../../run-page":8,"./education":6}],8:[function(require,module,exports){
 "use strict";
 
 const renderer = require("object-to-html-renderer")
@@ -565,19 +629,18 @@ module.exports = function runPage(PageComponent) {
     obj2htm.renderCycle();
 };
 
-},{"./template/template":9,"object-to-html-renderer":3}],8:[function(require,module,exports){
+},{"./template/template":10,"object-to-html-renderer":4}],9:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../constants");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 const NAV_MENU_ITEMS = [
     { url: "/games/", text: "Jeux" },
     {
         url: "/education/",
         text: "Pédagogie",
-        // submenu: [
-        //     { url: "/gamedev", text: "Création de jeux vidéo" },
-        // ]
     },
     { url: "/software-development/", text: "Software" }
 ];
@@ -603,6 +666,12 @@ class NavBar {
         });
     }
 
+    handle_chang_lang(lang) {
+        translator.update_translations(lang).then(() => {
+            obj2htm.renderCycle();
+        }).catch(err => console.log(err));
+    }
+
     renderHome() {
         return {
             tag: "div",
@@ -644,10 +713,20 @@ class NavBar {
                         {
                             tag: "a",
                             href,
-                            contents: text,
+                            contents: t(text),
                         },
                     ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
                 };
+            }).concat({
+                tag: "li",
+                class: "lang-flags",
+                contents: ["fr", "en"].map(lang => {
+                    return {
+                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
+                        class: translator.locale === lang ? "selected" : "",
+                        onclick: this.handle_chang_lang.bind(this, lang)
+                    }
+                })
             }),
         };
     }
@@ -675,12 +754,14 @@ class NavBar {
 
 module.exports = NavBar;
 
-},{"../../../constants":2}],9:[function(require,module,exports){
+},{"../../../constants":2,"ks-cheap-translator":3}],10:[function(require,module,exports){
 "use strict";
 
 const { in_construction } = require("../../config");
 const { images_url } = require("../../constants");
 const NavBar = require("./components/navbar");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator)
 
 class Template {
     constructor(props) {
@@ -701,7 +782,7 @@ class Template {
                         {
                             tag: "strong",
                             class: "page-contents-center",
-                            contents: "Site en construction ...",
+                            contents: t("Site en construction ..."),
                         },
                     ],
                 },
@@ -751,7 +832,7 @@ class Template {
                             contents: [
                                 {
                                     tag: "strong",
-                                    contents: "<blue>Sur les réseaux : </blue>",
+                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
                                 },
                                 {
                                     tag: "a",
@@ -773,11 +854,11 @@ class Template {
                             tag: "span",
                             contents: `Copyleft 🄯 ${new Date()
                                 .getFullYear()} Kuadrado Software | 
-                                Toutes les images du site ont été réalisées par mes soins et peuvent être réutilisées pour un usage personnel.`,
+                                ${t("kuadrado-footer-copyleft")}`,
                         },
                         {
                             tag: "div", contents: [
-                                { tag: "span", contents: "Ce site web est " },
+                                { tag: "span", contents: t("Ce site web est") + " " },
                                 {
                                     tag: "a", target: "_blank",
                                     style_rules: { fontWeight: "bold" },
@@ -795,4 +876,4 @@ class Template {
 
 module.exports = Template;
 
-},{"../../config":1,"../../constants":2,"./components/navbar":8}]},{},[6]);
+},{"../../config":1,"../../constants":2,"./components/navbar":9,"ks-cheap-translator":3}]},{},[7]);
diff --git a/public/games/games.js b/public/games/games.js
index ccc7535972968c2ee9e0a8ac5bd066deea800b3d..e6819c85c7dcfdf8808549806a780b33d5c22cef 100644
--- a/public/games/games.js
+++ b/public/games/games.js
@@ -14,10 +14,199 @@ module.exports = {
 module.exports = {
     images_url: `/assets/images`,
     data_url: `/assets/data`,
+    translations_url: "/assets/translations"
 };
 
 },{}],4:[function(require,module,exports){
 "use strict";
+/**
+ * A tiny library to handle text translation.
+ */
+
+/**
+ * init parameter object type
+ * @typedef TranslatorParam
+ * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes
+ * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en"
+ *
+ * @property {Boolean} use_url_locale_fragment - DEFAULT: true -
+ * If true, the 1st fragment of the window.location url will be parsed as a language code
+ * Example:
+ * window.location
+ * > https://example.com/en/some-page...
+ * then the /en/ part of the url will be taken in priority and locale will be set to "en".
+ * window.location
+ * > https://example.com/some-page...
+ * No locale fragment will be found so locale will not be set from url fragment.
+ * window.location
+ * > https://example.com/some-page/en
+ * Doesn't work, locale fragment must be the first framgment
+ *
+ * @property {String} local_storage_key  - DEFAULT: "translator-prefered-language"
+ *  The key used to saved the current locale into local storage
+ *
+ * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations
+ * Translations files are expected to be named with their corresponding locale code.
+ * Example:
+ * if supported_languages is set to ["en", "fr", "it"]
+ * and translations_url is set to "https://example.com/translations/""
+ * Then the expected translations files are
+ * https://example.com/translations/en.json
+ * https://example.com/translations/fr.json
+ * https://example.com/translations/it.json
+ * The json resources must simple key value maps, value being the translated text.
+ */
+
+module.exports = {
+    locale: "en", // ISO 639 lowercase language code
+    supported_languages: ["en"],
+    translations: {},
+    translations_url: "",
+    use_url_locale_fragment: true,
+    local_storage_key: "translator-prefered-language",
+
+    /**
+     * Initialize the lib with params
+     * @param {TranslatorParam} params 
+     * @returns {Promise}
+     */
+    init(params) {
+        Object.entries(params).forEach(k_v => {
+            const [key, value] = k_v;
+            if ([
+                "supported_languages",
+                "use_url_locale_fragment",
+                "local_storage_key",
+                "translations_url"
+            ].includes(key)) {
+                this[key] = value;
+            }
+        });
+
+        this.translations_url = this.format_translations_url(this.translations_url);
+        this.supported_languages = this.format_supported_languages(this.supported_languages);
+
+        return new Promise((resolve, reject) => {
+            const loc =
+                (() => {// Locale from url priority 1
+                    if (this.use_url_locale_fragment) {
+                        const first_url_fragment = window.location.pathname.substring(1).split("/")[0];
+                        const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
+                        return fragment_is_locale ? first_url_fragment : "";
+                    } else {
+                        return "";
+                    }
+                })()
+                || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2
+                || (() => { // locale from navigator priority 3
+                    const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase();
+                    return this.supported_languages.includes(navigator_locale)
+                        ? navigator_locale
+                        : this.supported_languages[0] // Default if navigator locale is not supported
+                })();
+
+            fetch(`${this.translations_url}${loc}.json`)
+                .then(response => response.json())
+                .then(response => {
+                    this.locale = loc;
+                    this.translations = response;
+                    resolve();
+                })
+                .catch(err => {
+                    this.locale = "en";
+                    reject(err);
+                });
+        });
+    },
+
+    /**
+     * Return a lowercase string without dash.
+     * If given locale is en-EN, then "en" will be returned.
+     * @param {String} locale 
+     * @returns A lowercase string
+     */
+    format_locale(locale) {
+        return locale.split("-")[0].toLowerCase();
+    },
+
+    /**
+     * Appends a slash at the end of the given string if missing, and returns the url.
+     * @param {String} url 
+     * @returns {String}
+     */
+    format_translations_url(url) {
+        if (url.charAt(url.length - 1) !== "/") {
+            url += "/"
+        }
+        return url;
+    },
+
+    /**
+     * Return the array of language codes formatted as lowsercase language code.
+     * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned.
+     * @param {String[]} languages_codes 
+     * @returns {String[]}
+     */
+    format_supported_languages(languages_codes) {
+        return languages_codes.map(lc => this.format_locale(lc));
+    },
+
+    /**
+     * Fetches a new set of translations in case the wanted language changes
+     * @param {String} locale A lowercase language code
+     * @returns {Promise}
+     */
+    update_translations(locale) {
+        locale = this.format_locale(locale);
+        return new Promise((resolve, reject) => {
+            fetch(`${this.translations_url}${locale}.json`)
+                .then(response => response.json())
+                .then(response => {
+                    this.translations = response;
+                    this.locale = locale;
+
+                    localStorage.setItem(this.local_storage_key, locale);
+
+                    const split_path = window.location.pathname.substring(1).split("/");
+                    const first_url_fragment = split_path[0];
+                    const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
+
+                    if (fragment_is_locale) {
+                        split_path.splice(0, 1, locale);
+                        const updated_path = split_path.join("/");
+                        window.history.replaceState(null, "", "/" + updated_path);
+                    }
+                    resolve();
+                })
+                .catch(err => {
+                    reject(err);
+                });
+        });
+    },
+
+    /**
+     * Tries to get the translation of the source string, or return the string as it.
+     * @param {String} text The source text to translate
+     * @param {Object} params Some dynamic element to insert in the text
+     * Params can be used if the translated text provide placeholder like {%some_word%}
+     * Example:
+     * translator.trad("Some trad key", {some_word: "a dynamic parameter"})
+     * -> translation for "Some trad key": "A translated text with {%some_word%}"
+     * -> will be return as : "A translated text with a dynamic parameter"
+     * @returns {String}
+     */
+    trad: function (text, params = {}) {
+        text = this.translations[text] || text;
+
+        Object.keys(params).forEach(k => {
+            text = text.replace(`{%${k}%}`, params[k]);
+        });
+
+        return text;
+    }
+};
+},{}],5:[function(require,module,exports){
+"use strict";
 
 const MtlAnimation = require("./model/animation");
 const MentaloEngine = require("./mentalo-engine");
@@ -45,7 +234,7 @@ module.exports = {
     color_tools,
     shape_tools,
 };
-},{"./lib/color-tools":5,"./lib/font-tools":6,"./lib/frame-rate-controller":7,"./lib/shape-tools":8,"./mentalo-engine":9,"./model/animation":10,"./model/choice":11,"./model/game":13,"./model/game-object":12,"./model/scene":17,"./model/scene-types":16,"./model/sound-track":18}],5:[function(require,module,exports){
+},{"./lib/color-tools":6,"./lib/font-tools":7,"./lib/frame-rate-controller":8,"./lib/shape-tools":9,"./mentalo-engine":10,"./model/animation":11,"./model/choice":12,"./model/game":14,"./model/game-object":13,"./model/scene":18,"./model/scene-types":17,"./model/sound-track":19}],6:[function(require,module,exports){
 "use strict";
 /**
  * Helpers to work with color values
@@ -152,7 +341,7 @@ module.exports = {
     rgba_array_to_hex,
     same_rgba,
 };
-},{}],6:[function(require,module,exports){
+},{}],7:[function(require,module,exports){
 "use strict";
 /**
  * Helpers to works with default web fonts
@@ -277,7 +466,7 @@ module.exports = {
     FONT_STYLE_OPTIONS,
     FONT_WEIGHT_OPTIONS
 };
-},{}],7:[function(require,module,exports){
+},{}],8:[function(require,module,exports){
 "use strict";
 
 /**
@@ -315,7 +504,7 @@ class FrameRateController {
 }
 
 module.exports = FrameRateController;
-},{}],8:[function(require,module,exports){
+},{}],9:[function(require,module,exports){
 "use strict";
 
 /**
@@ -375,7 +564,7 @@ function draw_rect(ctx, x, y, width, height, options = {
 module.exports = {
     draw_rect,
 }
-},{}],9:[function(require,module,exports){
+},{}],10:[function(require,module,exports){
 "use strict";
 
 const MtlGame = require("./model/game");
@@ -666,7 +855,7 @@ class MentaloEngine {
 }
 
 module.exports = MentaloEngine;
-},{"./model/game":13,"./model/scene-types":16,"./render/render":19,"./translation":20}],10:[function(require,module,exports){
+},{"./model/game":14,"./model/scene-types":17,"./render/render":20,"./translation":21}],11:[function(require,module,exports){
 "use strict";
 const Loadable = require("./loadable");
 
@@ -746,7 +935,7 @@ class MtlAnimation extends Loadable {
 }
 
 module.exports = MtlAnimation;
-},{"./loadable":15}],11:[function(require,module,exports){
+},{"./loadable":16}],12:[function(require,module,exports){
 "use strict";
 /**
  * The data type used for the choices of a MtlScene
@@ -780,7 +969,7 @@ class MtlChoice {
 }
 
 module.exports = MtlChoice;
-},{}],12:[function(require,module,exports){
+},{}],13:[function(require,module,exports){
 "use strict";
 const Loadable = require("./loadable");
 
@@ -834,7 +1023,7 @@ class MtlGameObject extends Loadable {
 }
 
 module.exports = MtlGameObject;
-},{"./loadable":15}],13:[function(require,module,exports){
+},{"./loadable":16}],14:[function(require,module,exports){
 "use strict";
 
 const MtlScene = require("./scene");
@@ -1092,7 +1281,7 @@ class MtlGame {
 }
 
 module.exports = MtlGame;
-},{"./scene":17,"./scene-types":16}],14:[function(require,module,exports){
+},{"./scene":18,"./scene-types":17}],15:[function(require,module,exports){
 "use strict";
 
 /**
@@ -1134,7 +1323,7 @@ class LoadableGroup {
 }
 
 module.exports = LoadableGroup;
-},{}],15:[function(require,module,exports){
+},{}],16:[function(require,module,exports){
 "use strict";
 
 /**
@@ -1194,7 +1383,7 @@ class Loadable {
 }
 
 module.exports = Loadable;
-},{}],16:[function(require,module,exports){
+},{}],17:[function(require,module,exports){
 /**
  * An enum like object to describe the 2 possible type that can have a MtlScene
  */
@@ -1203,7 +1392,7 @@ module.exports = {
     PLAYABLE: "Playable",
     CINEMATIC: "Cinematic",
 };
-},{}],17:[function(require,module,exports){
+},{}],18:[function(require,module,exports){
 "use strict";
 
 const SCENE_TYPES = require("./scene-types");
@@ -1288,7 +1477,7 @@ class MtlScene extends LoadableGroup {
 }
 
 module.exports = MtlScene;
-},{"./animation":10,"./choice":11,"./game-object":12,"./loadable-group":14,"./scene-types":16,"./sound-track":18}],18:[function(require,module,exports){
+},{"./animation":11,"./choice":12,"./game-object":13,"./loadable-group":15,"./scene-types":17,"./sound-track":19}],19:[function(require,module,exports){
 "use strict";
 
 const Loadable = require("./loadable");
@@ -1349,7 +1538,7 @@ class MtlSoundTrack extends Loadable {
 }
 
 module.exports = MtlSoundTrack;
-},{"./loadable":15}],19:[function(require,module,exports){
+},{"./loadable":16}],20:[function(require,module,exports){
 "use strict";
 
 const FrameRateController = require("../lib/frame-rate-controller");
@@ -2292,7 +2481,7 @@ class MtlRender {
 }
 
 module.exports = MtlRender;
-},{"../lib/color-tools":5,"../lib/font-tools":6,"../lib/frame-rate-controller":7,"../model/scene-types":16,"../ui-components/choice-cpt":21,"../ui-components/choices-panel-cpt":22,"../ui-components/closing-icon-cpt":23,"../ui-components/game-object-cpt":24,"../ui-components/inventory-cpt":25,"../ui-components/inventory-object-cpt":26,"../ui-components/inventory-slot-cpt":27,"../ui-components/scene-animation-cpt":28,"../ui-components/text-box-cpt":29,"../ui-components/user-error-popup":31}],20:[function(require,module,exports){
+},{"../lib/color-tools":6,"../lib/font-tools":7,"../lib/frame-rate-controller":8,"../model/scene-types":17,"../ui-components/choice-cpt":22,"../ui-components/choices-panel-cpt":23,"../ui-components/closing-icon-cpt":24,"../ui-components/game-object-cpt":25,"../ui-components/inventory-cpt":26,"../ui-components/inventory-object-cpt":27,"../ui-components/inventory-slot-cpt":28,"../ui-components/scene-animation-cpt":29,"../ui-components/text-box-cpt":30,"../ui-components/user-error-popup":32}],21:[function(require,module,exports){
 const supported_locales = ["en", "fr", "es"];
 
 /**
@@ -2324,7 +2513,7 @@ module.exports = {
     get_translated,
     supported_locales,
 };
-},{}],21:[function(require,module,exports){
+},{}],22:[function(require,module,exports){
 "use strict";
 
 const { get_canvas_font } = require("../lib/font-tools");
@@ -2403,7 +2592,7 @@ class ChoiceCpt extends MtlUiComponent {
 }
 
 module.exports = ChoiceCpt;
-},{"../lib/font-tools":6,"../lib/shape-tools":8,"./ui-component":30}],22:[function(require,module,exports){
+},{"../lib/font-tools":7,"../lib/shape-tools":9,"./ui-component":31}],23:[function(require,module,exports){
 "use strict";
 
 const MtlUiComponent = require("./ui-component");
@@ -2431,7 +2620,7 @@ class ChoicesPanelCpt extends MtlUiComponent {
 }
 
 module.exports = ChoicesPanelCpt;
-},{"./ui-component":30}],23:[function(require,module,exports){
+},{"./ui-component":31}],24:[function(require,module,exports){
 "use strict";
 
 const MtlUiComponent = require("./ui-component");
@@ -2483,7 +2672,7 @@ class ClosingIconCpt extends MtlUiComponent {
 }
 
 module.exports = ClosingIconCpt;
-},{"./ui-component":30}],24:[function(require,module,exports){
+},{"./ui-component":31}],25:[function(require,module,exports){
 "use strict";
 
 const MtlUiComponent = require("./ui-component");
@@ -2525,7 +2714,7 @@ class GameObjectCpt extends MtlUiComponent {
 }
 
 module.exports = GameObjectCpt;
-},{"./ui-component":30}],25:[function(require,module,exports){
+},{"./ui-component":31}],26:[function(require,module,exports){
 "use strict";
 
 const MtlUiComponent = require("./ui-component");
@@ -2554,7 +2743,7 @@ class InventoryCpt extends MtlUiComponent {
 }
 
 module.exports = InventoryCpt;
-},{"./ui-component":30}],26:[function(require,module,exports){
+},{"./ui-component":31}],27:[function(require,module,exports){
 "use strict";
 
 const { draw_rect } = require("../lib/shape-tools");
@@ -2604,7 +2793,7 @@ class InventoryObjectCpt extends MtlUiComponent {
 }
 
 module.exports = InventoryObjectCpt;
-},{"../lib/shape-tools":8,"./ui-component":30}],27:[function(require,module,exports){
+},{"../lib/shape-tools":9,"./ui-component":31}],28:[function(require,module,exports){
 "use strict";
 
 const { draw_rect } = require("../lib/shape-tools");
@@ -2642,7 +2831,7 @@ class InventorySlotCpt extends MtlUiComponent {
 }
 
 module.exports = InventorySlotCpt;
-},{"../lib/shape-tools":8,"./ui-component":30}],28:[function(require,module,exports){
+},{"../lib/shape-tools":9,"./ui-component":31}],29:[function(require,module,exports){
 "use strict";
 
 const MtlUiComponent = require("./ui-component");
@@ -2738,7 +2927,7 @@ class SceneAnimationCpt extends MtlUiComponent {
 }
 
 module.exports = SceneAnimationCpt;
-},{"./ui-component":30}],29:[function(require,module,exports){
+},{"./ui-component":31}],30:[function(require,module,exports){
 "use strict";
 
 const { get_canvas_font, get_canvas_char_size } = require("../lib/font-tools");
@@ -2913,7 +3102,7 @@ class TextBoxCpt extends MtlUiComponent {
 }
 
 module.exports = TextBoxCpt;
-},{"../lib/font-tools":6,"../lib/shape-tools":8,"./ui-component":30}],30:[function(require,module,exports){
+},{"../lib/font-tools":7,"../lib/shape-tools":9,"./ui-component":31}],31:[function(require,module,exports){
 "use strict";
 
 const { draw_rect } = require("../lib/shape-tools");
@@ -3062,7 +3251,7 @@ class MtlUiComponent {
 }
 
 module.exports = MtlUiComponent;
-},{"../lib/shape-tools":8}],31:[function(require,module,exports){
+},{"../lib/shape-tools":9}],32:[function(require,module,exports){
 "use strict";
 
 const MtlUiComponent = require("./ui-component");
@@ -3114,7 +3303,7 @@ class UserErrorPopup extends MtlUiComponent {
 }
 
 module.exports = UserErrorPopup;
-},{"./ui-component":30}],32:[function(require,module,exports){
+},{"./ui-component":31}],33:[function(require,module,exports){
 "use strict";
 
 module.exports = {
@@ -3331,7 +3520,7 @@ module.exports = {
         window.dispatchEvent(event);
     },
 };
-},{}],33:[function(require,module,exports){
+},{}],34:[function(require,module,exports){
 "use strict";
 
 class ImageCarousel {
@@ -3399,7 +3588,7 @@ class ImageCarousel {
 
 module.exports = ImageCarousel;
 
-},{}],34:[function(require,module,exports){
+},{}],35:[function(require,module,exports){
 "use strict";
 
 const { fetch_json_or_error_text } = require("./fetch");
@@ -3412,8 +3601,8 @@ function getArticleDate(date) {
     return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
 }
 
-function loadArticles(category) {
-    return fetch_json_or_error_text(`/articles/${category}`)
+function loadArticles(category, locale) {
+    return fetch_json_or_error_text(`/articles/${category}/${locale}`);
 }
 
 module.exports = {
@@ -3422,7 +3611,7 @@ module.exports = {
     getArticleDate,
 };
 
-},{"./fetch":35}],35:[function(require,module,exports){
+},{"./fetch":36}],36:[function(require,module,exports){
 "use strict";
 
 function fetchjson(url) {
@@ -3461,17 +3650,36 @@ module.exports = {
     fetch_json_or_error_text,
 };
 
-},{}],36:[function(require,module,exports){
+},{}],37:[function(require,module,exports){
 "use strict";
+const translator = require("ks-cheap-translator");
+const { translations_url } = require("../../constants");
 
 class WebPage {
     constructor(args) {
         Object.assign(this, args);
+
+        if (!this.id) {
+            this.id = "webpage-" + performance.now();
+        }
+
+        translator.init({
+            translations_url,
+            supported_languages: ["fr", "en"],
+        }).then(this.refresh_all.bind(this));
+    }
+
+    refresh() {
+        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
+    }
+
+    refresh_all() {
+        obj2htm.renderCycle()
     }
 }
 
 module.exports = WebPage;
-},{}],37:[function(require,module,exports){
+},{"../../constants":3,"ks-cheap-translator":4}],38:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../../../admin-frontend/src/constants");
@@ -3623,6 +3831,7 @@ class GameArticle {
                                         { tag: "label", contents: detail.label },
                                         {
                                             tag: "div",
+                                            class: "detail-value",
                                             contents: detail.value
                                         },
                                     ],
@@ -3638,11 +3847,12 @@ class GameArticle {
 
 module.exports = GameArticle;
 
-},{"../../../../../admin-frontend/src/constants":1,"../../../../constants":3,"../../../generic-components/image-carousel":33,"../../../lib/article-utils":34,"../../../lib/fetch":35,"mentalo-engine":4}],38:[function(require,module,exports){
+},{"../../../../../admin-frontend/src/constants":1,"../../../../constants":3,"../../../generic-components/image-carousel":34,"../../../lib/article-utils":35,"../../../lib/fetch":36,"mentalo-engine":5}],39:[function(require,module,exports){
 "use strict";
 
 const { loadArticles } = require("../../../lib/article-utils");
 const GameArticle = require("./game-article");
+const translator = require("ks-cheap-translator");
 
 class GameArticles {
     constructor(props) {
@@ -3650,12 +3860,12 @@ class GameArticles {
         this.state = {
             articles: [],
         };
-        this.id = performance.now();
+        this.id = "game-articles-section";
         this.loadArticles();
     }
 
     loadArticles() {
-        loadArticles("games")
+        loadArticles("games", translator.locale)
             .then(articles => {
                 this.state.articles = articles;
                 this.refresh();
@@ -3693,12 +3903,14 @@ class GameArticles {
 
 module.exports = GameArticles;
 
-},{"../../../lib/article-utils":34,"./game-article":37}],39:[function(require,module,exports){
+},{"../../../lib/article-utils":35,"./game-article":38,"ks-cheap-translator":4}],40:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../constants");
 const WebPage = require("../../lib/web-page");
 const GameArticles = require("./components/game-articles");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 class GamesPage extends WebPage {
     render() {
@@ -3725,11 +3937,10 @@ class GamesPage extends WebPage {
                                         },
                                     ],
                                 },
-                                { tag: "h1", contents: "Jeux" },
+                                { tag: "h1", contents: t("Jeux") },
                                 {
                                     tag: "p",
-                                    contents: `Création de jeux vidéos indépendants.
-                                    <br/>Jeux web, PC et projets en cours de développement`,
+                                    contents: t("games-page-intro"),
                                 },
                             ],
                         },
@@ -3743,7 +3954,7 @@ class GamesPage extends WebPage {
 
 module.exports = GamesPage;
 
-},{"../../../constants":3,"../../lib/web-page":36,"./components/game-articles":38}],40:[function(require,module,exports){
+},{"../../../constants":3,"../../lib/web-page":37,"./components/game-articles":39,"ks-cheap-translator":4}],41:[function(require,module,exports){
 "use strict";
 
 "use strict";
@@ -3751,7 +3962,7 @@ const runPage = require("../../run-page");
 const GamesPage = require("./games");
 runPage(GamesPage);
 
-},{"../../run-page":41,"./games":39}],41:[function(require,module,exports){
+},{"../../run-page":42,"./games":40}],42:[function(require,module,exports){
 "use strict";
 
 const renderer = require("object-to-html-renderer")
@@ -3764,19 +3975,18 @@ module.exports = function runPage(PageComponent) {
     obj2htm.renderCycle();
 };
 
-},{"./template/template":43,"object-to-html-renderer":32}],42:[function(require,module,exports){
+},{"./template/template":44,"object-to-html-renderer":33}],43:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../constants");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 const NAV_MENU_ITEMS = [
     { url: "/games/", text: "Jeux" },
     {
         url: "/education/",
         text: "Pédagogie",
-        // submenu: [
-        //     { url: "/gamedev", text: "Création de jeux vidéo" },
-        // ]
     },
     { url: "/software-development/", text: "Software" }
 ];
@@ -3802,6 +4012,12 @@ class NavBar {
         });
     }
 
+    handle_chang_lang(lang) {
+        translator.update_translations(lang).then(() => {
+            obj2htm.renderCycle();
+        }).catch(err => console.log(err));
+    }
+
     renderHome() {
         return {
             tag: "div",
@@ -3843,10 +4059,20 @@ class NavBar {
                         {
                             tag: "a",
                             href,
-                            contents: text,
+                            contents: t(text),
                         },
                     ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
                 };
+            }).concat({
+                tag: "li",
+                class: "lang-flags",
+                contents: ["fr", "en"].map(lang => {
+                    return {
+                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
+                        class: translator.locale === lang ? "selected" : "",
+                        onclick: this.handle_chang_lang.bind(this, lang)
+                    }
+                })
             }),
         };
     }
@@ -3874,12 +4100,14 @@ class NavBar {
 
 module.exports = NavBar;
 
-},{"../../../constants":3}],43:[function(require,module,exports){
+},{"../../../constants":3,"ks-cheap-translator":4}],44:[function(require,module,exports){
 "use strict";
 
 const { in_construction } = require("../../config");
 const { images_url } = require("../../constants");
 const NavBar = require("./components/navbar");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator)
 
 class Template {
     constructor(props) {
@@ -3900,7 +4128,7 @@ class Template {
                         {
                             tag: "strong",
                             class: "page-contents-center",
-                            contents: "Site en construction ...",
+                            contents: t("Site en construction ..."),
                         },
                     ],
                 },
@@ -3950,7 +4178,7 @@ class Template {
                             contents: [
                                 {
                                     tag: "strong",
-                                    contents: "<blue>Sur les réseaux : </blue>",
+                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
                                 },
                                 {
                                     tag: "a",
@@ -3972,11 +4200,11 @@ class Template {
                             tag: "span",
                             contents: `Copyleft 🄯 ${new Date()
                                 .getFullYear()} Kuadrado Software | 
-                                Toutes les images du site ont été réalisées par mes soins et peuvent être réutilisées pour un usage personnel.`,
+                                ${t("kuadrado-footer-copyleft")}`,
                         },
                         {
                             tag: "div", contents: [
-                                { tag: "span", contents: "Ce site web est " },
+                                { tag: "span", contents: t("Ce site web est") + " " },
                                 {
                                     tag: "a", target: "_blank",
                                     style_rules: { fontWeight: "bold" },
@@ -3994,4 +4222,4 @@ class Template {
 
 module.exports = Template;
 
-},{"../../config":2,"../../constants":3,"./components/navbar":42}]},{},[40]);
+},{"../../config":2,"../../constants":3,"./components/navbar":43,"ks-cheap-translator":4}]},{},[41]);
diff --git a/public/main.js b/public/main.js
index 7fd0ea16e95a3637ebe5ca2e0f68eabc403fd94f..1e3f2ac561a68051d057a398bb632213e26b0cca 100644
--- a/public/main.js
+++ b/public/main.js
@@ -10,10 +10,199 @@ module.exports = {
 module.exports = {
     images_url: `/assets/images`,
     data_url: `/assets/data`,
+    translations_url: "/assets/translations"
 };
 
 },{}],3:[function(require,module,exports){
 "use strict";
+/**
+ * A tiny library to handle text translation.
+ */
+
+/**
+ * init parameter object type
+ * @typedef TranslatorParam
+ * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes
+ * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en"
+ *
+ * @property {Boolean} use_url_locale_fragment - DEFAULT: true -
+ * If true, the 1st fragment of the window.location url will be parsed as a language code
+ * Example:
+ * window.location
+ * > https://example.com/en/some-page...
+ * then the /en/ part of the url will be taken in priority and locale will be set to "en".
+ * window.location
+ * > https://example.com/some-page...
+ * No locale fragment will be found so locale will not be set from url fragment.
+ * window.location
+ * > https://example.com/some-page/en
+ * Doesn't work, locale fragment must be the first framgment
+ *
+ * @property {String} local_storage_key  - DEFAULT: "translator-prefered-language"
+ *  The key used to saved the current locale into local storage
+ *
+ * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations
+ * Translations files are expected to be named with their corresponding locale code.
+ * Example:
+ * if supported_languages is set to ["en", "fr", "it"]
+ * and translations_url is set to "https://example.com/translations/""
+ * Then the expected translations files are
+ * https://example.com/translations/en.json
+ * https://example.com/translations/fr.json
+ * https://example.com/translations/it.json
+ * The json resources must simple key value maps, value being the translated text.
+ */
+
+module.exports = {
+    locale: "en", // ISO 639 lowercase language code
+    supported_languages: ["en"],
+    translations: {},
+    translations_url: "",
+    use_url_locale_fragment: true,
+    local_storage_key: "translator-prefered-language",
+
+    /**
+     * Initialize the lib with params
+     * @param {TranslatorParam} params 
+     * @returns {Promise}
+     */
+    init(params) {
+        Object.entries(params).forEach(k_v => {
+            const [key, value] = k_v;
+            if ([
+                "supported_languages",
+                "use_url_locale_fragment",
+                "local_storage_key",
+                "translations_url"
+            ].includes(key)) {
+                this[key] = value;
+            }
+        });
+
+        this.translations_url = this.format_translations_url(this.translations_url);
+        this.supported_languages = this.format_supported_languages(this.supported_languages);
+
+        return new Promise((resolve, reject) => {
+            const loc =
+                (() => {// Locale from url priority 1
+                    if (this.use_url_locale_fragment) {
+                        const first_url_fragment = window.location.pathname.substring(1).split("/")[0];
+                        const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
+                        return fragment_is_locale ? first_url_fragment : "";
+                    } else {
+                        return "";
+                    }
+                })()
+                || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2
+                || (() => { // locale from navigator priority 3
+                    const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase();
+                    return this.supported_languages.includes(navigator_locale)
+                        ? navigator_locale
+                        : this.supported_languages[0] // Default if navigator locale is not supported
+                })();
+
+            fetch(`${this.translations_url}${loc}.json`)
+                .then(response => response.json())
+                .then(response => {
+                    this.locale = loc;
+                    this.translations = response;
+                    resolve();
+                })
+                .catch(err => {
+                    this.locale = "en";
+                    reject(err);
+                });
+        });
+    },
+
+    /**
+     * Return a lowercase string without dash.
+     * If given locale is en-EN, then "en" will be returned.
+     * @param {String} locale 
+     * @returns A lowercase string
+     */
+    format_locale(locale) {
+        return locale.split("-")[0].toLowerCase();
+    },
+
+    /**
+     * Appends a slash at the end of the given string if missing, and returns the url.
+     * @param {String} url 
+     * @returns {String}
+     */
+    format_translations_url(url) {
+        if (url.charAt(url.length - 1) !== "/") {
+            url += "/"
+        }
+        return url;
+    },
+
+    /**
+     * Return the array of language codes formatted as lowsercase language code.
+     * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned.
+     * @param {String[]} languages_codes 
+     * @returns {String[]}
+     */
+    format_supported_languages(languages_codes) {
+        return languages_codes.map(lc => this.format_locale(lc));
+    },
+
+    /**
+     * Fetches a new set of translations in case the wanted language changes
+     * @param {String} locale A lowercase language code
+     * @returns {Promise}
+     */
+    update_translations(locale) {
+        locale = this.format_locale(locale);
+        return new Promise((resolve, reject) => {
+            fetch(`${this.translations_url}${locale}.json`)
+                .then(response => response.json())
+                .then(response => {
+                    this.translations = response;
+                    this.locale = locale;
+
+                    localStorage.setItem(this.local_storage_key, locale);
+
+                    const split_path = window.location.pathname.substring(1).split("/");
+                    const first_url_fragment = split_path[0];
+                    const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
+
+                    if (fragment_is_locale) {
+                        split_path.splice(0, 1, locale);
+                        const updated_path = split_path.join("/");
+                        window.history.replaceState(null, "", "/" + updated_path);
+                    }
+                    resolve();
+                })
+                .catch(err => {
+                    reject(err);
+                });
+        });
+    },
+
+    /**
+     * Tries to get the translation of the source string, or return the string as it.
+     * @param {String} text The source text to translate
+     * @param {Object} params Some dynamic element to insert in the text
+     * Params can be used if the translated text provide placeholder like {%some_word%}
+     * Example:
+     * translator.trad("Some trad key", {some_word: "a dynamic parameter"})
+     * -> translation for "Some trad key": "A translated text with {%some_word%}"
+     * -> will be return as : "A translated text with a dynamic parameter"
+     * @returns {String}
+     */
+    trad: function (text, params = {}) {
+        text = this.translations[text] || text;
+
+        Object.keys(params).forEach(k => {
+            text = text.replace(`{%${k}%}`, params[k]);
+        });
+
+        return text;
+    }
+};
+},{}],4:[function(require,module,exports){
+"use strict";
 
 module.exports = {
     register_key: "objectToHtmlRender",
@@ -229,7 +418,7 @@ module.exports = {
         window.dispatchEvent(event);
     },
 };
-},{}],4:[function(require,module,exports){
+},{}],5:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../constants");
@@ -267,18 +456,24 @@ class ThemeCard {
 
 module.exports = ThemeCard;
 
-},{"../../constants":2}],5:[function(require,module,exports){
+},{"../../constants":2}],6:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../constants");
 const ThemeCard = require("./home-page-components/theme-card");
 const WebPage = require("./lib/web-page");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 class HomePage extends WebPage {
+    constructor() {
+        super({ id: "home-page" });
+    }
+
     render() {
         return {
             tag: "div",
-            id: "home-page",
+            id: this.id,
             contents: [
                 {
                     tag: "div",
@@ -305,12 +500,12 @@ class HomePage extends WebPage {
                         {
                             tag: "p",
                             class: "page-contents-center",
-                            contents: `Studio de création de jeux vidéo basé en Ardèche, Vernoux en Vivarais.<br />Création artisitique numérique | Développement d'outillage logiciel libre et open source | Pédagogie.`,
+                            contents: t("kuadrado-home-description"),
                         },
                         {
                             tag: "ul",
                             class: "philo-bubbles",
-                            contents: ["Simplicité", "Légèreté", "Écologie"].map(word => {
+                            contents: [t("Simplicité"), t("Légèreté"), t("Écologie")].map(word => {
                                 return {
                                     tag: "li",
                                     contents: [{ tag: "span", contents: word }],
@@ -324,23 +519,23 @@ class HomePage extends WebPage {
                     class: "page-contents-center poles",
                     contents: [
                         {
-                            title: "Jeux",
+                            title: t("Jeux"),
                             img: "game_controller.svg",
                             href: "/games/",
                             description:
-                                "Créations vidéoludiques, jeux web et jeux PC, projets en cours.",
+                                t("games-description"),
                         },
                         {
-                            title: "Pédagogie",
+                            title: t("Pédagogie"),
                             img: "brain.svg",
                             href: "/education/",
-                            description: `S'approprier la technologie par le partage de connaissances.`,
+                            description: t("education-description"),
                         },
                         {
                             title: "Software",
                             img: "meca_proc.svg",
                             href: "/software-development/",
-                            description: `R&D, projets expérimentaux, web et outillage logiciel`,
+                            description: t("software-description"),
                         },
                     ].map(cardProps => new ThemeCard(cardProps).render()),
                 },
@@ -351,17 +546,36 @@ class HomePage extends WebPage {
 
 module.exports = HomePage;
 
-},{"../constants":2,"./home-page-components/theme-card":4,"./lib/web-page":6}],6:[function(require,module,exports){
+},{"../constants":2,"./home-page-components/theme-card":5,"./lib/web-page":7,"ks-cheap-translator":3}],7:[function(require,module,exports){
 "use strict";
+const translator = require("ks-cheap-translator");
+const { translations_url } = require("../../constants");
 
 class WebPage {
     constructor(args) {
         Object.assign(this, args);
+
+        if (!this.id) {
+            this.id = "webpage-" + performance.now();
+        }
+
+        translator.init({
+            translations_url,
+            supported_languages: ["fr", "en"],
+        }).then(this.refresh_all.bind(this));
+    }
+
+    refresh() {
+        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
+    }
+
+    refresh_all() {
+        obj2htm.renderCycle()
     }
 }
 
 module.exports = WebPage;
-},{}],7:[function(require,module,exports){
+},{"../../constants":2,"ks-cheap-translator":3}],8:[function(require,module,exports){
 "use strict";
 
 const HomePage = require("./homepage");
@@ -369,7 +583,7 @@ const runPage = require("./run-page");
 
 runPage(HomePage);
 
-},{"./homepage":5,"./run-page":8}],8:[function(require,module,exports){
+},{"./homepage":6,"./run-page":9}],9:[function(require,module,exports){
 "use strict";
 
 const renderer = require("object-to-html-renderer")
@@ -382,19 +596,18 @@ module.exports = function runPage(PageComponent) {
     obj2htm.renderCycle();
 };
 
-},{"./template/template":10,"object-to-html-renderer":3}],9:[function(require,module,exports){
+},{"./template/template":11,"object-to-html-renderer":4}],10:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../constants");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 const NAV_MENU_ITEMS = [
     { url: "/games/", text: "Jeux" },
     {
         url: "/education/",
         text: "Pédagogie",
-        // submenu: [
-        //     { url: "/gamedev", text: "Création de jeux vidéo" },
-        // ]
     },
     { url: "/software-development/", text: "Software" }
 ];
@@ -420,6 +633,12 @@ class NavBar {
         });
     }
 
+    handle_chang_lang(lang) {
+        translator.update_translations(lang).then(() => {
+            obj2htm.renderCycle();
+        }).catch(err => console.log(err));
+    }
+
     renderHome() {
         return {
             tag: "div",
@@ -461,10 +680,20 @@ class NavBar {
                         {
                             tag: "a",
                             href,
-                            contents: text,
+                            contents: t(text),
                         },
                     ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
                 };
+            }).concat({
+                tag: "li",
+                class: "lang-flags",
+                contents: ["fr", "en"].map(lang => {
+                    return {
+                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
+                        class: translator.locale === lang ? "selected" : "",
+                        onclick: this.handle_chang_lang.bind(this, lang)
+                    }
+                })
             }),
         };
     }
@@ -492,12 +721,14 @@ class NavBar {
 
 module.exports = NavBar;
 
-},{"../../../constants":2}],10:[function(require,module,exports){
+},{"../../../constants":2,"ks-cheap-translator":3}],11:[function(require,module,exports){
 "use strict";
 
 const { in_construction } = require("../../config");
 const { images_url } = require("../../constants");
 const NavBar = require("./components/navbar");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator)
 
 class Template {
     constructor(props) {
@@ -518,7 +749,7 @@ class Template {
                         {
                             tag: "strong",
                             class: "page-contents-center",
-                            contents: "Site en construction ...",
+                            contents: t("Site en construction ..."),
                         },
                     ],
                 },
@@ -568,7 +799,7 @@ class Template {
                             contents: [
                                 {
                                     tag: "strong",
-                                    contents: "<blue>Sur les réseaux : </blue>",
+                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
                                 },
                                 {
                                     tag: "a",
@@ -590,11 +821,11 @@ class Template {
                             tag: "span",
                             contents: `Copyleft 🄯 ${new Date()
                                 .getFullYear()} Kuadrado Software | 
-                                Toutes les images du site ont été réalisées par mes soins et peuvent être réutilisées pour un usage personnel.`,
+                                ${t("kuadrado-footer-copyleft")}`,
                         },
                         {
                             tag: "div", contents: [
-                                { tag: "span", contents: "Ce site web est " },
+                                { tag: "span", contents: t("Ce site web est") + " " },
                                 {
                                     tag: "a", target: "_blank",
                                     style_rules: { fontWeight: "bold" },
@@ -612,4 +843,4 @@ class Template {
 
 module.exports = Template;
 
-},{"../../config":1,"../../constants":2,"./components/navbar":9}]},{},[7]);
+},{"../../config":1,"../../constants":2,"./components/navbar":10,"ks-cheap-translator":3}]},{},[8]);
diff --git a/public/software-development/software-development.js b/public/software-development/software-development.js
index 76ff0b825f09ca3cde10efda75d1952fdaf9228b..c2ed85689dc4575b4264884d7be01b338cdbbeba 100644
--- a/public/software-development/software-development.js
+++ b/public/software-development/software-development.js
@@ -14,10 +14,199 @@ module.exports = {
 module.exports = {
     images_url: `/assets/images`,
     data_url: `/assets/data`,
+    translations_url: "/assets/translations"
 };
 
 },{}],4:[function(require,module,exports){
 "use strict";
+/**
+ * A tiny library to handle text translation.
+ */
+
+/**
+ * init parameter object type
+ * @typedef TranslatorParam
+ * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes
+ * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en"
+ *
+ * @property {Boolean} use_url_locale_fragment - DEFAULT: true -
+ * If true, the 1st fragment of the window.location url will be parsed as a language code
+ * Example:
+ * window.location
+ * > https://example.com/en/some-page...
+ * then the /en/ part of the url will be taken in priority and locale will be set to "en".
+ * window.location
+ * > https://example.com/some-page...
+ * No locale fragment will be found so locale will not be set from url fragment.
+ * window.location
+ * > https://example.com/some-page/en
+ * Doesn't work, locale fragment must be the first framgment
+ *
+ * @property {String} local_storage_key  - DEFAULT: "translator-prefered-language"
+ *  The key used to saved the current locale into local storage
+ *
+ * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations
+ * Translations files are expected to be named with their corresponding locale code.
+ * Example:
+ * if supported_languages is set to ["en", "fr", "it"]
+ * and translations_url is set to "https://example.com/translations/""
+ * Then the expected translations files are
+ * https://example.com/translations/en.json
+ * https://example.com/translations/fr.json
+ * https://example.com/translations/it.json
+ * The json resources must simple key value maps, value being the translated text.
+ */
+
+module.exports = {
+    locale: "en", // ISO 639 lowercase language code
+    supported_languages: ["en"],
+    translations: {},
+    translations_url: "",
+    use_url_locale_fragment: true,
+    local_storage_key: "translator-prefered-language",
+
+    /**
+     * Initialize the lib with params
+     * @param {TranslatorParam} params 
+     * @returns {Promise}
+     */
+    init(params) {
+        Object.entries(params).forEach(k_v => {
+            const [key, value] = k_v;
+            if ([
+                "supported_languages",
+                "use_url_locale_fragment",
+                "local_storage_key",
+                "translations_url"
+            ].includes(key)) {
+                this[key] = value;
+            }
+        });
+
+        this.translations_url = this.format_translations_url(this.translations_url);
+        this.supported_languages = this.format_supported_languages(this.supported_languages);
+
+        return new Promise((resolve, reject) => {
+            const loc =
+                (() => {// Locale from url priority 1
+                    if (this.use_url_locale_fragment) {
+                        const first_url_fragment = window.location.pathname.substring(1).split("/")[0];
+                        const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
+                        return fragment_is_locale ? first_url_fragment : "";
+                    } else {
+                        return "";
+                    }
+                })()
+                || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2
+                || (() => { // locale from navigator priority 3
+                    const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase();
+                    return this.supported_languages.includes(navigator_locale)
+                        ? navigator_locale
+                        : this.supported_languages[0] // Default if navigator locale is not supported
+                })();
+
+            fetch(`${this.translations_url}${loc}.json`)
+                .then(response => response.json())
+                .then(response => {
+                    this.locale = loc;
+                    this.translations = response;
+                    resolve();
+                })
+                .catch(err => {
+                    this.locale = "en";
+                    reject(err);
+                });
+        });
+    },
+
+    /**
+     * Return a lowercase string without dash.
+     * If given locale is en-EN, then "en" will be returned.
+     * @param {String} locale 
+     * @returns A lowercase string
+     */
+    format_locale(locale) {
+        return locale.split("-")[0].toLowerCase();
+    },
+
+    /**
+     * Appends a slash at the end of the given string if missing, and returns the url.
+     * @param {String} url 
+     * @returns {String}
+     */
+    format_translations_url(url) {
+        if (url.charAt(url.length - 1) !== "/") {
+            url += "/"
+        }
+        return url;
+    },
+
+    /**
+     * Return the array of language codes formatted as lowsercase language code.
+     * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned.
+     * @param {String[]} languages_codes 
+     * @returns {String[]}
+     */
+    format_supported_languages(languages_codes) {
+        return languages_codes.map(lc => this.format_locale(lc));
+    },
+
+    /**
+     * Fetches a new set of translations in case the wanted language changes
+     * @param {String} locale A lowercase language code
+     * @returns {Promise}
+     */
+    update_translations(locale) {
+        locale = this.format_locale(locale);
+        return new Promise((resolve, reject) => {
+            fetch(`${this.translations_url}${locale}.json`)
+                .then(response => response.json())
+                .then(response => {
+                    this.translations = response;
+                    this.locale = locale;
+
+                    localStorage.setItem(this.local_storage_key, locale);
+
+                    const split_path = window.location.pathname.substring(1).split("/");
+                    const first_url_fragment = split_path[0];
+                    const fragment_is_locale = this.supported_languages.includes(first_url_fragment);
+
+                    if (fragment_is_locale) {
+                        split_path.splice(0, 1, locale);
+                        const updated_path = split_path.join("/");
+                        window.history.replaceState(null, "", "/" + updated_path);
+                    }
+                    resolve();
+                })
+                .catch(err => {
+                    reject(err);
+                });
+        });
+    },
+
+    /**
+     * Tries to get the translation of the source string, or return the string as it.
+     * @param {String} text The source text to translate
+     * @param {Object} params Some dynamic element to insert in the text
+     * Params can be used if the translated text provide placeholder like {%some_word%}
+     * Example:
+     * translator.trad("Some trad key", {some_word: "a dynamic parameter"})
+     * -> translation for "Some trad key": "A translated text with {%some_word%}"
+     * -> will be return as : "A translated text with a dynamic parameter"
+     * @returns {String}
+     */
+    trad: function (text, params = {}) {
+        text = this.translations[text] || text;
+
+        Object.keys(params).forEach(k => {
+            text = text.replace(`{%${k}%}`, params[k]);
+        });
+
+        return text;
+    }
+};
+},{}],5:[function(require,module,exports){
+"use strict";
 
 module.exports = {
     register_key: "objectToHtmlRender",
@@ -233,7 +422,7 @@ module.exports = {
         window.dispatchEvent(event);
     },
 };
-},{}],5:[function(require,module,exports){
+},{}],6:[function(require,module,exports){
 "use strict";
 
 const { fetch_json_or_error_text } = require("./fetch");
@@ -246,8 +435,8 @@ function getArticleDate(date) {
     return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
 }
 
-function loadArticles(category) {
-    return fetch_json_or_error_text(`/articles/${category}`)
+function loadArticles(category, locale) {
+    return fetch_json_or_error_text(`/articles/${category}/${locale}`);
 }
 
 module.exports = {
@@ -256,7 +445,7 @@ module.exports = {
     getArticleDate,
 };
 
-},{"./fetch":6}],6:[function(require,module,exports){
+},{"./fetch":7}],7:[function(require,module,exports){
 "use strict";
 
 function fetchjson(url) {
@@ -295,17 +484,36 @@ module.exports = {
     fetch_json_or_error_text,
 };
 
-},{}],7:[function(require,module,exports){
+},{}],8:[function(require,module,exports){
 "use strict";
+const translator = require("ks-cheap-translator");
+const { translations_url } = require("../../constants");
 
 class WebPage {
     constructor(args) {
         Object.assign(this, args);
+
+        if (!this.id) {
+            this.id = "webpage-" + performance.now();
+        }
+
+        translator.init({
+            translations_url,
+            supported_languages: ["fr", "en"],
+        }).then(this.refresh_all.bind(this));
+    }
+
+    refresh() {
+        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
+    }
+
+    refresh_all() {
+        obj2htm.renderCycle()
     }
 }
 
 module.exports = WebPage;
-},{}],8:[function(require,module,exports){
+},{"../../constants":3,"ks-cheap-translator":4}],9:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../../../admin-frontend/src/constants");
@@ -370,6 +578,7 @@ class SoftwareArticle {
                                         { tag: "label", contents: detail.label },
                                         {
                                             tag: "div",
+                                            class: "detail-value",
                                             contents: detail.value
                                         },
                                     ],
@@ -384,11 +593,12 @@ class SoftwareArticle {
 }
 
 module.exports = SoftwareArticle;
-},{"../../../../../admin-frontend/src/constants":1,"../../../lib/article-utils":5}],9:[function(require,module,exports){
+},{"../../../../../admin-frontend/src/constants":1,"../../../lib/article-utils":6}],10:[function(require,module,exports){
 "use strict";
 
 const { loadArticles } = require("../../../lib/article-utils");
 const SoftwareArticle = require("./software-article");
+const translator = require("ks-cheap-translator");
 
 class SoftwareArticles {
     constructor(props) {
@@ -396,12 +606,12 @@ class SoftwareArticles {
         this.state = {
             articles: [],
         };
-        this.id = performance.now();
+        this.id = "software-articles-section";
         this.loadArticles();
     }
 
     loadArticles() {
-        loadArticles("software").then(articles => {
+        loadArticles("software", translator.locale).then(articles => {
             this.state.articles = articles;
             this.refresh();
             this.fixScroll();
@@ -452,12 +662,14 @@ class SoftwareArticles {
 
 module.exports = SoftwareArticles;
 
-},{"../../../lib/article-utils":5,"./software-article":8}],10:[function(require,module,exports){
+},{"../../../lib/article-utils":6,"./software-article":9,"ks-cheap-translator":4}],11:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../constants");
 const WebPage = require("../../lib/web-page");
 const SoftwareArticles = require("./components/software-articles");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 class SoftwareDevelopment extends WebPage {
     render() {
@@ -487,7 +699,7 @@ class SoftwareDevelopment extends WebPage {
                                 { tag: "h1", contents: "Software" },
                                 {
                                     tag: "p",
-                                    contents: `R&D, projets expérimentaux, outillage logiciel pour le développement de jeu ou pour le web.`,
+                                    contents: t("software-page-intro"),
                                 },
                             ],
                         },
@@ -501,7 +713,7 @@ class SoftwareDevelopment extends WebPage {
 
 module.exports = SoftwareDevelopment;
 
-},{"../../../constants":3,"../../lib/web-page":7,"./components/software-articles":9}],11:[function(require,module,exports){
+},{"../../../constants":3,"../../lib/web-page":8,"./components/software-articles":10,"ks-cheap-translator":4}],12:[function(require,module,exports){
 "use strict";
 
 "use strict";
@@ -509,7 +721,7 @@ const runPage = require("../../run-page");
 const SoftwareDevelopment = require("./software-development");
 runPage(SoftwareDevelopment);
 
-},{"../../run-page":12,"./software-development":10}],12:[function(require,module,exports){
+},{"../../run-page":13,"./software-development":11}],13:[function(require,module,exports){
 "use strict";
 
 const renderer = require("object-to-html-renderer")
@@ -522,19 +734,18 @@ module.exports = function runPage(PageComponent) {
     obj2htm.renderCycle();
 };
 
-},{"./template/template":14,"object-to-html-renderer":4}],13:[function(require,module,exports){
+},{"./template/template":15,"object-to-html-renderer":5}],14:[function(require,module,exports){
 "use strict";
 
 const { images_url } = require("../../../constants");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 const NAV_MENU_ITEMS = [
     { url: "/games/", text: "Jeux" },
     {
         url: "/education/",
         text: "Pédagogie",
-        // submenu: [
-        //     { url: "/gamedev", text: "Création de jeux vidéo" },
-        // ]
     },
     { url: "/software-development/", text: "Software" }
 ];
@@ -560,6 +771,12 @@ class NavBar {
         });
     }
 
+    handle_chang_lang(lang) {
+        translator.update_translations(lang).then(() => {
+            obj2htm.renderCycle();
+        }).catch(err => console.log(err));
+    }
+
     renderHome() {
         return {
             tag: "div",
@@ -601,10 +818,20 @@ class NavBar {
                         {
                             tag: "a",
                             href,
-                            contents: text,
+                            contents: t(text),
                         },
                     ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
                 };
+            }).concat({
+                tag: "li",
+                class: "lang-flags",
+                contents: ["fr", "en"].map(lang => {
+                    return {
+                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
+                        class: translator.locale === lang ? "selected" : "",
+                        onclick: this.handle_chang_lang.bind(this, lang)
+                    }
+                })
             }),
         };
     }
@@ -632,12 +859,14 @@ class NavBar {
 
 module.exports = NavBar;
 
-},{"../../../constants":3}],14:[function(require,module,exports){
+},{"../../../constants":3,"ks-cheap-translator":4}],15:[function(require,module,exports){
 "use strict";
 
 const { in_construction } = require("../../config");
 const { images_url } = require("../../constants");
 const NavBar = require("./components/navbar");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator)
 
 class Template {
     constructor(props) {
@@ -658,7 +887,7 @@ class Template {
                         {
                             tag: "strong",
                             class: "page-contents-center",
-                            contents: "Site en construction ...",
+                            contents: t("Site en construction ..."),
                         },
                     ],
                 },
@@ -708,7 +937,7 @@ class Template {
                             contents: [
                                 {
                                     tag: "strong",
-                                    contents: "<blue>Sur les réseaux : </blue>",
+                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
                                 },
                                 {
                                     tag: "a",
@@ -730,11 +959,11 @@ class Template {
                             tag: "span",
                             contents: `Copyleft 🄯 ${new Date()
                                 .getFullYear()} Kuadrado Software | 
-                                Toutes les images du site ont été réalisées par mes soins et peuvent être réutilisées pour un usage personnel.`,
+                                ${t("kuadrado-footer-copyleft")}`,
                         },
                         {
                             tag: "div", contents: [
-                                { tag: "span", contents: "Ce site web est " },
+                                { tag: "span", contents: t("Ce site web est") + " " },
                                 {
                                     tag: "a", target: "_blank",
                                     style_rules: { fontWeight: "bold" },
@@ -752,4 +981,4 @@ class Template {
 
 module.exports = Template;
 
-},{"../../config":2,"../../constants":3,"./components/navbar":13}]},{},[11]);
+},{"../../config":2,"../../constants":3,"./components/navbar":14,"ks-cheap-translator":4}]},{},[12]);
diff --git a/public/style/style.css b/public/style/style.css
index 3a5bebbd60c34e95366d34385e1915791bad3e04..db7b101962d4bf8d3e1fd5c1228b671f0fea983d 100644
--- a/public/style/style.css
+++ b/public/style/style.css
@@ -146,6 +146,7 @@ main header nav ul {
   margin: 0;
   list-style-type: none;
   height: 100%;
+  flex: 1;
 }
 main header nav ul li {
   position: relative;
@@ -190,6 +191,22 @@ main header nav ul li:hover .submenu a {
 main header nav ul li:hover .submenu li:hover a {
   color: #3c4144;
 }
+main header nav ul li.lang-flags {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-left: auto;
+  padding: 0 20px;
+}
+main header nav ul li.lang-flags img {
+  width: 35px;
+  height: 30px;
+  cursor: pointer;
+  opacity: 0.5;
+}
+main header nav ul li.lang-flags img.selected, main header nav ul li.lang-flags img:hover {
+  opacity: 1;
+}
 main header nav .burger {
   display: none;
 }
@@ -247,6 +264,11 @@ main header nav .burger {
     font-size: 14px;
     color: #96a5ae;
   }
+  main header nav ul.responsive-show li.lang-flags {
+    margin-left: unset;
+    justify-content: space-around;
+    padding: 20px;
+  }
 }
 main #page-container {
   width: 100%;
@@ -397,6 +419,9 @@ main #page-container .article-details ul.details-list .detail label {
   font-weight: bold;
   color: #6b7880;
 }
+main #page-container .article-details ul.details-list .detail .detail-value {
+  text-align: right;
+}
 main #page-container #home-page {
   display: flex;
   flex-direction: column;
diff --git a/src/service/articles.rs b/src/service/articles.rs
index 692ca3c5480ab452fa1e15c69b8a066db78650cc..13219d6e1ef5e7a4f9886513634ae6ce23f46ad5 100644
--- a/src/service/articles.rs
+++ b/src/service/articles.rs
@@ -107,13 +107,15 @@ pub async fn delete_article(
     }
 }
 
-#[get("/articles/{category}")]
+#[get("/articles/{category}/{locale}")]
 pub async fn get_articles_by_category(
     app_state: Data<AppState>,
-    category: Path<String>,
+    path: Path<(String, String)>,
 ) -> impl Responder {
+    let (category, locale) = path.into_inner();
+
     match get_collection(&app_state)
-        .find(doc! {"category": category.into_inner()}, None)
+        .find(doc! {"category": category, "locale": locale}, None)
         .await
     {
         Ok(mut cursor) => {
@@ -610,7 +612,7 @@ mod test_articles {
             .await
             .unwrap();
 
-        let req = test::TestRequest::with_uri("/articles/testing")
+        let req = test::TestRequest::with_uri("/articles/testing/fr")
             .header("Accept", "application/json")
             .method(Method::GET)
             .to_request();
diff --git a/website/constants.js b/website/constants.js
index bddb27a4d9bc2e3961443ee7a05a6e3b75ef5d7a..6fd1333287c8b8472dad3cdda10ff05d942d5254 100644
--- a/website/constants.js
+++ b/website/constants.js
@@ -1,4 +1,5 @@
 module.exports = {
     images_url: `/assets/images`,
     data_url: `/assets/data`,
+    translations_url: "/assets/translations"
 };
diff --git a/website/package-lock.json b/website/package-lock.json
index e343bea44b26ab0b6705fc1332b8efa83f2b7229..161971b07ea62e2b31cff5b7be5ef99f159749c3 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -9,6 +9,7 @@
             "version": "1.0.3",
             "license": "MIT",
             "dependencies": {
+                "ks-cheap-translator": "^0.1.0",
                 "mentalo-engine": "0.1.17",
                 "object-to-html-renderer": "^1.1.1"
             },
@@ -1139,6 +1140,11 @@
                 "node": "*"
             }
         },
+        "node_modules/ks-cheap-translator": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/ks-cheap-translator/-/ks-cheap-translator-0.1.0.tgz",
+            "integrity": "sha512-h9ymFx7Z36M4Te4jOLlqgIkhHhUnNCEw2X1LCQY10c9mC7X3EL33OLj/bwKlu8a5SyWy+1DnuGbq5//rJuHEyg=="
+        },
         "node_modules/labeled-stream-splicer": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz",
@@ -2908,6 +2914,11 @@
                 "through": ">=2.2.7 <3"
             }
         },
+        "ks-cheap-translator": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/ks-cheap-translator/-/ks-cheap-translator-0.1.0.tgz",
+            "integrity": "sha512-h9ymFx7Z36M4Te4jOLlqgIkhHhUnNCEw2X1LCQY10c9mC7X3EL33OLj/bwKlu8a5SyWy+1DnuGbq5//rJuHEyg=="
+        },
         "labeled-stream-splicer": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz",
diff --git a/website/package.json b/website/package.json
index 2260a39d6f72b2bfee8b751fa293427bc9cbeaf7..9be127e3029ffc7ddeef1ac8acd184489a4b020b 100644
--- a/website/package.json
+++ b/website/package.json
@@ -14,11 +14,12 @@
     "license": "MIT",
     "homepage": "https://gitlab.com/peter_rabbit/kuadrado-website#readme",
     "dependencies": {
-        "object-to-html-renderer": "^1.1.1",
-        "mentalo-engine": "0.1.17"
+        "ks-cheap-translator": "^0.1.0",
+        "mentalo-engine": "0.1.17",
+        "object-to-html-renderer": "^1.1.1"
     },
     "devDependencies": {
         "sass": "^1.32.0",
         "simple-browser-js-bundler": "^0.1.1"
     }
-}
\ No newline at end of file
+}
diff --git a/website/src/homepage.js b/website/src/homepage.js
index 59c22b12c21d90492d93239699580ffdf3723e84..fc03455a6459c93081dd6ac05451ed7dd4bac995 100644
--- a/website/src/homepage.js
+++ b/website/src/homepage.js
@@ -3,12 +3,18 @@
 const { images_url } = require("../constants");
 const ThemeCard = require("./home-page-components/theme-card");
 const WebPage = require("./lib/web-page");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 class HomePage extends WebPage {
+    constructor() {
+        super({ id: "home-page" });
+    }
+
     render() {
         return {
             tag: "div",
-            id: "home-page",
+            id: this.id,
             contents: [
                 {
                     tag: "div",
@@ -35,12 +41,12 @@ class HomePage extends WebPage {
                         {
                             tag: "p",
                             class: "page-contents-center",
-                            contents: `Studio de création de jeux vidéo basé en Ardèche, Vernoux en Vivarais.<br />Création artisitique numérique | Développement d'outillage logiciel libre et open source | Pédagogie.`,
+                            contents: t("kuadrado-home-description"),
                         },
                         {
                             tag: "ul",
                             class: "philo-bubbles",
-                            contents: ["Simplicité", "Légèreté", "Écologie"].map(word => {
+                            contents: [t("Simplicité"), t("Légèreté"), t("Écologie")].map(word => {
                                 return {
                                     tag: "li",
                                     contents: [{ tag: "span", contents: word }],
@@ -54,23 +60,23 @@ class HomePage extends WebPage {
                     class: "page-contents-center poles",
                     contents: [
                         {
-                            title: "Jeux",
+                            title: t("Jeux"),
                             img: "game_controller.svg",
                             href: "/games/",
                             description:
-                                "Créations vidéoludiques, jeux web et jeux PC, projets en cours.",
+                                t("games-description"),
                         },
                         {
-                            title: "Pédagogie",
+                            title: t("Pédagogie"),
                             img: "brain.svg",
                             href: "/education/",
-                            description: `S'approprier la technologie par le partage de connaissances.`,
+                            description: t("education-description"),
                         },
                         {
                             title: "Software",
                             img: "meca_proc.svg",
                             href: "/software-development/",
-                            description: `R&D, projets expérimentaux, web et outillage logiciel`,
+                            description: t("software-description"),
                         },
                     ].map(cardProps => new ThemeCard(cardProps).render()),
                 },
diff --git a/website/src/lib/article-utils.js b/website/src/lib/article-utils.js
index 3e00a88258938157bad464a9949e59a624e674b6..fbce5d4096d62bbe9affe2d6a2e90a935369b109 100644
--- a/website/src/lib/article-utils.js
+++ b/website/src/lib/article-utils.js
@@ -10,8 +10,8 @@ function getArticleDate(date) {
     return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
 }
 
-function loadArticles(category) {
-    return fetch_json_or_error_text(`/articles/${category}`)
+function loadArticles(category, locale) {
+    return fetch_json_or_error_text(`/articles/${category}/${locale}`);
 }
 
 module.exports = {
diff --git a/website/src/lib/web-page.js b/website/src/lib/web-page.js
index b58f4c0a88f85c2b45ca6220c6ed4651488dbfb1..39631cb3dd4f6c9ea1a0b0fc541ff05b13d02dcb 100644
--- a/website/src/lib/web-page.js
+++ b/website/src/lib/web-page.js
@@ -1,8 +1,27 @@
 "use strict";
+const translator = require("ks-cheap-translator");
+const { translations_url } = require("../../constants");
 
 class WebPage {
     constructor(args) {
         Object.assign(this, args);
+
+        if (!this.id) {
+            this.id = "webpage-" + performance.now();
+        }
+
+        translator.init({
+            translations_url,
+            supported_languages: ["fr", "en"],
+        }).then(this.refresh_all.bind(this));
+    }
+
+    refresh() {
+        obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" })
+    }
+
+    refresh_all() {
+        obj2htm.renderCycle()
     }
 }
 
diff --git a/website/src/pages/education/education.js b/website/src/pages/education/education.js
index 146cd3bf206eeb5edf6e04606f981fc4f96964be..efd67647628720b8b56333c6fda7f27620280728 100644
--- a/website/src/pages/education/education.js
+++ b/website/src/pages/education/education.js
@@ -2,55 +2,38 @@
 
 const { images_url } = require("../../../constants");
 const WebPage = require("../../lib/web-page");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 const EDU_THEMES = [
-    // {
-    //     title: "Création de jeux vidéo",
-    //     description: "Conception, graphisme et animation, programmation, je vous accompagne dans la découverte des techniques pour créer un jeu vidéo de A à Z",
-    //     image: "learning_theme_conception.png",
-    //     pageUrl: "gamedev",
-    // },
     {
         title: "Programmation",
-        description: `<b>Franchissez le mur du code !</b><br />
-        Apprenez à programmer avec différents langages (Python, Javascript, C ...), pour du web, du logiciel, du jeu vidéo ou autre.`,
+        description: "edu-learn-coding",
         image: "learning_theme_coding.png",
-        // pageUrl: "coding",
     },
     {
         title: "Dessin numérique et animation 2D",
-        description: `Apprenez à utiliser des logiciels libres de création graphique 2D et d'animation.<br />
-        Créez des personnages et des décors, menez votre projet de dessin animé, d'illustration ou de jeu vidéo.`,
+        description: "edu-learn-2d",
         image: "learning_theme_2d.png",
-        // pageUrl: "2d",
     },
     {
         title: "Maths et physique",
-        description: "Abordez les notions fondamentales de façon décontractée pour le plaisir de comprendre en s'appuyant sur les domaines d'application du jeu vidéo.",
+        description: "edu-learn-math",
         image: "learning_theme_math.png",
-        // pageUrl: "math",
     },
-    // {
-    //     title: "Musique et sons électroniques",
-    //     description: "Découvrez des logiciels libres de composition musicales, de synthèse sonore et de prise de son.",
-    //     image: "learning_theme_sound.png",
-    //     pageUrl: "sound",
-    // },
     {
         title: "Aide informatique générale",
-        description: "Perdu avec votre ordinateur ou votre smartphone, les logiciels, internet ? Prenez en main les fondamentaux apprenez pas à pas à utiliser sereinement la technologie.",
+        description: "edu-learn-computer",
         image: "learning_theme_pc.png",
-        // pageUrl: "popularization"
     },
     {
         title: "Stage GNU/Linux",
-        description: `<b>Passez le cap du libre ! </b><br/>
-        Apprenez à installer Linux, faites vos premiers pas avec les logiciels libres et acquérez une autonomie suffisante pour une utilisation basique.`,
+        description: "edu-learn-gnu",
         image: "learning_theme_linux.png"
     },
     {
         title: "Créer un jeu avec Mentalo",
-        description: "Créez un jeu en quelques séances avec l'application Mentalo. Manipulez des concepts logiques, narratifs et artistiques avec le maximum de simplicité.",
+        description: "edu-learn-mentalo",
         image: "learning_theme_mentalo.png",
     }
 ];
@@ -81,11 +64,10 @@ class EducationPage extends WebPage {
                                         },
                                     ],
                                 },
-                                { tag: "h1", contents: "Pédagogie" },
+                                { tag: "h1", contents: t("Pédagogie") },
                                 {
                                     tag: "p",
-                                    contents: `Ateliers, stages, workshops et cours particuliers accessibles à tous. 
-                                    Programmation, graphisme 2D, jeux vidéo, vulgarisation informatique, etc.`,
+                                    contents: t("edu-page-intro"),
                                 },
                             ],
                         },
@@ -112,8 +94,8 @@ class EducationPage extends WebPage {
                                             class: "edu-theme",
                                             contents: [
                                                 { tag: "img", width: 250, height: 140, class: "pixelated", src: `${images_url}/${theme.image}` },
-                                                { tag: "h3", contents: theme.title },
-                                                { tag: "p", contents: theme.description },
+                                                { tag: "h3", contents: t(theme.title) },
+                                                { tag: "p", contents: t(theme.description) },
                                             ]
                                         }
                                     })
@@ -130,131 +112,11 @@ class EducationPage extends WebPage {
                             tag: "div",
                             class: "page-contents-center",
                             contents: [
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Pour qui ?" },
-                                //         {
-                                //             tag: "p",
-                                //             class: "info-body",
-                                //             contents: `Les ateliers sont accessibles aux adultes comme aux enfants, plutôt à partir de 12 ans.<br/>
-                                //             Les séances ont lieu en groupes mixtes. Capacité limitée à 5 personnes.
-                                //             `
-                                //         }
-                                //     ]
-                                // },
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Où ça ?" },
-                                //         {
-                                //             tag: "p",
-                                //             class: "info-body",
-                                //             contents: "Dans mon local professionnel : <br /><blue>32 rue Simon Vialet, passage du Cheminou, 07240 Vernoux en Vivarais.</blue>"
-                                //         }
-                                //     ]
-                                // },
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Quel matériel ?" },
-                                //         {
-                                //             tag: "p",
-                                //             class: "info-body",
-                                //             contents: `Le matériel informatique est fourni sur place (ordinateurs et tablettes graphique) 
-                                //             mais il est possible d'amener le sien. 
-                                //             <br />Il est recommandé d'apporter au moins une clé USB pour faire ses sauvegardes.`
-                                //         }
-                                //     ]
-                                // },
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Quand ?" },
-                                //         {
-                                //             tag: "ul",
-                                //             class: "info-body tabled",
-                                //             contents: [
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Mardi" },
-                                //                         { tag: "span", contents: "16h - 18h" },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Mercredi" },
-                                //                         { tag: "span", contents: "14h - 16h" },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Jeudi" },
-                                //                         { tag: "span", contents: "16h - 18h" },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     class: "fullwidth",
-                                //                     contents: "<em><blue>Ouvert de Septembre à Juin, sauf vacances scolaires ou fermetures exceptionnelles</blue></em>"
-                                //                 }
-                                //             ]
-                                //         },
-                                //     ]
-                                // },
-                                // {
-                                //     tag: "div",
-                                //     class: "info-block",
-                                //     contents: [
-                                //         { tag: "h3", class: "info-title", contents: "Combien ça coûte ?" },
-                                //         {
-                                //             tag: "ul",
-                                //             class: "info-body tabled",
-                                //             contents: [
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Inscription au mois" },
-                                //                         { tag: "span", contents: "50€, accès à toutes les plages horaires." },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Inscription à la séance" },
-                                //                         { tag: "span", contents: "15€" },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Cours particuliers" },
-                                //                         { tag: "span", contents: "30€/h, sur place ou en visio. Horaires à définir." },
-                                //                     ]
-                                //                 },
-                                //                 {
-                                //                     tag: "li",
-                                //                     contents: [
-                                //                         { tag: "span", contents: "Stage 4 séances de 2h" },
-                                //                         { tag: "span", contents: "40€ par personne, horaires et dates à définir selon la demande." },
-                                //                     ]
-                                //                 }
-                                //             ]
-                                //         }
-                                //     ]
-                                // },
                                 {
                                     tag: "div",
                                     class: "info-block",
                                     contents: [
-                                        { tag: "h3", class: "info-title", contents: "Pour s'inscrire ou en savoir plus <em>(programme 2021 2022 à définir, plus d'infos bientôt)</em>" },
+                                        { tag: "h3", class: "info-title", contents: `${t("Pour s'inscrire ou en savoir plus")} <em>(programme 2022 à définir, plus d'infos bientôt)</em>` },
                                         {
                                             tag: "ul",
                                             class: "info-body",
@@ -262,7 +124,7 @@ class EducationPage extends WebPage {
                                                 {
                                                     tag: "li",
                                                     contents: [
-                                                        { tag: "span", contents: "Me contacter" },
+                                                        { tag: "span", contents: t("Me contacter") },
                                                         {
                                                             tag: "a",
                                                             href: "mailto:contact@kuadrado-software.fr",
@@ -282,12 +144,6 @@ class EducationPage extends WebPage {
                                                         },
                                                     ]
                                                 },
-                                                // {
-                                                //     tag: "li",
-                                                //     contents: [
-                                                //         { tag: "span", contents: "ou passer directement me voir au local !" }
-                                                //     ]
-                                                // }
                                             ]
                                         }
                                     ]
diff --git a/website/src/pages/games/components/game-article.js b/website/src/pages/games/components/game-article.js
index 6308e65a25509cbdc3752dfeec0b43fc4579c990..69774a810d3fba6ae32fad8626ebdf0282bffc78 100644
--- a/website/src/pages/games/components/game-article.js
+++ b/website/src/pages/games/components/game-article.js
@@ -149,6 +149,7 @@ class GameArticle {
                                         { tag: "label", contents: detail.label },
                                         {
                                             tag: "div",
+                                            class: "detail-value",
                                             contents: detail.value
                                         },
                                     ],
diff --git a/website/src/pages/games/components/game-articles.js b/website/src/pages/games/components/game-articles.js
index f50bc19951d94cdcdac784d1ad18a6c156c8b683..7ad6ad43703c7ab815a85cc786cba8de80e52249 100644
--- a/website/src/pages/games/components/game-articles.js
+++ b/website/src/pages/games/components/game-articles.js
@@ -2,6 +2,7 @@
 
 const { loadArticles } = require("../../../lib/article-utils");
 const GameArticle = require("./game-article");
+const translator = require("ks-cheap-translator");
 
 class GameArticles {
     constructor(props) {
@@ -9,12 +10,12 @@ class GameArticles {
         this.state = {
             articles: [],
         };
-        this.id = performance.now();
+        this.id = "game-articles-section";
         this.loadArticles();
     }
 
     loadArticles() {
-        loadArticles("games")
+        loadArticles("games", translator.locale)
             .then(articles => {
                 this.state.articles = articles;
                 this.refresh();
diff --git a/website/src/pages/games/games.js b/website/src/pages/games/games.js
index 246ae03c5e8c9038758cea324cd11e1c5bc45436..17ffd9371763a9b788fca0ffccd70564bdf7f97a 100644
--- a/website/src/pages/games/games.js
+++ b/website/src/pages/games/games.js
@@ -3,6 +3,8 @@
 const { images_url } = require("../../../constants");
 const WebPage = require("../../lib/web-page");
 const GameArticles = require("./components/game-articles");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 class GamesPage extends WebPage {
     render() {
@@ -29,11 +31,10 @@ class GamesPage extends WebPage {
                                         },
                                     ],
                                 },
-                                { tag: "h1", contents: "Jeux" },
+                                { tag: "h1", contents: t("Jeux") },
                                 {
                                     tag: "p",
-                                    contents: `Création de jeux vidéos indépendants.
-                                    <br/>Jeux web, PC et projets en cours de développement`,
+                                    contents: t("games-page-intro"),
                                 },
                             ],
                         },
diff --git a/website/src/pages/software-development/components/software-article.js b/website/src/pages/software-development/components/software-article.js
index ae5ad4f689ec7accf942fd31aa7314428c8228b5..71da71bf542b7ddaa11a63e026cb5308aaea34b7 100644
--- a/website/src/pages/software-development/components/software-article.js
+++ b/website/src/pages/software-development/components/software-article.js
@@ -62,6 +62,7 @@ class SoftwareArticle {
                                         { tag: "label", contents: detail.label },
                                         {
                                             tag: "div",
+                                            class: "detail-value",
                                             contents: detail.value
                                         },
                                     ],
diff --git a/website/src/pages/software-development/components/software-articles.js b/website/src/pages/software-development/components/software-articles.js
index acc5199a8c7041e90027bc13b1af4903db82d8cd..7ec6fce092a9b13a30e5432097d695519f9e20db 100644
--- a/website/src/pages/software-development/components/software-articles.js
+++ b/website/src/pages/software-development/components/software-articles.js
@@ -2,6 +2,7 @@
 
 const { loadArticles } = require("../../../lib/article-utils");
 const SoftwareArticle = require("./software-article");
+const translator = require("ks-cheap-translator");
 
 class SoftwareArticles {
     constructor(props) {
@@ -9,12 +10,12 @@ class SoftwareArticles {
         this.state = {
             articles: [],
         };
-        this.id = performance.now();
+        this.id = "software-articles-section";
         this.loadArticles();
     }
 
     loadArticles() {
-        loadArticles("software").then(articles => {
+        loadArticles("software", translator.locale).then(articles => {
             this.state.articles = articles;
             this.refresh();
             this.fixScroll();
diff --git a/website/src/pages/software-development/software-development.js b/website/src/pages/software-development/software-development.js
index 0745aa8b08fd1e3ec62644ad72a1e0e29543e6dc..a74945d9f111226776b7d4a8af4d16344f476ac1 100644
--- a/website/src/pages/software-development/software-development.js
+++ b/website/src/pages/software-development/software-development.js
@@ -3,6 +3,8 @@
 const { images_url } = require("../../../constants");
 const WebPage = require("../../lib/web-page");
 const SoftwareArticles = require("./components/software-articles");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 class SoftwareDevelopment extends WebPage {
     render() {
@@ -32,7 +34,7 @@ class SoftwareDevelopment extends WebPage {
                                 { tag: "h1", contents: "Software" },
                                 {
                                     tag: "p",
-                                    contents: `R&D, projets expérimentaux, outillage logiciel pour le développement de jeu ou pour le web.`,
+                                    contents: t("software-page-intro"),
                                 },
                             ],
                         },
diff --git a/website/src/style.scss b/website/src/style.scss
index 9fce3a9fed41050678982cc276c743b5f7a109e8..e3ca9c780a5ec05aec8bccf62914ffa7717c492b 100644
--- a/website/src/style.scss
+++ b/website/src/style.scss
@@ -147,6 +147,7 @@ main {
 				margin: 0;
 				list-style-type: none;
 				height: 100%;
+				flex: 1;
 				li {
 					position: relative;
 					a {
@@ -199,6 +200,23 @@ main {
 							}
 						}
 					}
+					&.lang-flags {
+						display: flex;
+						align-items: center;
+						gap: 10px;
+						margin-left: auto;
+						padding: 0 20px;
+						img {
+							width: 35px;
+							height: 30px;
+							cursor: pointer;
+							opacity: 0.5;
+							&.selected,
+							&:hover {
+								opacity: 1;
+							}
+						}
+					}
 				}
 			}
 			.burger {
@@ -255,6 +273,11 @@ main {
 								}
 								margin-left: 20px;
 							}
+							&.lang-flags {
+								margin-left: unset;
+								justify-content: space-around;
+								padding: 20px;
+							}
 						}
 					}
 				}
@@ -391,6 +414,9 @@ main {
 						font-weight: bold;
 						color: $medium_grey;
 					}
+					.detail-value {
+						text-align: right;
+					}
 				}
 			}
 		}
diff --git a/website/src/template/components/navbar.js b/website/src/template/components/navbar.js
index b2ae80d3c84ec2b5c55d1f8f8533763c019ab895..74c88f187ca79d0e74af6f5cf8c2859a15ef04f7 100644
--- a/website/src/template/components/navbar.js
+++ b/website/src/template/components/navbar.js
@@ -1,15 +1,14 @@
 "use strict";
 
 const { images_url } = require("../../../constants");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator);
 
 const NAV_MENU_ITEMS = [
     { url: "/games/", text: "Jeux" },
     {
         url: "/education/",
         text: "Pédagogie",
-        // submenu: [
-        //     { url: "/gamedev", text: "Création de jeux vidéo" },
-        // ]
     },
     { url: "/software-development/", text: "Software" }
 ];
@@ -35,6 +34,12 @@ class NavBar {
         });
     }
 
+    handle_chang_lang(lang) {
+        translator.update_translations(lang).then(() => {
+            obj2htm.renderCycle();
+        }).catch(err => console.log(err));
+    }
+
     renderHome() {
         return {
             tag: "div",
@@ -76,10 +81,20 @@ class NavBar {
                         {
                             tag: "a",
                             href,
-                            contents: text,
+                            contents: t(text),
                         },
                     ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []),
                 };
+            }).concat({
+                tag: "li",
+                class: "lang-flags",
+                contents: ["fr", "en"].map(lang => {
+                    return {
+                        tag: "img", src: `${images_url}/flag-${lang}.svg`,
+                        class: translator.locale === lang ? "selected" : "",
+                        onclick: this.handle_chang_lang.bind(this, lang)
+                    }
+                })
             }),
         };
     }
diff --git a/website/src/template/template.js b/website/src/template/template.js
index fbf9f58b8be3660527beb66663c2425d834272dd..acf1dad70da772f3aa42687ee7b80218816d34cd 100644
--- a/website/src/template/template.js
+++ b/website/src/template/template.js
@@ -3,6 +3,8 @@
 const { in_construction } = require("../../config");
 const { images_url } = require("../../constants");
 const NavBar = require("./components/navbar");
+const translator = require("ks-cheap-translator");
+const t = translator.trad.bind(translator)
 
 class Template {
     constructor(props) {
@@ -23,7 +25,7 @@ class Template {
                         {
                             tag: "strong",
                             class: "page-contents-center",
-                            contents: "Site en construction ...",
+                            contents: t("Site en construction ..."),
                         },
                     ],
                 },
@@ -73,7 +75,7 @@ class Template {
                             contents: [
                                 {
                                     tag: "strong",
-                                    contents: "<blue>Sur les réseaux : </blue>",
+                                    contents: `<blue>${t("Sur les réseaux")} : </blue>`,
                                 },
                                 {
                                     tag: "a",
@@ -95,11 +97,11 @@ class Template {
                             tag: "span",
                             contents: `Copyleft 🄯 ${new Date()
                                 .getFullYear()} Kuadrado Software | 
-                                Toutes les images du site ont été réalisées par mes soins et peuvent être réutilisées pour un usage personnel.`,
+                                ${t("kuadrado-footer-copyleft")}`,
                         },
                         {
                             tag: "div", contents: [
-                                { tag: "span", contents: "Ce site web est " },
+                                { tag: "span", contents: t("Ce site web est") + " " },
                                 {
                                     tag: "a", target: "_blank",
                                     style_rules: { fontWeight: "bold" },