diff --git a/bower.json b/bower.json index 6f7e3adde..1f9d474fb 100644 --- a/bower.json +++ b/bower.json @@ -7,15 +7,11 @@ "jquery.cookie": "~1.4.1", "bootstrap": "~3.3.1", "datatables-bootstrap3-plugin": "~0.2.0", - "angular": "~1.3.11", + "angular": "~1.3.13", "angular-ui-router": "~0.2.13", + "angular-gettext": "~2.0.2", "js-data-angular": "~2.1.0", "sockjs": "~0.3.4", - "jed": "~1.1.0" - }, - "overrides": { - "jed": { - "main": "jed.js" - } + "font-awesome-bower": "4.3.0" } } diff --git a/gulpfile.js b/gulpfile.js index 2cdd0723e..7a69a1ed4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,6 +16,7 @@ var argv = require('yargs').argv, gulp = require('gulp'), concat = require('gulp-concat'), gulpif = require('gulp-if'), + gettext = require('gulp-angular-gettext'), mainBowerFiles = require('main-bower-files'), minifyCSS = require('gulp-minify-css'), path = require('path'), @@ -54,5 +55,24 @@ gulp.task('fonts', function() { .pipe(gulp.dest(path.join(output_directory, 'fonts'))); }); +// Extracts translatable strings using angular-gettext and saves them in file +// openslides/locale/angular-gettext/template-en.pot. +gulp.task('pot', function () { + return gulp.src(['openslides/*/static/templates/*/*.html', + 'openslides/*/static/js/*/*.js']) + .pipe(gettext.extract('template-en.pot', {})) + .pipe(gulp.dest('openslides/locale/angular-gettext/')); +}); + +// Compiles translation files (*.po) to *.json and saves them in the directory +// openslides/static/i18n/. +gulp.task('translations', function () { + return gulp.src('openslides/locale/angular-gettext/*.po') + .pipe(gettext.compile({ + format: 'json' + })) + .pipe(gulp.dest(path.join(output_directory, 'i18n'))); +}); + // Gulp default task. Runs all other tasks before. gulp.task('default', ['js', 'css', 'fonts'], function() {}); diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index ce7c324b3..64854327d 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -22,7 +22,14 @@ body { padding: 7px 20px 0; position: relative; } -#header .logo img { +.navbar-brand { + padding: 2px; + height: 40px; +} +.navbar-text { + margin-top: 9px; +} +#logo { height: 30px; padding-left: 3px; } @@ -46,6 +53,13 @@ h1 { margin: 0px 0 30px; padding-bottom: 9px; } +#submenu { + float: right; + font-size: 85%; + position: absolute; + top: 0; + right: 15px; +} a:hover { text-decoration: none; } @@ -118,6 +132,7 @@ tr.total td { .user_details label { font-weight: bold; margin: 10px 0 0 0; + display: block; } .user_details label:after { content: ":"; @@ -129,29 +144,11 @@ tr.total td { } /** Forms **/ -input, textarea { - width: 320px; -} -.small-form input { - width: 55px; -} -.normal-form input { - width: 320px; -} -textarea { - height: 100px; -} -.help-inline { - font-size: 11px; -} -.errorlist{ - margin: 0; -} -.errorlist li { - list-style: none outside none; -} -form .required label:after { - content: " *"; + +/* Fix the top position by using fa-icons + * in bootstrap's form elements with form-control-feedback */ +.has-feedback i.form-control-feedback { + top: 35px; } legend + .control-group { margin-top: 0px !important; diff --git a/openslides/core/static/js/app.js b/openslides/core/static/js/app.js index f8ef10063..1fe78a0bc 100644 --- a/openslides/core/static/js/app.js +++ b/openslides/core/static/js/app.js @@ -1,6 +1,7 @@ angular.module('OpenSlidesApp', [ 'ui.router', 'js-data', + 'gettext', 'OpenSlidesApp.core', 'OpenSlidesApp.agenda', 'OpenSlidesApp.assignments', diff --git a/openslides/core/static/js/core.js b/openslides/core/static/js/core.js index b4d13eb73..58d5d49db 100644 --- a/openslides/core/static/js/core.js +++ b/openslides/core/static/js/core.js @@ -115,16 +115,6 @@ angular.module('OpenSlidesApp.core', []) }); }) -.run(function($rootScope, i18n) { - // Puts the gettext methods into each scope. - // Uses the methods that are known by xgettext by default. - methods = ['gettext', 'dgettext', 'dcgettext', 'ngettext', 'dngettext', - 'pgettext', 'dpgettext']; - _.forEach(methods, function(method) { - $rootScope[method] = _.bind(i18n[method], i18n); - }); -}) - .run(function($rootScope, Config) { // Puts the config object into each scope. // TODO: maybe rootscope.config has to set before findAll() is finished @@ -164,25 +154,39 @@ angular.module('OpenSlidesApp.core', []) return Autoupdate; }) -.factory('i18n', function($http) { - // TODO: there is a bug(?) in jed. I had to call val_idx++; in line 285 - // TODO: make the language variable and changeable at runtime - var i18n = new Jed({ - 'domain': 'de', - 'locale_data': {'de': {"": {}}}, - }); // TODO: use promise here - $http.get('/static/i18n/de.json') - .success(function(data) { - // TODO: check data. - i18n.options.locale_data['de'] = data; - }); - return i18n; -}) - .factory('Config', function(DS) { return DS.defineResource({ name: 'config/config', idAttribute: 'key', endpoint: '/rest/config/config/' }); +}) + +.controller("LanguageCtrl", function ($scope, gettextCatalog) { + // controller to switch app language + // TODO: detect browser language for default language + gettextCatalog.setCurrentLanguage('en'); + //TODO: for debug only! (helps to find untranslated strings by adding "[MISSING]:") + gettextCatalog.debug = true; + $scope.switchLanguage = function (lang) { + gettextCatalog.setCurrentLanguage(lang); + if (lang != 'en') { + gettextCatalog.loadRemote("static/i18n/" + lang + ".json"); + } + } +}) + +.directive('osFocusMe', function ($timeout) { + return { + link: function (scope, element, attrs, model) { + $timeout(function () { + element[0].focus(); + }); + } + }; +}); + +// some general JavaScript functions used in all OpenSlides apps +$(function () { + $('[data-toggle="tooltip"]').tooltip({'placement': 'bottom'}) }); diff --git a/openslides/core/static/templates/dashboard.html b/openslides/core/static/templates/dashboard.html index 24c08a0ea..af2eeeb59 100644 --- a/openslides/core/static/templates/dashboard.html +++ b/openslides/core/static/templates/dashboard.html @@ -1,4 +1,6 @@