From 525bc4a63da1b9f8bc6b8c80ddbbd3e3539fd74b Mon Sep 17 00:00:00 2001 From: cghislai Date: Sun, 5 Aug 2018 19:33:03 +0300 Subject: [PATCH] Jenkinsfile & translations --- Jenkinsfile | 103 ++++++++++++++++++ angular.json | 49 ++++++++- package-lock.json | 13 +++ package.json | 1 + src/.htaccess | 0 src/app/app.component.html | 4 + src/app/app.component.scss | 15 +++ src/app/app.component.ts | 37 ++++++- src/app/app.module.ts | 9 ++ src/app/app.routing-module.ts | 2 +- src/app/build-info.service.spec.ts | 15 +++ src/app/build-info.service.ts | 22 ++++ src/app/build-info.ts | 4 + .../contact-route.component.html | 4 +- src/app/info-route/info-route.component.html | 10 +- .../routes-header.component.html | 24 +++- .../routes-header.component.scss | 29 +++++ .../routes-header/routes-header.component.ts | 7 +- .../service-route.component.html | 2 +- src/assets/buildinfo.json | 4 + src/assets/flags/fr.png | Bin 0 -> 170 bytes src/assets/flags/gb.png | Bin 0 -> 1120 bytes src/locale/messages.fr.xlf | 103 ++++++++++++++++++ src/locale/messages.xlf.source | 88 +++++++++++++++ 24 files changed, 523 insertions(+), 22 deletions(-) create mode 100644 Jenkinsfile create mode 100644 src/.htaccess create mode 100644 src/app/build-info.service.spec.ts create mode 100644 src/app/build-info.service.ts create mode 100644 src/app/build-info.ts create mode 100644 src/assets/buildinfo.json create mode 100644 src/assets/flags/fr.png create mode 100644 src/assets/flags/gb.png create mode 100644 src/locale/messages.fr.xlf create mode 100644 src/locale/messages.xlf.source 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

    @@ -13,7 +13,7 @@
-

+

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

      @@ -21,11 +21,13 @@
    -

    +

    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 @@ + routerLinkActive="active" + i18n> Info + routerLinkActive="active" + i18n> Contact + routerLinkActive="active" + i18n> Services + + + + + + + + + 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 0000000000000000000000000000000000000000..3d8c69a1f94b7cffa728aa41906db7d2793d2200 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Eu!VDxcYag)#DU|@95LaiN*~_@OmvV8r>Caon z#pS9$56DP!`qVUWE>Q5PtnAkxzeDfv+5pwFl?3?(i;JJTeEI5gmpi|KBKn>#jv*4^ z$q9bUO?(D3I9d95(pE49OI$kDm|!lkhN)@hVx}qF3s^s?u!V&%x-uMG`D9{skA5#u OD}$%2pUXO@geCx;Z#0Ym literal 0 HcmV?d00001 diff --git a/src/assets/flags/gb.png b/src/assets/flags/gb.png new file mode 100644 index 0000000000000000000000000000000000000000..ead52f3d0741b00328ff61eaee67d00746d44437 GIT binary patch literal 1120 zcmV-m1fTnfP)@KR?((LD)h< z*hNO!M@OnlOWI9MtXWyzW@g`TaN>GmS1=kw+n8Ri-q z<{KO4Ix^-vGUqQP=Q1ehC@APVGU+iV=`%CwG&JcpHtA7H>9MrxI4oh6r zIWOx_NbF2Q?0k6ava#(pEA2Nd?Kv;)W?t=YYVJfj?rUc5udeRz`|mg`?^#gqp`h=( zy6^D#@9_KZPDJpXobd7d@r{Y`kd5*2{PKW#@{x@4+~M-xh<;d_P4Y4^7!`i`S$br_V)ev+}rox-uLqO_w@Sr z_xMz{`goLP@ci-d{PXkt^z{7v z{`~#^{p#uc^Yi`p_x=6<{r>*`^z;7t`TqL){`>m={QUm?{r>*`{{H{||NsB~{{R2~ z7X$@d00004bW%=Jg8cpcDg%Sh0005YNkls1qY7hlL6|%DBwTt)fFB9hF|8x|J3znz!YWcU6J9p&Zava&!0 zd3pIg+x9m}AQT9=?%%(lI4`fD0jNM)x-l~&qhiVab;>XWGeY)nZO+KZD48&KvLaBy z+=)f;@u|K0)@gKu6eRB7vwvZBe0)~t+_`#UEDS6nx^w4tB}YdWu2{YYq#$YkzLuEi zxVqVMedKvrfC^Z6RfTv#3Z$j?BLT1iBp@wKSb?l; z7#;u)2g()1B7yL*un1&6I3x-BqP=~_<~_SMuG$1suw(V=?R)ktY6k+K0$@O?&YhcP zZ&$j1|6ZsGGhJ+ar_I$A1qT!hr{>(L-jF!>m~2Y2n;D*H01>l5<;~jNO** zU$4#&j?IMq_4D@c4mPt+pF39ttRThQAYkYI`7(&~AYQhAf0em`*Yvqc96)dC80PNZ zUn7p3q6GD~?4M<;?a&4e2dn=5yG;doQIZ_5!m|Bq-L;H>3P6c=nG_3ps%GJ-2J;vh m7?@>c`B*R&u&{%8j0^xNArwDwd-%iv0000 + + +
    + + + + +
    + + + + 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 + + + + +