feat: SEO, accessibility

This commit is contained in:
xdrm 2024-09-24 23:48:02 +02:00
parent e1aa5b948a
commit 71ff315897
Signed by: xdrm-brackets
GPG Key ID: A942057C1730391F
14 changed files with 488 additions and 395 deletions

View File

@ -9,10 +9,12 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro&display=swap" as="style"> <link rel="preload" href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro|Fira%20Code&display=swap" as="style">
<link rel="stylesheet"href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro&display=swap" media="all"> <link rel="stylesheet"href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro|Fira%20Code&display=swap" media="all">
<title>xdrm()</title> <title>xdrm()</title>
<meta name="description" content="Portfolio d'Adrien Marquès, développeur freelance spécialisé en Go et IoT">
<meta name="keywords" content="développeur freelance, Go, IoT, web development">
</head> </head>
<body> <body>
<noscript> <noscript>

View File

@ -15,6 +15,10 @@ html, body{
overflow: hidden; overflow: hidden;
} }
button {
border: none;
}
body { body {
display: block; display: block;
position: absolute; position: absolute;

View File

@ -14,7 +14,6 @@ import Home from './components/Home.vue';
import Timeline from './components/Timeline.vue'; import Timeline from './components/Timeline.vue';
import SkillPicker from './components/SkillPicker.vue'; import SkillPicker from './components/SkillPicker.vue';
import Footer from './components/Footer.vue'; import Footer from './components/Footer.vue';
import { Locales } from './locales';
@Component({ @Component({
@ -26,8 +25,6 @@ import { Locales } from './locales';
}, },
}) })
export default class App extends Vue { export default class App extends Vue {
private selected: tID|null = null;
// skill picker selection -> filters the timeline // skill picker selection -> filters the timeline
protected onPick(id: tID|null) { protected onPick(id: tID|null) {
const timeline = this.$refs.timeline as Timeline; const timeline = this.$refs.timeline as Timeline;
@ -69,7 +66,7 @@ export default class App extends Vue {
font-size: 1rem; font-size: 1rem;
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro', sans-serif;
flex-flow: column nowrap; flex-flow: column nowrap;
overflow: hidden; overflow: hidden;
@ -86,7 +83,7 @@ export default class App extends Vue {
transition: color .2s ease-in-out; transition: color .2s ease-in-out;
&:after { &::after {
content: ''; content: '';
display: block; display: block;
@ -105,8 +102,8 @@ export default class App extends Vue {
&:hover{ &:hover{
color: #fff; color: #fff;
&:after { &::after {
width: 90%; width: 104%;
} }
} }
} }

View File

@ -1,26 +1,24 @@
<template> <template>
<div id='footer'> <footer id='footer'>
<div class='wave' aria-hidden="true"></div>
<div class='wave w2' aria-hidden="true"></div>
<div class='wave'></div> <div class="footer-content">
<div class='wave w2'></div>
<footer>
<div class='copyright'> <div class='copyright'>
2022 © xdrm-brackets <p>&copy; {{ currentYear }} xdrm-brackets</p>
<span>Adrien Marquès</span> <p>Adrien Marquès</p>
</div> </div>
<img class='logo' src='../assets/home/logo.svg'/> <img class='logo' src='../assets/home/logo.svg' alt='Logo Adrien Marquès' width="64" height="64"/>
<div class='contact'> <address class='contact'>
<span>xdrm.io</span> <p>xdrm.io</p>
<span>(+33) 06 69 05 19 10</span> <p><a href="tel:+33669051910">(+33) 06 69 05 19 10</a></p>
<span>xdrm.dev@gmail.com</span> <p><a href="mailto:xdrm.dev@gmail.com">xdrm.dev@gmail.com</a></p>
<span>Montauban, 82000</span> <p>Montauban, 82000</p>
</address>
</div> </div>
</footer> </footer>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -30,7 +28,9 @@ import { Component, Vue } from 'vue-property-decorator';
components: {}, components: {},
}) })
export default class Footer extends Vue { export default class Footer extends Vue {
get currentYear() {
return new Date().getFullYear();
}
} }
</script> </script>
@ -48,26 +48,45 @@ export default class Footer extends Vue {
background: lighten(#202228, 15%); background: lighten(#202228, 15%);
} }
footer { a {
color: inherit;
text-decoration: none;
&:hover, &:focus {
text-decoration: underline;
}
&::after {
display: none;
}
}
.footer-content {
display: flex; display: flex;
position: relative; position: relative;
margin-top: $wave-height; margin-top: $wave-height;
min-height: 20vh; min-height: 20vh;
color: #bcc6ce; color: #bcc6ce;
flex-flow: row wrap; flex-flow: row wrap;
justify-content: space-around; justify-content: space-around;
align-items: center; align-items: center;
font-family: 'Fira Code'; font-family: 'Fira Code';
}
.copyright, .contact { .copyright, .contact {
font-size: 1.3em; font-size: 1.3em;
text-align: center; text-align: center;
span { p {
display: block; margin: 0.5em 0;
}
}
.contact a {
color: inherit;
text-decoration: none;
&:hover, &:focus {
text-decoration: underline;
} }
} }
@ -79,7 +98,6 @@ export default class Footer extends Vue {
width: $logo-size; width: $logo-size;
height: $logo-size; height: $logo-size;
} }
}
@keyframes wave-x { @keyframes wave-x {
from{ background-position-x: 1px; } from{ background-position-x: 1px; }
@ -109,5 +127,4 @@ export default class Footer extends Vue {
z-index: 200; z-index: 200;
} }
</style> </style>

View File

@ -1,36 +1,34 @@
<template> <template>
<div id='home'> <main id='home'>
<LangPicker/>
<img class='logo' src='../assets/home/logo.svg' alt="Logo Adrien Marquès" width="128" height="128"/>
<LangPicker /> <header class='name'>
<img class='logo' src='../assets/home/logo.svg'/>
<section class='name'>
<h1>Adrien<br>Marquès</h1> <h1>Adrien<br>Marquès</h1>
<br> <p class="job-title">{{ $t('home.title') }}</p>
<br> </header>
<h2>{{ $t('home.title') }}</h2>
</section>
<div class='separator'></div> <div class='separator' aria-hidden="true"></div>
<section class='readme' ref='test'> <section class='readme' ref='test'>
<div class='header'>README.md</div> <h2 class='header'>README.md</h2>
<div class="readme-content">
<p ref='text1'>{{ $t('home.line1') }}</p> <p ref='text1'>{{ $t('home.line1') }}</p>
<p ref='text2'>{{ $t('home.line2-1') }} <u>{{ $t('home.line2-2') }}</u> {{$t('home.line2-3') }}</p> <p ref='text2'>{{ $t('home.line2-1') }} <span class="highlight">{{ $t('home.line2-2') }}</span> {{$t('home.line2-3') }}</p>
<p ref='text3'>{{ $t('home.line3') }}</p> <p ref='text3'>{{ $t('home.line3') }}</p>
<p ref='text4'>{{ $t('home.line4') }}</p> <p ref='text4'>{{ $t('home.line4') }}</p>
<p ref='text5'>{{ $t('home.line5-1') }} <u>Go</u> {{ $t('home.line5-2') }} <u>IoT</u> {{ $t('home.line5-3') }}</p> <p ref='text5'>{{ $t('home.line5-1') }} <span class="highlight">Go</span> {{ $t('home.line5-2') }} <span class="highlight">IoT</span> {{ $t('home.line5-3') }}</p>
</div>
</section> </section>
<div class='scroll-arrow' @click='scrollNext()'> <button class='scroll-arrow' @click='scrollNext()' aria-label="Défiler vers la section suivante">
<img src='@/assets/scroll_arrow.svg' /> <img src='@/assets/scroll_arrow.svg' alt="" aria-hidden="true" width="40" height="40"/>
</div> </button>
<div class='wave'></div> <div class='wave' aria-hidden="true"></div>
<div class='wave w2'></div> <div class='wave w2' aria-hidden="true"></div>
</div> </main>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -38,7 +36,6 @@ import { Component, Vue } from 'vue-property-decorator';
import { go } from '@/service/scroller'; import { go } from '@/service/scroller';
import { TypeWriter } from '@/service/typewriter'; import { TypeWriter } from '@/service/typewriter';
import LangPicker from './LangPicker.vue'; import LangPicker from './LangPicker.vue';
LangPicker;
@Component({ @Component({
components: { LangPicker }, components: { LangPicker },
@ -120,6 +117,7 @@ export default class Home extends Vue {
$scroll-btn-size: 5rem; $scroll-btn-size: 5rem;
$wave-height: 6.3rem; $wave-height: 6.3rem;
$primary-color: #6553d0;
#home { #home {
display: grid; display: grid;
@ -176,7 +174,7 @@ export default class Home extends Vue {
text-align: right; text-align: right;
h1 { font-size: 7em; font-weight: 800; line-height: 1em; } h1 { font-size: 7em; font-weight: 800; line-height: 1em; }
h2 { font-size: 2.5em; font-weight: 400; white-space: nowrap; } p { font-size: 2.5em; font-weight: 400; white-space: nowrap; }
} }
.separator { .separator {
@ -228,6 +226,12 @@ export default class Home extends Vue {
border-bottom: .15rem solid #323841; border-bottom: .15rem solid #323841;
} }
h2 {
font-size: 1em;
// ... autres styles existants ...
}
.readme-content {
p { p {
// typewriter animation changes it // typewriter animation changes it
display: none; display: none;
@ -241,38 +245,34 @@ export default class Home extends Vue {
margin-left: calc( 1em + 1.2em ); margin-left: calc( 1em + 1.2em );
text-indent: -1.2em; text-indent: -1.2em;
u { .highlight {
display: inline; display: inline;
position: relative; position: relative;
text-decoration: none; text-decoration: none;
&:after { &::after {
content: ''; content: '';
display: inline-block; display: inline-block;
position: absolute; position: absolute;
top: 60%; top: 60%;
left: -.2em; left: -.2em;
width: calc( 100% + 2*.2em ); width: calc( 100% + 2*.2em );
height: 30%; height: 30%;
background: darken($primary-color, 10%);
background: #6553d0;
z-index: -1; z-index: -1;
} }
z-index: 10; z-index: 1;
} }
&:before { &::before {
content: '> '; content: '> ';
color: #6553d0; color: $primary-color;
font-weight: 700; font-weight: 700;
} }
} }
}
} }

View File

@ -1,11 +1,13 @@
<template> <template>
<div class='lang-picker'> <div class='lang-picker' role="region" aria-label="fr-FR: Sélection de la langue, en-US: Language selection">
<div @click='current = locales[0]' :data-active='current == locales[0]'> <button @click='setLanguage(locales[0])' :aria-pressed='current === locales[0]'>
<img src='../assets/lang/en-US.svg'/> <img src='../assets/lang/en-US.svg' alt="English"/>
</div> <span class="visually-hidden">English</span>
<div @click='current = locales[1]' :data-active='current == locales[1]'> </button>
<img src='../assets/lang/fr-FR.svg'/> <button @click='setLanguage(locales[1])' :aria-pressed='current === locales[1]'>
</div> <img src='../assets/lang/fr-FR.svg' alt="Français"/>
<span class="visually-hidden">Français</span>
</button>
</div> </div>
</template> </template>
@ -20,28 +22,42 @@ export default class LangPicker extends Vue {
get current(): string { get current(): string {
return this.$i18n.locale; return this.$i18n.locale;
} }
set current(l: string) {
localStorage.setItem('lang', l); private setLanguage(lang: string) {
this.$i18n.locale = l; if (this.current !== lang) {
localStorage.setItem('lang', lang);
this.$i18n.locale = lang;
this.$nextTick(() => {
this.announceLanguageChange(lang);
});
}
}
private announceLanguageChange(lang: string) {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite');
if (lang === Locales.EN) {
announcement.textContent = `Language changed to ${lang === Locales.EN ? 'English' : 'French'}`;
} else {
announcement.textContent = `La langue a été changée en ${lang === Locales.EN ? 'anglais' : 'français'}`;
}
document.body.appendChild(announcement);
setTimeout(() => {
document.body.removeChild(announcement);
}, 1000);
} }
private mounted() { private mounted() {
const ls = localStorage.getItem('lang') as Locales|null; const ls = localStorage.getItem('lang') as Locales|null;
// already selected a language, stay with it if (ls != null && this.locales.indexOf(ls) >= 0) {
if ( ls != null && this.locales.indexOf(ls) >= 0 ) { this.setLanguage(ls);
this.current = ls; } else if (navigator.language.indexOf('fr') > -1) {
return; this.setLanguage(Locales.FR);
}
// otherwise, go with the navigator's default
if ( navigator.language.indexOf('fr') > -1 ) {
this.current = Locales.FR;
} else { } else {
this.current = Locales.EN; this.setLanguage(Locales.EN);
} }
} }
} }
</script> </script>
@ -54,13 +70,14 @@ export default class LangPicker extends Vue {
flex-flow: row nowrap; flex-flow: row nowrap;
div { button {
flex: 2em; flex: 2em;
display: block; display: block;
padding: .25em; padding: .25em;
cursor: pointer; cursor: pointer;
background: none;
transform-style: preserve-3d; transform-style: preserve-3d;
transition: filter .1s ease-in-out; transition: filter .1s ease-in-out;
@ -74,6 +91,10 @@ export default class LangPicker extends Vue {
&:not([data-active=true]):hover { &:not([data-active=true]):hover {
filter: grayscale(40%); filter: grayscale(40%);
} }
&:focus {
filter: grayscale(0);
}
} }
img { img {
@ -81,4 +102,16 @@ export default class LangPicker extends Vue {
} }
} }
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style> </style>

View File

@ -1,10 +1,10 @@
<template> <template>
<div class='level' > <div class='level' :aria-label="$t('skills.level-display', { level: level })">
<div ref='l1' class='l1'/> <div ref='l1' class='l1' :aria-hidden="true"/>
<div ref='l2' class='l2'/> <div ref='l2' class='l2' :aria-hidden="true"/>
<div ref='l3' class='l3'/> <div ref='l3' class='l3' :aria-hidden="true"/>
<div ref='l4' class='l4'/> <div ref='l4' class='l4' :aria-hidden="true"/>
<div ref='l5' class='l5'/> <div ref='l5' class='l5' :aria-hidden="true"/>
</div> </div>
</template> </template>

View File

@ -1,6 +1,17 @@
<template> <template>
<div class='skill-card' ref='root' :data-active='active ? "1" : "0"' @click='onClick()' :title='name()'> <div
<img class='icon' :src='icon()' /> class='skill-card'
ref='root'
:data-active='active ? "1" : "0"'
@click='onClick()'
:title='name()'
role="button"
:aria-pressed='active'
:tabindex="0"
@keydown.enter='onClick()'
@keydown.space='onClick()'
>
<img class='icon' :src='icon()' :alt="$t('skills.icon-alt', { skill: name() })"/>
<span class='name'> <span class='name'>
<span v-html='name()'></span> <span v-html='name()'></span>
</span> </span>

View File

@ -1,58 +1,56 @@
<template> <template>
<div id='skill-picker'> <section id='skill-picker' aria-labelledby="skill-picker-title">
<h2 id="skill-picker-title" class="visually-hidden">{{ $t('skills.title') }}</h2>
<div class='wrapper'> <div class='wrapper'>
<div class='container'> <div class='container'>
<nav class='categories' aria-label="Catégories de compétences">
<section class='categories'>
<SkillCard v-for='(t) of tags' <SkillCard v-for='(t) of tags'
:key='t' :key='t'
:active='t == tag' :active='t == tag'
:folder='$t(tagLabel(t))' :folder='$t(tagLabel(t))'
@pick='onTag(t, $event)'/> @pick='onTag(t, $event)'/>
</section> </nav>
<section class='skills'> <div class='skills' role="list" aria-label="Liste des compétences">
<SkillCard v-for='(id) of ids' <SkillCard v-for='(id) of ids'
:key='id' :key='id'
v-show='filtered.indexOf(id) >= 0' v-show='filtered.indexOf(id) >= 0'
:id='id' :id='id'
:active='id == sel' :active='id == sel'
@pick='onPick(id, $event)'/> @pick='onPick(id, $event)'
</section> role="listitem"/>
</div>
<section class='details' v-if='details != null'> <section class='details' v-if='details != null' aria-live="polite">
<header> <header>
<span class='interest'>{{ $t('skills.interest') }}<LevelDisplay :level='details.interest'/></span> <span class='interest'>{{ $t('skills.interest') }}<LevelDisplay :level='details.interest'/></span>
<img :src='details.icon'/> <img :src='details.icon' :alt="$t('skills.icon-alt', { skill: details.title })"/>
<span class='mastery'>{{ $t('skills.mastery') }}<LevelDisplay :level='details.mastery'/></span> <span class='mastery'>{{ $t('skills.mastery') }}<LevelDisplay :level='details.mastery'/></span>
</header> </header>
<h1 v-html='details.title'></h1> <h3>{{ details.title }}</h3>
<h2>{{ $t('skills.featured-before') }} <b>{{ details.projects.length }}</b> {{ details.projects.length > 1 ? $t('skills.featured-after-n') : $t('skills.featured-after-1') }}</h2> <h3>{{ $t('skills.featured-before') }} <b class="primary-color">{{ details.projects.length }}</b> {{ details.projects.length > 1 ? $t('skills.featured-after-n') : $t('skills.featured-after-1') }}</h3>
<h3> <p class="project-links">
<template v-for='(proj) of details.projects'> <template v-for='(proj, index) of details.projects'>
<a :key='"pick-" + proj.name' :href='"#" + sanitize(proj.name)' @click='$event.preventDefault(); scroll(sanitize(proj.name));'> <a :key='"pick-" + proj.name' :href='"#" + sanitize(proj.name)' @click.prevent='scroll(sanitize(proj.name))'>
{{ proj.name }} {{ proj.name }}
</a> </a><span v-if="index < details.projects.length - 1" :key='"separator-" + index'>, </span>
<span :key='proj.name'>, </span>
</template> </template>
</h3> </p>
<p v-html='details.text[$i18n.locale]'></p> <p v-html='details.text[$i18n.locale]'></p>
</section> </section>
<section class='guide' v-else> <section class='guide' v-else aria-live="polite">
<p v-html='$t("skills.guide")'></p> <p v-html='$t("skills.guide")'></p>
</section> </section>
</div> </div>
<input type='button' v-show='this.sel != null' :value="$t('skills.browse')" @click='browse()'/> <button v-if='this.sel != null' @click='browse()'>{{ $t('skills.browse') }}</button>
<input type='button' v-show='this.sel == null' :value="$t('skills.browse-all')" @click='browse()'/> <button v-else @click='browse()'>{{ $t('skills.browse-all') }}</button>
</div> </div>
<div class='spacer'></div> <div class='spacer' aria-hidden="true"></div>
</section>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -210,6 +208,8 @@ export default class SkillPicker extends Vue {
$bottom-spacer: 15vh; $bottom-spacer: 15vh;
$bottom-margin: 10vh; $bottom-margin: 10vh;
$primary-color: #745cfc;
#skill-picker { #skill-picker {
display: block; display: block;
position: relative; position: relative;
@ -377,9 +377,11 @@ export default class SkillPicker extends Vue {
margin-top: 2em; margin-top: 2em;
margin-bottom: 4rem; margin-bottom: 4rem;
font-size: 1.7em; font-size: 1.5em;
font-weight: 500; font-weight: 500;
text-align: justify; text-align: justify;
line-height: 1.5;
color: #c1c1c1; color: #c1c1c1;
padding: 1em; padding: 1em;
@ -391,6 +393,21 @@ export default class SkillPicker extends Vue {
overflow-y: auto; overflow-y: auto;
} }
.project-links {
display: inline;
font-size: 1.3em;
color: #616c7c;
background: none;
border: none;
padding: 1em;
margin: 0 2em;
}
.primary-color {
color: $primary-color;
}
} }
@ -427,7 +444,7 @@ export default class SkillPicker extends Vue {
} }
} }
input { input, button {
display: block; display: block;
position: absolute; position: absolute;
top: calc( 100% - #{$page-margin} ); top: calc( 100% - #{$page-margin} );
@ -482,4 +499,16 @@ export default class SkillPicker extends Vue {
} }
} }
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style> </style>

View File

@ -1,117 +0,0 @@
{
"home.title": "FREELANCE DEVELOPER",
"home.line1": "Hi, I am another freelance developer !",
"home.line2-1": "I've created this website so you can",
"home.line2-2": "browse",
"home.line2-3": "my skills and projects.",
"home.line3": "I started dev back in 2010. I now have worked on more than 50 projects.",
"home.line4": "I work hard to provide my clients with maintainable projects so that they can evolve at low cost (economic and ecological).",
"home.line5-1": "My top technologies at the moment are",
"home.line5-2": "and",
"home.line5-3": "(Arduino, Raspberry).",
"timeline.title": "Timeline of projects featuring",
"timeline.title-all": "Timeline of all projects",
"timeline.back": "Change skill",
"skills.featured-before": "Featured in",
"skills.featured-after-1": "project",
"skills.featured-after-n": "projects",
"skills.browse": "Browse projects",
"skills.browse-all": "Browse all projects",
"skills.guide": "You can select a skill to browse related projects. You can browse all projects by not selecting or deselecting the active skill.",
"skills.interest": "interest level",
"skills.mastery": "mastery level",
"tag.all": "All",
"tag.web": "Web",
"tag.storage": "Storage",
"tag.ui": "UI/UX",
"tag.system": "System",
"tag.mobile": "Mobile",
"tag.network": "Network",
"tag.iot": "IoT",
"tag.organization": "Organization",
"tag.language": "Language",
"tag.human": "Human",
"tag.other": "Other",
"skill.mariadb": "MariaDB",
"skill.postgres": "PostgreSQL",
"skill.mongo": "MongoDB",
"skill.vue": "Vue <i>(.js)</i>",
"skill.angular": "Angular <i>(7+)</i>",
"skill.parcel": "Parcel",
"skill.cordova": "Apache Cordova",
"skill.webpack": "Webpack",
"skill.webgl": "WebGL",
"skill.audioapi": "Audio API",
"skill.websocket": "Websocket",
"skill.docker": "Docker",
"skill.bash": "bash",
"skill.linux": "GNU/Linux",
"skill.systemd": "systemd",
"skill.git": "Git",
"skill.rpm": "RPM packaging",
"skill.raspberry": "Raspberry",
"skill.arduino": "Arduino",
"skill.php": "PHP",
"skill.html": "HTML5",
"skill.css": "CSS3",
"skill.js": "Javascript",
"skill.ajax": "AJAX",
"skill.ts": "Typescript",
"skill.c": "C (lang)",
"skill.cpp": "C++",
"skill.python": "Python",
"skill.go": "Go (lang)",
"skill.qt": "Qt",
"skill.opensource": "Open-source",
"skill.electronics": "Electronics",
"skill.web": "Web",
"skill.rest": "REST",
"skill.crypto": "Security/crypto",
"skill.imageprocessing": "Image processing",
"skill.ai": "Artificial Intelligence",
"skill.deeplearning": "Deep Learning",
"skill.neuralnetwork": "Neural Networks",
"skill.opti": "Program optimization",
"skill.sockets": "Sockets",
"skill.concurrency": "Concurrency",
"skill.uiux": "UI/UX",
"skill.inkscape": "Inkscape",
"skill.rnd": "R&D",
"skill.teamlead": "Team Lead",
"skill.needsanalysis": "Needs analysis",
"skill.grpc": "gRPC",
"skill.microservices": "Micro services",
"skill.kubernetes": "Kubernetes",
"skill.scrum": "SCRUM",
"skill.lora": "LoRa",
"skill.architecture": "Architecture",
"skill.chirpstack": "ChirpStack",
"skill.refactor": "Refactoring",
"skill.timescale": "Timescale",
"skill.atlassian": "Atlassian",
"time.dur-format": "took {duration}",
"time.cur-format": "since {duration}",
"time.diff-format": "{elapsed} ago",
"time.some": "sometime",
"time.second": "{n} second", "time.seconds": "{n} seconds",
"time.minute": "{n} minute", "time.minutes": "{n} minutes",
"time.hour": "{n} hour", "time.hours": "{n} hours",
"time.day": "{n} day", "time.days": "{n} days",
"time.month": "{n} month", "time.months": "{n} months",
"time.year": "{n} year", "time.years": "{n} years",
"project.created": "Created",
"project.sources": "Hosted at",
"project.doc": "Documentation at",
"project.end": "Project stopped in",
"project.still": "Project still active",
"project.username": "Adrien Marquès",
"end": ""
}

117
src/locales/en.ts Normal file
View File

@ -0,0 +1,117 @@
export default {
'home.title': 'FREELANCE DEVELOPER',
'home.line1': 'Hi, I am another freelance developer !',
'home.line2-1': 'I\'ve created this website so you can',
'home.line2-2': 'browse',
'home.line2-3': 'my skills and projects.',
'home.line3': 'I started dev back in 2010. I now have worked on more than 50 projects.',
'home.line4': 'I work hard to provide my clients with maintainable projects so that they can evolve at low cost (economic and ecological).',
'home.line5-1': 'My top technologies at the moment are',
'home.line5-2': 'and',
'home.line5-3': '(Arduino, Raspberry).',
'timeline.title': 'Timeline of projects featuring',
'timeline.title-all': 'Timeline of all projects',
'timeline.back': 'Change skill',
'skills.featured-before': 'Featured in',
'skills.featured-after-1': 'project',
'skills.featured-after-n': 'projects',
'skills.browse': 'Browse projects',
'skills.browse-all': 'Browse all projects',
'skills.guide': 'You can select a skill to browse related projects. You can browse all projects by not selecting or deselecting the active skill.',
'skills.interest': 'interest level',
'skills.mastery': 'mastery level',
'tag.all': 'All',
'tag.web': 'Web',
'tag.storage': 'Storage',
'tag.ui': 'UI/UX',
'tag.system': 'System',
'tag.mobile': 'Mobile',
'tag.network': 'Network',
'tag.iot': 'IoT',
'tag.organization': 'Organization',
'tag.language': 'Language',
'tag.human': 'Human',
'tag.other': 'Other',
'skill.mariadb': 'MariaDB',
'skill.postgres': 'PostgreSQL',
'skill.mongo': 'MongoDB',
'skill.vue': 'Vue <i>(.js)</i>',
'skill.angular': 'Angular <i>(7+)</i>',
'skill.parcel': 'Parcel',
'skill.cordova': 'Apache Cordova',
'skill.webpack': 'Webpack',
'skill.webgl': 'WebGL',
'skill.audioapi': 'Audio API',
'skill.websocket': 'Websocket',
'skill.docker': 'Docker',
'skill.bash': 'bash',
'skill.linux': 'GNU/Linux',
'skill.systemd': 'systemd',
'skill.git': 'Git',
'skill.rpm': 'RPM packaging',
'skill.raspberry': 'Raspberry',
'skill.arduino': 'Arduino',
'skill.php': 'PHP',
'skill.html': 'HTML5',
'skill.css': 'CSS3',
'skill.js': 'Javascript',
'skill.ajax': 'AJAX',
'skill.ts': 'Typescript',
'skill.c': 'C (lang)',
'skill.cpp': 'C++',
'skill.python': 'Python',
'skill.go': 'Go (lang)',
'skill.qt': 'Qt',
'skill.opensource': 'Open-source',
'skill.electronics': 'Electronics',
'skill.web': 'Web',
'skill.rest': 'REST',
'skill.crypto': 'Security/crypto',
'skill.imageprocessing': 'Image processing',
'skill.ai': 'Artificial Intelligence',
'skill.deeplearning': 'Deep Learning',
'skill.neuralnetwork': 'Neural Networks',
'skill.opti': 'Program optimization',
'skill.sockets': 'Sockets',
'skill.concurrency': 'Concurrency',
'skill.uiux': 'UI/UX',
'skill.inkscape': 'Inkscape',
'skill.rnd': 'R&D',
'skill.teamlead': 'Team Lead',
'skill.needsanalysis': 'Needs analysis',
'skill.grpc': 'gRPC',
'skill.microservices': 'Micro services',
'skill.kubernetes': 'Kubernetes',
'skill.scrum': 'SCRUM',
'skill.lora': 'LoRa',
'skill.architecture': 'Architecture',
'skill.chirpstack': 'ChirpStack',
'skill.refactor': 'Refactoring',
'skill.timescale': 'Timescale',
'skill.atlassian': 'Atlassian',
'time.dur-format': 'took {duration}',
'time.cur-format': 'since {duration}',
'time.diff-format': '{elapsed} ago',
'time.some': 'sometime',
'time.second': '{n} second', 'time.seconds': '{n} seconds',
'time.minute': '{n} minute', 'time.minutes': '{n} minutes',
'time.hour': '{n} hour', 'time.hours': '{n} hours',
'time.day': '{n} day', 'time.days': '{n} days',
'time.month': '{n} month', 'time.months': '{n} months',
'time.year': '{n} year', 'time.years': '{n} years',
'project.created': 'Created',
'project.sources': 'Hosted at',
'project.doc': 'Documentation at',
'project.end': 'Project stopped in',
'project.still': 'Project still active',
'project.username': 'Adrien Marquès',
'end': ''
}

View File

@ -1,118 +0,0 @@
{
"home.title": "DÉVELOPPEUR FREELANCE",
"home.line1": "Hello, je suis développeur freelance !",
"home.line2-1": "J'ai développé ce site afin de vous permettre de",
"home.line2-2": "parcourir",
"home.line2-3": "mes compétences et projets.",
"home.line3": "J'ai démarré le dev en 2010. Aujourd'hui je compte plus de 50 projets à mon actif.",
"home.line4": "Je suis très attaché à fournir à mes clients des projets maintenables afin qu'ils puissent évoluer à moindre coût (économique et écologique).",
"home.line5-1": "Les technologies qui m'intéressent actuellement sont le",
"home.line5-2": "et l'",
"home.line5-3": "(Arduino, Raspberry).",
"timeline.title": "Chronologie des projets avec",
"timeline.title-all": "Chronologie de tous les projets",
"timeline.back": "Changer de compétence",
"skills.featured-before": "Apparaît dans",
"skills.featured-after-1": "projet",
"skills.featured-after-n": "projets",
"skills.browse": "Parcourir les projets",
"skills.browse-all": "Parcourir tous les projets",
"skills.guide": "Vous pouvez sélectionner une compétence afin de parcourir les projets liés. Vous pouvez aussi parcourir tous les projets en ne sélectionnant pas de compétence.",
"skills.interest": "intérêt",
"skills.mastery": "maîtrise",
"tag.all": "Tout",
"tag.web": "Web",
"tag.storage": "Stockage",
"tag.ui": "UI/UX",
"tag.system": "Système",
"tag.mobile": "Mobile",
"tag.network": "Réseau",
"tag.iot": "IoT",
"tag.organization": "Organisation",
"tag.language": "Langage",
"tag.human": "Humain",
"tag.other": "Autre",
"skill.mariadb": "MariaDB",
"skill.postgres": "PostgreSQL",
"skill.mongo": "MongoDB",
"skill.vue": "Vue <i>(.js)</i>",
"skill.angular": "Angular <i>(7+)</i>",
"skill.parcel": "Parcel",
"skill.cordova": "Apache Cordova",
"skill.webpack": "Webpack",
"skill.webgl": "WebGL",
"skill.audioapi": "Audio API",
"skill.websocket": "Websocket",
"skill.docker": "Docker",
"skill.bash": "bash",
"skill.linux": "GNU/Linux",
"skill.systemd": "systemd",
"skill.git": "Git",
"skill.rpm": "RPM packaging",
"skill.raspberry": "Raspberry",
"skill.arduino": "Arduino",
"skill.php": "PHP",
"skill.html": "HTML5",
"skill.css": "CSS3",
"skill.js": "Javascript",
"skill.ajax": "AJAX",
"skill.ts": "Typescript",
"skill.c": "C (lang)",
"skill.cpp": "C++",
"skill.python": "Python",
"skill.go": "Go (lang)",
"skill.qt": "Qt",
"skill.opensource": "Open-source",
"skill.electronics": "Electronique",
"skill.web": "Web",
"skill.rest": "REST",
"skill.crypto": "Securité/crypto",
"skill.imageprocessing": "Traitement d'image",
"skill.ai": "Intelligence Artificielle",
"skill.deeplearning": "Deep Learning",
"skill.neuralnetwork": "Réseaux de neuronnes",
"skill.opti": "Optimization",
"skill.sockets": "Sockets",
"skill.concurrency": "Programmation Concurrente",
"skill.uiux": "UI/UX",
"skill.inkscape": "Inkscape",
"skill.rnd": "R&D",
"skill.teamlead": "Chef d'équipe",
"skill.needsanalysis": "Analyse besoin client",
"skill.grpc": "gRPC",
"skill.microservices": "Micro services",
"skill.kubernetes": "Kubernetes",
"skill.scrum": "SCRUM",
"skill.lora": "LoRa",
"skill.architecture": "Architecture",
"skill.chirpstack": "ChirpStack",
"skill.refactor": "Refactor",
"skill.timescale": "Timescale",
"skill.atlassian": "Atlassian",
"time.dur-format": "a duré {duration}",
"time.cur-format": "depuis {duration}",
"time.diff-format": "il y a {elapsed}",
"time.some": "un moment",
"time.second": "{n} seconde", "time.seconds": "{n} secondes",
"time.minute": "{n} minute", "time.minutes": "{n} minutes",
"time.hour": "{n} heure", "time.hours": "{n} heures",
"time.day": "{n} jour", "time.days": "{n} jours",
"time.month": "{n} mois", "time.months": "{n} mois",
"time.year": "{n} an", "time.years": "{n} ans",
"project.created": "Création de",
"project.sources": "Sources:",
"project.doc": "Documentation:",
"project.end": "Projet terminé en",
"project.still": "Projet toujours actif",
"project.username": "Adrien Marquès",
"end": ""
}

118
src/locales/fr.ts Normal file
View File

@ -0,0 +1,118 @@
export default {
'home.title': 'DÉVELOPPEUR FREELANCE',
'home.line1': 'Hello, je suis développeur freelance !',
'home.line2-1': 'J\'ai développé ce site afin de vous permettre de',
'home.line2-2': 'parcourir',
'home.line2-3': 'mes compétences et projets.',
'home.line3': 'J\'ai démarré le dev en 2010. Aujourd\'hui je compte plus de 50 projets à mon actif.',
'home.line4': 'Je suis très attaché à fournir à mes clients des projets maintenables afin qu\'ils puissent évoluer à moindre coût (économique et écologique).',
'home.line5-1': 'Les technologies qui m\'intéressent actuellement sont le',
'home.line5-2': 'et l\'',
'home.line5-3': '(Arduino, Raspberry).',
'timeline.title': 'Chronologie des projets avec',
'timeline.title-all': 'Chronologie de tous les projets',
'timeline.back': 'Changer de compétence',
'skills.featured-before': 'Apparaît dans',
'skills.featured-after-1': 'projet',
'skills.featured-after-n': 'projets',
'skills.browse': 'Parcourir les projets',
'skills.browse-all': 'Parcourir tous les projets',
'skills.guide': 'Vous pouvez sélectionner une compétence afin de parcourir les projets liés. Vous pouvez aussi parcourir tous les projets en ne sélectionnant pas de compétence.',
'skills.interest': 'intérêt',
'skills.mastery': 'maîtrise',
'tag.all': 'Tout',
'tag.web': 'Web',
'tag.storage': 'Stockage',
'tag.ui': 'UI/UX',
'tag.system': 'Système',
'tag.mobile': 'Mobile',
'tag.network': 'Réseau',
'tag.iot': 'IoT',
'tag.organization': 'Organisation',
'tag.language': 'Langage',
'tag.human': 'Humain',
'tag.other': 'Autre',
'skill.mariadb': 'MariaDB',
'skill.postgres': 'PostgreSQL',
'skill.mongo': 'MongoDB',
'skill.vue': 'Vue <i>(.js)</i>',
'skill.angular': 'Angular <i>(7+)</i>',
'skill.parcel': 'Parcel',
'skill.cordova': 'Apache Cordova',
'skill.webpack': 'Webpack',
'skill.webgl': 'WebGL',
'skill.audioapi': 'Audio API',
'skill.websocket': 'Websocket',
'skill.docker': 'Docker',
'skill.bash': 'bash',
'skill.linux': 'GNU/Linux',
'skill.systemd': 'systemd',
'skill.git': 'Git',
'skill.rpm': 'RPM packaging',
'skill.raspberry': 'Raspberry',
'skill.arduino': 'Arduino',
'skill.php': 'PHP',
'skill.html': 'HTML5',
'skill.css': 'CSS3',
'skill.js': 'Javascript',
'skill.ajax': 'AJAX',
'skill.ts': 'Typescript',
'skill.c': 'C (lang)',
'skill.cpp': 'C++',
'skill.python': 'Python',
'skill.go': 'Go (lang)',
'skill.qt': 'Qt',
'skill.opensource': 'Open-source',
'skill.electronics': 'Electronique',
'skill.web': 'Web',
'skill.rest': 'REST',
'skill.crypto': 'Securité/crypto',
'skill.imageprocessing': 'Traitement d\'image',
'skill.ai': 'Intelligence Artificielle',
'skill.deeplearning': 'Deep Learning',
'skill.neuralnetwork': 'Réseaux de neuronnes',
'skill.opti': 'Optimization',
'skill.sockets': 'Sockets',
'skill.concurrency': 'Programmation Concurrente',
'skill.uiux': 'UI/UX',
'skill.inkscape': 'Inkscape',
'skill.rnd': 'R&D',
'skill.teamlead': 'Chef d\'équipe',
'skill.needsanalysis': 'Analyse besoin client',
'skill.grpc': 'gRPC',
'skill.microservices': 'Micro services',
'skill.kubernetes': 'Kubernetes',
'skill.scrum': 'SCRUM',
'skill.lora': 'LoRa',
'skill.architecture': 'Architecture',
'skill.chirpstack': 'ChirpStack',
'skill.refactor': 'Refactor',
'skill.timescale': 'Timescale',
'skill.atlassian': 'Atlassian',
'time.dur-format': 'a duré {duration}',
'time.cur-format': 'depuis {duration}',
'time.diff-format': 'il y a {elapsed}',
'time.some': 'un moment',
'time.second': '{n} seconde', 'time.seconds': '{n} secondes',
'time.minute': '{n} minute', 'time.minutes': '{n} minutes',
'time.hour': '{n} heure', 'time.hours': '{n} heures',
'time.day': '{n} jour', 'time.days': '{n} jours',
'time.month': '{n} mois', 'time.months': '{n} mois',
'time.year': '{n} an', 'time.years': '{n} ans',
'project.created': 'Création de',
'project.sources': 'Sources:',
'project.doc': 'Documentation:',
'project.end': 'Projet terminé en',
'project.still': 'Projet toujours actif',
'project.username': 'Adrien Marquès',
'end': ''
}

View File

@ -9,8 +9,8 @@ export const LOCALES = [
]; ];
import en from './en.json'; import en from './en';
import fr from './fr.json'; import fr from './fr';
export const messages = { export const messages = {
[Locales.EN]: en, [Locales.EN]: en,