diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..1c2bf9a
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,103 @@
+pipeline {
+ agent any
+ options {
+ disableConcurrentBuilds()
+ buildDiscarder(logRotator(numToKeepStr: '10'))
+ }
+ parameters {
+ string(
+ name: 'CONF_PREFIX', defaultValue: 'production-',
+ description: 'Will be appended with the language'
+ )
+ string(
+ name: 'LANGUAGES', defaultValue: 'en fr',
+ description: 'Space-separated list of locales to build'
+ )
+ booleanParam(name: 'SKIP_TESTS', defaultValue: true, description: 'Skip tests')
+ string(
+ name: 'PUBLISH_URL', defaultValue: 'https://nexus.valuya.be/nexus/repository',
+ description: 'Deployment repository url'
+ )
+ string(
+ name: 'PUBLISH_REPO', defaultValue: 'web-snapshots',
+ description: 'Deployment repository'
+ )
+ }
+ stages {
+ stage ('Install') {
+ steps {
+ nodejs(nodeJSInstallationName: 'node 10', configId: 'npm-global-config') {
+ ansiColor('xterm') {
+ sh '''
+ rm -rfv dist*
+ npm install
+ '''
+ }
+ }
+ }
+ }
+ stage ('Build') {
+ steps {
+ withCredentials([usernameColonPassword(credentialsId: 'nexus-basic-auth', variable: 'NEXUS_BASIC_AUTH')]) {
+ nodejs(nodeJSInstallationName: 'node 10', configId: 'npm-global-config') { catchError {
+ ansiColor('xterm') {
+ sh '''
+ export DATE="$(date -Iseconds)"
+ export COMMIT="$(git rev-parse --short HEAD)"
+ echo '{"date":"'$DATE'","commit": "'$COMMIT'"}' > ./src/assets/buildinfo.json
+
+ for LANG in $LANGUAGES ; do
+ CONF_NAME="${CONF_PREFIX}${LANG}"
+
+ ./node_modules/.bin/ng build \
+ -c "$CONF_NAME"
+ done
+ '''
+ }
+ }}}
+ }
+ }
+ stage ('Publish') {
+ steps {
+ withCredentials([usernameColonPassword(credentialsId: 'nexus-basic-auth', variable: 'NEXUS_BASIC_AUTH')]) {
+ ansiColor('xterm') {
+ nodejs(nodeJSInstallationName: 'node 10', configId: 'npm-global-config') {
+ sh '''
+ export DATE="$(date -Iseconds)"
+ export COMMIT="$(git rev-parse --short HEAD)"
+ echo '{"date":"'$DATE'","commit": "'$COMMIT'"}' > ./src/assets/buildinfo.json
+
+ for LANG in $LANGUAGES ; do
+ export ARCHIVE="charlyghislaindotcom-${COMMIT}-$LANG.tgz"
+
+ # Compress
+ cd dist/${LANG}/
+ tar -cvzf ../${ARCHIVE} ./
+ cd ../..
+
+ # Upload archives
+ curl -v --user $NEXUS_BASIC_AUTH --upload-file dist/${ARCHIVE} \
+ ${PUBLISH_URL}/${PUBLISH_REPO}/com/charlyghislain/charlyghislaindotcom/${ARCHIVE}
+
+ # Create .latest 'links' (branch heads) if required
+ if [ "${BRANCH_NAME}" = "master" ] ; then
+ export ARCHIVE_LINK="master.${LANG}.latest"
+ echo "$ARCHIVE" > ./${ARCHIVE_LINK}
+ curl -v --user $NEXUS_BASIC_AUTH --upload-file ./${ARCHIVE_LINK} \
+ ${PUBLISH_URL}/${PUBLISH_REPO}/com/charlyghislain/charlyghislaindotcom/${ARCHIVE_LINK}
+
+ elif [ "${BRANCH_NAME}" = "dev" ] ; then
+ export ARCHIVE_LINK="dev.${LANG}.latest"
+ echo "$ARCHIVE" > ./${ARCHIVE_LINK}
+ curl -v --user $NEXUS_BASIC_AUTH --upload-file ./${ARCHIVE_LINK} \
+ ${PUBLISH_URL}/${PUBLISH_REPO}/com/charlyghislain/charlyghislaindotcom/${ARCHIVE_LINK}
+ fi
+ done
+ '''
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/angular.json b/angular.json
index 4f69ede..cbba2e4 100644
--- a/angular.json
+++ b/angular.json
@@ -13,22 +13,30 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
- "outputPath": "dist/charlyghislaindotcom",
+ "outputPath": "dist/en",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
+ "i18nLocale": "en",
"assets": [
"src/favicon.ico",
"src/assets"
],
+ "aot": true,
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
- "production": {
+ "fr": {
+ "outputPath": "dist/fr/",
+ "i18nFile": "src/locale/messages.fr.xlf",
+ "i18nFormat": "xlf",
+ "i18nLocale": "fr"
+ },
+ "production-en": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
@@ -43,7 +51,32 @@
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
- "buildOptimizer": true
+ "buildOptimizer": true,
+ "outputPath": "dist/en/",
+ "i18nLocale": "en",
+ "baseHref": "/en/"
+ },
+ "production-fr": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "outputPath": "dist/fr/",
+ "i18nFile": "src/locale/messages.fr.xlf",
+ "i18nFormat": "xlf",
+ "i18nLocale": "fr",
+ "baseHref": "/fr/"
}
}
},
@@ -53,8 +86,14 @@
"browserTarget": "charlyghislaindotcom:build"
},
"configurations": {
+ "fr": {
+ "browserTarget": "charlyghislaindotcom:build:fr"
+ },
"production": {
- "browserTarget": "charlyghislaindotcom:build:production"
+ "browserTarget": "charlyghislaindotcom:build:production-en"
+ },
+ "production-fr": {
+ "browserTarget": "charlyghislaindotcom:build:production-fr"
}
}
},
@@ -129,4 +168,4 @@
"styleext": "scss"
}
}
-}
\ No newline at end of file
+}
diff --git a/package-lock.json b/package-lock.json
index a02fc2e..44ba3e3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6499,6 +6499,19 @@
"minimist": "0.0.8"
}
},
+ "moment": {
+ "version": "2.22.2",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
+ "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
+ },
+ "moment-es6": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/moment-es6/-/moment-es6-1.0.0.tgz",
+ "integrity": "sha1-VS/PQF1iVlsKH+hObB5peseTMt8=",
+ "requires": {
+ "moment": "*"
+ }
+ },
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
diff --git a/package.json b/package.json
index 499456e..241ffce 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"@angular/platform-browser-dynamic": "^6.0.3",
"@angular/router": "^6.0.3",
"core-js": "^2.5.4",
+ "moment-es6": "^1.0.0",
"rxjs": "^6.0.0",
"zone.js": "^0.8.26"
},
diff --git a/src/.htaccess b/src/.htaccess
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 8b1367e..20ef127 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -3,3 +3,7 @@
+
+ {{buildDate | async}}
+ {{buildCommit| async}}
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index cf86a16..954e761 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -1,3 +1,18 @@
.content {
margin: 2em;
}
+
+.build-info {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+
+ opacity: .4;
+ font-size: xx-small;
+ text-align: right;
+
+ >* {
+ display: inline-block;
+ margin: .1em;
+ }
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 6155d99..7106617 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,9 +1,42 @@
-import {Component} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
+import {BuildInfoService} from './build-info.service';
+import {Observable, of} from 'rxjs';
+import {catchError, defaultIfEmpty, filter, map, publishReplay, refCount} from 'rxjs/operators';
+import {BuildInfo} from './build-info';
+import moment from 'moment-es6';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
-export class AppComponent {
+export class AppComponent implements OnInit {
+
+ buildDate: Observable;
+ buildCommit: Observable;
+
+ constructor(private buildinfoService: BuildInfoService) {
+ const buildInfo = this.buildinfoService.getBuildInfo()
+ .pipe(
+ catchError(e => of({date: null, commit: null})),
+ publishReplay(1), refCount(),
+ );
+ this.buildDate = buildInfo.pipe(
+ map(info => info.date),
+ map(date => this.parseDate(date)),
+ );
+ this.buildCommit = buildInfo.pipe(
+ map(info => info.commit),
+ filter(commit => commit != null),
+ defaultIfEmpty('HEAD'),
+ );
+ }
+
+ ngOnInit(): void {
+ }
+
+ private parseDate(dateString: string) {
+ const dateMoment = dateString == null ? moment() : moment(dateString);
+ return dateMoment.format('DD/MM/YYYY HH:mm:ss');
+ }
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b23b56c..78d9c95 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -7,10 +7,19 @@ import {ContactRouteComponent} from './contact-route/contact-route.component';
import {ServiceRouteComponent} from './service-route/service-route.component';
import {AppRoutingModule} from './app.routing-module';
import {RoutesHeaderComponent} from './routes-header/routes-header.component';
+import {CommonModule, registerLocaleData} from '@angular/common';
+import localeFr from '@angular/common/locales/fr';
+import {HttpClientModule} from '@angular/common/http';
+
+// the second parameter 'fr' is optional
+registerLocaleData(localeFr, 'fr');
+
@NgModule({
imports: [
BrowserModule,
+ CommonModule,
+ HttpClientModule,
AppRoutingModule,
],
declarations: [
diff --git a/src/app/app.routing-module.ts b/src/app/app.routing-module.ts
index 75645d5..2fb52ba 100644
--- a/src/app/app.routing-module.ts
+++ b/src/app/app.routing-module.ts
@@ -23,7 +23,7 @@ export const ROUTES: Route[] = [
component: ServiceRouteComponent,
},
{
- path: '*',
+ path: '**',
redirectTo: '/info',
},
];
diff --git a/src/app/build-info.service.spec.ts b/src/app/build-info.service.spec.ts
new file mode 100644
index 0000000..a8cd99c
--- /dev/null
+++ b/src/app/build-info.service.spec.ts
@@ -0,0 +1,15 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { BuildInfoService } from './build-info.service';
+
+describe('BuildInfoService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [BuildInfoService]
+ });
+ });
+
+ it('should be created', inject([BuildInfoService], (service: BuildInfoService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/src/app/build-info.service.ts b/src/app/build-info.service.ts
new file mode 100644
index 0000000..b3bdddd
--- /dev/null
+++ b/src/app/build-info.service.ts
@@ -0,0 +1,22 @@
+import {Injectable} from '@angular/core';
+import {HttpClient} from '@angular/common/http';
+import {BuildInfo} from './build-info';
+import {Observable} from 'rxjs';
+import {publishReplay, refCount} from 'rxjs/operators';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class BuildInfoService {
+
+ private buildInfo: Observable;
+
+ constructor(private http: HttpClient) {
+ this.buildInfo = this.http.get('./assets/buildinfo.json')
+ .pipe(publishReplay(1), refCount());
+ }
+
+ getBuildInfo(): Observable {
+ return this.buildInfo;
+ }
+}
diff --git a/src/app/build-info.ts b/src/app/build-info.ts
new file mode 100644
index 0000000..b300ba9
--- /dev/null
+++ b/src/app/build-info.ts
@@ -0,0 +1,4 @@
+export interface BuildInfo {
+ date: string;
+ commit: string;
+}
diff --git a/src/app/contact-route/contact-route.component.html b/src/app/contact-route/contact-route.component.html
index 7eba318..8bc1b62 100644
--- a/src/app/contact-route/contact-route.component.html
+++ b/src/app/contact-route/contact-route.component.html
@@ -1,4 +1,4 @@
-
+
You can reach me using the following channels
-
+
Bank information
diff --git a/src/app/info-route/info-route.component.html b/src/app/info-route/info-route.component.html
index cbefb04..10d1903 100644
--- a/src/app/info-route/info-route.component.html
+++ b/src/app/info-route/info-route.component.html
@@ -1,7 +1,7 @@
-
+
This is the homepage of Charles Ghislain
-
+
I'm a full stack developer familiar with the following technologies
-
+
You may find some of my work online under the cghislai nickname
diff --git a/src/app/routes-header/routes-header.component.html b/src/app/routes-header/routes-header.component.html
index fe91481..19f8497 100644
--- a/src/app/routes-header/routes-header.component.html
+++ b/src/app/routes-header/routes-header.component.html
@@ -2,27 +2,43 @@
+
+
+
+
+
+
+
+
+
charlyghislain.com
-
diff --git a/src/app/routes-header/routes-header.component.scss b/src/app/routes-header/routes-header.component.scss
index 4ed2bbc..a869e09 100644
--- a/src/app/routes-header/routes-header.component.scss
+++ b/src/app/routes-header/routes-header.component.scss
@@ -39,6 +39,35 @@ header {
}
}
+ > .lang {
+ > a {
+ display: inline-block;
+ width: 20px;
+ height: 16px;
+ text-align: center;
+ position: relative;
+
+ > img {
+ width: 20px;
+ height: 16px;
+ transition: all ease-in .2s;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ }
+ &:hover {
+ img {
+ width: 30px;
+ height: 24px;
+ left: -5px;
+ top: -4px;
+ }
+ }
+ }
+ }
+
> .menu {
flex: 1 1 auto;
display: inline-flex;
diff --git a/src/app/routes-header/routes-header.component.ts b/src/app/routes-header/routes-header.component.ts
index b4e013b..4e6957c 100644
--- a/src/app/routes-header/routes-header.component.ts
+++ b/src/app/routes-header/routes-header.component.ts
@@ -1,13 +1,14 @@
-import { Component, OnInit } from '@angular/core';
+import {Component, Inject, LOCALE_ID, OnInit} from '@angular/core';
@Component({
selector: 'app-routes-header',
templateUrl: './routes-header.component.html',
- styleUrls: ['./routes-header.component.scss']
+ styleUrls: ['./routes-header.component.scss'],
})
export class RoutesHeaderComponent implements OnInit {
- constructor() { }
+ constructor(@Inject(LOCALE_ID) public locale: string) {
+ }
ngOnInit() {
}
diff --git a/src/app/service-route/service-route.component.html b/src/app/service-route/service-route.component.html
index 010f50a..ecb5346 100644
--- a/src/app/service-route/service-route.component.html
+++ b/src/app/service-route/service-route.component.html
@@ -1,4 +1,4 @@
-
+
You may be looking for one of these services
diff --git a/src/assets/buildinfo.json b/src/assets/buildinfo.json
new file mode 100644
index 0000000..dda054d
--- /dev/null
+++ b/src/assets/buildinfo.json
@@ -0,0 +1,4 @@
+{
+ "date": null,
+ "commit": null
+}
diff --git a/src/assets/flags/fr.png b/src/assets/flags/fr.png
new file mode 100644
index 0000000..3d8c69a
Binary files /dev/null and b/src/assets/flags/fr.png differ
diff --git a/src/assets/flags/gb.png b/src/assets/flags/gb.png
new file mode 100644
index 0000000..ead52f3
Binary files /dev/null and b/src/assets/flags/gb.png differ
diff --git a/src/locale/messages.fr.xlf b/src/locale/messages.fr.xlf
new file mode 100644
index 0000000..126db8d
--- /dev/null
+++ b/src/locale/messages.fr.xlf
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+ This is the homepage of Charles Ghislain
+
+ Ceci est la page d'accueil de Charles Ghislain
+
+ app/info-route/info-route.component.html
+ 1
+
+
+
+
+ I'm a full stack developer familiar with the following technologies
+
+ Je suis un développeur 'full stack', familier avec les technologies suivantes
+
+ app/info-route/info-route.component.html
+ 4
+
+
+
+
+ You may find some of my work online under the cghislai nickname
+
+ Vous pouvez trouver une partie de mes contributions en ligne sous le pseudonyme 'cghislai'
+
+ app/info-route/info-route.component.html
+ 24
+
+
+
+
+ You can reach me using the following channels
+
+ Vous pouvez me contacter via les canaux suivants
+
+ app/contact-route/contact-route.component.html
+ 1
+
+
+
+
+ Bank information
+
+ Informations banquaires
+
+ app/contact-route/contact-route.component.html
+ 16
+
+
+
+
+ You may be looking for one of these services
+
+ Peut-être essayez-vous d'atteindre l'un des services suivants
+
+ app/service-route/service-route.component.html
+ 1
+
+
+
+
+ Info
+
+ Info
+
+ app/routes-header/routes-header.component.html
+ 6
+
+
+
+
+ Contact
+
+ Contact
+
+ app/routes-header/routes-header.component.html
+ 12
+
+
+
+
+ Services
+
+ Services
+
+ app/routes-header/routes-header.component.html
+ 18
+
+
+
+
+
diff --git a/src/locale/messages.xlf.source b/src/locale/messages.xlf.source
new file mode 100644
index 0000000..0f50d56
--- /dev/null
+++ b/src/locale/messages.xlf.source
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ This is the homepage of Charles Ghislain
+
+
+ app/info-route/info-route.component.html
+ 1
+
+
+
+
+ I'm a full stack developer familiar with the following technologies
+
+
+ app/info-route/info-route.component.html
+ 4
+
+
+
+
+ You may find some of my work online under the cghislai nickname
+
+
+ app/info-route/info-route.component.html
+ 24
+
+
+
+
+ You can reach me using the following channels
+
+
+ app/contact-route/contact-route.component.html
+ 1
+
+
+
+
+ Bank information
+
+
+ app/contact-route/contact-route.component.html
+ 16
+
+
+
+
+ You may be looking for one of these services
+
+
+ app/service-route/service-route.component.html
+ 1
+
+
+
+
+ Info
+
+
+ app/routes-header/routes-header.component.html
+ 6
+
+
+
+
+ Contact
+
+
+ app/routes-header/routes-header.component.html
+ 12
+
+
+
+
+ Services
+
+
+ app/routes-header/routes-header.component.html
+ 18
+
+
+
+
+