Initial translation implementation

This commit is contained in:
kiswa 2017-03-03 02:05:43 +00:00
parent daa1171e81
commit dd8a350825
22 changed files with 263 additions and 101 deletions

View File

@ -41,6 +41,7 @@ let gulp = require('gulp'),
'src/**/*.html',
'src/.htaccess'
],
json: 'src/json/**/*.json',
images: 'src/images/**/*.*',
scss: 'src/scss/**/*.scss',
scssMain: 'src/scss/main.scss',
@ -56,11 +57,7 @@ gulp.task('clean', () => {
'dist',
'build',
'tests.db',
'.coverrun',
'.coverdata',
'api-coverage',
'coverage',
'temp',
'src/api/vendor/'
]);
});
@ -70,6 +67,11 @@ gulp.task('html', () => {
.pipe(gulp.dest('dist/'));
});
gulp.task('json', () => {
return gulp.src(paths.json)
.pipe(gulp.dest('dist/strings/'));
});
gulp.task('fonts', () => {
return gulp.src('src/fonts/fontello.*')
.pipe(gulp.dest('dist/css/fonts/'));
@ -172,7 +174,7 @@ gulp.task('api', () => {
.pipe(gulp.dest('dist/api/'));
});
gulp.task('app', ['html', 'system-build', 'ts-lint']);
gulp.task('app', ['html', 'json', 'system-build', 'ts-lint']);
gulp.task('styles', ['html', 'scss-lint', 'scss']);
@ -261,6 +263,7 @@ gulp.task('default', [
'ts-lint',
'system-build',
'html',
'json',
'images',
'scss-lint',
'fonts',

View File

@ -50,6 +50,7 @@ class Auth extends BaseController {
$opts->show_animations = true;
$opts->show_assignee = true;
$opts->multiple_tasks_per_row = false;
$opts->language = 'en';
R::store($opts);
$admin->user_option_id = $opts->id;

View File

@ -222,10 +222,12 @@ class BeanLoader {
? (boolean)$data->show_assignee : '';
$opts->multiple_tasks_per_row = isset($data->multiple_tasks_per_row)
? (boolean)$data->multiple_tasks_per_row : '';
$opts->language = isset($data->language)
? $data->language : '';
if (!isset($data->new_tasks_at_bottom) ||
!isset($data->show_animations) || !isset($data->show_assignee) ||
!isset($data->multiple_tasks_per_row)) {
!isset($data->multiple_tasks_per_row) || !isset($data->language)) {
return false;
}

View File

@ -85,7 +85,10 @@ export class ApiHttp extends Http {
return observable
.map((res: Response) => {
let response: ApiResponse = res.json();
localStorage.setItem(this.JWT_KEY, response.data[0]);
if (response.data) {
localStorage.setItem(this.JWT_KEY, response.data[0]);
}
return res;
})

View File

@ -1,11 +1,14 @@
import { Component } from '@angular/core';
import { Notifications } from './shared/index';
import { Notifications, StringsService } from './shared/index';
@Component({
selector: 'app-component',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
constructor(strings: StringsService) {
// Nothing needed
}
}

View File

@ -12,13 +12,14 @@ import { API_HTTP_PROVIDERS } from './app.api-http';
import { Constants } from './shared/constants';
import {
AuthGuard,
AuthService,
Notifications,
NotificationsService,
Modal,
ModalService,
InlineEdit,
TopNav
Modal,
Notifications,
TopNav,
AuthService,
ModalService,
NotificationsService,
StringsService
} from './shared/index';
import { BoardService } from './board/board.service';
@ -31,20 +32,21 @@ import { BoardService } from './board/board.service';
DragulaModule
],
providers: [
Title,
API_HTTP_PROVIDERS,
AuthGuard,
AuthService,
NotificationsService,
ModalService,
Constants,
BoardService
Title,
AuthService,
BoardService,
ModalService,
NotificationsService,
StringsService
],
declarations: [
AppComponent,
Notifications,
Modal,
InlineEdit,
Modal,
Notifications,
TopNav,
...ROUTE_COMPONENTS
],

View File

@ -43,17 +43,17 @@ const ROUTES: Routes = [
];
export const ROUTE_COMPONENTS = [
Login,
AutoActions,
BoardAdmin,
BoardDisplay,
Calendar,
Charts,
ColumnDisplay,
Dashboard,
Login,
Settings,
UserAdmin,
BoardAdmin,
UserSettings,
AutoActions,
Dashboard,
Charts,
Calendar
UserSettings
];
export const APP_ROUTING = RouterModule.forRoot(ROUTES);

View File

@ -1,7 +1,7 @@
<tb-top-nav page-name="{{ pageName }}"></tb-top-nav>
<div class="board-nav">
<label>
<label *ngIf="boards && boards.length">
Select Board:
<select [(ngModel)]="boardNavId"
(change)="goToBoard()">
@ -40,14 +40,14 @@
</div>
</div>
<div class="no-boards center" *ngIf="!loading && (!boards || boards.length === 0)">
<div class="no-boards center" *ngIf="noBoards()">
<h1>No Boards</h1>
<p>{{ noBoardsMessage }}</p>
</div>
<div class="no-boards center"
*ngIf="!activeBoard && !activeUser.default_board_id">
*ngIf="!loading && !activeBoard && !noBoards() && !activeUser.default_board_id">
<h1>No Default Board</h1>
<p>

View File

@ -90,15 +90,20 @@ export class BoardDisplay implements OnInit {
private updateBoardsList(boards: Array<any>): void {
let activeBoards: Array<Board> = [];
boards.forEach((board: any) => {
let currentBoard = new Board(+board.id, board.name,
board.is_active === '1', board.ownColumn,
board.ownCategory, board.ownAutoAction,
board.ownIssuetracker, board.sharedUser);
if (currentBoard.is_active) {
activeBoards.push(currentBoard);
}
});
if (boards) {
boards.forEach((board: any) => {
let currentBoard = new Board(+board.id, board.name,
board.is_active === '1',
board.ownColumn,
board.ownCategory,
board.ownAutoAction,
board.ownIssuetracker,
board.sharedUser);
if (currentBoard.is_active) {
activeBoards.push(currentBoard);
}
});
}
this.boards = activeBoards;
@ -142,5 +147,15 @@ export class BoardDisplay implements OnInit {
this.noBoardsMessage = 'Go to Settings to create a board.';
}
}
private noBoards(): boolean {
if (!this.loading) {
if (!this.boards || this.boards.length === 0) {
return true;
}
}
return false;
}
}

View File

@ -1,100 +1,112 @@
<section>
<h2>My Settings</h2>
<h2>{{ strings['settings_userSettings'] }}</h2>
<div class="half">
<h3>Change Password</h3>
<h3>{{ strings['settings_changePassword'] }}</h3>
<form>
<label for="currentPassword" class="hidden">Current Password</label>
<label for="currentPassword" class="hidden">
{{ strings['settings_currentPassword'] }}</label>
<input type="password" id="currentPassword" name="currentPassword"
placeholder="Current Password"
placeholder="{{ strings['settings_currentPassword'] }}"
[(ngModel)]="changePassword.current">
<label for="newPassword" class="hidden">New Password</label>
<label for="newPassword" class="hidden">
{{ strings['settings_newPassword'] }}</label>
<input type="password" id="newPassword" name="newPassword"
placeholder="New Password"
placeholder="{{ strings['settings_newPassword'] }}"
[(ngModel)]="changePassword.newPass">
<label for="verifyPassword" class="hidden">Verify Password</label>
<label for="verifyPassword" class="hidden">
{{ strings['settings_verifyPassword'] }}</label>
<input type="password" id="verifyPassword" name="verifyPassword"
placeholder="Verify New Password"
placeholder="{{ strings['settings_verifyPassword'] }}"
[(ngModel)]="changePassword.verPass">
<button type="submit" [disabled]="changePassword.submitted"
(click)="updatePassword()">Change Password</button>
(click)="updatePassword()">{{ strings['settings_changePassword'] }}</button>
<button class="flat" [disabled] = "changePassword.submitted"
(click)="resetPasswordForm()">Reset</button>
(click)="resetPasswordForm()">{{ strings['reset'] }}</button>
</form>
<h3 class="tall">Global Options</h3>
<h3 class="tall">{{ strings['settings_globalOptions'] }}</h3>
<label>
Select default board:
{{ strings['settings_displayLanguage'] }}:
<select class="autosize"
[ngModel]="userOptions.language"
(ngModelChange)="onOptionChange('language', $event)">
<option value="en">English</option>
<option value="es">Español</option>
</select>
</label>
<label>
{{ strings['settings_defaultBoard'] }}:
<select class="autosize"
[ngModel]="user.default_board_id"
(ngModelChange)="updateDefaultBoard($event)">
<option value="0">None</option>
<option value="0">{{ strings['none'] }}</option>
<option *ngFor="let board of boards" value="{{ board.id }}">
{{ board.name }}
</option>
</select>
</label>
<label>
New tasks appear at column
{{ strings['settings_newTasks'] }}:
<select class="autosize"
[ngModel]="userOptions.new_tasks_at_bottom"
(ngModelChange)="onOptionChange('new_tasks', $event)">
<option value="true">bottom</option>
<option value="false">top</option>
<option value="true">{{ strings['settings_bottom'] }}</option>
<option value="false">{{ strings['settings_top'] }}</option>
</select>
</label>
<label>
Display tasks side-by-side in colums?
<input type="checkbox" class="hidden"
[ngModel]="userOptions.multiple_tasks_per_row"
(ngModelChange)="onOptionChange('mult_tasks', $event)">
<span class="toggle"></span>
</label>
</div>
<div class="half">
<h3>Change Username</h3>
<h3>{{ strings['settings_changeUsername'] }}</h3>
<form>
<label for="username" class="hidden">New Username</label>
<label for="username" class="hidden">{{ strings['settings_newUsername'] }}</label>
<input type="text" id="username" name="username"
placeholder="New Username"
placeholder="{{ strings['settings_newUsername'] }}"
[(ngModel)]="changeUsername.newName">
<button type="submit" [disabled]="changeUsername.submitted"
(click)="updateUsername()">Change Username</button>
(click)="updateUsername()">{{ strings['settings_changeUsername'] }}</button>
<button class="flat" [disabled]="changeUsername.submitted"
(click)="resetUsernameForm()">Reset</button>
(click)="resetUsernameForm()">{{ strings['reset'] }}</button>
</form>
<form>
<h3 class="tall">Change Email</h3>
<h3 class="tall">{{ strings['settings_changeEmail'] }}</h3>
<label for="email" class="hidden">New Email</label>
<label for="email" class="hidden">{{ strings['settings_newEmail'] }}</label>
<input type="text" id="email" name="email"
placeholder="New Email - Blank to disable"
placeholder="{{ strings['settings_newEmail'] }} - {{ strings['settings_blank'] }}"
[(ngModel)]="changeEmail.newEmail">
<button type="submit" [disabled]="changeEmail.submitted"
(click)="updateEmail()">Change Email</button>
(click)="updateEmail()">{{ strings['settings_changeEmail'] }}</button>
<button class="flat" [disabled]="changeEmail.submitted"
(click)="resetEmailForm()">Reset</button>
(click)="resetEmailForm()">{{ strings['reset'] }}</button>
</form>
<div class="hold-bottom">
<label>
Show animations?
{{ strings['settings_optionsDisplay'] }}
<input type="checkbox" class="hidden"
[ngModel]="userOptions.multiple_tasks_per_row"
(ngModelChange)="onOptionChange('mult_tasks', $event)">
<span class="toggle"></span>
</label>
<label>
{{ strings['settings_optionsAnimate'] }}
<input type="checkbox" class="hidden"
[ngModel]="userOptions.show_animations"
(ngModelChange)="onOptionChange('show_anim', $event)">
<span class="toggle"></span>
</label>
<label>
Show Assignee on task cards?
{{ strings['settings_optionsAssignee'] }}
<input type="checkbox" class="hidden"
[ngModel]="userOptions.show_assignee"
(ngModelChange)="onOptionChange('show_assign', $event)">

View File

@ -4,13 +4,14 @@ import { SettingsService } from '../settings.service';
import { UserSettingsService } from './user-settings.service';
import { PassForm, UsernameForm, EmailForm } from './user-settings.models';
import {
ApiResponse,
Board,
Notification,
User,
UserOptions,
AuthService,
NotificationsService,
User,
Board,
UserOptions,
Notification,
ApiResponse
StringsService
} from '../../shared/index';
@Component({
@ -25,13 +26,16 @@ export class UserSettings implements OnInit {
private changePassword: PassForm;
private changeUsername: UsernameForm;
private changeEmail: EmailForm;
private strings: any;
constructor(private auth: AuthService,
private notes: NotificationsService,
private settings: SettingsService,
private users: UserSettingsService) {
private users: UserSettingsService,
private stringsService: StringsService) {
this.boards = [];
this.changeEmail = new EmailForm();
this.strings = {};
auth.userChanged.subscribe(user => {
this.user = user;
@ -39,6 +43,10 @@ export class UserSettings implements OnInit {
this.userOptions = auth.userOptions;
});
stringsService.stringsChanged.subscribe(newStrings => {
this.strings = newStrings;
});
settings.boardsChanged.subscribe(boards => {
this.boards = boards;
});
@ -62,6 +70,9 @@ export class UserSettings implements OnInit {
case 'show_assign':
this.userOptions.show_assignee = event;
break;
case 'language':
this.userOptions.language = event;
break;
}
this.updateUserOptions();
}
@ -174,6 +185,10 @@ export class UserSettings implements OnInit {
this.changeEmail = new EmailForm(this.user.email);
}
private getString(key: string): string {
return this.strings.getString(key);
}
private addAlerts(alerts: Array<Notification>) {
alerts.forEach(msg => {
this.notes.add(msg);

View File

@ -103,8 +103,8 @@ export class UserSettingsService {
.map(res => {
let response: ApiResponse = res.json();
this.auth.updateUser(JSON.parse(response.data[2]));
this.auth.updateUser(JSON.parse(response.data[2]),
JSON.parse(response.data[1]));
return response;
})
.catch((res, caught) => {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Http } from '@angular/http';
import { Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@ -8,8 +8,13 @@ import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { User, UserOptions, ApiResponse } from '../index';
import {
ApiResponse,
User,
UserOptions
} from '../index';
import { Constants } from '../constants';
import { StringsService } from '../strings/strings.service';
@Injectable()
export class AuthService {
@ -19,15 +24,16 @@ export class AuthService {
public userChanged = this.activeUser.asObservable();
constructor(constants: Constants, private http: Http,
private router: Router) {
private router: Router, private strings: StringsService) {
}
updateUser(user: User, userOpts?: UserOptions): void {
this.activeUser.next(user);
if (userOpts) {
this.userOptions = this.convertOpts(userOpts);
this.strings.loadStrings(this.userOptions.language);
}
this.activeUser.next(user);
}
authenticate(): Observable<boolean> {
@ -83,6 +89,7 @@ export class AuthService {
converted.show_animations = opts.show_animations === '1';
converted.show_assignee = opts.show_assignee === '1';
converted.multiple_tasks_per_row = opts.multiple_tasks_per_row === '1';
converted.language = opts.language;
return converted;
}

View File

@ -1,8 +1,9 @@
export * from './auth/index';
export * from './constants';
export * from './auth/index';
export * from './inline-edit/inline-edit.component';
export * from './modal/index';
export * from './models/index';
export * from './notifications/index';
export * from './strings/strings.service';
export * from './top-nav/top-nav.component';

View File

@ -1,8 +1,9 @@
import { Notification } from './notification.model';
export interface ApiResponse {
alerts: Array<Notification>;
data: Array<any>;
status: string;
export class ApiResponse {
constructor(public alerts: Array<Notification> = [],
public data: Array<any> = [],
public status: string = '') {
}
}

View File

@ -1,10 +1,6 @@
export class Notification {
type: string;
text: string;
constructor(type: string = '', text: string = '') {
this.type = type;
this.text = text;
constructor(public type: string = '',
public text: string = '') {
}
}

View File

@ -4,5 +4,6 @@ export interface UserOptions {
show_animations: boolean;
show_assignee: boolean;
multiple_tasks_per_row: boolean;
language: string;
}

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/finally';
@Injectable()
export class StringsService {
private strings = new BehaviorSubject<any>({});
public stringsChanged = this.strings.asObservable();
constructor(private http: Http) {
}
loadStrings(language: string): void {
this.http.get('strings/' + language + '.json')
.subscribe((res: any) => {
this.strings.next(res.json());
});
}
}

24
src/json/en.json Normal file
View File

@ -0,0 +1,24 @@
{
"reset": "Reset",
"none": "None",
"settings_userSettings": "My Settings",
"settings_changePassword": "Change Password",
"settings_currentPassword": "Current Password",
"settings_newPassword": "New Password",
"settings_verifyPassword": "Verify Password",
"settings_changeUsername": "Change Username",
"settings_newUsername": "New Username",
"settings_changeEmail": "Change Email",
"settings_newEmail": "New Email",
"settings_blank": "Blank to disable",
"settings_globalOptions": "Global Options",
"settings_displayLanguage": "Display Language",
"settings_defaultBoard": "Select default board",
"settings_newTasks": "New tasks appear at column",
"settings_bottom": "bottom",
"settings_top": "top",
"settings_optionsDisplay": "Display tasks side-by-side in columns?",
"settings_optionsAnimate": "Show animations?",
"settings_optionsAssignee": "Show Assignee on task cards?"
}

24
src/json/es.json Normal file
View File

@ -0,0 +1,24 @@
{
"reset": "Reiniciar",
"none": "Nada",
"settings_userSettings": "Mi Configuración",
"settings_changePassword": "Cambia la Contraseña",
"settings_currentPassword": "Contraseña Actual",
"settings_newPassword": "Contraseña Nueva",
"settings_verifyPassword": "Verficar Contraseña",
"settings_changeUsername": "Cambia el Nombre de Usuario",
"settings_newUsername": "Nuevo Nombre de Usuario",
"settings_changeEmail": "Cambia Email",
"settings_newEmail": "Nuevo Email",
"settings_blank": "En blanco para desactivar",
"settings_globalOptions": "Opciones Globales",
"settings_displayLanguage": "Idioma de la Pantalla",
"settings_defaultBoard": "Seleccionar Tablero Predeterminada",
"settings_newTasks": "Nuevas tareas aparecen en la columna",
"settings_bottom": "parte inferior",
"settings_top": "parte superior",
"settings_optionsDisplay": "¿Mostrar tareas lado a lado en columnas?",
"settings_optionsAnimate": "¿Mostrar animaciones?",
"settings_optionsAssignee": "¿Mostrar al cesionario en las tarjetas de tarea?"
}

View File

@ -0,0 +1,27 @@
/* global expect */
var path = '../../../../build/shared/models/',
ApiResponse = require(path + 'api-response.model.js').ApiResponse;
describe('ApiResponse', () => {
var apiResponse;
beforeEach(() => {
apiResponse = new ApiResponse();
});
it('has an alerts array', () => {
expect(apiResponse.alerts).to.be.an('array');
expect(apiResponse.alerts.length).to.equal(0);
});
it('has a data array', () => {
expect(apiResponse.data).to.be.an('array');
expect(apiResponse.data.length).to.equal(0);
});
it('has a status string', () => {
expect(apiResponse.status).to.be.a('string');
expect(apiResponse.status).to.equal('');
});
});

View File

@ -9,12 +9,12 @@ describe('Notification', () => {
notification = new Notification();
});
it('has type', () => {
it('has a type string', () => {
expect(notification.type).to.be.a('string');
expect(notification.type).to.equal('');
});
it('has text', () => {
it('has a text string', () => {
expect(notification.text).to.be.a('string');
expect(notification.text).to.equal('');
});