diff options
| -rw-r--r-- | htdocs/index.html | 45 | ||||
| -rw-r--r-- | htdocs/src/js/app.js | 27 | ||||
| -rw-r--r-- | htdocs/src/js/config/projects.js | 34 | ||||
| -rw-r--r-- | htdocs/src/js/i18n/i18n.js | 42 | ||||
| -rw-r--r-- | htdocs/src/js/i18n/locales/en.js | 24 | ||||
| -rw-r--r-- | htdocs/src/js/i18n/locales/fr.js | 24 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | yarn.lock | 23 |
8 files changed, 175 insertions, 45 deletions
diff --git a/htdocs/index.html b/htdocs/index.html index e7aaace..47e41fd 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -1,19 +1,16 @@ <!DOCTYPE html> <html lang="en"> - <head> - <meta charset="UTF-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Armand Philippot</title> - <link rel="stylesheet" href="assets/css/style.css"> + <link rel="stylesheet" href="assets/css/style.css" /> </head> <body class="body"> <div class="toolbar"> - <button type="button" class="toolbar__options btn btn--menu"> - Menu - </button> + <button type="button" class="toolbar__options btn btn--menu">Menu</button> <button type="button" class="toolbar__options btn btn--details"> About app </button> @@ -22,37 +19,43 @@ <div class="branding"> <div class="branding__logo logo"> <a href="/" rel="home" class="logo__link"> - <img src="./assets/images/armand-philippot.jpg" alt="Back to homepage" - class="logo__image logo__image--front"> - <img src="./assets/images/armand-philippot-logo.svg" alt="Back to homepage" - class="logo__image logo__image--back"> + <img + src="./assets/images/armand-philippot.jpg" + alt="Back to homepage" + class="logo__image logo__image--front" + /> + <img + src="./assets/images/armand-philippot-logo.svg" + alt="Back to homepage" + class="logo__image logo__image--back" + /> </a> </div> <h1 class="branding__title"> <a href="/" rel="home" class="branding__link">Armand Philippot</a> </h1> - <p class="branding__description"> - Front-end developer - </p> + <p class="branding__description">Front-end developer</p> </div> <nav class="nav"> <p class="nav__label">App list:</p> - <ul class="nav__list"> - </ul> + <ul class="nav__list"></ul> </nav> </header> <main class="main"> - <div class="instructions">Select an app inside menu to see a live preview and app details.</div> + <div class="instructions"> + Select an app inside menu to see a live preview and app details. + </div> </main> <footer class="footer"> <div class="copyright"> - <span class="copyright__license">MIT</span> + <span class="copyright__license" title="License MIT">MIT</span> <span class="copyright__date">2021.</span> - <a href="https://www.armandphilippot.com/" class="copyright__author">Armand Philippot.</a> + <a href="https://www.armandphilippot.com/" class="copyright__author" + >Armand Philippot.</a + > </div> </footer> <script src="./assets/js/runtime.js"></script> <script src="./assets/js/app.js"></script> </body> - </html> diff --git a/htdocs/src/js/app.js b/htdocs/src/js/app.js index b605583..2837621 100644 --- a/htdocs/src/js/app.js +++ b/htdocs/src/js/app.js @@ -1,4 +1,5 @@ import projects from './config/projects'; +import { translate, setLocale, currentLocale } from './i18n/i18n'; import { hideToBottom, hideToLeft, @@ -134,7 +135,7 @@ function getRepos(repos) { const list = document.createElement('ul'); const items = repos.map((repo) => getRepoItem(repo.name, repo.url)); title.classList.add('project-details__title'); - title.textContent = 'Repositories:'; + title.textContent = translate('main.project.details.repo'); list.classList.add('list', 'list--repos'); list.append(...items); wrapper.append(title, list); @@ -151,7 +152,7 @@ function getTechs(technologies) { const title = document.createElement('h3'); title.classList.add('project-details__title'); - title.textContent = 'Technologies:'; + title.textContent = translate('main.project.details.tech'); const list = document.createElement('ul'); const items = technologies.map((technology) => { const item = document.createElement('li'); @@ -173,17 +174,20 @@ function getProjectDetails(project) { const title = document.createElement('h2'); const techList = project?.technologies ? getTechs(project.technologies) : []; const reposList = getRepos(project.repo); + const locale = currentLocale(); let description; if (project.description) { description = document.createElement('p'); - description.textContent = project.description; + description.textContent = project.description[locale] || ''; } else { description = ''; } title.classList.add('project-details__title'); - title.textContent = `About ${project.name}`; + title.textContent = translate('main.project.details.about', { + name: project.name, + }); details.classList.add('project-details'); if (!isSmallVw()) details.classList.add('fade-in'); details.replaceChildren(title, description, ...techList, ...reposList); @@ -228,7 +232,9 @@ function showProject(id) { if (isSmallVw()) details.classList.add('hide'); - detailsBtn.textContent = `About ${currentProject.name}`; + detailsBtn.textContent = translate('main.project.details.about', { + name: currentProject.name, + }); detailsBtn.addEventListener('click', toggleProjectDetails); window.history.pushState({}, currentProject.name, `#${id}`); main.replaceChildren(preview, details); @@ -311,10 +317,21 @@ function printRequestedPage() { } } +function translateHTMLContent() { + const brandingDesc = document.querySelector('.branding__description'); + const navLabel = document.querySelector('.nav__label'); + const license = document.querySelector('.copyright__license'); + brandingDesc.textContent = translate('branding.description'); + navLabel.textContent = translate('nav.title'); + license.title = translate('footer.license'); +} + /** * Initialize the website with the projects list. */ function init() { + setLocale('en'); + translateHTMLContent(); loadWebpackStyles(); printProjectsNav(); updateView(); diff --git a/htdocs/src/js/config/projects.js b/htdocs/src/js/config/projects.js index bb239db..f22fa9b 100644 --- a/htdocs/src/js/config/projects.js +++ b/htdocs/src/js/config/projects.js @@ -2,7 +2,10 @@ const projects = [ { id: 'bin2dec', name: 'Bin2Dec', - description: 'Convert a binary string to a decimal number.', + description: { + en: 'Convert a binary string to a decimal number.', + fr: 'Convertit un nombre binaire en un nombre décimal.', + }, path: './projects/js-small-apps/bin2dec/index.html', repo: [ { @@ -19,8 +22,10 @@ const projects = [ { id: 'budget-app', name: 'Budget App', - description: - 'By selecting a language in the initialization form, only the currency is converted (the app is not translated). Also, no data is saved on reload.', + description: { + en: 'By selecting a language in the initialization form, only the currency is converted (the app is not translated). Also, no data is saved on page reload.', + fr: "En sélectionnant une langue dans le formulaire d'initialisation, seul le format des nombres change (l'application n'est pas traduite). Aucune donnée n'est conservée après rechargement de la page.", + }, path: './projects/js-small-apps/budget-app/index.html', repo: [ { @@ -37,7 +42,10 @@ const projects = [ { id: 'calculator', name: 'Calculator', - description: 'A basic calculator.', + description: { + en: 'A basic calculator.', + fr: 'Une simple calculette.', + }, path: './projects/js-small-apps/calculator/index.html', repo: [ { @@ -54,8 +62,10 @@ const projects = [ { id: 'clock', name: 'Clock', - description: - 'What time is it? You can have the current hour in three formats: an analogic clock, a numeric display or a text.', + description: { + en: 'What time is it? You can have the current hour in three formats: an analogic clock, a numeric display or a text.', + fr: "Quelle heure est-il ? Vous pouvez voir l'heure actuelle dans trois formats : une horloge analogique, un affichage numérique et sous forme de texte.", + }, path: './projects/js-small-apps/clock/index.html', repo: [ { @@ -72,8 +82,10 @@ const projects = [ { id: 'css-border-previewer', name: 'CSS Border Previewer', - description: - 'Play with CSS borders (style, width, border-radius). Then, you can copy the generated code if the preview suits you.', + description: { + en: 'Play with CSS borders (style, width, radius). Then, you can copy the generated code if the preview suits you.', + fr: "Jouez avec les bordures CSS (style, largeur, radius). Puis, vous pouvez copier le code généré si l'aperçu vous satisfait.", + }, path: './projects/js-small-apps/css-border-previewer/index.html', repo: [ { @@ -90,8 +102,10 @@ const projects = [ { id: 'rps-game', name: 'Rock Paper Scissors', - description: - 'A basic implementation of the game. Try to beat your friend or the computer.', + description: { + en: 'A basic implementation of the game. Try to beat your friend or the computer.', + fr: "Une implémentation du jeu. Essayez de battre votre ami ou l'ordinateur.", + }, path: './projects/js-small-apps/rock-paper-scissors/index.html', repo: [ { diff --git a/htdocs/src/js/i18n/i18n.js b/htdocs/src/js/i18n/i18n.js new file mode 100644 index 0000000..6bdc7cd --- /dev/null +++ b/htdocs/src/js/i18n/i18n.js @@ -0,0 +1,42 @@ +import I18n from 'i18n-js'; +import en from './locales/en'; +import fr from './locales/fr'; + +const supportedLanguages = [ + { + code: 'en', + label: 'English', + translations: en, + }, + { + code: 'fr', + label: 'Français', + translations: fr, + }, +]; + +supportedLanguages.forEach((locale) => { + I18n.translations[locale.code] = locale.translations; +}); + +function setLocale(locale) { + I18n.locale = locale; +} + +function currentLocale() { + return I18n.currentLocale(); +} + +function translate(name, params = {}) { + return I18n.t(name, params); +} + +const { defaultLocale } = I18n; + +export { + supportedLanguages, + setLocale, + translate, + defaultLocale, + currentLocale, +}; diff --git a/htdocs/src/js/i18n/locales/en.js b/htdocs/src/js/i18n/locales/en.js new file mode 100644 index 0000000..d8fea40 --- /dev/null +++ b/htdocs/src/js/i18n/locales/en.js @@ -0,0 +1,24 @@ +const en = { + branding: { + description: 'Front-end developer', + }, + nav: { + title: 'Apps list:', + }, + main: { + instructions: + 'Select an app inside menu to see a live preview and app details.', + project: { + details: { + about: 'About {{name}}', + repo: 'Repositories:', + tech: 'Technologies:', + }, + }, + }, + footer: { + license: 'License MIT', + }, +}; + +export default en; diff --git a/htdocs/src/js/i18n/locales/fr.js b/htdocs/src/js/i18n/locales/fr.js new file mode 100644 index 0000000..f44d459 --- /dev/null +++ b/htdocs/src/js/i18n/locales/fr.js @@ -0,0 +1,24 @@ +const fr = { + branding: { + description: 'Intégrateur web', + }, + nav: { + title: 'Liste des applications :', + }, + main: { + instructions: + "Sélectionnez une application dans le menu pour afficher un aperçu en direct et les informations sur l'application.", + project: { + details: { + about: 'À propos de {{name}}', + repo: 'Dépôts :', + tech: 'Technologies :', + }, + }, + }, + footer: { + license: 'Licence MIT', + }, +}; + +export default fr; diff --git a/package.json b/package.json index f1dad95..fa680a2 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@babel/runtime": "^7.15.4", "dotenv": "^10.0.0", "dotenv-expand": "^5.1.0", + "i18n-js": "^3.8.0", "modern-normalize": "^1.1.0" } } @@ -1202,9 +1202,9 @@ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*": - version "16.11.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.5.tgz#e91be5ba4ab88c06095e7b61f9ad1767a1093faf" - integrity sha512-NyUV2DGcqYIx9op++MG2+Z4Nhw1tPhi0Wfs81TgncuX1aJC4zf2fgCJlJhl4BW9bCSS04e34VkqmOS96w0XQdg== + version "16.11.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" + integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2929,9 +2929,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.878: - version "1.3.878" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.878.tgz#baa9fb5c24b9b580f08fb245cbb52a22f8fc8fa8" - integrity sha512-O6yxWCN9ph2AdspAIszBnd9v8s11hQx8ub9w4UGApzmNRnoKhbulOWqbO8THEQec/aEHtvy+donHZMlh6l1rbA== + version "1.3.879" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.879.tgz#4aba9700cfb241fb95c6ed69e31785e3d1605a43" + integrity sha512-zJo+D9GwbJvM31IdFmwcGvychhk4KKbKYo2GWlsn+C/dxz2NwmbhGJjWwTfFSF2+eFH7VvfA8MCZ8SOqTrlnpw== emoji-regex@^8.0.0: version "8.0.0" @@ -4150,6 +4150,11 @@ husky@^7.0.2: resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== +i18n-js@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-3.8.0.tgz#b8fd6b12e1d88cb71f9806c29bca7c31c012e504" + integrity sha512-hDsGgPuvw/2P+lXSbOafAwspK8Ste8YrwuuUg17W3wEcO1JkQxBlPgsN1t2+852nTnz4YSYTjZc/1nAA2PC/nw== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -4881,9 +4886,9 @@ lines-and-columns@^1.1.6: integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= lint-staged@^11.2.3: - version "11.2.4" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.2.4.tgz#29bc84e0318e6e9d4a7a043513236d066e39ee1d" - integrity sha512-aTUqcPDSV05EyKlMT4N5h7tmnevKfAxI3xZkRb+DHfmcFaoCxfxVvpqlLMCVGy3EYle9JYER2nA5zc4eNTkZVQ== + version "11.2.5" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.2.5.tgz#afa4c4740fbf874fab1531a846f14504e448e828" + integrity sha512-bD6FeKyRtMA3jXcNg/J0fuJmwDN8yaoTdqliFcEl8pFTHiP9NYwQCeZwBoZ8FEXx4lj/pli3oFehr86XYvMNEA== dependencies: cli-truncate "2.1.0" colorette "^1.4.0" |
