From f5e650d58a09d38780035b3a3a412351e8b476c1 Mon Sep 17 00:00:00 2001 From: Alex ORLUC <alex.orluc@maarch.org> Date: Thu, 11 Apr 2019 19:02:09 +0200 Subject: [PATCH] FEAT #8805 add internationalization functionality --- package.json | 6 ++ src/frontend/app/app.component.ts | 5 +- src/frontend/app/app.module.ts | 40 +++++++--- .../app/profile/profile.component.html | 73 ++++++++----------- src/frontend/app/profile/profile.component.ts | 43 +++++++---- .../app/service/notification.service.ts | 2 +- src/frontend/assets/i18n/en.json | 40 ++++++++++ src/frontend/assets/i18n/fr.json | 40 ++++++++++ 8 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 src/frontend/assets/i18n/en.json create mode 100644 src/frontend/assets/i18n/fr.json diff --git a/package.json b/package.json index cc4524b83f..ce432c1424 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "build": "ng build", "build-prod": "ng build --prod", "build-watch": "ng build --watch", + "build-lang": "ng xi18n --i18n-locale fr --output-path locale", + "extract-translations": "ngx-translate-extract i src/frontend/app -p /**/*.html -o src/frontend/assets/i18n/en.json src/frontend/assets/i18n/fr.json --format namespaced-json", "build-css": "node-sass --include-path scss src/frontend/css/maarch-material.scss src/frontend/css/maarch-material.css --output-style compressed", "test": "ng test", "lint": "ng lint", @@ -22,6 +24,8 @@ "@fortawesome/fontawesome-free": "^5.8.1", "@ngrx/store": "^7.4.0", "@ngrx/store-devtools": "^7.4.0", + "@ngx-translate/core": "^11.0.1", + "@ngx-translate/http-loader": "^4.0.0", "angular2-draggable": "^2.2.2", "angular2-signaturepad": "^2.8.0", "core-js": "^2.6.5", @@ -33,6 +37,7 @@ "ngx-cookie-service": "^2.1.0", "ngx-scroll-event": "^1.0.8", "pdfjs-dist": "^2.0.943", + "rxjs": "^6.4.0", "simple-pdf-viewer": "^2.0.3", "zone.js": "~0.8.29" }, @@ -52,6 +57,7 @@ "@angular/platform-browser": "^7.2.12", "@angular/platform-browser-dynamic": "^7.2.12", "@angular/router": "^7.2.12", + "@biesbjerg/ngx-translate-extract": "^2.3.4", "@types/hammerjs": "^2.0.36", "@types/jasmine": "^3.3.12", "@types/jasminewd2": "^2.0.6", diff --git a/src/frontend/app/app.component.ts b/src/frontend/app/app.component.ts index 630a144679..c27a4e171a 100755 --- a/src/frontend/app/app.component.ts +++ b/src/frontend/app/app.component.ts @@ -4,6 +4,7 @@ import { SignaturesContentService } from './service/signatures.service'; import { HttpClient } from '@angular/common/http'; import { NotificationService } from './service/notification.service'; import { DomSanitizer } from '@angular/platform-browser'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-root', @@ -14,7 +15,8 @@ import { DomSanitizer } from '@angular/platform-browser'; export class AppComponent { - constructor(public http: HttpClient, public signaturesService: SignaturesContentService, public sanitizer: DomSanitizer, private cookieService: CookieService, public notificationService: NotificationService) { + constructor(private translate: TranslateService, public http: HttpClient, public signaturesService: SignaturesContentService, public sanitizer: DomSanitizer, private cookieService: CookieService, public notificationService: NotificationService) { + translate.setDefaultLang('en'); if (this.cookieService.check('maarchParapheurAuth')) { const cookieInfo = JSON.parse(atob(this.cookieService.get('maarchParapheurAuth'))); @@ -22,6 +24,7 @@ export class AppComponent { this.http.get('../rest/users/' + cookieInfo.id) .subscribe((data: any) => { this.signaturesService.userLogged = data.user; + this.translate.use(this.signaturesService.userLogged.preferences.lang); }, (err: any) => { this.notificationService.handleErrors(err); diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index e33f452e08..55fcd1f8f2 100755 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -1,16 +1,19 @@ import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, HttpClient } from '@angular/common/http'; import { RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser'; +// import ngx-translate and the http loader +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; -export class CustomHammerConfig extends HammerGestureConfig { +export class CustomHammerConfig extends HammerGestureConfig { overrides = <any>{ - 'pinch': { enable: false }, - 'rotate': { enable: false } + 'pinch': { enable: false }, + 'rotate': { enable: false } }; } @@ -72,16 +75,24 @@ import { SignaturesContentService } from './service/signatures.service'; BrowserAnimationsModule, HttpClientModule, RouterModule, + HttpClientModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), SignaturePadModule, ScrollEventModule, AngularDraggableModule, SimplePdfViewerModule, AppMaterialModule, RouterModule.forRoot([ - { path: 'documents/:id', component: DocumentComponent}, - { path: 'documents', component: DocumentComponent}, - { path: 'login', component: LoginComponent}, - { path: '**', redirectTo: 'login', pathMatch: 'full' }, + { path: 'documents/:id', component: DocumentComponent }, + { path: 'documents', component: DocumentComponent }, + { path: 'login', component: LoginComponent }, + { path: '**', redirectTo: 'login', pathMatch: 'full' }, ], { useHash: true }), ], entryComponents: [ @@ -97,11 +108,16 @@ import { SignaturesContentService } from './service/signatures.service'; { provide: HAMMER_GESTURE_CONFIG, useClass: CustomHammerConfig - }, + }, CookieService], - exports: [ - RouterModule - ], + exports: [ + RouterModule + ], bootstrap: [AppComponent] }) export class AppModule { } + +// For traductions +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './frontend/assets/i18n/', '.json'); +} diff --git a/src/frontend/app/profile/profile.component.html b/src/frontend/app/profile/profile.component.html index 2bbc738793..ec3cc01e51 100644 --- a/src/frontend/app/profile/profile.component.html +++ b/src/frontend/app/profile/profile.component.html @@ -2,7 +2,7 @@ <div class="main-header"> <header class="profile-header"> <div class="user" style="color: #F99830"> - Mon Profil + {{'lang.myProfil' | translate}} </div> <div class="avatarProfile" [ngStyle]="{'background': 'url(' + this.profileInfo.picture + ') no-repeat scroll center center / cover'}" @@ -15,26 +15,26 @@ <form (ngSubmit)="submitProfile()" #profileForm="ngForm"> <mat-tab-group #tabProfile (selectedTabChange)="initProfileTab($event);" (swipeleft)="siwtchToleft(tabProfile)" (swiperight)="siwtchToRight(tabProfile)"> - <mat-tab label="Informations"> + <mat-tab label="{{'lang.informations' | translate}}"> <div class="profile-content"> <mat-form-field class="input-row"> - <input name="login" matInput placeholder="Courriel" type="mail" + <input name="login" matInput placeholder="{{'lang.email' | translate}}" type="mail" [(ngModel)]="profileInfo.email" (keyup)="newLogin.mail=newLogin.mail.toLowerCase()" disabled required> </mat-form-field> <mat-form-field class="input-row"> - <input name="firstname" matInput placeholder="Prénom" [(ngModel)]="profileInfo.firstname" + <input name="firstname" matInput placeholder="{{'lang.firstname' | translate}}" [(ngModel)]="profileInfo.firstname" required> </mat-form-field> <mat-form-field class="input-row"> - <input name="nom" matInput placeholder="Nom" [(ngModel)]="profileInfo.lastname" required> + <input name="nom" matInput placeholder="{{'lang.lastname' | translate}}" [(ngModel)]="profileInfo.lastname" required> </mat-form-field> <mat-accordion> <mat-expansion-panel (closed)="showPassword=false" (opened)="changePasswd()" #passwordContent> <mat-expansion-panel-header> <mat-panel-title> - Modifier mon mot de passe + {{'lang.updatePassword' | translate}} </mat-panel-title> <mat-panel-description> </mat-panel-description> @@ -42,7 +42,7 @@ <ng-container *ngIf="showPassword"> <mat-form-field class="input-row"> <input name="currentPassword" matInput [(ngModel)]="password.currentPassword" - placeholder="Mot de passe actuel" + placeholder="{{'lang.currentPassword' | translate}}" [type]="hideCurrentPassword ? 'password' : 'text'"> <mat-icon matSuffix (click)="hideCurrentPassword = !hideCurrentPassword" class="fa fa-2x" @@ -50,7 +50,7 @@ </mat-form-field> <mat-form-field class="input-row"> <input name="newPassword" matInput [(ngModel)]="password.newPassword" - placeholder="Nouveau mot de passe" + placeholder="{{'lang.newPassword' | translate}}" [type]="hideNewPassword ? 'password' : 'text'" (keyup)="checkPasswordValidity(password.newPassword)"> <mat-icon matSuffix (click)="hideNewPassword = !hideNewPassword" @@ -61,34 +61,32 @@ <mat-form-field class="input-row"> <input name="passwordConfirmation" matInput [(ngModel)]="password.passwordConfirmation" - placeholder="Confirmer le nouveau mot de passe" + placeholder="{{'lang.passwordConfirmation' | translate}}" [type]="hideNewPasswordConfirm ? 'password' : 'text'"> <mat-icon matSuffix (click)="hideNewPasswordConfirm = !hideNewPasswordConfirm" class="fa fa-2x" [ngClass]="[hideNewPasswordConfirm ? 'fa-eye-slash' : 'fa-eye']"></mat-icon> <mat-hint style="color:red;" - *ngIf="password.passwordConfirmation !== password.newPassword">Les - mots de passe ne correspondent pas !</mat-hint> + *ngIf="password.passwordConfirmation !== password.newPassword">{{'lang.passwordNotMatch' | translate}}</mat-hint> <mat-hint style="color:green;" *ngIf="password.passwordConfirmation === password.newPassword && password.newPassword.length > 0 && password.passwordConfirmation.length> 0"> - Les - mots de passe sont identiques</mat-hint> + {{'lang.samePassword' | translate}}</mat-hint> </mat-form-field> </ng-container> </mat-expansion-panel> </mat-accordion> </div> </mat-tab> - <mat-tab label="Préférences"> + <mat-tab label="{{'lang.preferences' | translate}}"> <div class="profile-content"> <div class="input-row"> <fieldset> - <legend align="left">Notifications</legend> + <legend align="left">{{'lang.notifications' | translate}}</legend> <div class="form-container"> <div class="form-2-col"> <mat-slide-toggle [checked]="this.profileInfo.preferences.notifications" (change)="this.profileInfo.preferences.notifications=!this.profileInfo.preferences.notifications" - color="primary">Recevoir les notifications</mat-slide-toggle> + color="primary">{{'lang.receiveNotif' | translate}}</mat-slide-toggle> </div> <div class="form-2-col" style="text-align: justify;font-size: 12px;"> @@ -98,13 +96,13 @@ </div> <div class="input-row"> <fieldset> - <legend align="left">Langue</legend> + <legend align="left">{{'lang.language' | translate}}</legend> <div class="form-container"> <div class="form-2-col"> <mat-form-field> - <mat-select name="langUser" [(ngModel)]="this.profileInfo.preferences.lang"> + <mat-select #langSelect name="langUser" [(ngModel)]="this.profileInfo.preferences.lang"> <mat-option value="fr">Français</mat-option> - <mat-option value="en">Anglais</mat-option> + <mat-option value="en">English</mat-option> </mat-select> </mat-form-field> </div> @@ -112,34 +110,27 @@ </div> </div> - </fieldset> </div> <div class="input-row"> <fieldset> - <legend align="left">Mode de l'annotation</legend> + <legend align="left">{{'lang.annotationMode' | translate}}</legend> <div class="form-container"> <div class="form-2-col"> <mat-form-field> <mat-select name="writingMode" [(ngModel)]="this.profileInfo.preferences.writingMode"> - <mat-option value="direct">Libre</mat-option> - <mat-option value="stylus">Stylet Apple <i class="fab fa-apple"></i> + <mat-option value="direct">{{'lang.free' | translate}}</mat-option> + <mat-option value="stylus">{{'lang.appleStylus' | translate}} <i class="fab fa-apple"></i> </mat-option> </mat-select> </mat-form-field> </div> <div *ngIf="this.profileInfo.preferences.writingMode == 'stylus'" class="form-2-col" - style="text-align: justify;font-size: 12px;"> - Vous ne pourrez annoter les documents qu'avec le <b>stylet Apple</b>.<br /> - Cela a pour avantage de pouvoir poser la main sur la tablette sans perturber - l'écriture. + style="text-align: justify;font-size: 12px;" [innerHTML]="'lang.freeModeInfo' | translate"> </div> <div *ngIf="this.profileInfo.preferences.writingMode == 'direct'" class="form-2-col" - style="text-align: justify;font-size: 12px;"> - Mode standard, permettant l'annotation avec tout format (souris, main, stylets - de - toutes marques). + style="text-align: justify;font-size: 12px;" [innerHTML]="'lang.standardModeInfo' | translate"> </div> </div> @@ -147,7 +138,7 @@ </div> <div class="input-row"> <fieldset> - <legend align="left">Épaisseur du trait</legend> + <legend align="left">{{'lang.stylusWidh' | translate}}</legend> <div class="form-container"> <div class="form-2-col"> <mat-form-field> @@ -167,15 +158,15 @@ </div> <div class="input-row"> <fieldset style="display:table;"> - <legend align="left">Couleur par défaut</legend> + <legend align="left">{{'lang.defaultColor' | translate}}</legend> <div class="form-container"> <div class="form-2-col"> <mat-form-field> <mat-select name="writingColor" [(ngModel)]="this.profileInfo.preferences.writingColor"> - <mat-option style="color:#000000" value="#000000">Noir</mat-option> - <mat-option style="color:#1a75ff" value="#1a75ff">Bleu</mat-option> - <mat-option style="color:#FF0000" value="#FF0000">Rouge</mat-option> + <mat-option style="color:#000000" value="#000000">{{'lang.black' | translate}}</mat-option> + <mat-option style="color:#1a75ff" value="#1a75ff">{{'lang.blue' | translate}}</mat-option> + <mat-option style="color:#FF0000" value="#FF0000">{{'lang.red' | translate}}</mat-option> </mat-select> </mat-form-field> </div> @@ -189,11 +180,11 @@ </div> </div> </mat-tab> - <mat-tab label="Administrations" *ngIf="signaturesService.userLogged.canManageRestUsers"> + <mat-tab label="{{'lang.administrations' | translate}}" *ngIf="signaturesService.userLogged.canManageRestUsers"> <div class="profile-content"> <div class="input-row"> <fieldset> - <legend align="left">Utilisateurs Webservice</legend> + <legend align="left">{{'lang.wsUser' | translate}}</legend> <div class="form-container"> <div class="form-col" style="width:35%;"> <mat-form-field> @@ -207,7 +198,7 @@ <div class="form-col" style="width:35%;"> <mat-form-field class="input-row"> <input name="newPasswordRest" matInput [(ngModel)]="currentUserRestPassword" - placeholder="Nouveau mot de passe" + placeholder="{{'lang.newPassword' | translate}}" [type]="hideNewPassword ? 'password' : 'text'" (keyup)="checkPasswordValidity(currentUserRestPassword)"> <mat-icon matSuffix (click)="hideNewPassword = !hideNewPassword" @@ -219,7 +210,7 @@ <div class="form-col" style="width:29%;"> <button mat-raised-button type="button" color="primary" [disabled]="handlePassword.error || currentUserRestPassword.length === 0" - (click)="updateRestUser()">Modifier</button> + (click)="updateRestUser()">{{'lang.update' | translate}}</button> </div> </div> </fieldset> @@ -230,7 +221,7 @@ <span class="actions"> <button class="validate" mat-button color="primary" type="submit" [disabled]="allowValidate() || !profileForm.form.valid">{{ - msgButton }}</button> + msgButton | translate}}</button> <button class="cancel" mat-icon-button type="button" (tap)="closeProfile();"> <mat-icon fontSet="fas" fontIcon="fa-arrow-left fa-2x"></mat-icon> </button> diff --git a/src/frontend/app/profile/profile.component.ts b/src/frontend/app/profile/profile.component.ts index bfab89ba71..cc68daab6e 100644 --- a/src/frontend/app/profile/profile.component.ts +++ b/src/frontend/app/profile/profile.component.ts @@ -6,6 +6,8 @@ import { SignaturesContentService } from '../service/signatures.service'; import { NotificationService } from '../service/notification.service'; import { CookieService } from 'ngx-cookie-service'; import * as EXIF from 'exif-js'; +import { TranslateService } from '@ngx-translate/core'; +import {_} from '@biesbjerg/ngx-translate-extract/dist/utils/utils'; @Component({ selector: 'app-my-profile', @@ -54,9 +56,9 @@ export class ProfileComponent implements OnInit { showPassword = false; disableState = false; - msgButton = 'Valider'; + msgButton = 'lang.validate'; - constructor(public http: HttpClient, iconReg: MatIconRegistry, public sanitizer: DomSanitizer, public notificationService: NotificationService, public signaturesService: SignaturesContentService, private cookieService: CookieService) { + constructor(private translate: TranslateService, public http: HttpClient, iconReg: MatIconRegistry, public sanitizer: DomSanitizer, public notificationService: NotificationService, public signaturesService: SignaturesContentService, private cookieService: CookieService) { iconReg.addSvgIcon('maarchLogo', sanitizer.bypassSecurityTrustResourceUrl('../src/frontend/assets/logo_white.svg')); } @@ -176,7 +178,7 @@ export class ProfileComponent implements OnInit { submitProfile() { this.disableState = true; - this.msgButton = 'Envoi...'; + this.msgButton = 'lang.sending'; let profileToSend = { 'firstname': this.profileInfo.firstname, 'lastname': this.profileInfo.lastname, @@ -198,6 +200,8 @@ export class ProfileComponent implements OnInit { this.signaturesService.userLogged.picture = data.user.picture; this.signaturesService.userLogged.preferences = data.user.preferences; this.profileInfo.picture = data.user.picture; + this.setLang(this.signaturesService.userLogged.preferences.lang); + $('.avatarProfile').css({ 'transform': 'rotate(0deg)' }); if (this.showPassword) { @@ -206,15 +210,15 @@ export class ProfileComponent implements OnInit { this.password.newPassword = ''; this.password.passwordConfirmation = ''; this.password.currentPassword = ''; - this.notificationService.success('Profil modifié'); + this.notificationService.success('lang.profileUpdated'); this.disableState = false; - this.msgButton = 'Valider'; + this.msgButton = 'lang.validate'; this.closeProfile(); }, (err) => { this.disableState = false; - this.msgButton = 'Valider'; + this.msgButton = 'lang.validate'; if (err.status === 401) { - this.notificationService.error('Mauvais mot de passe'); + this.notificationService.error('lang.badPassword'); } else { this.notificationService.handleErrors(err); } @@ -222,14 +226,14 @@ export class ProfileComponent implements OnInit { } if (!this.showPassword) { - this.notificationService.success('Profil modifié'); + this.notificationService.success('lang.profileUpdated'); this.disableState = false; - this.msgButton = 'Valider'; + this.msgButton = 'lang.validate'; this.closeProfile(); } }, (err) => { this.disableState = false; - this.msgButton = 'Valider'; + this.msgButton = 'lang.validate'; this.notificationService.handleErrors(err); this.disableState = false; }); @@ -272,10 +276,10 @@ export class ProfileComponent implements OnInit { }; myReader.readAsDataURL(fileToUpload); } else { - this.notificationService.error('Ceci n\'est pas une image'); + this.notificationService.error('lang.notAnImage'); } } else { - this.notificationService.error('Image trop volumineuse (5mo max.)'); + this.notificationService.error('lang.imageTooBig'); } } @@ -332,18 +336,25 @@ export class ProfileComponent implements OnInit { this.currentUserRest = data.users[0]; }, (err: any) => { - this.notificationService.handleErrors(err); - }); + this.notificationService.handleErrors(err); + }); } } updateRestUser() { - this.http.put('../rest/users/' + this.currentUserRest.id + '/password', {'newPassword' : this.currentUserRestPassword}) + this.http.put('../rest/users/' + this.currentUserRest.id + '/password', { 'newPassword': this.currentUserRestPassword }) .subscribe(() => { this.currentUserRestPassword = ''; - this.notificationService.success('Mot de passe de ' + this.currentUserRest.firstname + ' ' + this.currentUserRest.lastname + ' modifié'); + this.translate.get('lang.passwordOfUserUpdated', {user: this.currentUserRest.firstname + ' ' + this.currentUserRest.lastname}).subscribe((res: string) => { + this.notificationService.success(res); + }); }, (err) => { this.notificationService.handleErrors(err); }); } + + + setLang(lang: any) { + this.translate.use(lang); + } } diff --git a/src/frontend/app/service/notification.service.ts b/src/frontend/app/service/notification.service.ts index 523ffafddd..f5940ed59d 100644 --- a/src/frontend/app/service/notification.service.ts +++ b/src/frontend/app/service/notification.service.ts @@ -5,7 +5,7 @@ import { Router } from '@angular/router'; @Component({ selector: 'app-custom-snackbar', - template: '{{data.message}}' // You may also use a HTML file + template: '{{data.message | translate}}' // You may also use a HTML file }) export class CustomSnackbarComponent { constructor(@Inject(MAT_SNACK_BAR_DATA) public data: any) { } diff --git a/src/frontend/assets/i18n/en.json b/src/frontend/assets/i18n/en.json new file mode 100644 index 0000000000..024c33449e --- /dev/null +++ b/src/frontend/assets/i18n/en.json @@ -0,0 +1,40 @@ +{ + "lang": { + "administrations": "Administrations", + "annotationMode": "Annotation mode", + "appleStylus": "Apple stylus", + "badPassword": "Bad password", + "black": "Black", + "blue": "Blue", + "currentPassword": "Actual password", + "defaultColor": "Default color", + "email": "Email", + "firstname": "Firstname", + "free": "Free", + "freeModeInfo": "", + "imageTooBig": "Image is too big (5mo max.)", + "informations": "Informations", + "language": "Language", + "lastname": "Lastname", + "myProfil": "My profile", + "newPassword": "New password", + "notAnImage": "This is not an image", + "notifications": "Notifications", + "password": "Password", + "passwordConfirmation": "New password confirmation", + "passwordNotMatch": "Passwords does not match", + "passwordOfUserUpdated": "Password of {{user}} updated", + "preferences": "Preferences", + "profileUpdated": "Profile updated", + "receiveNotif": "Receive notifications", + "red": "Red", + "samePassword": "Passwords match", + "sending": "Sending...", + "standardModeInfo": "", + "stylusWidh": "Stylus width", + "update": "Update", + "updatePassword": "Update password", + "validate": "Validate", + "wsUser": "Web service user" + } +} diff --git a/src/frontend/assets/i18n/fr.json b/src/frontend/assets/i18n/fr.json new file mode 100644 index 0000000000..c50f961522 --- /dev/null +++ b/src/frontend/assets/i18n/fr.json @@ -0,0 +1,40 @@ +{ + "lang": { + "administrations": "Administrations", + "annotationMode": "Mode de l'annotation", + "appleStylus": "Stylet Apple", + "badPassword": "Mauvais mot de passe", + "black": "Noir", + "blue": "Bleu", + "currentPassword": "Mot de passe actuel", + "defaultColor": "Couleur par défaut", + "email": "Courriel", + "firstname": "Prénom", + "free": "Libre", + "freeModeInfo": "Vous ne pourrez annoter les documents qu'avec le <b>stylet Apple</b>.<br />Cela a pour avantage de pouvoir poser la main sur la tablette sans perturber l'écriture.", + "imageTooBig": "Image trop volumineuse (5mo max.)", + "informations": "Informations", + "language": "Langue", + "lastname": "Nom", + "myProfil": "Mon profil", + "newPassword": "Nouveau mot de passe", + "notAnImage": "Ceci n'est pas une image", + "notifications": "Notifications", + "password": "Mot de passe", + "passwordConfirmation": "Confirmer le nouveau mot de passe", + "passwordNotMatch": "Les mots de passe ne correspondent pas", + "passwordOfUserUpdated": "Mot de passe de {{user}} modifié", + "preferences": "Préférences", + "profileUpdated": "Profil modifié", + "receiveNotif": "Recevoir les notifications", + "red": "Rouge", + "samePassword": "Les mots de passe sont identiques", + "sending": "Envoi...", + "standardModeInfo": "Mode standard, permettant l'annotation avec tout format (souris, main, stylets de toutes marques).", + "stylusWidh": "Épaisseur du trait", + "update": "Modifier", + "updatePassword": "Modifier le mot de passe", + "validate": "Valider", + "wsUser": "Utilisateur web service" + } +} -- GitLab