From 48f019236ea0a572aa38bcbb8164b7667fad27fc Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 29 Jul 2014 15:33:31 +0100 Subject: [PATCH 01/26] added first translations --- .../web/app/views/project/list/side-bar.jade | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.jade index 7be015e7b0..862da5e471 100644 --- a/services/web/app/views/project/list/side-bar.jade +++ b/services/web/app/views/project/list/side-bar.jade @@ -2,52 +2,52 @@ a.btn.btn-primary.dropdown-toggle( href="#", data-toggle="dropdown" - ) New Project + ) #{translate("new_project")} ul.dropdown-menu(role="menu") li a( href, ng-click="openCreateProjectModal()" - ) Blank Project + ) #{translate("blank_project")} li a( href, ng-click="openCreateProjectModal('example')" - ) Example Project + ) #{translate("example_project")} li a( href, ng-click="openUploadProjectModal()" - ) Upload Project + ) #{translate("upload_project")} li.divider - li.dropdown-header Templates + li.dropdown-header #{translate("templates")} li - a.menu-indent(href="/templates/cv") CV or Resume + a.menu-indent(href="/templates/cv") #{translate("cv_or_resume")} li - a.menu-indent(href="/templates/cover-letters") Cover Letter + a.menu-indent(href="/templates/cover-letters") #{translate("cover_letter")} li - a.menu-indent(href="/templates/journals") Journal Article + a.menu-indent(href="/templates/journals") #{translate("journal_article")} li - a.menu-indent(href="/templates/presentations") Presentation + a.menu-indent(href="/templates/presentations") #{translate("presentation")} li - a.menu-indent(href="/templates/thesis") Thesis + a.menu-indent(href="/templates/thesis") #{translate("thesis")} li - a.menu-indent(href="/templates/bibliographies") Bibliographies + a.menu-indent(href="/templates/bibliographies") #{translate("bibliographies")} li - a.menu-indent(href="/templates") View All » + a.menu-indent(href="/templates") #{translate("view_all")} » .row-spaced(ng-if="projects.length > 0", ng-cloak) ul.list-unstyled.folders-menu( ng-controller="TagListController" ) li(ng-class="{active: (filter == 'all')}") - a(href, ng-click="filterProjects('all')") All projects + a(href, ng-click="filterProjects('all')") #{translate("all_projects")} li(ng-class="{active: (filter == 'owned')}") - a(href, ng-click="filterProjects('owned')") Your projects + a(href, ng-click="filterProjects('owned')") #{translate("your_projects")} li(ng-class="{active: (filter == 'shared')}") - a(href, ng-click="filterProjects('shared')") Shared with you + a(href, ng-click="filterProjects('shared')") #{translate("shared_with_you")} li(ng-class="{active: (filter == 'archived')}") - a(href, ng-click="filterProjects('archived')") Deleted projects + a(href, ng-click="filterProjects('archived')") #{translate("deleted_projects")} li h2 Folders li( @@ -67,14 +67,14 @@ li(ng-cloak) a.tag(href, ng-click="openNewTagModal()") i.fa.fa-fw.fa-plus - span.name New Folder + span.name #{translate("new_folder")} .row-spaced(ng-if="projects.length == 0", ng-cloak) .first-project div i.fa.fa-arrow-up.fa-2x div - strong Create your first project! + strong #{translate("create_your_first_project")} - if (showUserDetailsArea) .row-spaced#userProfileInformation(ng-if="projects.length > 0", ng-cloak) @@ -85,14 +85,12 @@ .progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}") p.small - | Your profile is - strong {{percentComplete}}% - | complete + | #{translate("profile_complete_percentage", {percent:FIX ME})} button#completeUserProfileInformation.btn.btn-info( ng-hide="formVisable", ng-click="openUserProfileModal()" - ) Complete + ) #{translate("complete")} -if (settings.enableSubscriptions && !hasSubscription) .row-spaced(ng-if="projects.length > 0", ng-cloak).text-centered From bdf1fed4626a53256eb72460d482faf474a9d1bd Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 30 Jul 2014 14:22:36 +0100 Subject: [PATCH 02/26] jquery translations work --- .../infrastructure/ExpressLocals.coffee | 3 +++ .../app/coffee/infrastructure/Server.coffee | 4 ++++ services/web/app/views/layout.jade | 7 +++++++ services/web/app/views/project/list.jade | 14 +++++++++++++ .../web/app/views/project/list/side-bar.jade | 5 +++-- services/web/public/coffee/libs.coffee | 4 ++++ .../public/coffee/main/project-list.coffee | 4 +--- .../coffee/utils/i18nextProvider.coffee | 20 +++++++++++++++++++ 8 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 services/web/public/coffee/utils/i18nextProvider.coffee diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 34035e66fe..0dbacd28ca 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -50,6 +50,9 @@ module.exports = (app)-> next() app.use (req, res, next)-> + console.log req.i18n + console.log req.i18n.t("profile_complete_percentage", {percent:88}) + console.log req.i18n.translate("profile_complete_percentage", {percent:87}) res.locals.translate = req.i18n.translate next() diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index 39bf636e91..27ec298946 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -40,6 +40,8 @@ ignoreCsrfRoutes = [] app.ignoreCsrf = (method, route) -> ignoreCsrfRoutes.push new express.Route(method, route) + + app.configure () -> if Settings.behindProxy app.enable('trust proxy') @@ -71,6 +73,8 @@ app.configure () -> app.use ReferalConnect.use app.use express.methodOverride() + app.use translations.serverStaticFiles + expressLocals(app) diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index 0f38f197ca..c1ed572b3a 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -3,6 +3,8 @@ html(itemscope, itemtype='http://schema.org/Product') block vars head + + - if (typeof(priority_title) !== "undefined" && priority_title) title= title + ' - Online LaTeX Editor ShareLaTeX' - else @@ -34,6 +36,11 @@ html(itemscope, itemtype='http://schema.org/Product') block scripts script(src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.14/angular.min.js") + script(src="/js/libs/i18next.js") + //- script(src="/js/libs/ng-i18next/provider.js") + //- script(src="/js/libs/ng-i18next/filter/filter.js") + //- script(src="/js/libs/ng-i18next/directive/directive.js") + script. window.sharelatex = { diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index 06a0024c98..7ff274fb06 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -3,6 +3,9 @@ extends ../layout block content //- We need to do .replace(/\//g, '\\/') do that '' -> '<\/script>' //- and doesn't prematurely end the script tag. + + + script(type="text/javascript"). window.data = { projects: !{JSON.stringify(projects).replace(/\//g, '\\/')}, @@ -15,6 +18,17 @@ block content } }; + + + $.i18n.init({ + lng: 'en-gb', + fallbackLng: 'en', + resGetPath: 'locales/__lng__.json' + }, function() { + console.log($.t('new_project')); // -> i18n (from en-US resourcefile) + }); + + .content.content-alt(ng-controller="ProjectPageController") .container .row diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.jade index 862da5e471..0d23c900d5 100644 --- a/services/web/app/views/project/list/side-bar.jade +++ b/services/web/app/views/project/list/side-bar.jade @@ -2,7 +2,8 @@ a.btn.btn-primary.dropdown-toggle( href="#", data-toggle="dropdown" - ) #{translate("new_project")} + ) + p(ng-i18next="new_project") ul.dropdown-menu(role="menu") li a( @@ -85,7 +86,7 @@ .progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}") p.small - | #{translate("profile_complete_percentage", {percent:FIX ME})} + | #{translate("profile_complete_percentage", {percent:"FIX ME"})} button#completeUserProfileInformation.btn.btn-info( ng-hide="formVisable", diff --git a/services/web/public/coffee/libs.coffee b/services/web/public/coffee/libs.coffee index b25e273586..3a21822e03 100644 --- a/services/web/public/coffee/libs.coffee +++ b/services/web/public/coffee/libs.coffee @@ -8,4 +8,8 @@ define [ "libs/jquery.storage" "libs/fineuploader" "libs/angular-sanitize-1.2.17" + "libs/i18next" + "libs/ng-i18next/provider" + "libs/ng-i18next/directive/directive" + "libs/ng-i18next/filter/filter" ], () -> diff --git a/services/web/public/coffee/main/project-list.coffee b/services/web/public/coffee/main/project-list.coffee index f18b9eda6d..aadd94c568 100644 --- a/services/web/public/coffee/main/project-list.coffee +++ b/services/web/public/coffee/main/project-list.coffee @@ -1,7 +1,7 @@ define [ "base" ], (App) -> - App.factory "queuedHttp", ["$http", "$q", ($http, $q) -> + App.factory "queuedHttp", ($http, $q) -> pendingRequests = [] inflight = false @@ -48,8 +48,6 @@ define [ return queuedHttp - ] - App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout) -> $scope.projects = window.data.projects $scope.tags = window.data.tags diff --git a/services/web/public/coffee/utils/i18nextProvider.coffee b/services/web/public/coffee/utils/i18nextProvider.coffee new file mode 100644 index 0000000000..f536f3bd05 --- /dev/null +++ b/services/web/public/coffee/utils/i18nextProvider.coffee @@ -0,0 +1,20 @@ +define [ + +], () -> + + + console.log "hello", angular.module('jm.i18next') + angular.module('jm.i18next').config ($i18nextProvider)-> + console.log "hello 222" + $i18nextProvider.options = { + lng: 'en-GB', + useCookie: false, + useLocalStorage: false, + fallbackLng: 'dev', + resGetPath: '../locales/__lng__/__ns__.json', + defaultLoadingValue: '' # ng-i18next option, *NOT* directly supported by i18next + } + console.log "SUP" + console.log $i18nextProvider + + From 1a8c3d17b2e0955ae9723559844b8f49233ea3c7 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 30 Jul 2014 15:05:13 +0100 Subject: [PATCH 03/26] working example of directive for i18n new project button --- services/web/app/views/layout.jade | 6 +++--- services/web/app/views/project/list.jade | 11 ---------- services/web/public/coffee/base.coffee | 1 + .../public/coffee/main/project-list.coffee | 16 ++++++++++++++- .../coffee/utils/i18nextProvider.coffee | 20 ------------------- 5 files changed, 19 insertions(+), 35 deletions(-) diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index c1ed572b3a..86e30d68b1 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -37,9 +37,9 @@ html(itemscope, itemtype='http://schema.org/Product') script(src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.14/angular.min.js") script(src="/js/libs/i18next.js") - //- script(src="/js/libs/ng-i18next/provider.js") - //- script(src="/js/libs/ng-i18next/filter/filter.js") - //- script(src="/js/libs/ng-i18next/directive/directive.js") + script(src="/js/libs/ng-i18next/provider.js") + script(src="/js/libs/ng-i18next/filter/filter.js") + script(src="/js/libs/ng-i18next/directive/directive.js") script. diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index 7ff274fb06..7544da209c 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -18,17 +18,6 @@ block content } }; - - - $.i18n.init({ - lng: 'en-gb', - fallbackLng: 'en', - resGetPath: 'locales/__lng__.json' - }, function() { - console.log($.t('new_project')); // -> i18n (from en-US resourcefile) - }); - - .content.content-alt(ng-controller="ProjectPageController") .container .row diff --git a/services/web/public/coffee/base.coffee b/services/web/public/coffee/base.coffee index 90d522ead8..8c60e29857 100644 --- a/services/web/public/coffee/base.coffee +++ b/services/web/public/coffee/base.coffee @@ -10,6 +10,7 @@ define [ "ng-context-menu" "underscore" "ngSanitize" + "jm.i18next" ]) return App \ No newline at end of file diff --git a/services/web/public/coffee/main/project-list.coffee b/services/web/public/coffee/main/project-list.coffee index aadd94c568..c526b8b68c 100644 --- a/services/web/public/coffee/main/project-list.coffee +++ b/services/web/public/coffee/main/project-list.coffee @@ -1,6 +1,19 @@ define [ "base" ], (App) -> + + + angular.module('jm.i18next').config ['$i18nextProvider', ($i18nextProvider)-> + console.log("hello") + $i18nextProvider.options = + lng: 'en-GB', + useCookie: false, + useLocalStorage: false, + fallbackLng: 'en', + resGetPath: '/locales/__lng__.json' + ] + + App.factory "queuedHttp", ($http, $q) -> pendingRequests = [] inflight = false @@ -48,7 +61,8 @@ define [ return queuedHttp - App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout) -> + App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout, $i18next) -> + console.log $i18next, $i18next("new_project"), "- NEW PROJECT" $scope.projects = window.data.projects $scope.tags = window.data.tags $scope.allSelected = false diff --git a/services/web/public/coffee/utils/i18nextProvider.coffee b/services/web/public/coffee/utils/i18nextProvider.coffee index f536f3bd05..e69de29bb2 100644 --- a/services/web/public/coffee/utils/i18nextProvider.coffee +++ b/services/web/public/coffee/utils/i18nextProvider.coffee @@ -1,20 +0,0 @@ -define [ - -], () -> - - - console.log "hello", angular.module('jm.i18next') - angular.module('jm.i18next').config ($i18nextProvider)-> - console.log "hello 222" - $i18nextProvider.options = { - lng: 'en-GB', - useCookie: false, - useLocalStorage: false, - fallbackLng: 'dev', - resGetPath: '../locales/__lng__/__ns__.json', - defaultLoadingValue: '' # ng-i18next option, *NOT* directly supported by i18next - } - console.log "SUP" - console.log $i18nextProvider - - From 7344f745e4de376ab8383c169b5036e7c2707028 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 30 Jul 2014 16:04:26 +0100 Subject: [PATCH 04/26] example of interpolation working --- services/web/app/views/project/list/side-bar.jade | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.jade index 0d23c900d5..2bc524cd9e 100644 --- a/services/web/app/views/project/list/side-bar.jade +++ b/services/web/app/views/project/list/side-bar.jade @@ -85,8 +85,7 @@ .progress .progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}") - p.small - | #{translate("profile_complete_percentage", {percent:"FIX ME"})} + p.small(ng-i18next="[i18next]({percent:percentComplete})profile_complete_percentage") button#completeUserProfileInformation.btn.btn-info( ng-hide="formVisable", From 9631d4da984801e6e0675657d8f5b3a6f251b593 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 30 Jul 2014 16:06:53 +0100 Subject: [PATCH 05/26] much simpler version of interpolation --- services/web/app/views/project/list/side-bar.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.jade index 2bc524cd9e..87610b33d4 100644 --- a/services/web/app/views/project/list/side-bar.jade +++ b/services/web/app/views/project/list/side-bar.jade @@ -85,8 +85,8 @@ .progress .progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}") - p.small(ng-i18next="[i18next]({percent:percentComplete})profile_complete_percentage") - + p.small #{translate("profile_complete_percentage")} + button#completeUserProfileInformation.btn.btn-info( ng-hide="formVisable", ng-click="openUserProfileModal()" From 49c7d5220830da22a4e8952365014cdc65412fc4 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 30 Jul 2014 16:13:02 +0100 Subject: [PATCH 06/26] i18n is cleaned and working with requirejs --- services/web/app/views/layout.jade | 5 ----- services/web/public/coffee/main.coffee | 2 +- services/web/public/coffee/main/project-list.coffee | 11 ----------- .../web/public/coffee/utils/i18nextProvider.coffee | 9 +++++++++ 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index 86e30d68b1..47cc832bfa 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -36,11 +36,6 @@ html(itemscope, itemtype='http://schema.org/Product') block scripts script(src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js") script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.14/angular.min.js") - script(src="/js/libs/i18next.js") - script(src="/js/libs/ng-i18next/provider.js") - script(src="/js/libs/ng-i18next/filter/filter.js") - script(src="/js/libs/ng-i18next/directive/directive.js") - script. window.sharelatex = { diff --git a/services/web/public/coffee/main.coffee b/services/web/public/coffee/main.coffee index f8ea3aa273..4ac69ae83e 100644 --- a/services/web/public/coffee/main.coffee +++ b/services/web/public/coffee/main.coffee @@ -18,6 +18,6 @@ define [ "directives/selectAll" "directives/maxHeight" "filters/formatDate" - + "utils/i18nextProvider" ], () -> angular.bootstrap(document.body, ["SharelatexApp"]) diff --git a/services/web/public/coffee/main/project-list.coffee b/services/web/public/coffee/main/project-list.coffee index c526b8b68c..0a958ab36c 100644 --- a/services/web/public/coffee/main/project-list.coffee +++ b/services/web/public/coffee/main/project-list.coffee @@ -3,17 +3,6 @@ define [ ], (App) -> - angular.module('jm.i18next').config ['$i18nextProvider', ($i18nextProvider)-> - console.log("hello") - $i18nextProvider.options = - lng: 'en-GB', - useCookie: false, - useLocalStorage: false, - fallbackLng: 'en', - resGetPath: '/locales/__lng__.json' - ] - - App.factory "queuedHttp", ($http, $q) -> pendingRequests = [] inflight = false diff --git a/services/web/public/coffee/utils/i18nextProvider.coffee b/services/web/public/coffee/utils/i18nextProvider.coffee index e69de29bb2..27b02d0304 100644 --- a/services/web/public/coffee/utils/i18nextProvider.coffee +++ b/services/web/public/coffee/utils/i18nextProvider.coffee @@ -0,0 +1,9 @@ +angular.module('jm.i18next').config ['$i18nextProvider', ($i18nextProvider)-> + console.log("hello") + $i18nextProvider.options = + lng: 'en-GB', + useCookie: false, + useLocalStorage: false, + fallbackLng: 'en', + resGetPath: '/locales/__lng__.json' +] \ No newline at end of file From 270c92c2a28330fc108c22b8ddd698c45208f589 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 30 Jul 2014 17:17:13 +0100 Subject: [PATCH 07/26] converted project list page added missing js files --- .../app/views/project/list/project-list.jade | 34 +- .../web/app/views/project/list/side-bar.jade | 13 +- services/web/public/js/libs/i18next.js | 2734 +++++++++++++++++ .../js/libs/ng-i18next/directive/directive.js | 185 ++ .../js/libs/ng-i18next/filter/filter.js | 12 + .../web/public/js/libs/ng-i18next/index.js | 8 + .../web/public/js/libs/ng-i18next/provider.js | 123 + 7 files changed, 3086 insertions(+), 23 deletions(-) create mode 100644 services/web/public/js/libs/i18next.js create mode 100755 services/web/public/js/libs/ng-i18next/directive/directive.js create mode 100755 services/web/public/js/libs/ng-i18next/filter/filter.js create mode 100644 services/web/public/js/libs/ng-i18next/index.js create mode 100755 services/web/public/js/libs/ng-i18next/provider.js diff --git a/services/web/app/views/project/list/project-list.jade b/services/web/app/views/project/list/project-list.jade index 14ab4fc3ce..2e8f313e12 100644 --- a/services/web/app/views/project/list/project-list.jade +++ b/services/web/app/views/project/list/project-list.jade @@ -52,7 +52,7 @@ role="menu" ng-controller="TagListController" ) - li.dropdown-header Add to folder + li.dropdown-header #{translate("add_to_folder")} li( ng-repeat="tag in tags | filter:nonEmpty | orderBy:'name'", ng-controller="TagDropdownItemController" @@ -68,25 +68,25 @@ | {{tag.name}} li.divider li - a(href="#", ng-click="openNewTagModal()", stop-propagation="click") Create New Folder + a(href="#", ng-click="openNewTagModal()", stop-propagation="click") #{translate("create_new_folder")} .btn-group(ng-hide="selectedProjects.length != 1").dropdown a.btn.btn-default.dropdown-toggle( href='#', data-toggle="dropdown" - ) More + ) #{translate("more")} span.caret ul.dropdown-menu.dropdown-menu-right(role="menu") li(ng-show="getFirstSelectedProject().accessLevel == 'owner'") a( href='#', ng-click="openRenameProjectModal()" - ) Rename + ) #{translate("rename")} li a( href='#', ng-click="openCloneProjectModal()" - ) Make a copy + ) #{translate("make_copy")} .btn-toolbar(ng-show="filter == 'archived'") .btn-group(ng-hide="selectedProjects.length < 1") @@ -96,7 +96,7 @@ data-toggle="tooltip", data-placement="bottom", ng-click="restoreSelectedProjects()" - ) Restore + ) #{translate("restore")} .btn-group(ng-hide="selectedProjects.length < 1") a.btn.btn-danger( @@ -105,7 +105,7 @@ data-toggle="tooltip", data-placement="bottom", ng-click="openDeleteProjectsModal()" - ) Delete Forever + ) #{translate("delete_forever")} .row.row-spaced .col-xs-12 @@ -123,13 +123,13 @@ select-all, type="checkbox" ) - span.header.clickable Title + span.header.clickable #{translate("title")} i.tablesort.fa(ng-class="getSortIconClass('name')") .col-xs-2(ng-click="changePredicate('accessLevel')") - span.header.clickable Owner + span.header.clickable #{translate("owner")} i.tablesort.fa(ng-class="getSortIconClass('accessLevel')") .col-xs-4(ng-click="changePredicate('lastUpdated')") - span.header.clickable Last Modified + span.header.clickable #{translate("last_modified")} i.tablesort.fa(ng-class="getSortIconClass('lastUpdated')") li.project_entry.container-fluid( ng-repeat="project in visibleProjects | orderBy:predicate:reverse", @@ -162,13 +162,13 @@ ) .row .col-xs-12.text-centered - small No projects + small #{translate("no_projects")} div.welcome.text-centered(ng-if="projects.length == 0", ng-cloak) - h2 Welcome to ShareLaTeX! - p New to LaTeX? Start by having a look at our - a(href="/templates") templates - | or - a(href="/learn") help guides + h2 #{translate("welcome_to_sl")} + p #{translate("new_to_latex_look_at")} + a(href="/templates") #{translate("templates").toLowerCase()} + | #{translate("or")} + a(href="/learn") #{translate("latex_help_guide")} | , br - | or create your first project on the left. \ No newline at end of file + | #{translate("or_create_project_left")} \ No newline at end of file diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.jade index 87610b33d4..cf3d95dbb5 100644 --- a/services/web/app/views/project/list/side-bar.jade +++ b/services/web/app/views/project/list/side-bar.jade @@ -2,8 +2,9 @@ a.btn.btn-primary.dropdown-toggle( href="#", data-toggle="dropdown" - ) - p(ng-i18next="new_project") + ) + | #{translate("new_project")} + ul.dropdown-menu(role="menu") li a( @@ -95,9 +96,9 @@ -if (settings.enableSubscriptions && !hasSubscription) .row-spaced(ng-if="projects.length > 0", ng-cloak).text-centered hr - p.small You are using the free version of ShareLaTeX. + p.small #{translate("on_free_sl")} p - a(href="/user/subscription/plans").btn.btn-primary Upgrade + a(href="/user/subscription/plans").btn.btn-primary #{translate("upgrade")} p.small - | or unlock some free bonus features by - a(href="/user/bonus") sharing ShareLaTeX. \ No newline at end of file + | #{translate("or_unlock_features_bonus")} + a(href="/user/bonus") #{translate("sharing_sl")} . \ No newline at end of file diff --git a/services/web/public/js/libs/i18next.js b/services/web/public/js/libs/i18next.js new file mode 100644 index 0000000000..7d12e279bd --- /dev/null +++ b/services/web/public/js/libs/i18next.js @@ -0,0 +1,2734 @@ +// i18next, v1.7.3 +// Copyright (c)2014 Jan Mühlemann (jamuhl). +// Distributed under MIT license +// http://i18next.com +(function() { + + // add indexOf to non ECMA-262 standard compliant browsers + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { + "use strict"; + if (this == null) { + throw new TypeError(); + } + var t = Object(this); + var len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n != n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n != 0 && n != Infinity && n != -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + } + } + + // add lastIndexOf to non ECMA-262 standard compliant browsers + if (!Array.prototype.lastIndexOf) { + Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) { + "use strict"; + if (this == null) { + throw new TypeError(); + } + var t = Object(this); + var len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = len; + if (arguments.length > 1) { + n = Number(arguments[1]); + if (n != n) { + n = 0; + } else if (n != 0 && n != (1 / 0) && n != -(1 / 0)) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + var k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); + for (; k >= 0; k--) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + }; + } + + // Add string trim for IE8. + if (typeof String.prototype.trim !== 'function') { + String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g, ''); + } + } + + var root = this + , $ = root.jQuery || root.Zepto + , i18n = {} + , resStore = {} + , currentLng + , replacementCounter = 0 + , languages = [] + , initialized = false; + + + // Export the i18next object for **CommonJS**. + // If we're not in CommonJS, add `i18n` to the + // global object or to jquery. + if (typeof module !== 'undefined' && module.exports) { + module.exports = i18n; + } else { + if ($) { + $.i18n = $.i18n || i18n; + } + + root.i18n = root.i18n || i18n; + } + // defaults + var o = { + lng: undefined, + load: 'all', + preload: [], + lowerCaseLng: false, + returnObjectTrees: false, + fallbackLng: ['dev'], + fallbackNS: [], + detectLngQS: 'setLng', + ns: 'translation', + fallbackOnNull: true, + fallbackOnEmpty: false, + fallbackToDefaultNS: false, + nsseparator: ':', + keyseparator: '.', + selectorAttr: 'data-i18n', + debug: false, + + resGetPath: 'locales/__lng__/__ns__.json', + resPostPath: 'locales/add/__lng__/__ns__', + + getAsync: true, + postAsync: true, + + resStore: undefined, + useLocalStorage: false, + localStorageExpirationTime: 7*24*60*60*1000, + + dynamicLoad: false, + sendMissing: false, + sendMissingTo: 'fallback', // current | all + sendType: 'POST', + + interpolationPrefix: '__', + interpolationSuffix: '__', + reusePrefix: '$t(', + reuseSuffix: ')', + pluralSuffix: '_plural', + pluralNotFound: ['plural_not_found', Math.random()].join(''), + contextNotFound: ['context_not_found', Math.random()].join(''), + escapeInterpolation: false, + + setJqueryExt: true, + defaultValueFromContent: true, + useDataAttrOptions: false, + cookieExpirationTime: undefined, + useCookie: true, + cookieName: 'i18next', + cookieDomain: undefined, + + objectTreeKeyHandler: undefined, + postProcess: undefined, + parseMissingKey: undefined, + + shortcutFunction: 'sprintf' // or: defaultValue + }; + function _extend(target, source) { + if (!source || typeof source === 'function') { + return target; + } + + for (var attr in source) { target[attr] = source[attr]; } + return target; + } + + function _each(object, callback, args) { + var name, i = 0, + length = object.length, + isObj = length === undefined || Object.prototype.toString.apply(object) !== '[object Array]' || typeof object === "function"; + + if (args) { + if (isObj) { + for (name in object) { + if (callback.apply(object[name], args) === false) { + break; + } + } + } else { + for ( ; i < length; ) { + if (callback.apply(object[i++], args) === false) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if (isObj) { + for (name in object) { + if (callback.call(object[name], name, object[name]) === false) { + break; + } + } + } else { + for ( ; i < length; ) { + if (callback.call(object[i], i, object[i++]) === false) { + break; + } + } + } + } + + return object; + } + + var _entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': '"', + "'": ''', + "/": '/' + }; + + function _escape(data) { + if (typeof data === 'string') { + return data.replace(/[&<>"'\/]/g, function (s) { + return _entityMap[s]; + }); + }else{ + return data; + } + } + + function _ajax(options) { + + // v0.5.0 of https://github.com/goloroden/http.js + var getXhr = function (callback) { + // Use the native XHR object if the browser supports it. + if (window.XMLHttpRequest) { + return callback(null, new XMLHttpRequest()); + } else if (window.ActiveXObject) { + // In Internet Explorer check for ActiveX versions of the XHR object. + try { + return callback(null, new ActiveXObject("Msxml2.XMLHTTP")); + } catch (e) { + return callback(null, new ActiveXObject("Microsoft.XMLHTTP")); + } + } + + // If no XHR support was found, throw an error. + return callback(new Error()); + }; + + var encodeUsingUrlEncoding = function (data) { + if(typeof data === 'string') { + return data; + } + + var result = []; + for(var dataItem in data) { + if(data.hasOwnProperty(dataItem)) { + result.push(encodeURIComponent(dataItem) + '=' + encodeURIComponent(data[dataItem])); + } + } + + return result.join('&'); + }; + + var utf8 = function (text) { + text = text.replace(/\r\n/g, '\n'); + var result = ''; + + for(var i = 0; i < text.length; i++) { + var c = text.charCodeAt(i); + + if(c < 128) { + result += String.fromCharCode(c); + } else if((c > 127) && (c < 2048)) { + result += String.fromCharCode((c >> 6) | 192); + result += String.fromCharCode((c & 63) | 128); + } else { + result += String.fromCharCode((c >> 12) | 224); + result += String.fromCharCode(((c >> 6) & 63) | 128); + result += String.fromCharCode((c & 63) | 128); + } + } + + return result; + }; + + var base64 = function (text) { + var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + text = utf8(text); + var result = '', + chr1, chr2, chr3, + enc1, enc2, enc3, enc4, + i = 0; + + do { + chr1 = text.charCodeAt(i++); + chr2 = text.charCodeAt(i++); + chr3 = text.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if(isNaN(chr2)) { + enc3 = enc4 = 64; + } else if(isNaN(chr3)) { + enc4 = 64; + } + + result += + keyStr.charAt(enc1) + + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + + keyStr.charAt(enc4); + chr1 = chr2 = chr3 = ''; + enc1 = enc2 = enc3 = enc4 = ''; + } while(i < text.length); + + return result; + }; + + var mergeHeaders = function () { + // Use the first header object as base. + var result = arguments[0]; + + // Iterate through the remaining header objects and add them. + for(var i = 1; i < arguments.length; i++) { + var currentHeaders = arguments[i]; + for(var header in currentHeaders) { + if(currentHeaders.hasOwnProperty(header)) { + result[header] = currentHeaders[header]; + } + } + } + + // Return the merged headers. + return result; + }; + + var ajax = function (method, url, options, callback) { + // Adjust parameters. + if(typeof options === 'function') { + callback = options; + options = {}; + } + + // Set default parameter values. + options.cache = options.cache || false; + options.data = options.data || {}; + options.headers = options.headers || {}; + options.jsonp = options.jsonp || false; + options.async = options.async === undefined ? true : options.async; + + // Merge the various header objects. + var headers = mergeHeaders({ + 'accept': '*/*', + 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8' + }, ajax.headers, options.headers); + + // Encode the data according to the content-type. + var payload; + if (headers['content-type'] === 'application/json') { + payload = JSON.stringify(options.data); + } else { + payload = encodeUsingUrlEncoding(options.data); + } + + // Specially prepare GET requests: Setup the query string, handle caching and make a JSONP call + // if neccessary. + if(method === 'GET') { + // Setup the query string. + var queryString = []; + if(payload) { + queryString.push(payload); + payload = null; + } + + // Handle caching. + if(!options.cache) { + queryString.push('_=' + (new Date()).getTime()); + } + + // If neccessary prepare the query string for a JSONP call. + if(options.jsonp) { + queryString.push('callback=' + options.jsonp); + queryString.push('jsonp=' + options.jsonp); + } + + // Merge the query string and attach it to the url. + queryString = queryString.join('&'); + if (queryString.length > 1) { + if (url.indexOf('?') > -1) { + url += '&' + queryString; + } else { + url += '?' + queryString; + } + } + + // Make a JSONP call if neccessary. + if(options.jsonp) { + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + head.appendChild(script); + return; + } + } + + // Since we got here, it is no JSONP request, so make a normal XHR request. + getXhr(function (err, xhr) { + if(err) return callback(err); + + // Open the request. + xhr.open(method, url, options.async); + + // Set the request headers. + for(var header in headers) { + if(headers.hasOwnProperty(header)) { + xhr.setRequestHeader(header, headers[header]); + } + } + + // Handle the request events. + xhr.onreadystatechange = function () { + if(xhr.readyState === 4) { + var data = xhr.responseText || ''; + + // If no callback is given, return. + if(!callback) { + return; + } + + // Return an object that provides access to the data as text and JSON. + callback(xhr.status, { + text: function () { + return data; + }, + + json: function () { + return JSON.parse(data); + } + }); + } + }; + + // Actually send the XHR request. + xhr.send(payload); + }); + }; + + // Define the external interface. + var http = { + authBasic: function (username, password) { + ajax.headers['Authorization'] = 'Basic ' + base64(username + ':' + password); + }, + + connect: function (url, options, callback) { + return ajax('CONNECT', url, options, callback); + }, + + del: function (url, options, callback) { + return ajax('DELETE', url, options, callback); + }, + + get: function (url, options, callback) { + return ajax('GET', url, options, callback); + }, + + head: function (url, options, callback) { + return ajax('HEAD', url, options, callback); + }, + + headers: function (headers) { + ajax.headers = headers || {}; + }, + + isAllowed: function (url, verb, callback) { + this.options(url, function (status, data) { + callback(data.text().indexOf(verb) !== -1); + }); + }, + + options: function (url, options, callback) { + return ajax('OPTIONS', url, options, callback); + }, + + patch: function (url, options, callback) { + return ajax('PATCH', url, options, callback); + }, + + post: function (url, options, callback) { + return ajax('POST', url, options, callback); + }, + + put: function (url, options, callback) { + return ajax('PUT', url, options, callback); + }, + + trace: function (url, options, callback) { + return ajax('TRACE', url, options, callback); + } + }; + + + var methode = options.type ? options.type.toLowerCase() : 'get'; + + http[methode](options.url, options, function (status, data) { + if (status === 200) { + options.success(data.json(), status, null); + } else { + options.error(data.text(), status, null); + } + }); + } + + var _cookie = { + create: function(name,value,minutes,domain) { + var expires; + if (minutes) { + var date = new Date(); + date.setTime(date.getTime()+(minutes*60*1000)); + expires = "; expires="+date.toGMTString(); + } + else expires = ""; + domain = (domain)? "domain="+domain+";" : ""; + document.cookie = name+"="+value+expires+";"+domain+"path=/"; + }, + + read: function(name) { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length); + } + return null; + }, + + remove: function(name) { + this.create(name,"",-1); + } + }; + + var cookie_noop = { + create: function(name,value,minutes,domain) {}, + read: function(name) { return null; }, + remove: function(name) {} + }; + + + + // move dependent functions to a container so that + // they can be overriden easier in no jquery environment (node.js) + var f = { + extend: $ ? $.extend : _extend, + each: $ ? $.each : _each, + ajax: $ ? $.ajax : (typeof document !== 'undefined' ? _ajax : function() {}), + cookie: typeof document !== 'undefined' ? _cookie : cookie_noop, + detectLanguage: detectLanguage, + escape: _escape, + log: function(str) { + if (o.debug && typeof console !== "undefined") console.log(str); + }, + toLanguages: function(lng) { + var languages = []; + if (typeof lng === 'string' && lng.indexOf('-') > -1) { + var parts = lng.split('-'); + + lng = o.lowerCaseLng ? + parts[0].toLowerCase() + '-' + parts[1].toLowerCase() : + parts[0].toLowerCase() + '-' + parts[1].toUpperCase(); + + if (o.load !== 'unspecific') languages.push(lng); + if (o.load !== 'current') languages.push(parts[0]); + } else { + languages.push(lng); + } + + for (var i = 0; i < o.fallbackLng.length; i++) { + if (languages.indexOf(o.fallbackLng[i]) === -1 && o.fallbackLng[i]) languages.push(o.fallbackLng[i]); + } + + return languages; + }, + regexEscape: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + }; + function init(options, cb) { + + if (typeof options === 'function') { + cb = options; + options = {}; + } + options = options || {}; + + // override defaults with passed in options + f.extend(o, options); + delete o.fixLng; /* passed in each time */ + + // create namespace object if namespace is passed in as string + if (typeof o.ns == 'string') { + o.ns = { namespaces: [o.ns], defaultNs: o.ns}; + } + + // fallback namespaces + if (typeof o.fallbackNS == 'string') { + o.fallbackNS = [o.fallbackNS]; + } + + // fallback languages + if (typeof o.fallbackLng == 'string' || typeof o.fallbackLng == 'boolean') { + o.fallbackLng = [o.fallbackLng]; + } + + // escape prefix/suffix + o.interpolationPrefixEscaped = f.regexEscape(o.interpolationPrefix); + o.interpolationSuffixEscaped = f.regexEscape(o.interpolationSuffix); + + if (!o.lng) o.lng = f.detectLanguage(); + if (o.lng) { + // set cookie with lng set (as detectLanguage will set cookie on need) + if (o.useCookie) f.cookie.create(o.cookieName, o.lng, o.cookieExpirationTime, o.cookieDomain); + } else { + o.lng = o.fallbackLng[0]; + if (o.useCookie) f.cookie.remove(o.cookieName); + } + + languages = f.toLanguages(o.lng); + currentLng = languages[0]; + f.log('currentLng set to: ' + currentLng); + + var lngTranslate = translate; + if (options.fixLng) { + lngTranslate = function(key, options) { + options = options || {}; + options.lng = options.lng || lngTranslate.lng; + return translate(key, options); + }; + lngTranslate.lng = currentLng; + } + + pluralExtensions.setCurrentLng(currentLng); + + // add JQuery extensions + if ($ && o.setJqueryExt) addJqueryFunct(); + + // jQuery deferred + var deferred; + if ($ && $.Deferred) { + deferred = $.Deferred(); + } + + // return immidiatly if res are passed in + if (o.resStore) { + resStore = o.resStore; + initialized = true; + if (cb) cb(lngTranslate); + if (deferred) deferred.resolve(lngTranslate); + if (deferred) return deferred.promise(); + return; + } + + // languages to load + var lngsToLoad = f.toLanguages(o.lng); + if (typeof o.preload === 'string') o.preload = [o.preload]; + for (var i = 0, l = o.preload.length; i < l; i++) { + var pres = f.toLanguages(o.preload[i]); + for (var y = 0, len = pres.length; y < len; y++) { + if (lngsToLoad.indexOf(pres[y]) < 0) { + lngsToLoad.push(pres[y]); + } + } + } + + // else load them + i18n.sync.load(lngsToLoad, o, function(err, store) { + resStore = store; + initialized = true; + + if (cb) cb(lngTranslate); + if (deferred) deferred.resolve(lngTranslate); + }); + + if (deferred) return deferred.promise(); + } + function preload(lngs, cb) { + if (typeof lngs === 'string') lngs = [lngs]; + for (var i = 0, l = lngs.length; i < l; i++) { + if (o.preload.indexOf(lngs[i]) < 0) { + o.preload.push(lngs[i]); + } + } + return init(cb); + } + + function addResourceBundle(lng, ns, resources) { + if (typeof ns !== 'string') { + resources = ns; + ns = o.ns.defaultNs; + } else if (o.ns.namespaces.indexOf(ns) < 0) { + o.ns.namespaces.push(ns); + } + + resStore[lng] = resStore[lng] || {}; + resStore[lng][ns] = resStore[lng][ns] || {}; + + f.extend(resStore[lng][ns], resources); + } + + function removeResourceBundle(lng, ns) { + if (typeof ns !== 'string') { + ns = o.ns.defaultNs; + } + + resStore[lng] = resStore[lng] || {}; + resStore[lng][ns] = {}; + } + + function setDefaultNamespace(ns) { + o.ns.defaultNs = ns; + } + + function loadNamespace(namespace, cb) { + loadNamespaces([namespace], cb); + } + + function loadNamespaces(namespaces, cb) { + var opts = { + dynamicLoad: o.dynamicLoad, + resGetPath: o.resGetPath, + getAsync: o.getAsync, + customLoad: o.customLoad, + ns: { namespaces: namespaces, defaultNs: ''} /* new namespaces to load */ + }; + + // languages to load + var lngsToLoad = f.toLanguages(o.lng); + if (typeof o.preload === 'string') o.preload = [o.preload]; + for (var i = 0, l = o.preload.length; i < l; i++) { + var pres = f.toLanguages(o.preload[i]); + for (var y = 0, len = pres.length; y < len; y++) { + if (lngsToLoad.indexOf(pres[y]) < 0) { + lngsToLoad.push(pres[y]); + } + } + } + + // check if we have to load + var lngNeedLoad = []; + for (var a = 0, lenA = lngsToLoad.length; a < lenA; a++) { + var needLoad = false; + var resSet = resStore[lngsToLoad[a]]; + if (resSet) { + for (var b = 0, lenB = namespaces.length; b < lenB; b++) { + if (!resSet[namespaces[b]]) needLoad = true; + } + } else { + needLoad = true; + } + + if (needLoad) lngNeedLoad.push(lngsToLoad[a]); + } + + if (lngNeedLoad.length) { + i18n.sync._fetch(lngNeedLoad, opts, function(err, store) { + var todo = namespaces.length * lngNeedLoad.length; + + // load each file individual + f.each(namespaces, function(nsIndex, nsValue) { + + // append namespace to namespace array + if (o.ns.namespaces.indexOf(nsValue) < 0) { + o.ns.namespaces.push(nsValue); + } + + f.each(lngNeedLoad, function(lngIndex, lngValue) { + resStore[lngValue] = resStore[lngValue] || {}; + resStore[lngValue][nsValue] = store[lngValue][nsValue]; + + todo--; // wait for all done befor callback + if (todo === 0 && cb) { + if (o.useLocalStorage) i18n.sync._storeLocal(resStore); + cb(); + } + }); + }); + }); + } else { + if (cb) cb(); + } + } + + function setLng(lng, options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; + } else if (!options) { + options = {}; + } + + options.lng = lng; + return init(options, cb); + } + + function lng() { + return currentLng; + } + function addJqueryFunct() { + // $.t shortcut + $.t = $.t || translate; + + function parse(ele, key, options) { + if (key.length === 0) return; + + var attr = 'text'; + + if (key.indexOf('[') === 0) { + var parts = key.split(']'); + key = parts[1]; + attr = parts[0].substr(1, parts[0].length-1); + } + + if (key.indexOf(';') === key.length-1) { + key = key.substr(0, key.length-2); + } + + var optionsToUse; + if (attr === 'html') { + optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options; + ele.html($.t(key, optionsToUse)); + } else if (attr === 'text') { + optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.text() }, options) : options; + ele.text($.t(key, optionsToUse)); + } else if (attr === 'prepend') { + optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options; + ele.prepend($.t(key, optionsToUse)); + } else if (attr === 'append') { + optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options; + ele.append($.t(key, optionsToUse)); + } else if (attr.indexOf("data-") === 0) { + var dataAttr = attr.substr(("data-").length); + optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.data(dataAttr) }, options) : options; + var translated = $.t(key, optionsToUse); + //we change into the data cache + ele.data(dataAttr, translated); + //we change into the dom + ele.attr(attr, translated); + } else { + optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.attr(attr) }, options) : options; + ele.attr(attr, $.t(key, optionsToUse)); + } + } + + function localize(ele, options) { + var key = ele.attr(o.selectorAttr); + if (!key && typeof key !== 'undefined' && key !== false) key = ele.text() || ele.val(); + if (!key) return; + + var target = ele + , targetSelector = ele.data("i18n-target"); + if (targetSelector) { + target = ele.find(targetSelector) || ele; + } + + if (!options && o.useDataAttrOptions === true) { + options = ele.data("i18n-options"); + } + options = options || {}; + + if (key.indexOf(';') >= 0) { + var keys = key.split(';'); + + $.each(keys, function(m, k) { + if (k !== '') parse(target, k, options); + }); + + } else { + parse(target, key, options); + } + + if (o.useDataAttrOptions === true) ele.data("i18n-options", options); + } + + // fn + $.fn.i18n = function (options) { + return this.each(function() { + // localize element itself + localize($(this), options); + + // localize childs + var elements = $(this).find('[' + o.selectorAttr + ']'); + elements.each(function() { + localize($(this), options); + }); + }); + }; + } + function applyReplacement(str, replacementHash, nestedKey, options) { + if (!str) return str; + + options = options || replacementHash; // first call uses replacement hash combined with options + if (str.indexOf(options.interpolationPrefix || o.interpolationPrefix) < 0) return str; + + var prefix = options.interpolationPrefix ? f.regexEscape(options.interpolationPrefix) : o.interpolationPrefixEscaped + , suffix = options.interpolationSuffix ? f.regexEscape(options.interpolationSuffix) : o.interpolationSuffixEscaped + , unEscapingSuffix = 'HTML'+suffix; + + f.each(replacementHash, function(key, value) { + var nextKey = nestedKey ? nestedKey + o.keyseparator + key : key; + if (typeof value === 'object' && value !== null) { + str = applyReplacement(str, value, nextKey, options); + } else { + if (options.escapeInterpolation || o.escapeInterpolation) { + str = str.replace(new RegExp([prefix, nextKey, unEscapingSuffix].join(''), 'g'), value); + str = str.replace(new RegExp([prefix, nextKey, suffix].join(''), 'g'), f.escape(value)); + } else { + str = str.replace(new RegExp([prefix, nextKey, suffix].join(''), 'g'), value); + } + // str = options.escapeInterpolation; + } + }); + return str; + } + + // append it to functions + f.applyReplacement = applyReplacement; + + function applyReuse(translated, options) { + var comma = ','; + var options_open = '{'; + var options_close = '}'; + + var opts = f.extend({}, options); + delete opts.postProcess; + + while (translated.indexOf(o.reusePrefix) != -1) { + replacementCounter++; + if (replacementCounter > o.maxRecursion) { break; } // safety net for too much recursion + var index_of_opening = translated.lastIndexOf(o.reusePrefix); + var index_of_end_of_closing = translated.indexOf(o.reuseSuffix, index_of_opening) + o.reuseSuffix.length; + var token = translated.substring(index_of_opening, index_of_end_of_closing); + var token_without_symbols = token.replace(o.reusePrefix, '').replace(o.reuseSuffix, ''); + + + if (token_without_symbols.indexOf(comma) != -1) { + var index_of_token_end_of_closing = token_without_symbols.indexOf(comma); + if (token_without_symbols.indexOf(options_open, index_of_token_end_of_closing) != -1 && token_without_symbols.indexOf(options_close, index_of_token_end_of_closing) != -1) { + var index_of_opts_opening = token_without_symbols.indexOf(options_open, index_of_token_end_of_closing); + var index_of_opts_end_of_closing = token_without_symbols.indexOf(options_close, index_of_opts_opening) + options_close.length; + try { + opts = f.extend(opts, JSON.parse(token_without_symbols.substring(index_of_opts_opening, index_of_opts_end_of_closing))); + token_without_symbols = token_without_symbols.substring(0, index_of_token_end_of_closing); + } catch (e) { + } + } + } + + var translated_token = _translate(token_without_symbols, opts); + translated = translated.replace(token, translated_token); + } + return translated; + } + + function hasContext(options) { + return (options.context && (typeof options.context == 'string' || typeof options.context == 'number')); + } + + function needsPlural(options) { + return (options.count !== undefined && typeof options.count != 'string' && options.count !== 1); + } + + function exists(key, options) { + options = options || {}; + + var notFound = _getDefaultValue(key, options) + , found = _find(key, options); + + return found !== undefined || found === notFound; + } + + function translate(key, options) { + options = options || {}; + + if (!initialized) { + f.log('i18next not finished initialization. you might have called t function before loading resources finished.') + return options.defaultValue || ''; + }; + replacementCounter = 0; + return _translate.apply(null, arguments); + } + + function _getDefaultValue(key, options) { + return (options.defaultValue !== undefined) ? options.defaultValue : key; + } + + function _injectSprintfProcessor() { + + var values = []; + + // mh: build array from second argument onwards + for (var i = 1; i < arguments.length; i++) { + values.push(arguments[i]); + } + + return { + postProcess: 'sprintf', + sprintf: values + }; + } + + function _translate(potentialKeys, options) { + if (options && typeof options !== 'object') { + if (o.shortcutFunction === 'sprintf') { + // mh: gettext like sprintf syntax found, automatically create sprintf processor + options = _injectSprintfProcessor.apply(null, arguments); + } else if (o.shortcutFunction === 'defaultValue') { + options = { + defaultValue: options + } + } + } else { + options = options || {}; + } + + if (potentialKeys === undefined || potentialKeys === null) return ''; + + if (typeof potentialKeys == 'string') { + potentialKeys = [potentialKeys]; + } + + var key = potentialKeys[0]; + + if (potentialKeys.length > 1) { + for (var i = 0; i < potentialKeys.length; i++) { + key = potentialKeys[i]; + if (exists(key, options)) { + break; + } + } + } + + var notFound = _getDefaultValue(key, options) + , found = _find(key, options) + , lngs = options.lng ? f.toLanguages(options.lng) : languages + , ns = options.ns || o.ns.defaultNs + , parts; + + // split ns and key + if (key.indexOf(o.nsseparator) > -1) { + parts = key.split(o.nsseparator); + ns = parts[0]; + key = parts[1]; + } + + if (found === undefined && o.sendMissing) { + if (options.lng) { + sync.postMissing(lngs[0], ns, key, notFound, lngs); + } else { + sync.postMissing(o.lng, ns, key, notFound, lngs); + } + } + + var postProcessor = options.postProcess || o.postProcess; + if (found !== undefined && postProcessor) { + if (postProcessors[postProcessor]) { + found = postProcessors[postProcessor](found, key, options); + } + } + + // process notFound if function exists + var splitNotFound = notFound; + if (notFound.indexOf(o.nsseparator) > -1) { + parts = notFound.split(o.nsseparator); + splitNotFound = parts[1]; + } + if (splitNotFound === key && o.parseMissingKey) { + notFound = o.parseMissingKey(notFound); + } + + if (found === undefined) { + notFound = applyReplacement(notFound, options); + notFound = applyReuse(notFound, options); + + if (postProcessor && postProcessors[postProcessor]) { + var val = _getDefaultValue(key, options); + found = postProcessors[postProcessor](val, key, options); + } + } + + return (found !== undefined) ? found : notFound; + } + + function _find(key, options) { + options = options || {}; + + var optionWithoutCount, translated + , notFound = _getDefaultValue(key, options) + , lngs = languages; + + if (!resStore) { return notFound; } // no resStore to translate from + + // CI mode + if (lngs[0].toLowerCase() === 'cimode') return notFound; + + // passed in lng + if (options.lng) { + lngs = f.toLanguages(options.lng); + + if (!resStore[lngs[0]]) { + var oldAsync = o.getAsync; + o.getAsync = false; + + i18n.sync.load(lngs, o, function(err, store) { + f.extend(resStore, store); + o.getAsync = oldAsync; + }); + } + } + + var ns = options.ns || o.ns.defaultNs; + if (key.indexOf(o.nsseparator) > -1) { + var parts = key.split(o.nsseparator); + ns = parts[0]; + key = parts[1]; + } + + if (hasContext(options)) { + optionWithoutCount = f.extend({}, options); + delete optionWithoutCount.context; + optionWithoutCount.defaultValue = o.contextNotFound; + + var contextKey = ns + o.nsseparator + key + '_' + options.context; + + translated = translate(contextKey, optionWithoutCount); + if (translated != o.contextNotFound) { + return applyReplacement(translated, { context: options.context }); // apply replacement for context only + } // else continue translation with original/nonContext key + } + + if (needsPlural(options)) { + optionWithoutCount = f.extend({}, options); + delete optionWithoutCount.count; + optionWithoutCount.defaultValue = o.pluralNotFound; + + var pluralKey = ns + o.nsseparator + key + o.pluralSuffix; + var pluralExtension = pluralExtensions.get(lngs[0], options.count); + if (pluralExtension >= 0) { + pluralKey = pluralKey + '_' + pluralExtension; + } else if (pluralExtension === 1) { + pluralKey = ns + o.nsseparator + key; // singular + } + + translated = translate(pluralKey, optionWithoutCount); + if (translated != o.pluralNotFound) { + return applyReplacement(translated, { + count: options.count, + interpolationPrefix: options.interpolationPrefix, + interpolationSuffix: options.interpolationSuffix + }); // apply replacement for count only + } // else continue translation with original/singular key + } + + var found; + var keys = key.split(o.keyseparator); + for (var i = 0, len = lngs.length; i < len; i++ ) { + if (found !== undefined) break; + + var l = lngs[i]; + + var x = 0; + var value = resStore[l] && resStore[l][ns]; + while (keys[x]) { + value = value && value[keys[x]]; + x++; + } + if (value !== undefined) { + var valueType = Object.prototype.toString.apply(value); + if (typeof value === 'string') { + value = applyReplacement(value, options); + value = applyReuse(value, options); + } else if (valueType === '[object Array]' && !o.returnObjectTrees && !options.returnObjectTrees) { + value = value.join('\n'); + value = applyReplacement(value, options); + value = applyReuse(value, options); + } else if (value === null && o.fallbackOnNull === true) { + value = undefined; + } else if (value !== null) { + if (!o.returnObjectTrees && !options.returnObjectTrees) { + if (o.objectTreeKeyHandler && typeof o.objectTreeKeyHandler == 'function') { + value = o.objectTreeKeyHandler(key, value, l, ns, options); + } else { + value = 'key \'' + ns + ':' + key + ' (' + l + ')\' ' + + 'returned an object instead of string.'; + f.log(value); + } + } else if (valueType !== '[object Number]' && valueType !== '[object Function]' && valueType !== '[object RegExp]') { + var copy = (valueType === '[object Array]') ? [] : {}; // apply child translation on a copy + f.each(value, function(m) { + copy[m] = _translate(ns + o.nsseparator + key + o.keyseparator + m, options); + }); + value = copy; + } + } + + if (typeof value === 'string' && value.trim() === '' && o.fallbackOnEmpty === true) + value = undefined; + + found = value; + } + } + + if (found === undefined && !options.isFallbackLookup && (o.fallbackToDefaultNS === true || (o.fallbackNS && o.fallbackNS.length > 0))) { + // set flag for fallback lookup - avoid recursion + options.isFallbackLookup = true; + + if (o.fallbackNS.length) { + + for (var y = 0, lenY = o.fallbackNS.length; y < lenY; y++) { + found = _find(o.fallbackNS[y] + o.nsseparator + key, options); + + if (found) { + /* compare value without namespace */ + var foundValue = found.indexOf(o.nsseparator) > -1 ? found.split(o.nsseparator)[1] : found + , notFoundValue = notFound.indexOf(o.nsseparator) > -1 ? notFound.split(o.nsseparator)[1] : notFound; + + if (foundValue !== notFoundValue) break; + } + } + } else { + found = _find(key, options); // fallback to default NS + } + } + + return found; + } + function detectLanguage() { + var detectedLng; + + // get from qs + var qsParm = []; + if (typeof window !== 'undefined') { + (function() { + var query = window.location.search.substring(1); + var parms = query.split('&'); + for (var i=0; i 0) { + var key = parms[i].substring(0,pos); + var val = parms[i].substring(pos+1); + qsParm[key] = val; + } + } + })(); + if (qsParm[o.detectLngQS]) { + detectedLng = qsParm[o.detectLngQS]; + } + } + + // get from cookie + if (!detectedLng && typeof document !== 'undefined' && o.useCookie ) { + var c = f.cookie.read(o.cookieName); + if (c) detectedLng = c; + } + + // get from navigator + if (!detectedLng && typeof navigator !== 'undefined') { + detectedLng = (navigator.language) ? navigator.language : navigator.userLanguage; + } + + return detectedLng; + } + var sync = { + + load: function(lngs, options, cb) { + if (options.useLocalStorage) { + sync._loadLocal(lngs, options, function(err, store) { + var missingLngs = []; + for (var i = 0, len = lngs.length; i < len; i++) { + if (!store[lngs[i]]) missingLngs.push(lngs[i]); + } + + if (missingLngs.length > 0) { + sync._fetch(missingLngs, options, function(err, fetched) { + f.extend(store, fetched); + sync._storeLocal(fetched); + + cb(null, store); + }); + } else { + cb(null, store); + } + }); + } else { + sync._fetch(lngs, options, function(err, store){ + cb(null, store); + }); + } + }, + + _loadLocal: function(lngs, options, cb) { + var store = {} + , nowMS = new Date().getTime(); + + if(window.localStorage) { + + var todo = lngs.length; + + f.each(lngs, function(key, lng) { + var local = window.localStorage.getItem('res_' + lng); + + if (local) { + local = JSON.parse(local); + + if (local.i18nStamp && local.i18nStamp + options.localStorageExpirationTime > nowMS) { + store[lng] = local; + } + } + + todo--; // wait for all done befor callback + if (todo === 0) cb(null, store); + }); + } + }, + + _storeLocal: function(store) { + if(window.localStorage) { + for (var m in store) { + store[m].i18nStamp = new Date().getTime(); + window.localStorage.setItem('res_' + m, JSON.stringify(store[m])); + } + } + return; + }, + + _fetch: function(lngs, options, cb) { + var ns = options.ns + , store = {}; + + if (!options.dynamicLoad) { + var todo = ns.namespaces.length * lngs.length + , errors; + + // load each file individual + f.each(ns.namespaces, function(nsIndex, nsValue) { + f.each(lngs, function(lngIndex, lngValue) { + + // Call this once our translation has returned. + var loadComplete = function(err, data) { + if (err) { + errors = errors || []; + errors.push(err); + } + store[lngValue] = store[lngValue] || {}; + store[lngValue][nsValue] = data; + + todo--; // wait for all done befor callback + if (todo === 0) cb(errors, store); + }; + + if(typeof options.customLoad == 'function'){ + // Use the specified custom callback. + options.customLoad(lngValue, nsValue, options, loadComplete); + } else { + //~ // Use our inbuilt sync. + sync._fetchOne(lngValue, nsValue, options, loadComplete); + } + }); + }); + } else { + // Call this once our translation has returned. + var loadComplete = function(err, data) { + cb(null, data); + }; + + if(typeof options.customLoad == 'function'){ + // Use the specified custom callback. + options.customLoad(lngs, ns.namespaces, options, loadComplete); + } else { + var url = applyReplacement(options.resGetPath, { lng: lngs.join('+'), ns: ns.namespaces.join('+') }); + // load all needed stuff once + f.ajax({ + url: url, + success: function(data, status, xhr) { + f.log('loaded: ' + url); + loadComplete(null, data); + }, + error : function(xhr, status, error) { + f.log('failed loading: ' + url); + loadComplete('failed loading resource.json error: ' + error); + }, + dataType: "json", + async : options.getAsync + }); + } + } + }, + + _fetchOne: function(lng, ns, options, done) { + var url = applyReplacement(options.resGetPath, { lng: lng, ns: ns }); + f.ajax({ + url: url, + success: function(data, status, xhr) { + f.log('loaded: ' + url); + done(null, data); + }, + error : function(xhr, status, error) { + if ((status && status == 200) || (xhr && xhr.status && xhr.status == 200)) { + // file loaded but invalid json, stop waste time ! + f.log('There is a typo in: ' + url); + } else if ((status && status == 404) || (xhr && xhr.status && xhr.status == 404)) { + f.log('Does not exist: ' + url); + } else { + var theStatus = status ? status : ((xhr && xhr.status) ? xhr.status : null); + f.log(theStatus + ' when loading ' + url); + } + + done(error, {}); + }, + dataType: "json", + async : options.getAsync + }); + }, + + postMissing: function(lng, ns, key, defaultValue, lngs) { + var payload = {}; + payload[key] = defaultValue; + + var urls = []; + + if (o.sendMissingTo === 'fallback' && o.fallbackLng[0] !== false) { + for (var i = 0; i < o.fallbackLng.length; i++) { + urls.push({lng: o.fallbackLng[i], url: applyReplacement(o.resPostPath, { lng: o.fallbackLng[i], ns: ns })}); + } + } else if (o.sendMissingTo === 'current' || (o.sendMissingTo === 'fallback' && o.fallbackLng[0] === false) ) { + urls.push({lng: lng, url: applyReplacement(o.resPostPath, { lng: lng, ns: ns })}); + } else if (o.sendMissingTo === 'all') { + for (var i = 0, l = lngs.length; i < l; i++) { + urls.push({lng: lngs[i], url: applyReplacement(o.resPostPath, { lng: lngs[i], ns: ns })}); + } + } + + for (var y = 0, len = urls.length; y < len; y++) { + var item = urls[y]; + f.ajax({ + url: item.url, + type: o.sendType, + data: payload, + success: function(data, status, xhr) { + f.log('posted missing key \'' + key + '\' to: ' + item.url); + + // add key to resStore + var keys = key.split('.'); + var x = 0; + var value = resStore[item.lng][ns]; + while (keys[x]) { + if (x === keys.length - 1) { + value = value[keys[x]] = defaultValue; + } else { + value = value[keys[x]] = value[keys[x]] || {}; + } + x++; + } + }, + error : function(xhr, status, error) { + f.log('failed posting missing key \'' + key + '\' to: ' + item.url); + }, + dataType: "json", + async : o.postAsync + }); + } + } + }; + // definition http://translate.sourceforge.net/wiki/l10n/pluralforms + var pluralExtensions = { + + rules: { + "ach": { + "name": "Acholi", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "af": { + "name": "Afrikaans", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ak": { + "name": "Akan", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "am": { + "name": "Amharic", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "an": { + "name": "Aragonese", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ar": { + "name": "Arabic", + "numbers": [ + 0, + 1, + 2, + 3, + 11, + 100 + ], + "plurals": function(n) { return Number(n===0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); } + }, + "arn": { + "name": "Mapudungun", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "ast": { + "name": "Asturian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ay": { + "name": "Aymar\u00e1", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "az": { + "name": "Azerbaijani", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "be": { + "name": "Belarusian", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "bg": { + "name": "Bulgarian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "bn": { + "name": "Bengali", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "bo": { + "name": "Tibetan", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "br": { + "name": "Breton", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "bs": { + "name": "Bosnian", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "ca": { + "name": "Catalan", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "cgg": { + "name": "Chiga", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "cs": { + "name": "Czech", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); } + }, + "csb": { + "name": "Kashubian", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "cy": { + "name": "Welsh", + "numbers": [ + 1, + 2, + 3, + 8 + ], + "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3); } + }, + "da": { + "name": "Danish", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "de": { + "name": "German", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "dz": { + "name": "Dzongkha", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "el": { + "name": "Greek", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "en": { + "name": "English", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "eo": { + "name": "Esperanto", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "es": { + "name": "Spanish", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "es_ar": { + "name": "Argentinean Spanish", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "et": { + "name": "Estonian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "eu": { + "name": "Basque", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "fa": { + "name": "Persian", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "fi": { + "name": "Finnish", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "fil": { + "name": "Filipino", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "fo": { + "name": "Faroese", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "fr": { + "name": "French", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "fur": { + "name": "Friulian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "fy": { + "name": "Frisian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ga": { + "name": "Irish", + "numbers": [ + 1, + 2, + 3, + 7, + 11 + ], + "plurals": function(n) { return Number(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4) ;} + }, + "gd": { + "name": "Scottish Gaelic", + "numbers": [ + 1, + 2, + 3, + 20 + ], + "plurals": function(n) { return Number((n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3); } + }, + "gl": { + "name": "Galician", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "gu": { + "name": "Gujarati", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "gun": { + "name": "Gun", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "ha": { + "name": "Hausa", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "he": { + "name": "Hebrew", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "hi": { + "name": "Hindi", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "hr": { + "name": "Croatian", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "hu": { + "name": "Hungarian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "hy": { + "name": "Armenian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ia": { + "name": "Interlingua", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "id": { + "name": "Indonesian", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "is": { + "name": "Icelandic", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n%10!=1 || n%100==11); } + }, + "it": { + "name": "Italian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ja": { + "name": "Japanese", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "jbo": { + "name": "Lojban", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "jv": { + "name": "Javanese", + "numbers": [ + 0, + 1 + ], + "plurals": function(n) { return Number(n !== 0); } + }, + "ka": { + "name": "Georgian", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "kk": { + "name": "Kazakh", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "km": { + "name": "Khmer", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "kn": { + "name": "Kannada", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ko": { + "name": "Korean", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "ku": { + "name": "Kurdish", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "kw": { + "name": "Cornish", + "numbers": [ + 1, + 2, + 3, + 4 + ], + "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3); } + }, + "ky": { + "name": "Kyrgyz", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "lb": { + "name": "Letzeburgesch", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ln": { + "name": "Lingala", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "lo": { + "name": "Lao", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "lt": { + "name": "Lithuanian", + "numbers": [ + 1, + 2, + 10 + ], + "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "lv": { + "name": "Latvian", + "numbers": [ + 1, + 2, + 0 + ], + "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n !== 0 ? 1 : 2); } + }, + "mai": { + "name": "Maithili", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "mfe": { + "name": "Mauritian Creole", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "mg": { + "name": "Malagasy", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "mi": { + "name": "Maori", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "mk": { + "name": "Macedonian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n==1 || n%10==1 ? 0 : 1); } + }, + "ml": { + "name": "Malayalam", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "mn": { + "name": "Mongolian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "mnk": { + "name": "Mandinka", + "numbers": [ + 0, + 1, + 2 + ], + "plurals": function(n) { return Number(0 ? 0 : n==1 ? 1 : 2); } + }, + "mr": { + "name": "Marathi", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ms": { + "name": "Malay", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "mt": { + "name": "Maltese", + "numbers": [ + 1, + 2, + 11, + 20 + ], + "plurals": function(n) { return Number(n==1 ? 0 : n===0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3); } + }, + "nah": { + "name": "Nahuatl", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "nap": { + "name": "Neapolitan", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "nb": { + "name": "Norwegian Bokmal", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ne": { + "name": "Nepali", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "nl": { + "name": "Dutch", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "nn": { + "name": "Norwegian Nynorsk", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "no": { + "name": "Norwegian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "nso": { + "name": "Northern Sotho", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "oc": { + "name": "Occitan", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "or": { + "name": "Oriya", + "numbers": [ + 2, + 1 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "pa": { + "name": "Punjabi", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "pap": { + "name": "Papiamento", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "pl": { + "name": "Polish", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "pms": { + "name": "Piemontese", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ps": { + "name": "Pashto", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "pt": { + "name": "Portuguese", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "pt_br": { + "name": "Brazilian Portuguese", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "rm": { + "name": "Romansh", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ro": { + "name": "Romanian", + "numbers": [ + 1, + 2, + 20 + ], + "plurals": function(n) { return Number(n==1 ? 0 : (n===0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); } + }, + "ru": { + "name": "Russian", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "sah": { + "name": "Yakut", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "sco": { + "name": "Scots", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "se": { + "name": "Northern Sami", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "si": { + "name": "Sinhala", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "sk": { + "name": "Slovak", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); } + }, + "sl": { + "name": "Slovenian", + "numbers": [ + 5, + 1, + 2, + 3 + ], + "plurals": function(n) { return Number(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); } + }, + "so": { + "name": "Somali", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "son": { + "name": "Songhay", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "sq": { + "name": "Albanian", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "sr": { + "name": "Serbian", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "su": { + "name": "Sundanese", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "sv": { + "name": "Swedish", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "sw": { + "name": "Swahili", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "ta": { + "name": "Tamil", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "te": { + "name": "Telugu", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "tg": { + "name": "Tajik", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "th": { + "name": "Thai", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "ti": { + "name": "Tigrinya", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "tk": { + "name": "Turkmen", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "tr": { + "name": "Turkish", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "tt": { + "name": "Tatar", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "ug": { + "name": "Uyghur", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "uk": { + "name": "Ukrainian", + "numbers": [ + 1, + 2, + 5 + ], + "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } + }, + "ur": { + "name": "Urdu", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "uz": { + "name": "Uzbek", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "vi": { + "name": "Vietnamese", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "wa": { + "name": "Walloon", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n > 1); } + }, + "wo": { + "name": "Wolof", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + }, + "yo": { + "name": "Yoruba", + "numbers": [ + 1, + 2 + ], + "plurals": function(n) { return Number(n != 1); } + }, + "zh": { + "name": "Chinese", + "numbers": [ + 1 + ], + "plurals": function(n) { return 0; } + } + }, + + // for demonstration only sl and ar is added but you can add your own pluralExtensions + addRule: function(lng, obj) { + pluralExtensions.rules[lng] = obj; + }, + + setCurrentLng: function(lng) { + if (!pluralExtensions.currentRule || pluralExtensions.currentRule.lng !== lng) { + var parts = lng.split('-'); + + pluralExtensions.currentRule = { + lng: lng, + rule: pluralExtensions.rules[parts[0]] + }; + } + }, + + get: function(lng, count) { + var parts = lng.split('-'); + + function getResult(l, c) { + var ext; + if (pluralExtensions.currentRule && pluralExtensions.currentRule.lng === lng) { + ext = pluralExtensions.currentRule.rule; + } else { + ext = pluralExtensions.rules[l]; + } + if (ext) { + var i = ext.plurals(c); + var number = ext.numbers[i]; + if (ext.numbers.length === 2 && ext.numbers[0] === 1) { + if (number === 2) { + number = -1; // regular plural + } else if (number === 1) { + number = 1; // singular + } + }//console.log(count + '-' + number); + return number; + } else { + return c === 1 ? '1' : '-1'; + } + } + + return getResult(parts[0], count); + } + + }; + var postProcessors = {}; + var addPostProcessor = function(name, fc) { + postProcessors[name] = fc; + }; + // sprintf support + var sprintf = (function() { + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); + } + function str_repeat(input, multiplier) { + for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} + return output.join(''); + } + + var str_format = function() { + if (!str_format.cache.hasOwnProperty(arguments[0])) { + str_format.cache[arguments[0]] = str_format.parse(arguments[0]); + } + return str_format.format.call(null, str_format.cache[arguments[0]], arguments); + }; + + str_format.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]); + if (node_type === 'string') { + output.push(parse_tree[i]); + } + else if (node_type === 'array') { + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); + } + arg = arg[match[2][k]]; + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } + + if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); + } + switch (match[8]) { + case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'd': arg = parseInt(arg, 10); break; + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; + case 'o': arg = arg.toString(8); break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = Math.abs(arg); break; + case 'x': arg = arg.toString(16); break; + case 'X': arg = arg.toString(16).toUpperCase(); break; + } + arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; + + str_format.cache = {}; + + str_format.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + while (_fmt) { + if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { + parse_tree.push(match[0]); + } + else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { + parse_tree.push('%'); + } + else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1; + var field_list = [], replacement_field = match[2], field_match = []; + if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else { + throw('[sprintf] huh?'); + } + } + } + else { + throw('[sprintf] huh?'); + } + match[2] = field_list; + } + else { + arg_names |= 2; + } + if (arg_names === 3) { + throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); + } + parse_tree.push(match); + } + else { + throw('[sprintf] huh?'); + } + _fmt = _fmt.substring(match[0].length); + } + return parse_tree; + }; + + return str_format; + })(); + + var vsprintf = function(fmt, argv) { + argv.unshift(fmt); + return sprintf.apply(null, argv); + }; + + addPostProcessor("sprintf", function(val, key, opts) { + if (!opts.sprintf) return val; + + if (Object.prototype.toString.apply(opts.sprintf) === '[object Array]') { + return vsprintf(val, opts.sprintf); + } else if (typeof opts.sprintf === 'object') { + return sprintf(val, opts.sprintf); + } + + return val; + }); + // public api interface + i18n.init = init; + i18n.setLng = setLng; + i18n.preload = preload; + i18n.addResourceBundle = addResourceBundle; + i18n.removeResourceBundle = removeResourceBundle; + i18n.loadNamespace = loadNamespace; + i18n.loadNamespaces = loadNamespaces; + i18n.setDefaultNamespace = setDefaultNamespace; + i18n.t = translate; + i18n.translate = translate; + i18n.exists = exists; + i18n.detectLanguage = f.detectLanguage; + i18n.pluralExtensions = pluralExtensions; + i18n.sync = sync; + i18n.functions = f; + i18n.lng = lng; + i18n.addPostProcessor = addPostProcessor; + i18n.options = o; + +})(); \ No newline at end of file diff --git a/services/web/public/js/libs/ng-i18next/directive/directive.js b/services/web/public/js/libs/ng-i18next/directive/directive.js new file mode 100755 index 0000000000..ce5dedf262 --- /dev/null +++ b/services/web/public/js/libs/ng-i18next/directive/directive.js @@ -0,0 +1,185 @@ +angular.module('jm.i18next').directive('ngI18next', ['$rootScope', '$i18next', '$compile', '$parse', '$interpolate', function ($rootScope, $i18next, $compile, $parse, $interpolate) { + + 'use strict'; + + console.log("running directive") + var watchUnregister; + + function parse(scope, element, key) { + + var attr = 'text', + attrs = [attr], + string, + i; + + // If there was a watched value, unregister it + if (watchUnregister) { + watchUnregister(); + } + + key = key.trim(); + + /* + * Check if we want to translate an attribute + */ + if (key.indexOf('[') === 0) { + + var parts = key.split(']'); + + // If there are more than two parts because of multiple "]", concatenate them again. + if (parts.length > 2) { + for (i = 2; i < parts.length; i++) { + parts[1] += ']' + parts[i]; + parts[i] = null; + } + } + + key = parts[1]; + attr = parts[0].substr(1, parts[0].length - 1); + + } + /* + * Cut of the ";" that might be at the end of the string + */ + if (key.indexOf(';') === key.length - 1) { + key = key.substr(0, key.length - 2).trim(); + } + /* + * If passing options, split attr + */ + if (attr.indexOf(':') >= 0) { + attrs = attr.split(':'); + attr = attrs[0]; + } else if (attr === 'i18next') { + attrs[1] = 'i18next'; + attr = 'text'; + } + + if (attr !== 'i18next' && attrs[1] !== 'i18next') { + + string = $i18next(key); + + } else { + + var options = {}, + strippedKey = key; + + if (key.indexOf('(') >= 0 && key.indexOf(')') >= 0) { + + var keys = key.split(')'); + + keys[0] = keys[0].substr(1, keys[0].length); + + if (keys.length > 2) { + + strippedKey = keys.pop(); + + options = $parse(keys.join(')'))(scope); + + } else { + + options = $parse(keys[0])(scope); + strippedKey = keys[1].trim(); + + } + + if (options.sprintf) { + options.postProcess = 'sprintf'; + } + + } + + string = $i18next(strippedKey, options); + + } + + if (attr === 'html') { + + element.empty().append(string); + + /* + * Now compile the content of the element and bind the variables to + * the scope + */ + $compile(element.contents())(scope); + + } else { + var insertText = element.text.bind(element); + + if (attr !== 'text') { + insertText = element.attr.bind(element, attr); + } + + watchUnregister = scope.$watch($interpolate(string), insertText); + insertText(string); + } + + if (!$rootScope.$$phase) { + $rootScope.$digest(); + } + } + + + function localize(scope, element, key) { + + if (key.indexOf(';') >= 0) { + + var keys = key.split(';'); + + for (var i = 0; i < keys.length; i++) { + if (keys[i] !== '') { + parse(scope, element, keys[i]); + } + } + + } else { + parse(scope, element, key); + } + + } + + return { + + // 'A': only as attribute + restrict: 'A', + + scope: false, + + link: function postLink(scope, element, attrs) { + + var translationValue; + + function observe(value) { + translationValue = value.replace(/^\s+|\s+$/g, ''); // RegEx removes whitespace + + if (translationValue === '') { + return setupWatcher(); + } + + localize(scope, element, translationValue); + } + + function setupWatcher() { + // Prevent from executing this method twice + if (setupWatcher.done) { + return; + } + + // interpolate is allowing to transform {{expr}} into text + var interpolation = $interpolate(element.html()); + + scope.$watch(interpolation, observe); + + setupWatcher.done = true; + } + + attrs.$observe('ngI18next', observe); + + scope.$on('i18nextLanguageChange', function () { + localize(scope, element, translationValue); + }); + } + + }; + +}]); diff --git a/services/web/public/js/libs/ng-i18next/filter/filter.js b/services/web/public/js/libs/ng-i18next/filter/filter.js new file mode 100755 index 0000000000..0e3a144a94 --- /dev/null +++ b/services/web/public/js/libs/ng-i18next/filter/filter.js @@ -0,0 +1,12 @@ +angular.module('jm.i18next').filter('i18next', ['$i18next', function ($i18next) { + + 'use strict'; + + console.log("running filter") + return function (string, options) { + + return $i18next(string, options); + + }; + +}]); diff --git a/services/web/public/js/libs/ng-i18next/index.js b/services/web/public/js/libs/ng-i18next/index.js new file mode 100644 index 0000000000..0fcd4f424c --- /dev/null +++ b/services/web/public/js/libs/ng-i18next/index.js @@ -0,0 +1,8 @@ +(function() { + define(["libs/ng-i18next/provider", "libs/ng-i18next/directive/directive", "libs/ng-i18next/filter/filter"], function() { + + console.log("hello") + + }); + +}).call(this); diff --git a/services/web/public/js/libs/ng-i18next/provider.js b/services/web/public/js/libs/ng-i18next/provider.js new file mode 100755 index 0000000000..c10cadb659 --- /dev/null +++ b/services/web/public/js/libs/ng-i18next/provider.js @@ -0,0 +1,123 @@ +angular.module('jm.i18next', ['ng']); +angular.module('jm.i18next').provider('$i18next', function () { + + 'use strict'; + console.log("Running provider") + + var self = this, + /** + * This will be our translation function (see code below) + */ + t = null, + translations = {}, + globalOptions = null, + triesToLoadI18next = 0; + + self.options = {}; + + self.$get = ['$rootScope', '$timeout', function ($rootScope, $timeout) { + + function init(options) { + + if (window.i18n) { + + window.i18n.init(options, function (localize) { + + translations = {}; + + t = localize; + + if (!$rootScope.$$phase) { + $rootScope.$digest(); + } + + $rootScope.$broadcast('i18nextLanguageChange'); + + }); + + } else { + + triesToLoadI18next++; + // only check 4 times for i18next + if (triesToLoadI18next < 5) { + + $timeout(function () { + init(options); + }, 400); + + } else { + throw new Error('[ng-i18next] Can\'t find i18next!'); + } + + } + } + + function optionsChange(newOptions, oldOptions) { + + $i18nextTanslate.debugMsg.push(['i18next options changed:', oldOptions, newOptions]); + + globalOptions = newOptions; + + init(globalOptions); + + } + + /** + * Translates `key` with given options and puts the translation into `translations`. + * @param {Boolean} hasOwnOptions hasOwnOptions means that we are passing options to + * $i18next so we can't use previous saved translation. + */ + function translate(key, options, hasOwnOptions) { + + var lng = options.lng || 'auto'; + + if (!translations[lng]) { + translations[lng] = {}; + } + + if (!t) { + translations[lng][key] = 'defaultLoadingValue' in options ? options.defaultLoadingValue : + 'defaultValue' in options ? options.defaultValue : + 'defaultLoadingValue' in globalOptions ? globalOptions.defaultLoadingValue : key; + } else if (!translations[lng][key] || hasOwnOptions) { + translations[lng][key] = t(key, options); + } + + } + + function $i18nextTanslate(key, options) { + + var optionsObj = options || {}, + mergedOptions = options ? angular.extend({}, optionsObj, options) : optionsObj; + + translate(key, mergedOptions, !!options); + + return (options && options.lng) ? translations[options.lng][key] : + !!optionsObj.lng ? translations[optionsObj.lng][key] : translations['auto'][key]; + + } + + $i18nextTanslate.debugMsg = []; + + $i18nextTanslate.options = self.options; + + if (self.options !== globalOptions) { + optionsChange(self.options, globalOptions); + } + + $i18nextTanslate.reInit = function () { + optionsChange(globalOptions, globalOptions); + }; + + $rootScope.$watch(function () { return $i18nextTanslate.options; }, function (newOptions, oldOptions) { + // Check whether there are new options and whether the new options are different from the old options. + if (!!newOptions && oldOptions !== newOptions) { + optionsChange(newOptions, oldOptions); + } + }, true); + + return $i18nextTanslate; + + }]; + +}); From 5f8bb9ea1f378e0974e889c17ad4d458828680e9 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 31 Jul 2014 11:54:19 +0100 Subject: [PATCH 08/26] converted settings page --- services/web/app/views/user/settings.jade | 75 +++++++++++------------ 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/services/web/app/views/user/settings.jade b/services/web/app/views/user/settings.jade index ef23470149..e51bb35193 100644 --- a/services/web/app/views/user/settings.jade +++ b/services/web/app/views/user/settings.jade @@ -7,20 +7,20 @@ block content .col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 .card .page-header - h1 Account Settings + h1 #{translate("account_settings")} .account-settings(ng-controller="AccountSettingsController", ng-cloak) form-messages(for="settingsForm") .alert.alert-success(ng-show="settingsForm.response.success") - | Thanks, your settings have been updated. + | #{translate("thanks_settings_updated")} form-messages(for="changePasswordForm") .container-fluid .row .col-md-5 - h3 Update Account Info + h3 #{translate("update_account_info")} form(async-form="settings", name="settingsForm", action="/user/settings", novalidate) input(type="hidden", name="_csrf", value=csrfToken) .form-group - label(for='email') Email + label(for='email') #{translate("email")} input.form-control( type='email', name='email', @@ -31,16 +31,16 @@ block content ng-model-options="{ updateOn: 'blur' }" ) span.small.text-primary(ng-show="settingsForm.email.$invalid && settingsForm.email.$dirty") - | Must be an email address + | #{translate("must_be_email_address")} .form-group - label(for='firstName').control-label First Name + label(for='firstName').control-label #{translate("first_name")} input.form-control( type='text', name='first_name', value=user.first_name ) .form-group - label(for='lastName').control-label Last Name + label(for='lastName').control-label #{translate("last_name")} input.form-control( type='text', name='last_name', @@ -50,14 +50,14 @@ block content button.btn.btn-primary( type='submit', ng-disabled="settingsForm.$invalid" - ) Update + ) #{translate("update")} .col-md-5.col-md-offset-1 - h3 Change Password + h3 #{translate("change_password")} form(async-form="changepassword", name="changePasswordForm", action="/user/password/update", novalidate) input(type="hidden", name="_csrf", value=csrfToken) .form-group - label(for='currentPassword') Current Password + label(for='currentPassword') #{translate("current_password")} input.form-control( type='password', name='currentPassword', @@ -66,9 +66,9 @@ block content required ) span.small.text-primary(ng-show="changePasswordForm.currentPassword.$invalid && changePasswordForm.currentPassword.$dirty") - | Required + | #{translate("required")} .form-group - label(for='newPassword1') New Password + label(for='newPassword1') #{translate("new_password")} input.form-control( type='password', name='newPassword1', @@ -77,71 +77,68 @@ block content required ) span.small.text-primary(ng-show="changePasswordForm.newPassword1.$invalid && changePasswordForm.newPassword1.$dirty") - | Required + | #{translate("required")} .form-group - label(for='newPassword2') Confirm New Password + label(for='newPassword2') #{translate("confirm_new_password")} input.form-control( type='password', name='newPassword2', placeholder='*********', ng-model="newPassword2", - equals="{{newPassword1}}" + ng-equals="{{newPassword1}}" ) span.small.text-primary(ng-show="changePasswordForm.newPassword2.$invalid && changePasswordForm.newPassword2.$dirty") - | Doesn't match + | #{translate("doesnt_match")} .actions button.btn.btn-primary( type='submit', ng-disabled="changePasswordForm.$invalid" - ) Change + ) #{translate("change")} hr.soften - h3 Dropbox Integration + h3 #{translate("dropbox_integration")} span.small - a(href='/help/kb/dropbox-2') (Learn more) + a(href='/help/kb/dropbox-2') (#{translate("learn_more")}) - if(!userHasDropboxFeature) - .alert.alert-info Dropbox sync is a premium feature     - a.btn.btn-info(href='/user/subscription/plans') Upgrade + .alert.alert-info #{translate("dropbox_is_premium")}     + a.btn.btn-info(href='/user/subscription/plans') #{translate("upgrade")} - else if(userIsRegisteredWithDropbox) - .alert.alert-success Account is linked! + .alert.alert-success #{translate("account_is_linked")} row - a(href='/dropbox/unlink').btn Unlink Dropbox + a(href='/dropbox/unlink').btn #{translate("unlink_dropbox")} - else - a.btn.btn-info(href='/dropbox/beginAuth') Link to dropbox + a.btn.btn-info(href='/dropbox/beginAuth') #{translate("link_to_dropbox")} hr.soften p.small - | Every few months we send a newsletter out summarizing the new features available. - | If you would prefer not to receive this email then you can unsubscribe at any time: + | #{translate("newsletter_info_and_unsubscribe")} a( href, ng-click="unsubscribe()", ng-show="subscribed && !unsubscribing" - ) Unsubscribe + ) #{translate("unsubscribe")} span( ng-show="unsubscribing" ) - i.fa.fa-spin.fa-refresh - | Unsubscribing + i.fa.fa-spin.fa-refresh + | #{translate("unsubscribing")} span.text-success( ng-show="!subscribed" ) i.fa.fa-check - | Unsubscribed + | #{translate("unsubscribed")} - p Need to leave? - a(href, ng-click="deleteAccount()") Delete your account + p #{translate("need_to_leave")} + a(href, ng-click="deleteAccount()") #{translate("delete_your_account")} script(type='text/ng-template', id='deleteAccountModalTemplate') .modal-header - h3 Delete Account + h3 #{translate("delete_account")} .modal-body - p - | You are about to permanently delete all of your account data, including your projects - | and settings. Please type DELETE into the box below to proceed. + p !{translate("delete_account_warning_message")} form(novalidate, name="deleteAccountForm") input.form-control( type="text", @@ -153,11 +150,11 @@ block content .modal-footer button.btn.btn-default( ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-danger( ng-disabled="!state.isValid || state.inflight" ng-click="delete()" ) - span(ng-hide="state.inflight") Delete - span(ng-show="state.inflight") Deleting... + span(ng-hide="state.inflight") #{translate("delete")} + span(ng-show="state.inflight") #{translate("deleting")}... From 43750e9cb636bb04604687227f740a0f9126feb7 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 31 Jul 2014 13:38:24 +0100 Subject: [PATCH 09/26] converted plans page --- .../web/app/views/subscriptions/plans.jade | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/services/web/app/views/subscriptions/plans.jade b/services/web/app/views/subscriptions/plans.jade index c390850246..6877fd9cfb 100644 --- a/services/web/app/views/subscriptions/plans.jade +++ b/services/web/app/views/subscriptions/plans.jade @@ -7,11 +7,11 @@ block content .row .col-md-12 .page-header.centered.plans-header.text-centered - h1 Start Your 30-Day Free Trial Today! + h1 #{translate("start_30_day_trial")} .row .col-md-8.col-md-offset-2 - p.text-centered ShareLaTeX is the world's easiest to use LaTeX editor. Stay up to date with your collaborators, keep track of all changes to your work, and use our LaTeX environment from anywhere in the world. + p.text-centered #{translate("sl_benefits_plans")} .row(ng-cloak) .col-md-12 @@ -20,55 +20,55 @@ block content a( href, ng-click="switchToMonthly()" - ) Monthly + ) #{translate("monthly")} li(ng-class="{'active': ui.view == 'annual'}") a( href ng-click="switchToAnnual()" - ) Annual + ) #{translate("annual")} li(ng-class="{'active': ui.view == 'student'}") a( href, ng-click="switchToStudent()" - ) Half Price Student Plans + ) #{translate("half_price_student")} .row(ng-cloak) .col-md-12 .card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'") .card .card-header - h2 Personal - .circle Free + h2 #{translate("personal")} + .circle #{translate("free")} ul.list-unstyled - li Only one collaborator + li #{translate("one_collaborator")} li   li   li br - a.btn.btn-info(href="/register") Sign up now + a.btn.btn-info(href="/register") #{translate("sign_up_now")} .card.highlighted .card-header - h2 Collaborator + h2 #{translate("collaborator")} .circle span(ng-if="ui.view == 'monthly'") | $15 span.small /mo span(ng-if="ui.view == 'annual'") | $180 - span.small/yr + span.small /yr ul.list-unstyled li - strong 10 collaborators per project - li Full document history - li Sync to Dropbox + strong #{translate("collabs_per_proj", {collabcount:10})} + li #{translate("full_doc_history")} + li #{translate("sync_to_dropbox")} li br a.btn.btn-info( ng-href="#{baseUrl}/user/subscription/new?planCode=collaborator{{ ui.view == 'annual' && '-annual' || ''}}_free_trial", ng-click="signUpNowClicked('collaborator')" - ) Start Free Trial! + ) #{translate("start_free_trial")} .card .card-header - h2 Professional + h2 #{translate("professional")} .circle span(ng-if="ui.view == 'monthly'") | $30 @@ -78,91 +78,91 @@ block content span.small /yr ul.list-unstyled li - strong Unlimited collaborators - li Full document history - li Sync to Dropbox + strong #{translate("unlimited_collabs")} + li #{translate("full_doc_history")} + li #{translate("sync_to_dropbox")} li br a.btn.btn-info( ng-href="#{baseUrl}/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '-annual' || ''}}_free_trial", ng-click="signUpNowClicked('professional')" - ) Start Free Trial! + ) #{translate("start_free_trial")} .card-group.text-centered(ng-if="ui.view == 'student'") .card .card-header - h2 Personal - .circle Free + h2 #{translate("personal")} + .circle #{translate("free")} ul.list-unstyled - li Only one collaborator + li #{translate("one_collaborator")} li   li   li br - a.btn.btn-info(href="/register") Sign up now + a.btn.btn-info(href="/register") #{translate("sign_up_now")} .card.highlighted .card-header - h2 Student + h2 #{translate("student")} .circle span | $8 span.small /mo ul.list-unstyled li - strong 6 collaborators per project - li Full document history - li Sync to Dropbox + strong #{translate("collabs_per_proj", {collabcount:6})} + li #{translate("full_doc_history")} + li #{translate("sync_to_dropbox")} li br a.btn.btn-info( ng-href="#{baseUrl}/user/subscription/new?planCode=student_free_trial", ng-click="signUpNowClicked('student')" - ) Start Free Trial! + ) #{translate("start_free_trial")} .card .card-header - h2 Student (Annual) + h2 #{translate("student")} (#{translate("annual")}) .circle span | $80 span.small /yr ul.list-unstyled li - strong 6 collaborators per project - li Full document history - li Sync to Dropbox + strong #{translate("collabs_per_proj", {collabcount:6})} + li #{translate("full_doc_history")} + li #{translate("sync_to_dropbox")} li br a.btn.btn-info( ng-href="#{baseUrl}/user/subscription/new?planCode=stud-ann_free_trial", ng-click="signUpNowClicked('student')" - ) Start Free Trial! + ) #{translate("start_free_trial")} .row(ng-cloak) - p.text-centered Choose the plan that works for you with our 30-day free trial. Cancel at any time. + p.text-centered #{translate("start_free_trial")} #{translate("choose_plan_works_for_you")} .row(ng-cloak) .col-md-8.col-md-offset-2 .alert.alert-info.text-centered - | Interested in using ShareLaTeX with a group, team or department wide account? + | #{translate("interested_in_group_licence")} br - a(href, ng-click="openGroupPlanModal()") Get in touch for details! + a(href, ng-click="openGroupPlanModal()") #{translate("get_in_touch_for_details")} script(type="text/ng-template", id="groupPlanModalTemplate") .modal-header - h3 Group Plan Enquiry + h3 #{translate("group_plan_enquiry")} .modal-body form(name='form1', autocomplete='off', enctype='multipart/form-data', method='post', novalidate='', action='https://sharelatex.wufoo.com/forms/z7x3p3/#public', _lpchecked='1') .form-group - label(for='Field9') Name + label(for='Field9') #{translate("name")} input.form-control(name='Field9', type='text', value='', maxlength='255', tabindex='1', onkeyup='') .form-group - label(for='Field11') Email + label(for='Field11') #{translate("email")} input.form-control(name='Field11', type='email', spellcheck='false', value='', maxlength='255', tabindex='2') .form-group - label(for='Field12') University + label(for='Field12') #{translate("university")} input.form-control(name='Field12', type='text', value='', maxlength='255', tabindex='3', onkeyup='') .form-group - label(for='Field13') Position + label(for='Field13') #{translate("position")} input.form-control(name='Field13', type='text', value='', maxlength='255', tabindex='4', onkeyup='') .form-group @@ -176,19 +176,19 @@ block content .row .col-md-12 .page-header.plans-header.plans-subheader.text-centered - h2 Enjoy all of these great features + h2 #{translate("enjoy_these_features")} .col-md-4 .card.features.text-centered i.fa.fa-file-text-o.fa-5x - h4 Unlimited projects - p Create as many projects as you need. + h4 #{translate("unlimited_projects")} + p #{translate("create_unlimited_projects")} .col-md-4 .card.features.text-centered i.fa.fa-clock-o.fa-5x - h4 Full document history - p Never lose a step, we've got your back. + h4 #{translate("full_doc_history")} + p #{translate("never_loose_work")} .col-md-4 .card.features.text-centered i.fa.fa-dropbox.fa-5x - h4 Sync to Dropbox - p Access your projects everywhere. + h4 #{translate("sync_to_dropbox")} + p #{translate("access_projects_anywhere")} From ab10d84f6a952c9bb86987a6e0e5101855a8b31a Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 31 Jul 2014 14:01:37 +0100 Subject: [PATCH 10/26] did some of the forms --- services/web/app/views/user/login.jade | 12 ++++++------ services/web/app/views/user/passwordReset.jade | 10 +++++----- services/web/app/views/user/setPassword.jade | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/services/web/app/views/user/login.jade b/services/web/app/views/user/login.jade index 132aa4fbf9..aa200d5db3 100644 --- a/services/web/app/views/user/login.jade +++ b/services/web/app/views/user/login.jade @@ -7,7 +7,7 @@ block content .col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4 .card .page-header - h1 Log In + h1 #{translate("log_in")} form(async-form="login", name="loginForm", action='/login', ng-cloak) input(name='_csrf', type='hidden', value=csrfToken) input(name='redir', type='hidden', value=redir) @@ -23,7 +23,7 @@ block content focus="true" ) span.small.text-primary(ng-show="loginForm.email.$invalid && loginForm.email.$dirty") - | Must be an email address + | #{translate("must_be_email_address")} .form-group input.form-control( type='password', @@ -33,12 +33,12 @@ block content ng-model="password" ) span.small.text-primary(ng-show="loginForm.password.$invalid && loginForm.password.$dirty") - | Required + | #{translate("required")} .actions button.btn-primary.btn( type='submit', ng-disabled="loginForm.inflight" ) - span(ng-show="!loginForm.inflight") Login - span(ng-show="loginForm.inflight") Logging in... - a.pull-right(href='/user/password/reset') Forgot your password? + span(ng-show="!loginForm.inflight") #{translate("login")} + span(ng-show="loginForm.inflight") #{translate("logging_in")}... + a.pull-right(href='/user/password/reset') #{translate("forgot_your_password")}? diff --git a/services/web/app/views/user/passwordReset.jade b/services/web/app/views/user/passwordReset.jade index 11957138d8..4db644d916 100644 --- a/services/web/app/views/user/passwordReset.jade +++ b/services/web/app/views/user/passwordReset.jade @@ -7,7 +7,7 @@ block content .col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4 .card .page-header - h1 Password Reset + h1 #{translate("password_reset")} .messageArea form( async-form="password-reset-request", @@ -18,9 +18,9 @@ block content input(type="hidden", name="_csrf", value=csrfToken) form-messages(for="passwordResetForm") .alert.alert-success(ng-show="passwordResetForm.response.success") - | You have been sent an email to complete your password reset. + | #{translate("password_reset_email_sent")} .form-group - label(for='email') Please enter your email address + label(for='email') #{translate("please_enter_email")} input.form-control( type='email', name='email', @@ -31,9 +31,9 @@ block content ) span.small.text-primary( ng-show="passwordResetForm.email.$invalid && passwordResetForm.email.$dirty" - ) Must be a valid email address + ) #{translate("must_be_email_address")} .actions button.btn.btn-primary( type='submit', ng-disabled="passwordResetForm.$invalid" - ) Request password reset + ) #{translate("request_password_reset")} diff --git a/services/web/app/views/user/setPassword.jade b/services/web/app/views/user/setPassword.jade index 067a0dfecb..815f0030ca 100644 --- a/services/web/app/views/user/setPassword.jade +++ b/services/web/app/views/user/setPassword.jade @@ -7,7 +7,7 @@ block content .col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4 .card .page-header - h1 Reset your password + h1 #{translate("reset_your_password")} form( async-form="password-reset", name="passwordResetForm", @@ -17,8 +17,8 @@ block content input(type="hidden", name="_csrf", value=csrfToken) form-messages(for="passwordResetForm") .alert.alert-success(ng-show="passwordResetForm.response.success") - | Your password has been reset. - a(href='/login') Login here + | #{translate("password_has_been_reset")} + a(href='/login') #{translate("login_here")} .form-group input.form-control( @@ -31,7 +31,7 @@ block content ) span.small.text-primary( ng-show="passwordResetForm.password.$invalid && passwordResetForm.password.$dirty" - ) Required + ) #{translate("required")} input( type="hidden", name="passwordResetToken", @@ -41,4 +41,4 @@ block content button.btn.btn-primary( type='submit', ng-disabled="passwordResetForm.$invalid" - ) Set new password \ No newline at end of file + ) #{translate("set_new_password")} \ No newline at end of file From cb685f5ea654fbc56406b28a32c7baa969662b2e Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 31 Jul 2014 17:07:43 +0100 Subject: [PATCH 11/26] done loads of more pages --- services/web/app/views/general/404.jade | 4 +- services/web/app/views/project/editor.jade | 20 +++--- .../app/views/project/editor/binary-file.jade | 4 +- .../web/app/views/project/editor/chat.jade | 6 +- .../web/app/views/project/editor/dropbox.jade | 24 ++++--- .../web/app/views/project/editor/editor.jade | 2 +- .../app/views/project/editor/file-tree.jade | 62 +++++++++---------- .../web/app/views/project/editor/hotkeys.jade | 10 +-- .../app/views/project/editor/left-menu.jade | 50 +++++++-------- .../web/app/views/project/editor/pdf.jade | 41 ++++++------ .../project/editor/publish-template.jade | 22 +++---- .../web/app/views/project/editor/share.jade | 52 ++++++++-------- .../views/project/editor/track-changes.jade | 33 +++++----- .../web/app/views/project/list/modals.jade | 56 ++++++++--------- .../web/app/views/project/list/side-bar.jade | 4 +- services/web/app/views/user/register.jade | 25 ++++---- 16 files changed, 205 insertions(+), 210 deletions(-) diff --git a/services/web/app/views/general/404.jade b/services/web/app/views/general/404.jade index 425e056c61..018a11c2ac 100644 --- a/services/web/app/views/general/404.jade +++ b/services/web/app/views/general/404.jade @@ -6,8 +6,8 @@ block content .row .col-md-8.col-md-offset-2.text-center .page-header - h2 Sorry, we can't find the page you are looking for. + h2 #{translate("cant_find_page")} p a(href="/") i.fa.fa-arrow-circle-o-left - | Take me home! + | #{translate("take_me_home")} diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade index b362a2d806..100610aa15 100644 --- a/services/web/app/views/project/editor.jade +++ b/services/web/app/views/project/editor.jade @@ -10,30 +10,28 @@ block content .editor(ng-controller="IdeController").full-size .loading-screen(ng-show="state.loading") .container - h3 Loading... + h3 #{translate("loading")}... .progress .progress-bar(style="width: 20%", ng-style="{'width': state.load_progress + '%'}") .global-alerts(ng-cloak) .alert.alert-danger.small(ng-if="connection.forced_disconnect") - strong Disconnected - | Please refresh the page to continue. + strong #{translate("disconnected")} + | #{translate("please_refresh")} .alert.alert-warning.small(ng-if="connection.reconnection_countdown") - strong Lost Connection. - | Reconnecting in {{ connection.reconnection_countdown }} secs. - a.pull-right(href, ng-click="tryReconnectNow()") Try Now + strong #{translate("lost_connection")}. + | #{translate("reconnecting_in_x_secs", {seconds:"{{ connection.reconnection_countdown }}"})}. + a.pull-right(href, ng-click="tryReconnectNow()") #{translate("try_now")} .alert.alert-warning.small(ng-if="connection.reconnecting") - strong Reconnecting... + strong #{translate("reconnecting")}... .div(ng-controller="SavingNotificationController") .alert.alert-warning.small( ng-repeat="(doc_id, state) in docSavingStatus" ng-if="state.unsavedSeconds > 3" - ) - | Saving {{ state.doc.name }}... ({{ state.unsavedSeconds }} seconds of unsaved changes) - + ) #{translate("saving_notification_with_seconds", {docname:"{{ state.doc.name }}", seconds:"{{ state.unsavedSeconds }}"})} include ./editor/left-menu @@ -78,7 +76,7 @@ block content h3 {{ title }} .modal-body {{ message }} .modal-footer - button.btn.btn-info(ng-click="done()") OK + button.btn.btn-info(ng-click="done()") #{translate("ok")} script(src='/socket.io/socket.io.js') diff --git a/services/web/app/views/project/editor/binary-file.jade b/services/web/app/views/project/editor/binary-file.jade index 015724288f..ca25a6d806 100644 --- a/services/web/app/views/project/editor/binary-file.jade +++ b/services/web/app/views/project/editor/binary-file.jade @@ -13,7 +13,7 @@ div.binary-file.full-size( ) p.no-preview( ng-if="['png', 'jpg', 'jpeg', 'gif', 'pdf', 'eps'].indexOf(extension(openFile)) == -1" - ) Sorry, no preview is available. + ) #{translate("no_preview_available")} a.btn.btn-info( ng-href="/project/{{ project_id }}/file/{{ openFile.id }}" - ) Download {{ openFile.name }} + ) #{translate("download")} {{ openFile.name }} diff --git a/services/web/app/views/project/editor/chat.jade b/services/web/app/views/project/editor/chat.jade index 85d08d147a..64e6a1a132 100644 --- a/services/web/app/views/project/editor/chat.jade +++ b/services/web/app/views/project/editor/chat.jade @@ -12,11 +12,11 @@ aside.chat( .infinite-scroll-inner .loading(ng-show="chat.loading") i.fa.fa-fw.fa-spin.fa-refresh - |   Loading... + |   #{translate("loading")}... .no-messages.text-center.small(ng-show='!chat.loading && chat.messages.length == 0') - | No messages + | #{translate("no_messages")} .first-message.text-center(ng-show='!chat.loading && chat.messages.length == 0') - | Send your first message + | #{translate("send_first_message")} br i.fa.fa-arrow-down ul.list-unstyled( diff --git a/services/web/app/views/project/editor/dropbox.jade b/services/web/app/views/project/editor/dropbox.jade index 40d2cce851..362fc66112 100644 --- a/services/web/app/views/project/editor/dropbox.jade +++ b/services/web/app/views/project/editor/dropbox.jade @@ -5,41 +5,39 @@ script(type="text/ng-template", id="dropboxModalTemplate") data-dismiss="modal" ng-click="cancel()" ) × - h3 Dropbox link + h3 #{translate("dropbox_link")} .modal-body.modal-body-share div(ng-show="dbState.gotLinkStatus") div(ng-hide="dbState.userIsLinkedToDropbox || !dbState.hasDropboxFeature") - span(ng-hide="dbState.startedLinkProcess") Your account is not linked to dropbox + span(ng-hide="dbState.startedLinkProcess") #{translate("account_not_linked_to_dropbox")} |     - a(ng-click="linkToDropbox()").btn.btn-info Update Dropbox Settings + a(ng-click="linkToDropbox()").btn.btn-info #{translate("update_dropbox_settings")} p.small.text-center(ng-show="dbState.startedLinkProcess") - | Please refresh this page after starting your free trial. + | #{translate("refresh_page_after_starting_free_trial")} div(ng-show="dbState.hasDropboxFeature && dbState.userIsLinkedToDropbox") progressbar.progress-striped.active(value='dbState.percentageLeftTillNextPoll', type="info") span - strong {{dbState.minsTillNextPoll}} minutes - span until dropbox is next checked for changes. + strong {{dbState.minsTillNextPoll}} #{translate("minutes")} + span #{translate("until_db_checked_for_changes")} div.text-center(ng-hide="dbState.hasDropboxFeature") - p You need to upgrade your account to link to dropbox. + p #{translate("need_to_upgrade_for_dropbox")} p - a.btn.btn-info(ng-click="startFreeTrial('dropbox')") Start Free Trial + a.btn.btn-info(ng-click="startFreeTrial('dropbox')") #{translate("start_free_trial")} p.small(ng-show="startedFreeTrial") - | Please refresh this page after starting your free trial. + | #{translate("refresh_page_after_starting_free_trial")} div(ng-hide="dbState.gotLinkStatus") - span.small   checking dropbox status   + span.small   #{translate("checking_dropbox_status")}   i.fa.fa-refresh.fa-spin - - .modal-footer() button.btn.btn-default( ng-click="cancel()", ) - span Dismiss + span #{translate("dismiss")} diff --git a/services/web/app/views/project/editor/editor.jade b/services/web/app/views/project/editor/editor.jade index 4a327dc40a..ffa0fbfb57 100644 --- a/services/web/app/views/project/editor/editor.jade +++ b/services/web/app/views/project/editor/editor.jade @@ -9,7 +9,7 @@ div.full-size( .ui-layout-center .loading-panel(ng-show="!editor.sharejs_doc || editor.opening") i.fa.fa-spin.fa-refresh - |   Loading... + |   #{translate("loading")}... #editor( ace-editor="editor", diff --git a/services/web/app/views/project/editor/file-tree.jade b/services/web/app/views/project/editor/file-tree.jade index 8753bc82d7..1459af4b59 100644 --- a/services/web/app/views/project/editor/file-tree.jade +++ b/services/web/app/views/project/editor/file-tree.jade @@ -71,7 +71,7 @@ aside#file-tree(ng-controller="FileTreeController").full-size ) div(ng-show="deletedDocs.length > 0 && ui.view == 'track-changes'") - h3 Deleted Files + h3 #{translate("deleted_files")} ul.list-unstyled.file-tree-list.deleted-docs li( ng-class="{ 'selected': entity.selected }", @@ -137,13 +137,13 @@ script(type='text/ng-template', id='entityListItemTemplate') href="#" prevent-default="click" ng-click="startRenaming()" - ) Rename + ) #{translate("rename")} li a( href="#" prevent-default="click" ng-click="openDeleteModal()" - ) Delete + ) #{translate("delete")} div.dropdown.context-menu( id="context-menu-{{ entity.id }}", @@ -156,14 +156,14 @@ script(type='text/ng-template', id='entityListItemTemplate') prevent-default="click" stop-propagation="click" ng-click="startRenaming()" - ) Rename + ) #{translate("rename")} li a( href="#" prevent-default="click" stop-propagation="click" ng-click="openDeleteModal()" - ) Delete + ) #{translate("delete")} .entity(ng-if="entity.type == 'folder'", ng-controller="FileTreeFolderController") @@ -221,32 +221,32 @@ script(type='text/ng-template', id='entityListItemTemplate') href="#" prevent-default="click" ng-click="startRenaming()" - ) Rename + ) #{translate("rename")} li a( href="#" prevent-default="click" ng-click="openDeleteModal()" - ) Delete + ) #{translate("delete")} li.divider li a( href="#" prevent-default="click" ng-click="openNewDocModal()" - ) New File + ) #{translate("new_file")} li a( href="#" prevent-default="click" ng-click="openNewFolderModal()" - ) New Folder + ) #{translate("new_folder")} li a( href="#" prevent-default="click" ng-click="openUploadFileModal()" - ) Upload File + ) #{translate("upload_file")} .dropdown.context-menu( ng-if="permissions.write" @@ -259,14 +259,14 @@ script(type='text/ng-template', id='entityListItemTemplate') prevent-default="click" stop-propagation="click" ng-click="startRenaming()" - ) Rename + ) #{translate("rename")} li a( href="#" prevent-default="click" stop-propagation="click" ng-click="openDeleteModal()" - ) Delete + ) #{translate("delete")} li.divider li a( @@ -274,21 +274,21 @@ script(type='text/ng-template', id='entityListItemTemplate') prevent-default="click" stop-propagation="click" ng-click="openNewDocModal()" - ) New File + ) #{translate("new_file")} li a( href="#" prevent-default="click" stop-propagation="click" ng-click="openNewFolderModal()" - ) New Folder + ) #{translate("new_folder")} li a( href="#" prevent-default="click" stop-propagation="click" ng-click="openUploadFileModal()" - ) Upload File + ) #{translate("upload_file")} ul.list-unstyled( ng-if="entity.type == 'folder'" @@ -305,7 +305,7 @@ script(type='text/ng-template', id='entityListItemTemplate') script(type='text/ng-template', id='newDocModalTemplate') .modal-header - h3 New File + h3 #{translate("new_file")} .modal-body form(novalidate, name="newDocForm") input.form-control( @@ -320,17 +320,17 @@ script(type='text/ng-template', id='newDocModalTemplate') button.btn.btn-default( ng-disabled="state.inflight" ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-primary( ng-disabled="newDocForm.$invalid || state.inflight" ng-click="create()" ) - span(ng-hide="state.inflight") Create - span(ng-show="state.inflight") Creating... + span(ng-hide="state.inflight") #{translate("create")} + span(ng-show="state.inflight") #{translate("creating")}... script(type='text/ng-template', id='newFolderModalTemplate') .modal-header - h3 New Folder + h3 #{translate("new_folder")} .modal-body form(novalidate, name="newFolderForm") input.form-control( @@ -345,17 +345,17 @@ script(type='text/ng-template', id='newFolderModalTemplate') button.btn.btn-default( ng-disabled="state.inflight" ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-primary( ng-disabled="newFolderForm.$invalid || state.inflight" ng-click="create()" ) - span(ng-hide="state.inflight") Create - span(ng-show="state.inflight") Creating... + span(ng-hide="state.inflight") #{translate("create")} + span(ng-show="state.inflight") #{translate("creating")}... script(type="text/ng-template", id="uploadFileModalTemplate") .modal-header - h3 Upload File(s) + h3 #{translate("upload_files")} .modal-body( fine-upload endpoint="/project/{{ project_id }}/upload" @@ -369,23 +369,23 @@ script(type="text/ng-template", id="uploadFileModalTemplate") on-upload-callback="onUpload" params="{'folder_id': parent_folder_id}" ) - span Upload file(s) + span #{translate("upload_files")} .modal-footer - button.btn.btn-default(ng-click="cancel()") Cancel + button.btn.btn-default(ng-click="cancel()") #{translate("cancel")} script(type='text/ng-template', id='deleteEntityModalTemplate') .modal-header - h3 Delete {{ entity.name }} + h3 #{translate("delete")} {{ entity.name }} .modal-body - p Are you sure you want to permanently delete {{ entity.name }}? + p #{translate("sure_you_want_to_delete")} .modal-footer button.btn.btn-default( ng-disabled="state.inflight" ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-danger( ng-disabled="state.inflight" ng-click="delete()" ) - span(ng-hide="state.inflight") Delete - span(ng-show="state.inflight") Deleting... + span(ng-hide="state.inflight") #{translate("delete")} + span(ng-show="state.inflight") #{translate("deleting")}... diff --git a/services/web/app/views/project/editor/hotkeys.jade b/services/web/app/views/project/editor/hotkeys.jade index e1f76039ef..c584e5eb28 100644 --- a/services/web/app/views/project/editor/hotkeys.jade +++ b/services/web/app/views/project/editor/hotkeys.jade @@ -5,9 +5,9 @@ script(type="text/ng-template", id="hotkeysModalTemplate") data-dismiss="modal" ng-click="cancel()" ) × - h3 Hotkeys + h3 #{translate("hotkeys")} .modal-body.modal-hotkeys - h3 Common + h3 #{translate("common")} .row .col-xs-6 .hotkey @@ -24,7 +24,7 @@ script(type="text/ng-template", id="hotkeysModalTemplate") span.combination {{ctrl}} + Y span.description Redo - h3 Navigation + h3 #{translate("navigation")} .row .col-xs-6 .hotkey @@ -38,7 +38,7 @@ script(type="text/ng-template", id="hotkeysModalTemplate") span.combination {{ctrl}} + L span.description Go To Line - h3 Editing + h3 #{translate("editing")} .row .col-xs-6 .hotkey @@ -63,4 +63,4 @@ script(type="text/ng-template", id="hotkeysModalTemplate") .modal-footer button.btn.btn-default( ng-click="cancel()" - ) OK \ No newline at end of file + ) #{translate("ok")} \ No newline at end of file diff --git a/services/web/app/views/project/editor/left-menu.jade b/services/web/app/views/project/editor/left-menu.jade index 1bba98a4b7..283a5fdc68 100644 --- a/services/web/app/views/project/editor/left-menu.jade +++ b/services/web/app/views/project/editor/left-menu.jade @@ -2,7 +2,7 @@ aside#left-menu.full-size( ng-class="{ 'shown': ui.leftMenuShown }" ng-cloak ) - h4 Download + h4 #{translate("download")} ul.list-unstyled.nav.nav-downloads.text-center li @@ -12,7 +12,7 @@ aside#left-menu.full-size( ) i.fa.fa-file-archive-o.fa-2x br - | Source + | #{translate("source")} li a( ng-href="{{pdf.url}}" @@ -32,7 +32,7 @@ aside#left-menu.full-size( | PDF span(ng-show="!anonymous") - h4 Actions + h4 #{translate("Actions")} ul.list-unstyled.nav li(ng-controller="CloneProjectController") a( @@ -40,26 +40,26 @@ aside#left-menu.full-size( ng-click="openCloneProjectModal()" ) i.fa.fa-fw.fa-copy - |   Copy Project + |   #{translate("copy_project")} li(ng-controller="TemplatesController", ng-show="permissions.admin") a(ng-click="openPublishTemplateModal()") i.fa.fa-external-link.fa-fw - |   Publish as Template + |    #{translate("publish_as_template")} span(ng-controller="DropboxController", ng-show="permissions.admin") - h4() Sync + h4() #{translate("sync")} ul.list-unstyled.nav() li a(ng-click="openDropboxModal()") i.fa.fa-dropbox.fa-fw |    Dropbox - h4(ng-show="!anonymous") Settings + h4(ng-show="!anonymous") #{translate("settings")} form.settings(ng-controller="SettingsController", ng-show="!anonymous") .containter-fluid .form-controls(ng-show="permissions.write") - label(for="compiler") Compiler + label(for="compiler") #{translate("compiler")} select( name="compiler" ng-model="project.compiler" @@ -70,7 +70,7 @@ aside#left-menu.full-size( option(value='lualatex') LuaLaTeX .form-controls(ng-show="permissions.write") - label(for="rootDoc_id") Main document + label(for="rootDoc_id") #{translate("main_document")} select( name="rootDoc_id", ng-model="project.rootDoc_id", @@ -78,12 +78,12 @@ aside#left-menu.full-size( ) .form-controls - label(for="spellCheckLanguage") Spell Check + label(for="spellCheckLanguage") #{translate("spell_check")} select( name="spellCheckLanguage" ng-model="project.spellCheckLanguage" ) - option(value="") Off + option(value="") #{translate("off")} optgroup(label="Language") for language in languages option( @@ -91,7 +91,7 @@ aside#left-menu.full-size( )= language.name .form-controls - label(for="autoComplete") Auto-Complete + label(for="autoComplete") #{translate("auto_complete")} select( name="autoComplete" ng-model="settings.autoComplete" @@ -99,7 +99,7 @@ aside#left-menu.full-size( ) .form-controls - label(for="theme") Theme + label(for="theme") #{translate("theme")} select( name="theme" ng-model="settings.theme" @@ -108,7 +108,7 @@ aside#left-menu.full-size( option(value=theme) #{theme.replace(/_/g, ' ')} .form-controls(ng-show="!anonymous") - label(for="mode") Keybindings + label(for="mode") #{translate("keybindings")} select( name="mode" ng-model="settings.mode" @@ -118,7 +118,7 @@ aside#left-menu.full-size( option(value='emacs') Emacs .form-controls - label(for="fontSize") Font Size + label(for="fontSize") #{translate("font_size")} select( name="fontSize" ng-model="settings.fontSize" @@ -127,20 +127,20 @@ aside#left-menu.full-size( option(value=size) #{size}px .form-controls - label(for="pdfViewer") PDF Viewer + label(for="pdfViewer") select( name="pdfViewer" ng-model="settings.pdfViewer" ) - option(value="pdfjs") Built-In - option(value="native") Native + option(value="pdfjs") #{translate("built_in")} + option(value="native") #{translate("native")} - h4 Hotkeys + h4 #{translate("hotkeys")} ul.list-unstyled.nav li(ng-controller="HotkeysController") a(ng-click="openHotkeysModal()") i.fa.fa-keyboard-o.fa-fw - |    Show Hotkeys + |    #{translate("show_hotkeys")} #left-menu-mask( ng-show="ui.leftMenuShown", @@ -150,11 +150,11 @@ aside#left-menu.full-size( script(type='text/ng-template', id='cloneProjectModalTemplate') .modal-header - h3 Copy Project + h3 #{translate("copy_project")} .modal-body form(name="cloneProjectForm", novalidate) .form-group - label New Name + label #{translate("new_name")} input.form-control( type="text", placeholder="New Project Name", @@ -167,10 +167,10 @@ script(type='text/ng-template', id='cloneProjectModalTemplate') button.btn.btn-default( ng-disabled="state.inflight" ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-primary( ng-disabled="cloneProjectForm.$invalid || state.inflight" ng-click="clone()" ) - span(ng-hide="state.inflight") Copy - span(ng-show="state.inflight") Copying... + span(ng-hide="state.inflight") #{translate("copy")} + span(ng-show="state.inflight") #{translate("copying")}... diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 0af9b60425..16f3c84a54 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -9,8 +9,8 @@ div.full-size.pdf(ng-controller="PdfController") ng-class="{'fa-spin': pdf.compiling }" ) |    - span(ng-show="!pdf.compiling") Recompile - span(ng-show="pdf.compiling") Compiling... + span(ng-show="!pdf.compiling") #{translate("recompile")} + span(ng-show="pdf.compiling") #{translate("compiling")}... a.log-btn( href ng-click="toggleLogs()" @@ -76,26 +76,24 @@ div.full-size.pdf(ng-controller="PdfController") .pdf-uncompiled(ng-show="pdf.uncompiled && !pdf.compiling") |   i.fa.fa-level-up.fa-flip-horizontal.fa-2x - |   Click here to preview your work as a PDF. + |   #{translate("click_here_to_preview_pdf")} .pdf-errors(ng-show="pdf.timedout || pdf.error") .alert.alert-danger(ng-show="pdf.error") - strong Server Error. - span Sorry, something went wrong and your project could not be compiled. Please try again in a few moments. + strong #{translate("server_error")} + span #{translate("somthing_went_wrong_compiling")} .alert.alert-danger(ng-show="pdf.timedout") - strong Timed out. - span Sorry, your compile was taking too long and timed out. - | This may be due to a large number of high-res images, or lots of complicated diagrams. - | Please try to make your document simpler, or contact support for help. + strong #{translate("timedout")}. + span #{translate("proj_timed_out_reason")} .pdf-logs(ng-show="(pdf.view == 'logs' || pdf.failure) && !pdf.error && !pdf.timeout && !pdf.uncompiled") .alert.alert-success(ng-show="pdf.logEntries.all.length == 0") - | No errors, good job! + | #{translate("no_errors_good_job")} .alert.alert-danger(ng-show="pdf.failure") - strong Compile Error. - span Sorry, your LaTeX code couldn't compile for some reason. Please check the errors below for details, or view the raw log. + strong #{translate("compile_error")}. + span #{translate("generic_failed_compile_message")}. div(ng-repeat="entry in pdf.logEntries.all", ng-controller="PdfLogEntryController") .alert( @@ -128,7 +126,7 @@ div.full-size.pdf(ng-controller="PdfController") href dropdown-toggle ) - | Other logs & files + | #{translate("other_logs_and_files")} span.caret ul.dropdown-menu.dropdown-menu-right li(ng-repeat="file in pdf.outputFiles") @@ -138,26 +136,25 @@ div.full-size.pdf(ng-controller="PdfController") ng-click="openOutputFile(file)" ) {{ file.name }} a.btn.btn-info.btn-sm(href, ng-click="toggleRawLog()") - span(ng-show="!pdf.showRawLog") View Raw Logs - span(ng-show="pdf.showRawLog") Hide Raw Logs + span(ng-show="!pdf.showRawLog") #{translate("view_raw_logs")} + span(ng-show="pdf.showRawLog") #{translate("hide_raw_logs")} pre(ng-bind="pdf.rawLog", ng-show="pdf.showRawLog") script(type='text/ng-template', id='clearCacheModalTemplate') .modal-header - h3 Clear cache? + h3 #{translate("clear_cache")}? .modal-body - p This will clear all hidden LaTeX files (.aux, .bbl, etc) from our compile server. - | You generally don't need to do this unless you're having trouble with references. - p Your project files will not be deleted or changed. + p #{translate("clear_cache_explanation")} + p #{translate("clear_cache_is_safe")} .modal-footer button.btn.btn-default( ng-click="cancel()" ng-disabled="state.inflight" - ) Cancel + ) #{translate("cancel")} button.btn.btn-info( ng-click="clear()" ng-disabled="state.inflight" ) - span(ng-show="!state.inflight") Clear cache - span(ng-show="state.inflight") Clearing... + span(ng-show="!state.inflight") #{translate("clear_cache")} + span(ng-show="state.inflight") #{translate("clearing")}... diff --git a/services/web/app/views/project/editor/publish-template.jade b/services/web/app/views/project/editor/publish-template.jade index f7ff01694b..69c50fd52c 100644 --- a/services/web/app/views/project/editor/publish-template.jade +++ b/services/web/app/views/project/editor/publish-template.jade @@ -5,11 +5,11 @@ script(type="text/ng-template", id="publishProjectAsTemplateModalTemplate") data-dismiss="modal" ng-click="cancel()" ) × - h3 Publish as Template + h3 #{translate("publish_as_template")} .modal-body.modal-body-share span(ng-hide="problemTalkingToTemplateApi") form() - label(for='Description') Template Description + label(for='Description') #{translate("template_description")} .form-group textarea.form-control( rows=5, @@ -19,11 +19,11 @@ script(type="text/ng-template", id="publishProjectAsTemplateModalTemplate") value="" ) div(ng-show="templateDetails.exists").text-center.templateDetails - | Your project was last published at + | #{translate("project_last_published_at")} strong {{templateDetails.publishedDate}}. - a(ng-href="{{templateDetails.canonicalUrl}}") View it in template gallery. + a(ng-href="{{templateDetails.canonicalUrl}}") #{translate("view_in_template_gallery")}. - span(ng-show="problemTalkingToTemplateApi") There is a problem with our publishing service, please try again in a few minutes. + span(ng-show="problemTalkingToTemplateApi") #{translate("problem_talking_to_publishing_service")}. @@ -33,20 +33,20 @@ script(type="text/ng-template", id="publishProjectAsTemplateModalTemplate") ng-click="cancel()", ng-disabled="state.publishInflight || state.unpublishInflight" ) - span Cancel + span #{translate("cancel")} button.btn.btn-info( ng-click="unpublishTemplate()", ng-disabled="state.publishInflight || state.unpublishInflight" ng-show="templateDetails.exists" ) - span(ng-show="!state.unpublishInflight") Unpublish - span(ng-show="state.unpublishInflight") Unpublishing... + span(ng-show="!state.unpublishInflight") #{translate("unpublish")} + span(ng-show="state.unpublishInflight") #{translate("unpublishing")}... button.btn.btn-primary( ng-click="publishTemplate()", ng-disabled="state.publishInflight || state.unpublishInflight" ) - span(ng-show="!state.publishInflight && !templateDetails.exists") Publish - span(ng-show="!state.publishInflight && templateDetails.exists") Republish - span(ng-show="state.publishInflight") Publishing... \ No newline at end of file + span(ng-show="!state.publishInflight && !templateDetails.exists") #{translate("publish")} + span(ng-show="!state.publishInflight && templateDetails.exists") #{translate("republish")} + span(ng-show="state.publishInflight") #{translate("publishing")}... \ No newline at end of file diff --git a/services/web/app/views/project/editor/share.jade b/services/web/app/views/project/editor/share.jade index fb47236cc8..8dddb51a3a 100644 --- a/services/web/app/views/project/editor/share.jade +++ b/services/web/app/views/project/editor/share.jade @@ -5,26 +5,26 @@ script(type='text/ng-template', id='shareProjectModalTemplate') data-dismiss="modal" ng-click="cancel()" ) × - h3 Share Project + h3 #{translate("share_project")} .modal-body.modal-body-share .container-fluid .row.public-access-level(ng-show="project.publicAccesLevel == 'private'") .col-xs-12.text-center - | This project is private and can only be accessed by the people below. + | #{translate("this_project_is_private")} |    a( href ng-click="openMakePublicModal()" - ) Make Public + ) #{translate("make_public")} .row.public-access-level(ng-show="project.publicAccesLevel != 'private'") .col-xs-12.text-center - strong(ng-if="project.publicAccesLevel == 'readAndWrite'") This project is public and can be edited by anyone with the URL. - strong(ng-if="project.publicAccesLevel == 'readOnly'") This project is public and can be viewed by anyone with the URL. + strong(ng-if="project.publicAccesLevel == 'readAndWrite'") #{translate("this_project_is_public")} + strong(ng-if="project.publicAccesLevel == 'readOnly'") #{translate("this_project_is_public")} |    a( href ng-click="openMakePrivateModal()" - ) Make Private + ) #{translate("make_private")} .row.project-member .col-xs-8 {{ project.owner.email }} .text-right( @@ -33,8 +33,8 @@ script(type='text/ng-template', id='shareProjectModalTemplate') .row.project-member(ng-repeat="member in project.members") .col-xs-8 {{ member.email }} .col-xs-3.text-right - span(ng-show="member.privileges == 'readAndWrite'") Can Edit - span(ng-show="member.privileges == 'readOnly'") Read Only + span(ng-show="member.privileges == 'readAndWrite'") #{translate("can_edit")} + span(ng-show="member.privileges == 'readOnly'") #{translate("read_only")} .col-xs-1 a( href @@ -45,7 +45,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate') i.fa.fa-times .row.invite-controls form(ng-show="canAddCollaborators") - .small Share with your collaborators + .small #{translate("share_with_your_collabs")} .form-group input.form-control( type="email" @@ -59,19 +59,19 @@ script(type='text/ng-template', id='shareProjectModalTemplate') ng-model="inputs.privileges" name="privileges" ) - option(value="readAndWrite") Can Edit - option(value="readOnly") Read Only + option(value="readAndWrite") #{translate("can_edit")} + option(value="readOnly") #{translate("read_only")} |    button.btn.btn-info( type="submit" ng-click="addMember()" - ) Share + ) #{translate("share")} div.text-center(ng-hide="canAddCollaborators") - p You need to upgrade your account to add more collaborators. + p #{translate("need_to_upgrade_for_more_collabs")}. p - a.btn.btn-info(href, ng-click="startFreeTrial('projectMembers')") Start Free Trial + a.btn.btn-info(href, ng-click="startFreeTrial('projectMembers')") #{translate("start_free_trial")} p.small(ng-show="startedFreeTrial") - | Please refresh this page after starting your free trial. + | #{translate("refresh_page_after_starting_free_trial")}. .modal-footer .modal-footer-left @@ -79,7 +79,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate') span.text-danger.error(ng-show="state.error") {{ state.error }} button.btn.btn-primary( ng-click="done()" - ) Done + ) #{translate("done")} script(type="text/ng-template", id="makePublicModalTemplate") .modal-header @@ -88,23 +88,23 @@ script(type="text/ng-template", id="makePublicModalTemplate") data-dismiss="modal" ng-click="cancel()" ) × - h3 Make project public? + h3 #{translate("make_project_public")}? .modal-body.modal-body-share - p If you make your project public then anyone with the URL will be able to access it. + p #{translate("make_project_public_consequences")} p select.form-control( ng-model="inputs.privileges" name="privileges" ) - option(value="readAndWrite") Allow public editing - option(value="readOnly") Allow public read only access + option(value="readAndWrite") #{translate("allow_public_editing")} + option(value="readOnly") #{translate("allow_public_read_only")} .modal-footer button.btn.btn-default( ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-info( ng-click="makePublic()" - ) Make public + ) #{translate("make_public")} script(type="text/ng-template", id="makePrivateModalTemplate") .modal-header @@ -113,13 +113,13 @@ script(type="text/ng-template", id="makePrivateModalTemplate") data-dismiss="modal" ng-click="cancel()" ) × - h3 Make project private? + h3 #{translate("make_project_private")}? .modal-body.modal-body-share - p If you make your project private then only the people you choose to share it with will have access. + p #{translate("make_project_private_consequences")} .modal-footer button.btn.btn-default( ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-info( ng-click="makePrivate()" - ) Make private + ) #{translate("make_private")} diff --git a/services/web/app/views/project/editor/track-changes.jade b/services/web/app/views/project/editor/track-changes.jade index 92bc4228f1..0fe6b3ea42 100644 --- a/services/web/app/views/project/editor/track-changes.jade +++ b/services/web/app/views/project/editor/track-changes.jade @@ -1,15 +1,15 @@ div#trackChanges(ng-show="ui.view == 'track-changes'") .upgrade-prompt(ng-show="!project.features.versioning") .message(ng-show="project.owner._id == user.id") - p You need to upgrade your account to use the History feature. + p #{translate("need_to_upgrade_for_history")} p a.btn.btn-info( href ng-click="startFreeTrial('track-changes')" - ) Start Free Trial - p.small(ng-show="startedFreeTrial") Please refresh the page after starting your free trial. + ) #{translate("start_free_trial")} + p.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")} .message(ng-show="project.owner._id != user.id") - p Please ask the project owner to upgrade to use the History feature. + p #{translate("ask_proj_owner_to_upgrade_for_history")} aside.change-list( ng-controller="TrackChangesListController" @@ -73,11 +73,11 @@ div#trackChanges(ng-show="ui.view == 'track-changes'") .name(ng-if="update_user.id == user.id") You div.user(ng-if="update.meta.users.length == 0") .color-square(style="background-color: hsl(100, 100%, 50%)") - span Anonymous + span #{translate("anonymous")} .loading(ng-show="trackChanges.loading") i.fa.fa-spin.fa-refresh - |   Loading... + |    #{translate("loading")}... .diff-panel.full-size(ng-controller="TrackChangesDiffController") .diff( @@ -98,7 +98,7 @@ div#trackChanges(ng-show="ui.view == 'track-changes'") a.btn.btn-danger.btn-sm( href, ng-click="openRestoreDiffModal()" - ) Restore to before these changes + ) #{translate("restore_to_before_these_changes")} .diff-editor.hide-ace-cursor( ace-editor="track-changes", theme="settings.theme", @@ -112,17 +112,18 @@ div#trackChanges(ng-show="ui.view == 'track-changes'") .diff-deleted.text-centered( ng-show="trackChanges.diff.deleted" ) - p.text-serif {{ trackChanges.diff.doc.name }} has been deleted. + p.text-serif #{translate("file_has_been_deleted", filename:"{{ trackChanges.diff.doc.name }} ")} + p a.btn.btn-primary.btn-lg( href, ng-click="restoreDeletedDoc()" - ) Restore + ) #{translate("restore")} .loading-panel(ng-show="trackChanges.diff.loading") i.fa.fa-spin.fa-refresh - |   Loading... + |   #{translate("loading")}... .error-panel(ng-show="trackChanges.diff.error") - .alert.alert-danger Sorry, something went wrong :( + .alert.alert-danger #{translate("generic_something_went_wrong")} script(type="text/ng-template", id="trackChangesRestoreDiffModalTemplate") .modal-header @@ -131,17 +132,17 @@ script(type="text/ng-template", id="trackChangesRestoreDiffModalTemplate") data-dismiss="modal" ng-click="cancel()" ) × - h3 Restore {{diff.doc.name}} + h3 #{translate("restore")} {{diff.doc.name}} .modal-body.modal-body-share - p Are you sure you want to restore {{diff.doc.name}} to before the changes on {{diff.start_ts | formatDate}}? + p #{translate("sure_you_want_to_restore_before", {filename:"{{diff.doc.name}}", date:"{{diff.start_ts | formatDate}}"})} .modal-footer button.btn.btn-default( ng-click="cancel()", ng-disabled="state.inflight" - ) Cancel + ) #{translate("cancel")} button.btn.btn-danger( ng-click="restore()", ng-disabled="state.inflight" ) - span(ng-show="!state.inflight") Restore - span(ng-show="state.inflight") Restoring... + span(ng-show="!state.inflight") #{translate("restore")} + span(ng-show="state.inflight") #{translate("restoring")} ... diff --git a/services/web/app/views/project/list/modals.jade b/services/web/app/views/project/list/modals.jade index ca5f6fbbe3..e4ea1e2542 100644 --- a/services/web/app/views/project/list/modals.jade +++ b/services/web/app/views/project/list/modals.jade @@ -5,7 +5,7 @@ script(type='text/ng-template', id='newTagModalTemplate') data-dismiss="modal" ng-click="cancel()" ) × - h3 Create New Folder + h3 #{translate("create_new_folder")} .modal-body form(name="newTagForm", novalidate) input.form-control( @@ -23,12 +23,12 @@ script(type='text/ng-template', id='newTagModalTemplate') button.btn.btn-default( ng-click="cancel()" stop-propagation="click" - ) Cancel + ) #{translate("cancel")} button.btn.btn-primary( ng-disabled="newTagForm.$invalid" ng-click="create()" stop-propagation="click" - ) Create + ) #{translate("create")} script(type='text/ng-template', id='renameProjectModalTemplate') .modal-header @@ -37,7 +37,7 @@ script(type='text/ng-template', id='renameProjectModalTemplate') data-dismiss="modal" ng-click="cancel()" ) × - h3 Rename Project + h3 #{translate("rename_project")} .modal-body form(name="renameProjectForm", novalidate) input.form-control( @@ -49,11 +49,11 @@ script(type='text/ng-template', id='renameProjectModalTemplate') focus-on="open" ) .modal-footer - button.btn.btn-default(ng-click="cancel()") Cancel + button.btn.btn-default(ng-click="cancel()") #{translate("cancel")} button.btn.btn-primary( ng-click="rename()", ng-disabled="renameProjectForm.$invalid" - ) Rename + ) #{translate("rename")} script(type='text/ng-template', id='cloneProjectModalTemplate') .modal-header @@ -62,11 +62,11 @@ script(type='text/ng-template', id='cloneProjectModalTemplate') data-dismiss="modal" ng-click="cancel()" ) × - h3 Copy Project + h3 #{translate("copy_project")} .modal-body form(name="cloneProjectForm", novalidate) .form-group - label New Name + label #{translate("new_name")} input.form-control( type="text", placeholder="New Project Name", @@ -79,13 +79,13 @@ script(type='text/ng-template', id='cloneProjectModalTemplate') button.btn.btn-default( ng-disabled="state.inflight" ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-primary( ng-disabled="cloneProjectForm.$invalid || state.inflight" ng-click="clone()" ) - span(ng-hide="state.inflight") Copy - span(ng-show="state.inflight") Copying... + span(ng-hide="state.inflight") #{translate("copy")} + span(ng-show="state.inflight") #{translate("coping")} ... script(type='text/ng-template', id='newProjectModalTemplate') .modal-header @@ -94,7 +94,7 @@ script(type='text/ng-template', id='newProjectModalTemplate') data-dismiss="modal" ng-click="cancel()" ) × - h3 New Project + h3 #{translate("new_project")} .modal-body form(novalidate, name="newProjectForm") input.form-control( @@ -109,13 +109,13 @@ script(type='text/ng-template', id='newProjectModalTemplate') button.btn.btn-default( ng-disabled="state.inflight" ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-primary( ng-disabled="newProjectForm.$invalid || state.inflight" ng-click="create()" ) - span(ng-hide="state.inflight") Create - span(ng-show="state.inflight") Creating... + span(ng-hide="state.inflight") #{translate("create")} + span(ng-show="state.inflight") #{translate("creating")} ... script(type='text/ng-template', id='deleteProjectsModalTemplate') .modal-header @@ -124,22 +124,22 @@ script(type='text/ng-template', id='deleteProjectsModalTemplate') data-dismiss="modal" ng-click="cancel()" ) × - h3 {{action}} Projects + h3 {{action}} #{translate("projects")} .modal-body div(ng-show="projectsToDelete.length > 0") - p You are about to delete the following projects: + p #{translate("about_to_delete_projects")} ul li(ng-repeat="project in projectsToDelete | orderBy:'name'") strong {{project.name}} div(ng-show="projectsToLeave.length > 0") - p You are about to leave the following projects: + p #{translate("about_to_leave_projects")} ul li(ng-repeat="project in projectsToLeave | orderBy:'name'") strong {{project.name}} .modal-footer button.btn.btn-default( ng-click="cancel()" - ) Cancel + ) #{translate("cancel")} button.btn.btn-danger( ng-click="delete()" ) {{action}} @@ -151,7 +151,7 @@ script(type="text/ng-template", id="uploadProjectModalTemplate") data-dismiss="modal" ng-click="cancel()" ) × - h3 Upload Zipped Project + h3 #{translate("upload_zipped_project")} .modal-body( fine-upload endpoint="/project/new/upload" @@ -163,9 +163,9 @@ script(type="text/ng-template", id="uploadProjectModalTemplate") allowed-extensions="['zip']" on-complete-callback="onComplete" ) - span Upload a zipped project + span #{translate("upload_a_zipped_project")} .modal-footer - button.btn.btn-default(ng-click="cancel()") Cancel + button.btn.btn-default(ng-click="cancel()") #{translate("cancel")} script(type="text/ng-template", id="userProfileModalTemplate") .modal-header @@ -174,12 +174,12 @@ script(type="text/ng-template", id="userProfileModalTemplate") data-dismiss="modal" ng-click="done()" ) × - h3 Your Profile + h3 #{translate("your_profile")} .modal-body form(enctype='multipart/form-data', method='post') .form-group - label(for="first_name") First Name + label(for="first_name") #{translate("first_name")} input.form-control( type='text', name='first_name', @@ -189,7 +189,7 @@ script(type="text/ng-template", id="userProfileModalTemplate") ) .form-group - label(for="last_name") Last Name + label(for="last_name") #{translate("last_name")} input.form-control( type='text', name='last_name', @@ -198,7 +198,7 @@ script(type="text/ng-template", id="userProfileModalTemplate") ) .form-group.user_details_auto_complete - label(for="institution") Institution + label(for="institution") #{translate("institution")} autocomplete( ng-model="userInfoForm.institution", name="institution", @@ -209,7 +209,7 @@ script(type="text/ng-template", id="userProfileModalTemplate") ) .form-group.user_details_auto_complete - label(for="role") Role + label(for="role") #{translate("role")} autocomplete( ng-model="userInfoForm.role", name="role", @@ -220,4 +220,4 @@ script(type="text/ng-template", id="userProfileModalTemplate") .modal-footer - button.btn.btn-info(ng-click="done()") Done + button.btn.btn-info(ng-click="done()") #{translate("done")} diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.jade index cf3d95dbb5..c560ebc3c3 100644 --- a/services/web/app/views/project/list/side-bar.jade +++ b/services/web/app/views/project/list/side-bar.jade @@ -51,7 +51,7 @@ li(ng-class="{active: (filter == 'archived')}") a(href, ng-click="filterProjects('archived')") #{translate("deleted_projects")} li - h2 Folders + h2 #{translate("folders")} li( ng-repeat="tag in tags | filter:nonEmpty", ng-class="{active: tag.selected}", @@ -86,7 +86,7 @@ .progress .progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}") - p.small #{translate("profile_complete_percentage")} + p.small #{translate("profile_complete_percentage_test", {percentval:"{{percentComplete}}"})} button#completeUserProfileInformation.btn.btn-info( ng-hide="formVisable", diff --git a/services/web/app/views/user/register.jade b/services/web/app/views/user/register.jade index a2faf4fe25..0b9010d0cc 100644 --- a/services/web/app/views/user/register.jade +++ b/services/web/app/views/user/register.jade @@ -6,24 +6,25 @@ block content .row .registration_message if sharedProjectData.user_first_name !== undefined - h1 #{sharedProjectData.user_first_name} would like you to view '#{sharedProjectData.project_name}' - div Join ShareLaTeX to view this project + h1 #{translate("user_wants_you_to_see_project", {username:sharedProjectData.user_first_name, projectname:sharedProjectData.project_name})} + div #{translate("join_sl_to_view_project")} else if newTemplateData.templateName !== undefined - h1 Please register to edit the '#{newTemplateData.templateName}' template - div Already have a ShareLaTeX account? - a(href="/login") Login here + h1 #{translate("register_to_edit_template", {templatename:newTemplateData.templateName})} + + div #{translate("already_have_sl_account")} + a(href="/login") #{translate("login_here")} .row .col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4 .card .page-header - h1 Register + h1 #{translate("register")} form(async-form="register", name="registerForm", action="/register", ng-cloak) input(name='_csrf', type='hidden', value=csrfToken) input(name='redir', type='hidden', value=redir) form-messages(for="registerForm") .form-group - label(for='email') Email + label(for='email') #{translate("email")} input.form-control( type='email', name='email', @@ -35,9 +36,9 @@ block content focus="true" ) span.small.text-primary(ng-show="registerForm.email.$invalid && registerForm.email.$dirty") - | Must be an email address + | #{translate("must_be_email_address")} .form-group - label(for='password') Password + label(for='password') #{translate("password")} input.form-control( type='password', name='password', @@ -46,11 +47,11 @@ block content ng-model="password" ) span.small.text-primary(ng-show="registerForm.password.$invalid && registerForm.password.$dirty") - | Required + | #{translate("required")} .actions button.btn-primary.btn( type='submit' ng-disabled="registerForm.inflight" ) - span(ng-show="!registerForm.inflight") Register - span(ng-show="registerForm.inflight") Registering... + span(ng-show="!registerForm.inflight") #{translate("register")} + span(ng-show="registerForm.inflight") #{translate("registering")}... From fdc5a5154a7775d6cccb4230d001057896d3bdc2 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 1 Aug 2014 12:34:53 +0100 Subject: [PATCH 12/26] added a load more pages --- services/web/app/views/layout.jade | 6 +-- services/web/app/views/referal/bonus.jade | 40 +++++++++---------- .../app/views/subscriptions/dashboard.jade | 40 +++++++++---------- .../subscriptions/edit-billing-details.jade | 4 +- .../app/views/subscriptions/group_admin.jade | 18 ++++----- services/web/app/views/subscriptions/new.jade | 4 +- .../successful_subscription.jade | 23 +++++------ services/web/app/views/templates/index.jade | 2 +- services/web/app/views/templates/tag.jade | 2 +- .../web/app/views/templates/template.jade | 6 +-- services/web/app/views/user/restricted.jade | 4 +- 11 files changed, 74 insertions(+), 75 deletions(-) diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index 47cc832bfa..53f70c3eca 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -4,11 +4,11 @@ html(itemscope, itemtype='http://schema.org/Product') head - - if (typeof(priority_title) !== "undefined" && priority_title) - title= title + ' - Online LaTeX Editor ShareLaTeX' + title= title + ' - '+ translate("online_latex_editor") - else - title= 'Online LaTeX Editor ShareLaTeX - ' +title + title= translate("online_latex_editor") +' ShareLaTeX - ' +title + link(rel="icon", href="/favicon.ico") link(rel='stylesheet', href='/stylesheets/style.css?fingerprint='+fingerprint('/stylesheets/style.css')) link(href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css",rel="stylesheet") diff --git a/services/web/app/views/referal/bonus.jade b/services/web/app/views/referal/bonus.jade index 1bcc874d20..3439e4694e 100644 --- a/services/web/app/views/referal/bonus.jade +++ b/services/web/app/views/referal/bonus.jade @@ -10,12 +10,12 @@ block content .row .col-md-12 .page-header - h1 Help us spread the word about ShareLaTeX. + h1 #{translate("help_us_spread_word")}. .row .col-md-10.col-md-offset-1 - h2 Share ShareLaTeX with your friends and colleagues and unlock the rewards below + h2 #{translate("share_sl_to_get_rewards")} .row .col-md-8.col-md-offset-2.bonus-banner @@ -29,31 +29,31 @@ block content .row .col-md-8.col-md-offset-2.bonus-banner .title - a(href='#', onclick='postToFeed(); return false;').facebook Post on Facebook + a(href='#', onclick='postToFeed(); return false;').facebook #{translate("post_on_facebook")} .row .col-md-8.col-md-offset-2.bonus-banner .title - a(href="https://plus.google.com/share?url=#{encodeURIComponent(buildReferalUrl('gp'))}", onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;").google-plus Share us on Google+ + a(href="https://plus.google.com/share?url=#{encodeURIComponent(buildReferalUrl('gp'))}", onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;").google-plus #{translate("share_us_on_googleplus")} .row .col-md-8.col-md-offset-2.bonus-banner .title - a(href='mailto:?subject=Online LaTeX editor you may like &body=Hey, I have been using the online LaTeX editor ShareLaTeX recently and thought you might like to check it out. #{encodeURIComponent(buildReferalUrl("e"))}', title='Share by Email').email Email us to your friends + a(href='mailto:?subject=Online LaTeX editor you may like &body=Hey, I have been using the online LaTeX editor ShareLaTeX recently and thought you might like to check it out. #{encodeURIComponent(buildReferalUrl("e"))}', title='Share by Email').email #{translate("email_us_to_your_friends")} .row .col-md-8.col-md-offset-2.bonus-banner .title - a(href='#link-modal', data-toggle="modal", ng-click="openLinkToUsModal()").link Link to us from your website + a(href='#link-modal', data-toggle="modal", ng-click="openLinkToUsModal()").link #{translate("link_to_us")} .row .col-md-10.col-md-offset-1.bonus-banner - h2.direct-link Direct Link + h2.direct-link #{translate("direct_link")} pre.text-centered #{buildReferalUrl("d")} .row.ab-bonus .col-md-10.col-md-offset-1.bonus-banner - p.thanks When someone starts using ShareLaTeX after your recommendation we'll give you some free stuff to say thanks! Check your progress below. + p.thanks #{translate("sl_gives_you_free_stuff_see_progress_below")} .row.ab-bonus .col-md-10.col-md-offset-1.bonus-banner(style="position: relative; height: 30px; margin-top: 20px;") - for (var i = 0; i <= 10; i++) { @@ -67,24 +67,24 @@ block content .col-md-10.col-md-offset-1.bonus-banner .progress - if (refered_user_count == 0) - div(style="text-align: center; padding: 4px;") Spread the word and fill this bar up + div(style="text-align: center; padding: 4px;") #{translate("spread_the_word_and_fill_bar")} .progress-bar.progress-bar-info(style="width: #{refered_user_count}0%") .row.ab-bonus .col-md-10.col-md-offset-1.bonus-banner(style="position: relative; height: 70px;") - .perk(style="left: 10%;", class = refered_user_count >= 1 ? "active" : "") One free collaborator - .perk(style="left: 30%;", class = refered_user_count >= 3 ? "active" : "") Three free collaborators - .perk(style="left: 60%;", class = refered_user_count >= 6 ? "active" : "") Free Dropbox and History - .perk(style="left: 90%;", class = refered_user_count >= 9 ? "active" : "") Free Professional account + .perk(style="left: 10%;", class = refered_user_count >= 1 ? "active" : "") #{translate("one_free_collab")} + .perk(style="left: 30%;", class = refered_user_count >= 3 ? "active" : "") #{translate("three_free_collab")} + .perk(style="left: 60%;", class = refered_user_count >= 6 ? "active" : "") #{translate("free_dropbox_and_history")} + .perk(style="left: 90%;", class = refered_user_count >= 9 ? "active" : "") #{translate("free_prof_account")} .row.ab-bonus .col-md-10.col-md-offset-1.bonus-banner - if (refered_user_count == 0) - p.thanks You've not introduced anyone to ShareLaTeX yet. Get sharing! + p.thanks #{translate("you_not_introed_anyone_to_sl")} - else if (refered_user_count == 1) - p.thanks You've introduced #{refered_user_count} person to ShareLaTeX. Good job, but can you get some more? + p.thanks #{translate("you_introed_small_number", {numberOfPeople:"#{refered_user_count}"})} - else - p.thanks You've introduced #{refered_user_count} people to ShareLaTeX. Good job! + p.thanks #{translate("you_introed_high_number", {numberOfPeople:"#{refered_user_count}"})} script(type="text/ng-template", id="BonusLinkToUsModal") .modal-header @@ -93,22 +93,22 @@ block content data-dismiss="modal" ng-click="cancel()" ) × - h3 Link to ShareLaTeX + h3 #{translate("link_to_sl")} .modal-body.modal-body-share.link-modal - p You can link to ShareLaTeX with the following HTML: + p #{translate("can_link_to_sl_with_html")} p textarea.col-md-12(readonly=true) Online LaTeX Editor ShareLaTeX - p Thanks! + p #{translate("thanks")}! .modal-footer() button.btn.btn-default( ng-click="cancel()", ) - span Close + span #{translate("close")} diff --git a/services/web/app/views/subscriptions/dashboard.jade b/services/web/app/views/subscriptions/dashboard.jade index 7b5bc077b1..39bb5087d1 100644 --- a/services/web/app/views/subscriptions/dashboard.jade +++ b/services/web/app/views/subscriptions/dashboard.jade @@ -9,14 +9,14 @@ mixin printPlan(plan) strong #{plan.name} td -if (plan.annual) - | $#{plan.price / 100} / year + | $#{plan.price / 100} / #{translate("year")} -else - | $#{plan.price / 100} / month + | $#{plan.price / 100} / #{translate("month")} td -if (subscription.state == "free-trial") - a(href="/user/subscription/new?planCode=#{plan.planCode}").btn.btn-success Subscribe to this plan + a(href="/user/subscription/new?planCode=#{plan.planCode}").btn.btn-success #{translate("subscribe_to_this_plan")} -else if (plan.planCode == subscription.planCode) - button.btn.disabled Your plan + button.btn.disabled #{translate("your_plan")} -else form(action="/user/subscription/update",method="post") input(type="hidden", name="_csrf", value=csrfToken) @@ -34,44 +34,44 @@ block content .col-md-8.col-md-offset-2 .card .page-header - h1 Your Subscription + h1 #{translate("your_subscription")} case subscription.state when "free-trial" - p You are currently using a free trial which expires on #{subscription.expiresAt}. - p Choose a plan below to subscribe to. + p #{translate("on_free_trial_expiring_at", {expiresAt:"#{subscription.expiresAt}"})} + p #{translate("choose_a_plan_below")} when "active" - p You are currently subscribed to the #{subscription.name} plan. - a(href, ng-click="changePlan = true") Change Plan. - p The next payment of #{subscription.price} will be collected on #{subscription.nextPaymentDueAt} + p #{translate("currently_subscribed_to_plan", {planName:"#{subscription.name}"})} + a(href, ng-click="changePlan = true") #{translate("change_plan")}. + p #{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"#{subscription.price}", collectionDate:"#{subscription.nextPaymentDueAt}"})} p.pull-right p: form(action="/user/subscription/cancel",method="post") input(type="hidden", name="_csrf", value=csrfToken) - a(href="/user/subscription/billing-details/edit").btn.btn-info Update your billing details + a(href="/user/subscription/billing-details/edit").btn.btn-info #{translate("update_your_billing_details")} |   input(type="submit", value="Cancel your subscription").btn.btn-primary#cancelSubscription when "canceled" - p You are currently subscribed to the #{subscription.name} plan. - p Your subscription has been canceled and will terminate on #{subscription.nextPaymentDueAt}. No further payments will be taken. + p #{translate("currently_subscribed_to_plan", {planName:"#{subscription.name}"})} + p #{translate("subscription_canceled_and_terminate_on_x", {terminateDate:"#{subscription.nextPaymentDueAt}"})} p: form(action="/user/subscription/reactivate",method="post") input(type="hidden", name="_csrf", value=csrfToken) input(type="submit",value="Reactivate your subscription").btn.btn-success when "expired" - p Your subscription has expired. - a(href="/user/subscription/plans") Create New Subscription + p #{translate("your_subscription_has_expired")} + a(href="/user/subscription/plans") #{translate("create_new_subscription")} default - p There is a problem with your subscription. Please contact us for more information. + p #{translate("problem_with_subscription_contact_us")} -if(subscription.groupPlan) - a(href="/subscription/group").btn.btn-success Manage Group + a(href="/subscription/group").btn.btn-success #{translate("manage_group")} div(ng-show="changePlan", ng-cloak) hr - h2 Change plan + h2 #{translate("change_plan")} p: table.table tr - th Name - th Price + th #{translate("name")} + th #{translate("price")} th mixin printPlans(plans.studentAccounts) mixin printPlans(plans.individualMonthlyPlans) diff --git a/services/web/app/views/subscriptions/edit-billing-details.jade b/services/web/app/views/subscriptions/edit-billing-details.jade index b3fa4114d9..bc47efb4cb 100644 --- a/services/web/app/views/subscriptions/edit-billing-details.jade +++ b/services/web/app/views/subscriptions/edit-billing-details.jade @@ -11,8 +11,8 @@ block content .col-md-6.col-md-offset-3 .card .page-header - h1.text-centered Update Your Billing Details - #billingDetailsForm Loading billing details form... + h1.text-centered #{translate("update_your_billing_details")} + #billingDetailsForm #{translate("loading_billing_form")}... script(type="text/javascript"). diff --git a/services/web/app/views/subscriptions/group_admin.jade b/services/web/app/views/subscriptions/group_admin.jade index d75f93d1e9..ca3dec0478 100644 --- a/services/web/app/views/subscriptions/group_admin.jade +++ b/services/web/app/views/subscriptions/group_admin.jade @@ -8,13 +8,13 @@ block content .card(ng-controller="GroupMembersController") .page-header .pull-right(ng-cloak) - small(ng-show="selectedUsers.length == 0") You have added {{ users.length }} of {{ groupSize }} available members + small(ng-show="selectedUsers.length == 0") #{translate("you_have_added_x_of_group_size_y", {addedUsersSize:"{{ users.length }}", groupSize:"{{ groupSize }}"})} a.btn.btn-danger( href, ng-show="selectedUsers.length > 0" ng-click="removeMembers()" - ) Remove from group - h1 Group Account + ) #{translate("remove_from_group")} + h1 #{translate("group_account")} .row-spaced-small ul.list-unstyled.structured-list( @@ -28,11 +28,11 @@ block content select-all, type="checkbox" ) - span.header Email + span.header #{translate("email")} .col-md-5 - span.header Name + span.header #{translate("name")} .col-md-2 - span.header Registered + span.header #{translate("registered")} li.container-fluid( ng-repeat="user in users | orderBy:'email':true", ng-controller="GroupMemberListItemController" @@ -57,12 +57,12 @@ block content ) .row .col-md-12.text-centered - small No members + small #{translate("no_members")} div(ng-if="users.length < groupSize", ng-cloak) hr p - .small Add more members + .small #{translate("add_more_members")} form.form .row .col-xs-6 @@ -74,7 +74,7 @@ block content on-enter="addMembers()" ) .col-xs-6 - button.btn.btn-primary(ng-click="addMembers()") Add + button.btn.btn-primary(ng-click="addMembers()") #{translate("add")} script(type="text/javascript"). window.users = !{JSON.stringify(users)}; diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.jade index 9852db25f8..0e6ea577c7 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.jade @@ -11,8 +11,8 @@ block content .col-md-6.col-md-offset-3 .card .page-header - h1.text-centered New Subscription - #subscribeForm Loading subscription form... + h1.text-centered #{translate("new_subscription")} + #subscribeForm #{translate("loading_billing_form")}... script(type="text/javascript") ga('send', 'event', 'pageview', 'payment_form', "#{plan_code}") diff --git a/services/web/app/views/subscriptions/successful_subscription.jade b/services/web/app/views/subscriptions/successful_subscription.jade index 1e1fd24b3e..86d9484480 100644 --- a/services/web/app/views/subscriptions/successful_subscription.jade +++ b/services/web/app/views/subscriptions/successful_subscription.jade @@ -7,23 +7,22 @@ block content .col-md-8.col-md-offset-2 .card .page-header - h2 Thanks for subscribing! + h2 #{translate("thanks_for_subscribing")} .alert.alert-success - p Your card will be charged soon. - p The next payment of #{subscription.price} will be collected on #{subscription.nextPaymentDueAt}. - p If you do not want to be charged again - a(href="/user/subscription") click here to cancel. + p #{translate("your_card_will_be_charged_soon")} + p #{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"#{subscription.price}", collectionDate:"#{subscription.nextPaymentDueAt}"})} + p #{translate("if_you_dont_want_to_be_charged")} + a(href="/user/subscription") #{translate("click_here_to_cancel")}. p - if (subscription.groupPlan == true) - a.btn.btn-success.btn-large(href="/subscription/group") Add your first group members now + a.btn.btn-success.btn-large(href="/subscription/group") #{translate("add_your_first_group_member_now")} p.letter-from-founders - p Thank you for subscribing to the #{subscription.name} plan. It's support from people like yourself that allows ShareLaTeX to continue to grow and improve. - - p If there is anything you ever need please feel free to contact us directly at + p #{translate("thanks_for_subscribing_you_help_sl", {planName:subscription.name})} + p #{translate("need_anything_contact_us_at")} a(href='mailto:support@sharelatex.com') support@sharelatex.com - | . It goes straight to both our inboxes. - p Regards, + | . #{translate("goes_straight_to_our_inboxes")}. + p #{translate("regards")}, br | Henry and James .portraits @@ -33,4 +32,4 @@ block content span.img-circle img(src="/img/about/james_allen.jpg") p - a.btn.btn-primary(href="/project") < Back to your projects + a.btn.btn-primary(href="/project") < #{translate("back_to_your_projects")} diff --git a/services/web/app/views/templates/index.jade b/services/web/app/views/templates/index.jade index feade78f51..3965732f94 100644 --- a/services/web/app/views/templates/index.jade +++ b/services/web/app/views/templates/index.jade @@ -7,7 +7,7 @@ block content .col-md-2 h2 - a(href="/templates") Templates + a(href="/templates") #{translate("templates")} .col-md-8 form.project-search.form-horizontal(role="form") .form-group.has-feedback.has-feedback-left.col-md-12 diff --git a/services/web/app/views/templates/tag.jade b/services/web/app/views/templates/tag.jade index 2ac601a596..4ee7e70b76 100644 --- a/services/web/app/views/templates/tag.jade +++ b/services/web/app/views/templates/tag.jade @@ -6,7 +6,7 @@ block content .row .page-header h2 - a(href="/templates") Templates + a(href="/templates") #{translate("templates")} | › a(href=tag.tagPagePath) #{tag.name} .row diff --git a/services/web/app/views/templates/template.jade b/services/web/app/views/templates/template.jade index 50c2db74fb..d403bfa576 100644 --- a/services/web/app/views/templates/template.jade +++ b/services/web/app/views/templates/template.jade @@ -6,7 +6,7 @@ block content .row .page-header h2 - a(href="/templates") Templates + a(href="/templates") #{translate("templates")} | › - if(tag) a(href=tag.tagPagePath) #{tag.name} @@ -22,7 +22,7 @@ block content .col-md-6 .template-details-section - h3 About + h3 #{translate("about")} div !{template.description} div(ng-controller="openInSlController").download-buttons a.btn.btn-primary.btn-large(href=template.open_in_sharelatex_url, ng-click='open()', ng-disabled="isDisabled", rel='nofollow') {{openInSlText}} @@ -47,7 +47,7 @@ block content a.addthis_button_compact script(type='text/javascript', src='//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-517c16586439faa7') - h3 Comment + h3 #{translate("comment")} #disqus_thread script(type='text/javascript'). /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */ diff --git a/services/web/app/views/user/restricted.jade b/services/web/app/views/user/restricted.jade index 4c4ebcd52a..99d9099e23 100644 --- a/services/web/app/views/user/restricted.jade +++ b/services/web/app/views/user/restricted.jade @@ -6,8 +6,8 @@ block content .row .col-md-8.col-md-offset-2.text-center .page-header - h2 Restricted, sorry you don't have permission to load this page. + h2 #{translate("restricted_no_permission")} p a(href="/") i.fa.fa-arrow-circle-o-left - | Take me home! \ No newline at end of file + | #{translate("take_me_home")} \ No newline at end of file From 1b8c8b8c48913cdc98c0b416a8e3c9a12a0ce80b Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 1 Aug 2014 13:47:14 +0100 Subject: [PATCH 13/26] sorted out titles --- .../PasswordReset/PasswordResetController.coffee | 4 ++-- .../coffee/Features/Project/ProjectController.coffee | 2 +- .../coffee/Features/Referal/ReferalController.coffee | 2 +- .../Subscription/SubscriptionController.coffee | 10 +++++----- .../Subscription/SubscriptionGroupController.coffee | 2 +- .../Features/Templates/TemplatesWebController.coffee | 4 ++-- .../coffee/Features/User/UserPagesController.coffee | 6 +++--- services/web/app/coffee/infrastructure/Server.coffee | 2 +- services/web/app/views/layout.jade | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee index c52aa9b454..d406c4e705 100644 --- a/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee @@ -6,7 +6,7 @@ module.exports = renderRequestResetForm: (req, res)-> res.render "user/passwordReset", - title:"Reset Password" + title:"reset_password" requestReset: (req, res)-> email = req.body.email.trim().toLowerCase() @@ -26,7 +26,7 @@ module.exports = renderSetPasswordForm: (req, res)-> res.render "user/setPassword", - title:"Set Password" + title:"set_password" passwordResetToken:req.query.passwordResetToken setNewUserPassword: (req, res)-> diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index cb8db05c00..62e24f343a 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -157,7 +157,7 @@ module.exports = ProjectController = loadEditor: (req, res, next)-> timer = new metrics.Timer("load-editor") if !Settings.editorIsOpen - return res.render("general/closed", {title:"updating site"}) + return res.render("general/closed", {title:"updating_site"}) if req.session.user? user_id = req.session.user._id diff --git a/services/web/app/coffee/Features/Referal/ReferalController.coffee b/services/web/app/coffee/Features/Referal/ReferalController.coffee index 65247febd7..fbe812a22e 100644 --- a/services/web/app/coffee/Features/Referal/ReferalController.coffee +++ b/services/web/app/coffee/Features/Referal/ReferalController.coffee @@ -5,6 +5,6 @@ module.exports = bonus: (req, res)-> ReferalHandler.getReferedUserIds req.session.user._id, (err, refered_users)-> res.render "referal/bonus", - title: "Bonus - Please recommend us" + title: "bonus_please_recommend_us" refered_users: refered_users refered_user_count: (refered_users or []).length diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index 508656b3fc..5ea4eeb90b 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee @@ -21,7 +21,7 @@ module.exports = SubscriptionController = viewName = "subscriptions/plans" logger.log viewName:viewName, "showing plans page" res.render viewName, - title: "Plans and Pricing" + title: "plans_and_pricing" plans: plans baseUrl: baseUrl @@ -41,7 +41,7 @@ module.exports = SubscriptionController = }, (error, signature) -> return next(error) if error? res.render "subscriptions/new", - title : "Subscribe" + title : "subscribe" plan_code: req.query.planCode recurlyConfig: JSON.stringify currency: "USD" @@ -74,7 +74,7 @@ module.exports = SubscriptionController = logger.log user: user, subscription:subscription, hasSubOrFreeTrial:hasSubOrFreeTrial, "showing subscription dashboard" plans = SubscriptionViewModelBuilder.buildViewModel() res.render "subscriptions/dashboard", - title: "Your Subscription" + title: "your_subscription" plans: plans subscription: subscription subscriptionTabActive: true @@ -92,7 +92,7 @@ module.exports = SubscriptionController = }, (error, signature) -> return next(error) if error? res.render "subscriptions/edit-billing-details", - title : "Update Billing Details" + title : "update_billing_details" recurlyConfig: JSON.stringify currency: "USD" subdomain: Settings.apis.recurly.subdomain @@ -115,7 +115,7 @@ module.exports = SubscriptionController = SecurityManager.getCurrentUser req, (error, user) => SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel user, (error, subscription) -> res.render "subscriptions/successful_subscription", - title: "Thank you!" + title: "thank_you" subscription:subscription cancelSubscription: (req, res, next) -> diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee index 4ba79f5757..674c5affbe 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupController.coffee @@ -29,6 +29,6 @@ module.exports = return res.redirect("/") SubscriptionGroupHandler.getPopulatedListOfMembers user_id, (err, users)-> res.render "subscriptions/group_admin", - title: 'Group Admin' + title: 'group_admin' users: users subscription: subscription diff --git a/services/web/app/coffee/Features/Templates/TemplatesWebController.coffee b/services/web/app/coffee/Features/Templates/TemplatesWebController.coffee index aaca34a52c..cc86f90f07 100644 --- a/services/web/app/coffee/Features/Templates/TemplatesWebController.coffee +++ b/services/web/app/coffee/Features/Templates/TemplatesWebController.coffee @@ -10,7 +10,7 @@ module.exports = TemplatesWebController = if err? or !data? logger.err err:err, "something went wrong in renderTemplatesIndexPage" return res.send 500 - data.title = "LaTeX Templates" + data.title = "latex_templates" res.render "templates/index", data renerTemplateInTag: (req, res)-> @@ -60,7 +60,7 @@ module.exports = TemplatesWebController = if err? logger.err err:err, user_id:user_id, "something went wrong in _renderCanonicalPage" return res.send 500 - data.title = "All Templates" + data.title = "all_templates" res.render "templates/tag", data _renderTagPage: (req, res)-> diff --git a/services/web/app/coffee/Features/User/UserPagesController.coffee b/services/web/app/coffee/Features/User/UserPagesController.coffee index c082440e76..b5c1330231 100644 --- a/services/web/app/coffee/Features/User/UserPagesController.coffee +++ b/services/web/app/coffee/Features/User/UserPagesController.coffee @@ -16,7 +16,7 @@ module.exports = newTemplateData.templateName = req.session.templateData.templateName res.render 'user/register', - title: 'Register' + title: 'register' redir: req.query.redir sharedProjectData: sharedProjectData newTemplateData: newTemplateData @@ -25,7 +25,7 @@ module.exports = loginPage : (req, res)-> console.info req res.render 'user/login', - title: 'Login', + title: 'login', redir: req.query.redir settingsPage : (req, res)-> @@ -34,7 +34,7 @@ module.exports = dropboxHandler.getUserRegistrationStatus user._id, (err, status)-> userIsRegisteredWithDropbox = !err? and status.registered res.render 'user/settings', - title:'Your settings', + title:'your_settings', userHasDropboxFeature: user.features.dropbox userIsRegisteredWithDropbox: userIsRegisteredWithDropbox user: user, diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index 27ec298946..32f8c42fe5 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -94,7 +94,7 @@ app.use (req, res, next)-> app.use (req, res, next) -> if !Settings.editorIsOpen res.status(503) - res.render("general/closed", {title:"Maintenance"}) + res.render("general/closed", {title:"maintenance"}) else next() diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index 53f70c3eca..bf462d473d 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -7,8 +7,8 @@ html(itemscope, itemtype='http://schema.org/Product') - if (typeof(priority_title) !== "undefined" && priority_title) title= title + ' - '+ translate("online_latex_editor") - else - title= translate("online_latex_editor") +' ShareLaTeX - ' +title - + title= translate("online_latex_editor") +' ShareLaTeX - ' +translate(title) + link(rel="icon", href="/favicon.ico") link(rel='stylesheet', href='/stylesheets/style.css?fingerprint='+fingerprint('/stylesheets/style.css')) link(href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css",rel="stylesheet") From d047d44079c27bad36ec279ca541a8840ecd9b1e Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 1 Aug 2014 14:03:38 +0100 Subject: [PATCH 14/26] Changed the error messages which are sent down to the client to be translated first fixed up tests from titles we check when rendering, deleted them as they never catch anything important, more hastle than they are worth imo. --- .../Authentication/AuthenticationController.coffee | 4 ++-- .../PasswordReset/PasswordResetController.coffee | 2 +- .../web/app/coffee/Features/User/UserController.coffee | 6 +++++- services/web/app/coffee/Features/User/UserUpdater.coffee | 2 +- .../Authentication/AuthenticationControllerTests.coffee | 9 +++++---- .../PasswordReset/PasswordResetControllerTests.coffee | 2 ++ .../Subscription/SubscriptionControllerTests.coffee | 7 ------- .../web/test/UnitTests/coffee/helpers/MockRequest.coffee | 2 ++ 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee index 6de2ce7110..bb323ce052 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee @@ -18,7 +18,7 @@ module.exports = AuthenticationController = res.statusCode = 429 return res.send message: - text: 'This account has had too many login requests. Please wait 2 minutes before trying to log in again', + text: req.i18n.translate("to_many_login_requests_2_mins"), type: 'error' AuthenticationManager.authenticate email: email, password, (error, user) -> return next(error) if error? @@ -33,7 +33,7 @@ module.exports = AuthenticationController = AuthenticationController._recordFailedLogin() logger.log email: email, "failed log in" res.send message: - text: 'Your email or password were incorrect. Please try again', + text: req.i18n.translate("email_or_password_wrong_try_again"), type: 'error' getAuthToken: (req, res, next = (error) ->) -> diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee index d406c4e705..53a0a8bb6e 100644 --- a/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetController.coffee @@ -17,7 +17,7 @@ module.exports = throttle: 6 RateLimiter.addCount opts, (err, canCompile)-> if !canCompile - return res.send 500, { message: "Rate limit hit. Please wait a while before retrying" } + return res.send 500, { message: req.i18n.translate("rate_limit_hit_wait")} PasswordResetHandler.generateAndEmailResetToken email, (err)-> if err? res.send 500, {message:err?.message} diff --git a/services/web/app/coffee/Features/User/UserController.coffee b/services/web/app/coffee/Features/User/UserController.coffee index 62b8d8b203..ba41904812 100644 --- a/services/web/app/coffee/Features/User/UserController.coffee +++ b/services/web/app/coffee/Features/User/UserController.coffee @@ -63,7 +63,11 @@ module.exports = UserUpdater.changeEmailAddress user_id, newEmail, (err)-> if err? logger.err err:err, user_id:user_id, newEmail:newEmail, "problem updaing users email address" - return res.send 500, {message:err?.message} + if err.message == "alread_exists" + message = req.i18n.translate("alread_exists") + else + message = req.i18n.translate("problem_changing_email_address") + return res.send 500, {message:message} res.send(200) logout : (req, res)-> diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 86ea60240d..2ed2d2bad0 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -19,7 +19,7 @@ module.exports = UserUpdater = logger.log user_id:user_id, newEmail:newEmail, "updaing email address of user" UserLocator.findByEmail newEmail, (error, user) -> if user? - return callback({message:"User with that email already exists."}) + return callback({message:"alread_exists"}) self.updateUser user_id.toString(), { $set: { "email": newEmail}, }, (err) -> diff --git a/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee b/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee index 45c370e185..f6d55d2a4a 100644 --- a/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee @@ -96,10 +96,11 @@ describe "AuthenticationController", -> @AuthenticationController.login(@req, @res) it "should return an error", -> - expect(@res.body).to.deep.equal - message: - text: 'Your email or password were incorrect. Please try again', - type: 'error' + # @res.body.should.exist + expect(@res.body.message).to.exist + # message: + # text: 'Your email or password were incorrect. Please try again', + # type: 'error' it "should not establish a session", -> @AuthenticationController._establishUserSession.called.should.equal false diff --git a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetControllerTests.coffee b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetControllerTests.coffee index 187719dcd3..6b9968318e 100644 --- a/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/PasswordReset/PasswordResetControllerTests.coffee @@ -30,6 +30,8 @@ describe "PasswordResetController", -> email:@email passwordResetToken:@token password:@password + i18n: + translate:-> @res = {} diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee index 5df9d5d1e2..78bb13c617 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee @@ -78,7 +78,6 @@ describe "Subscription controller sanboxed", -> it "should set the correct variables for the template", -> should.exist @res.renderedVariables.signature - @res.renderedVariables.title.should.equal "Update Billing Details" @res.renderedVariables.successURL.should.equal "#{Settings.siteUrl}/user/subscription/update" @res.renderedVariables.user.id.should.equal @user.id @@ -137,9 +136,6 @@ describe "Subscription controller sanboxed", -> @res.callback = done @SubscriptionController.successful_subscription @req, @res - it "should render the thank you page", -> - @res.renderedVariables.title.should.equal "Thank you!" - describe "userSubscriptionPage", -> describe "with a user without a subscription", -> beforeEach (done) -> @@ -164,7 +160,6 @@ describe "Subscription controller sanboxed", -> done() it "should set the correct subscription details", -> - @res.renderedVariables.title.should.equal "Your Subscription" @res.renderedVariables.subscription.should.deep.equal @activeRecurlySubscription describe "with a user with a free trial", -> @@ -175,11 +170,9 @@ describe "Subscription controller sanboxed", -> @SubscriptionController.userSubscriptionPage @req, @res it "should render the dashboard", -> - @res.rendered.should.equal true @res.renderedTemplate.should.equal "subscriptions/dashboard" it "should set the correct subscription details", -> - @res.renderedVariables.title.should.equal "Your Subscription" @res.renderedVariables.subscription.should.deep.equal @activeRecurlySubscription describe "createSubscription", -> diff --git a/services/web/test/UnitTests/coffee/helpers/MockRequest.coffee b/services/web/test/UnitTests/coffee/helpers/MockRequest.coffee index 61a54f11c3..9c83844654 100644 --- a/services/web/test/UnitTests/coffee/helpers/MockRequest.coffee +++ b/services/web/test/UnitTests/coffee/helpers/MockRequest.coffee @@ -5,6 +5,8 @@ class MockRequest params: {} query: {} + i18n: + translate:-> module.exports = MockRequest From 03db0bc79711a7137491985dd8c4f9ca9728cd18 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 1 Aug 2014 14:43:06 +0100 Subject: [PATCH 15/26] added ugly dropdown for testing --- services/web/app/views/layout/navbar.jade | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/web/app/views/layout/navbar.jade b/services/web/app/views/layout/navbar.jade index 0b6b7de692..488189f81f 100644 --- a/services/web/app/views/layout/navbar.jade +++ b/services/web/app/views/layout/navbar.jade @@ -6,6 +6,17 @@ nav.navbar.navbar-default a(href='/').navbar-brand .navbar-collapse.collapse(collapse="navCollapsed") + + .dropdown + button.btn.btn-default.dropdown-toggle Lang + span.caret + ul.dropdown-menu + li + a(href="/?setLng=en-GB") british + a(href="/?setLng=en-US") USA + a(href="/?setLng=fr-FR") french + + ul.nav.navbar-nav.navbar-right each item in nav.header if ((item.only_when_logged_in && session && session.user) || (item.only_when_logged_out && (!session || !session.user)) || (!item.only_when_logged_out && !item.only_when_logged_in)) From 9b8d77cb0f4ecc8ae020238c6bb642fb71e0ae9e Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 1 Aug 2014 14:49:43 +0100 Subject: [PATCH 16/26] removed client side i18n as we don't really need it #wasteoftime --- services/web/public/coffee/base.coffee | 1 - services/web/public/coffee/libs.coffee | 4 ---- services/web/public/coffee/main.coffee | 1 - services/web/public/coffee/main/project-list.coffee | 3 +-- services/web/public/coffee/utils/i18nextProvider.coffee | 9 --------- 5 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 services/web/public/coffee/utils/i18nextProvider.coffee diff --git a/services/web/public/coffee/base.coffee b/services/web/public/coffee/base.coffee index 8c60e29857..90d522ead8 100644 --- a/services/web/public/coffee/base.coffee +++ b/services/web/public/coffee/base.coffee @@ -10,7 +10,6 @@ define [ "ng-context-menu" "underscore" "ngSanitize" - "jm.i18next" ]) return App \ No newline at end of file diff --git a/services/web/public/coffee/libs.coffee b/services/web/public/coffee/libs.coffee index 3a21822e03..b25e273586 100644 --- a/services/web/public/coffee/libs.coffee +++ b/services/web/public/coffee/libs.coffee @@ -8,8 +8,4 @@ define [ "libs/jquery.storage" "libs/fineuploader" "libs/angular-sanitize-1.2.17" - "libs/i18next" - "libs/ng-i18next/provider" - "libs/ng-i18next/directive/directive" - "libs/ng-i18next/filter/filter" ], () -> diff --git a/services/web/public/coffee/main.coffee b/services/web/public/coffee/main.coffee index 4ac69ae83e..da6e361893 100644 --- a/services/web/public/coffee/main.coffee +++ b/services/web/public/coffee/main.coffee @@ -18,6 +18,5 @@ define [ "directives/selectAll" "directives/maxHeight" "filters/formatDate" - "utils/i18nextProvider" ], () -> angular.bootstrap(document.body, ["SharelatexApp"]) diff --git a/services/web/public/coffee/main/project-list.coffee b/services/web/public/coffee/main/project-list.coffee index 0a958ab36c..c552dad532 100644 --- a/services/web/public/coffee/main/project-list.coffee +++ b/services/web/public/coffee/main/project-list.coffee @@ -50,8 +50,7 @@ define [ return queuedHttp - App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout, $i18next) -> - console.log $i18next, $i18next("new_project"), "- NEW PROJECT" + App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout) -> $scope.projects = window.data.projects $scope.tags = window.data.tags $scope.allSelected = false diff --git a/services/web/public/coffee/utils/i18nextProvider.coffee b/services/web/public/coffee/utils/i18nextProvider.coffee deleted file mode 100644 index 27b02d0304..0000000000 --- a/services/web/public/coffee/utils/i18nextProvider.coffee +++ /dev/null @@ -1,9 +0,0 @@ -angular.module('jm.i18next').config ['$i18nextProvider', ($i18nextProvider)-> - console.log("hello") - $i18nextProvider.options = - lng: 'en-GB', - useCookie: false, - useLocalStorage: false, - fallbackLng: 'en', - resGetPath: '/locales/__lng__.json' -] \ No newline at end of file From 687b3f2888a870081cf1a757984630c3fddd5055 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 4 Aug 2014 12:18:31 +0100 Subject: [PATCH 17/26] removed external libs for i18n which we no longer need --- services/web/public/js/libs/i18next.js | 2734 ----------------- .../js/libs/ng-i18next/directive/directive.js | 185 -- .../js/libs/ng-i18next/filter/filter.js | 12 - .../web/public/js/libs/ng-i18next/index.js | 8 - .../web/public/js/libs/ng-i18next/provider.js | 123 - 5 files changed, 3062 deletions(-) delete mode 100644 services/web/public/js/libs/i18next.js delete mode 100755 services/web/public/js/libs/ng-i18next/directive/directive.js delete mode 100755 services/web/public/js/libs/ng-i18next/filter/filter.js delete mode 100644 services/web/public/js/libs/ng-i18next/index.js delete mode 100755 services/web/public/js/libs/ng-i18next/provider.js diff --git a/services/web/public/js/libs/i18next.js b/services/web/public/js/libs/i18next.js deleted file mode 100644 index 7d12e279bd..0000000000 --- a/services/web/public/js/libs/i18next.js +++ /dev/null @@ -1,2734 +0,0 @@ -// i18next, v1.7.3 -// Copyright (c)2014 Jan Mühlemann (jamuhl). -// Distributed under MIT license -// http://i18next.com -(function() { - - // add indexOf to non ECMA-262 standard compliant browsers - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { - "use strict"; - if (this == null) { - throw new TypeError(); - } - var t = Object(this); - var len = t.length >>> 0; - if (len === 0) { - return -1; - } - var n = 0; - if (arguments.length > 0) { - n = Number(arguments[1]); - if (n != n) { // shortcut for verifying if it's NaN - n = 0; - } else if (n != 0 && n != Infinity && n != -Infinity) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - if (n >= len) { - return -1; - } - var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; - } - } - return -1; - } - } - - // add lastIndexOf to non ECMA-262 standard compliant browsers - if (!Array.prototype.lastIndexOf) { - Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) { - "use strict"; - if (this == null) { - throw new TypeError(); - } - var t = Object(this); - var len = t.length >>> 0; - if (len === 0) { - return -1; - } - var n = len; - if (arguments.length > 1) { - n = Number(arguments[1]); - if (n != n) { - n = 0; - } else if (n != 0 && n != (1 / 0) && n != -(1 / 0)) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - var k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); - for (; k >= 0; k--) { - if (k in t && t[k] === searchElement) { - return k; - } - } - return -1; - }; - } - - // Add string trim for IE8. - if (typeof String.prototype.trim !== 'function') { - String.prototype.trim = function() { - return this.replace(/^\s+|\s+$/g, ''); - } - } - - var root = this - , $ = root.jQuery || root.Zepto - , i18n = {} - , resStore = {} - , currentLng - , replacementCounter = 0 - , languages = [] - , initialized = false; - - - // Export the i18next object for **CommonJS**. - // If we're not in CommonJS, add `i18n` to the - // global object or to jquery. - if (typeof module !== 'undefined' && module.exports) { - module.exports = i18n; - } else { - if ($) { - $.i18n = $.i18n || i18n; - } - - root.i18n = root.i18n || i18n; - } - // defaults - var o = { - lng: undefined, - load: 'all', - preload: [], - lowerCaseLng: false, - returnObjectTrees: false, - fallbackLng: ['dev'], - fallbackNS: [], - detectLngQS: 'setLng', - ns: 'translation', - fallbackOnNull: true, - fallbackOnEmpty: false, - fallbackToDefaultNS: false, - nsseparator: ':', - keyseparator: '.', - selectorAttr: 'data-i18n', - debug: false, - - resGetPath: 'locales/__lng__/__ns__.json', - resPostPath: 'locales/add/__lng__/__ns__', - - getAsync: true, - postAsync: true, - - resStore: undefined, - useLocalStorage: false, - localStorageExpirationTime: 7*24*60*60*1000, - - dynamicLoad: false, - sendMissing: false, - sendMissingTo: 'fallback', // current | all - sendType: 'POST', - - interpolationPrefix: '__', - interpolationSuffix: '__', - reusePrefix: '$t(', - reuseSuffix: ')', - pluralSuffix: '_plural', - pluralNotFound: ['plural_not_found', Math.random()].join(''), - contextNotFound: ['context_not_found', Math.random()].join(''), - escapeInterpolation: false, - - setJqueryExt: true, - defaultValueFromContent: true, - useDataAttrOptions: false, - cookieExpirationTime: undefined, - useCookie: true, - cookieName: 'i18next', - cookieDomain: undefined, - - objectTreeKeyHandler: undefined, - postProcess: undefined, - parseMissingKey: undefined, - - shortcutFunction: 'sprintf' // or: defaultValue - }; - function _extend(target, source) { - if (!source || typeof source === 'function') { - return target; - } - - for (var attr in source) { target[attr] = source[attr]; } - return target; - } - - function _each(object, callback, args) { - var name, i = 0, - length = object.length, - isObj = length === undefined || Object.prototype.toString.apply(object) !== '[object Array]' || typeof object === "function"; - - if (args) { - if (isObj) { - for (name in object) { - if (callback.apply(object[name], args) === false) { - break; - } - } - } else { - for ( ; i < length; ) { - if (callback.apply(object[i++], args) === false) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if (isObj) { - for (name in object) { - if (callback.call(object[name], name, object[name]) === false) { - break; - } - } - } else { - for ( ; i < length; ) { - if (callback.call(object[i], i, object[i++]) === false) { - break; - } - } - } - } - - return object; - } - - var _entityMap = { - "&": "&", - "<": "<", - ">": ">", - '"': '"', - "'": ''', - "/": '/' - }; - - function _escape(data) { - if (typeof data === 'string') { - return data.replace(/[&<>"'\/]/g, function (s) { - return _entityMap[s]; - }); - }else{ - return data; - } - } - - function _ajax(options) { - - // v0.5.0 of https://github.com/goloroden/http.js - var getXhr = function (callback) { - // Use the native XHR object if the browser supports it. - if (window.XMLHttpRequest) { - return callback(null, new XMLHttpRequest()); - } else if (window.ActiveXObject) { - // In Internet Explorer check for ActiveX versions of the XHR object. - try { - return callback(null, new ActiveXObject("Msxml2.XMLHTTP")); - } catch (e) { - return callback(null, new ActiveXObject("Microsoft.XMLHTTP")); - } - } - - // If no XHR support was found, throw an error. - return callback(new Error()); - }; - - var encodeUsingUrlEncoding = function (data) { - if(typeof data === 'string') { - return data; - } - - var result = []; - for(var dataItem in data) { - if(data.hasOwnProperty(dataItem)) { - result.push(encodeURIComponent(dataItem) + '=' + encodeURIComponent(data[dataItem])); - } - } - - return result.join('&'); - }; - - var utf8 = function (text) { - text = text.replace(/\r\n/g, '\n'); - var result = ''; - - for(var i = 0; i < text.length; i++) { - var c = text.charCodeAt(i); - - if(c < 128) { - result += String.fromCharCode(c); - } else if((c > 127) && (c < 2048)) { - result += String.fromCharCode((c >> 6) | 192); - result += String.fromCharCode((c & 63) | 128); - } else { - result += String.fromCharCode((c >> 12) | 224); - result += String.fromCharCode(((c >> 6) & 63) | 128); - result += String.fromCharCode((c & 63) | 128); - } - } - - return result; - }; - - var base64 = function (text) { - var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; - - text = utf8(text); - var result = '', - chr1, chr2, chr3, - enc1, enc2, enc3, enc4, - i = 0; - - do { - chr1 = text.charCodeAt(i++); - chr2 = text.charCodeAt(i++); - chr3 = text.charCodeAt(i++); - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if(isNaN(chr2)) { - enc3 = enc4 = 64; - } else if(isNaN(chr3)) { - enc4 = 64; - } - - result += - keyStr.charAt(enc1) + - keyStr.charAt(enc2) + - keyStr.charAt(enc3) + - keyStr.charAt(enc4); - chr1 = chr2 = chr3 = ''; - enc1 = enc2 = enc3 = enc4 = ''; - } while(i < text.length); - - return result; - }; - - var mergeHeaders = function () { - // Use the first header object as base. - var result = arguments[0]; - - // Iterate through the remaining header objects and add them. - for(var i = 1; i < arguments.length; i++) { - var currentHeaders = arguments[i]; - for(var header in currentHeaders) { - if(currentHeaders.hasOwnProperty(header)) { - result[header] = currentHeaders[header]; - } - } - } - - // Return the merged headers. - return result; - }; - - var ajax = function (method, url, options, callback) { - // Adjust parameters. - if(typeof options === 'function') { - callback = options; - options = {}; - } - - // Set default parameter values. - options.cache = options.cache || false; - options.data = options.data || {}; - options.headers = options.headers || {}; - options.jsonp = options.jsonp || false; - options.async = options.async === undefined ? true : options.async; - - // Merge the various header objects. - var headers = mergeHeaders({ - 'accept': '*/*', - 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8' - }, ajax.headers, options.headers); - - // Encode the data according to the content-type. - var payload; - if (headers['content-type'] === 'application/json') { - payload = JSON.stringify(options.data); - } else { - payload = encodeUsingUrlEncoding(options.data); - } - - // Specially prepare GET requests: Setup the query string, handle caching and make a JSONP call - // if neccessary. - if(method === 'GET') { - // Setup the query string. - var queryString = []; - if(payload) { - queryString.push(payload); - payload = null; - } - - // Handle caching. - if(!options.cache) { - queryString.push('_=' + (new Date()).getTime()); - } - - // If neccessary prepare the query string for a JSONP call. - if(options.jsonp) { - queryString.push('callback=' + options.jsonp); - queryString.push('jsonp=' + options.jsonp); - } - - // Merge the query string and attach it to the url. - queryString = queryString.join('&'); - if (queryString.length > 1) { - if (url.indexOf('?') > -1) { - url += '&' + queryString; - } else { - url += '?' + queryString; - } - } - - // Make a JSONP call if neccessary. - if(options.jsonp) { - var head = document.getElementsByTagName('head')[0]; - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = url; - head.appendChild(script); - return; - } - } - - // Since we got here, it is no JSONP request, so make a normal XHR request. - getXhr(function (err, xhr) { - if(err) return callback(err); - - // Open the request. - xhr.open(method, url, options.async); - - // Set the request headers. - for(var header in headers) { - if(headers.hasOwnProperty(header)) { - xhr.setRequestHeader(header, headers[header]); - } - } - - // Handle the request events. - xhr.onreadystatechange = function () { - if(xhr.readyState === 4) { - var data = xhr.responseText || ''; - - // If no callback is given, return. - if(!callback) { - return; - } - - // Return an object that provides access to the data as text and JSON. - callback(xhr.status, { - text: function () { - return data; - }, - - json: function () { - return JSON.parse(data); - } - }); - } - }; - - // Actually send the XHR request. - xhr.send(payload); - }); - }; - - // Define the external interface. - var http = { - authBasic: function (username, password) { - ajax.headers['Authorization'] = 'Basic ' + base64(username + ':' + password); - }, - - connect: function (url, options, callback) { - return ajax('CONNECT', url, options, callback); - }, - - del: function (url, options, callback) { - return ajax('DELETE', url, options, callback); - }, - - get: function (url, options, callback) { - return ajax('GET', url, options, callback); - }, - - head: function (url, options, callback) { - return ajax('HEAD', url, options, callback); - }, - - headers: function (headers) { - ajax.headers = headers || {}; - }, - - isAllowed: function (url, verb, callback) { - this.options(url, function (status, data) { - callback(data.text().indexOf(verb) !== -1); - }); - }, - - options: function (url, options, callback) { - return ajax('OPTIONS', url, options, callback); - }, - - patch: function (url, options, callback) { - return ajax('PATCH', url, options, callback); - }, - - post: function (url, options, callback) { - return ajax('POST', url, options, callback); - }, - - put: function (url, options, callback) { - return ajax('PUT', url, options, callback); - }, - - trace: function (url, options, callback) { - return ajax('TRACE', url, options, callback); - } - }; - - - var methode = options.type ? options.type.toLowerCase() : 'get'; - - http[methode](options.url, options, function (status, data) { - if (status === 200) { - options.success(data.json(), status, null); - } else { - options.error(data.text(), status, null); - } - }); - } - - var _cookie = { - create: function(name,value,minutes,domain) { - var expires; - if (minutes) { - var date = new Date(); - date.setTime(date.getTime()+(minutes*60*1000)); - expires = "; expires="+date.toGMTString(); - } - else expires = ""; - domain = (domain)? "domain="+domain+";" : ""; - document.cookie = name+"="+value+expires+";"+domain+"path=/"; - }, - - read: function(name) { - var nameEQ = name + "="; - var ca = document.cookie.split(';'); - for(var i=0;i < ca.length;i++) { - var c = ca[i]; - while (c.charAt(0)==' ') c = c.substring(1,c.length); - if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length); - } - return null; - }, - - remove: function(name) { - this.create(name,"",-1); - } - }; - - var cookie_noop = { - create: function(name,value,minutes,domain) {}, - read: function(name) { return null; }, - remove: function(name) {} - }; - - - - // move dependent functions to a container so that - // they can be overriden easier in no jquery environment (node.js) - var f = { - extend: $ ? $.extend : _extend, - each: $ ? $.each : _each, - ajax: $ ? $.ajax : (typeof document !== 'undefined' ? _ajax : function() {}), - cookie: typeof document !== 'undefined' ? _cookie : cookie_noop, - detectLanguage: detectLanguage, - escape: _escape, - log: function(str) { - if (o.debug && typeof console !== "undefined") console.log(str); - }, - toLanguages: function(lng) { - var languages = []; - if (typeof lng === 'string' && lng.indexOf('-') > -1) { - var parts = lng.split('-'); - - lng = o.lowerCaseLng ? - parts[0].toLowerCase() + '-' + parts[1].toLowerCase() : - parts[0].toLowerCase() + '-' + parts[1].toUpperCase(); - - if (o.load !== 'unspecific') languages.push(lng); - if (o.load !== 'current') languages.push(parts[0]); - } else { - languages.push(lng); - } - - for (var i = 0; i < o.fallbackLng.length; i++) { - if (languages.indexOf(o.fallbackLng[i]) === -1 && o.fallbackLng[i]) languages.push(o.fallbackLng[i]); - } - - return languages; - }, - regexEscape: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - } - }; - function init(options, cb) { - - if (typeof options === 'function') { - cb = options; - options = {}; - } - options = options || {}; - - // override defaults with passed in options - f.extend(o, options); - delete o.fixLng; /* passed in each time */ - - // create namespace object if namespace is passed in as string - if (typeof o.ns == 'string') { - o.ns = { namespaces: [o.ns], defaultNs: o.ns}; - } - - // fallback namespaces - if (typeof o.fallbackNS == 'string') { - o.fallbackNS = [o.fallbackNS]; - } - - // fallback languages - if (typeof o.fallbackLng == 'string' || typeof o.fallbackLng == 'boolean') { - o.fallbackLng = [o.fallbackLng]; - } - - // escape prefix/suffix - o.interpolationPrefixEscaped = f.regexEscape(o.interpolationPrefix); - o.interpolationSuffixEscaped = f.regexEscape(o.interpolationSuffix); - - if (!o.lng) o.lng = f.detectLanguage(); - if (o.lng) { - // set cookie with lng set (as detectLanguage will set cookie on need) - if (o.useCookie) f.cookie.create(o.cookieName, o.lng, o.cookieExpirationTime, o.cookieDomain); - } else { - o.lng = o.fallbackLng[0]; - if (o.useCookie) f.cookie.remove(o.cookieName); - } - - languages = f.toLanguages(o.lng); - currentLng = languages[0]; - f.log('currentLng set to: ' + currentLng); - - var lngTranslate = translate; - if (options.fixLng) { - lngTranslate = function(key, options) { - options = options || {}; - options.lng = options.lng || lngTranslate.lng; - return translate(key, options); - }; - lngTranslate.lng = currentLng; - } - - pluralExtensions.setCurrentLng(currentLng); - - // add JQuery extensions - if ($ && o.setJqueryExt) addJqueryFunct(); - - // jQuery deferred - var deferred; - if ($ && $.Deferred) { - deferred = $.Deferred(); - } - - // return immidiatly if res are passed in - if (o.resStore) { - resStore = o.resStore; - initialized = true; - if (cb) cb(lngTranslate); - if (deferred) deferred.resolve(lngTranslate); - if (deferred) return deferred.promise(); - return; - } - - // languages to load - var lngsToLoad = f.toLanguages(o.lng); - if (typeof o.preload === 'string') o.preload = [o.preload]; - for (var i = 0, l = o.preload.length; i < l; i++) { - var pres = f.toLanguages(o.preload[i]); - for (var y = 0, len = pres.length; y < len; y++) { - if (lngsToLoad.indexOf(pres[y]) < 0) { - lngsToLoad.push(pres[y]); - } - } - } - - // else load them - i18n.sync.load(lngsToLoad, o, function(err, store) { - resStore = store; - initialized = true; - - if (cb) cb(lngTranslate); - if (deferred) deferred.resolve(lngTranslate); - }); - - if (deferred) return deferred.promise(); - } - function preload(lngs, cb) { - if (typeof lngs === 'string') lngs = [lngs]; - for (var i = 0, l = lngs.length; i < l; i++) { - if (o.preload.indexOf(lngs[i]) < 0) { - o.preload.push(lngs[i]); - } - } - return init(cb); - } - - function addResourceBundle(lng, ns, resources) { - if (typeof ns !== 'string') { - resources = ns; - ns = o.ns.defaultNs; - } else if (o.ns.namespaces.indexOf(ns) < 0) { - o.ns.namespaces.push(ns); - } - - resStore[lng] = resStore[lng] || {}; - resStore[lng][ns] = resStore[lng][ns] || {}; - - f.extend(resStore[lng][ns], resources); - } - - function removeResourceBundle(lng, ns) { - if (typeof ns !== 'string') { - ns = o.ns.defaultNs; - } - - resStore[lng] = resStore[lng] || {}; - resStore[lng][ns] = {}; - } - - function setDefaultNamespace(ns) { - o.ns.defaultNs = ns; - } - - function loadNamespace(namespace, cb) { - loadNamespaces([namespace], cb); - } - - function loadNamespaces(namespaces, cb) { - var opts = { - dynamicLoad: o.dynamicLoad, - resGetPath: o.resGetPath, - getAsync: o.getAsync, - customLoad: o.customLoad, - ns: { namespaces: namespaces, defaultNs: ''} /* new namespaces to load */ - }; - - // languages to load - var lngsToLoad = f.toLanguages(o.lng); - if (typeof o.preload === 'string') o.preload = [o.preload]; - for (var i = 0, l = o.preload.length; i < l; i++) { - var pres = f.toLanguages(o.preload[i]); - for (var y = 0, len = pres.length; y < len; y++) { - if (lngsToLoad.indexOf(pres[y]) < 0) { - lngsToLoad.push(pres[y]); - } - } - } - - // check if we have to load - var lngNeedLoad = []; - for (var a = 0, lenA = lngsToLoad.length; a < lenA; a++) { - var needLoad = false; - var resSet = resStore[lngsToLoad[a]]; - if (resSet) { - for (var b = 0, lenB = namespaces.length; b < lenB; b++) { - if (!resSet[namespaces[b]]) needLoad = true; - } - } else { - needLoad = true; - } - - if (needLoad) lngNeedLoad.push(lngsToLoad[a]); - } - - if (lngNeedLoad.length) { - i18n.sync._fetch(lngNeedLoad, opts, function(err, store) { - var todo = namespaces.length * lngNeedLoad.length; - - // load each file individual - f.each(namespaces, function(nsIndex, nsValue) { - - // append namespace to namespace array - if (o.ns.namespaces.indexOf(nsValue) < 0) { - o.ns.namespaces.push(nsValue); - } - - f.each(lngNeedLoad, function(lngIndex, lngValue) { - resStore[lngValue] = resStore[lngValue] || {}; - resStore[lngValue][nsValue] = store[lngValue][nsValue]; - - todo--; // wait for all done befor callback - if (todo === 0 && cb) { - if (o.useLocalStorage) i18n.sync._storeLocal(resStore); - cb(); - } - }); - }); - }); - } else { - if (cb) cb(); - } - } - - function setLng(lng, options, cb) { - if (typeof options === 'function') { - cb = options; - options = {}; - } else if (!options) { - options = {}; - } - - options.lng = lng; - return init(options, cb); - } - - function lng() { - return currentLng; - } - function addJqueryFunct() { - // $.t shortcut - $.t = $.t || translate; - - function parse(ele, key, options) { - if (key.length === 0) return; - - var attr = 'text'; - - if (key.indexOf('[') === 0) { - var parts = key.split(']'); - key = parts[1]; - attr = parts[0].substr(1, parts[0].length-1); - } - - if (key.indexOf(';') === key.length-1) { - key = key.substr(0, key.length-2); - } - - var optionsToUse; - if (attr === 'html') { - optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options; - ele.html($.t(key, optionsToUse)); - } else if (attr === 'text') { - optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.text() }, options) : options; - ele.text($.t(key, optionsToUse)); - } else if (attr === 'prepend') { - optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options; - ele.prepend($.t(key, optionsToUse)); - } else if (attr === 'append') { - optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options; - ele.append($.t(key, optionsToUse)); - } else if (attr.indexOf("data-") === 0) { - var dataAttr = attr.substr(("data-").length); - optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.data(dataAttr) }, options) : options; - var translated = $.t(key, optionsToUse); - //we change into the data cache - ele.data(dataAttr, translated); - //we change into the dom - ele.attr(attr, translated); - } else { - optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.attr(attr) }, options) : options; - ele.attr(attr, $.t(key, optionsToUse)); - } - } - - function localize(ele, options) { - var key = ele.attr(o.selectorAttr); - if (!key && typeof key !== 'undefined' && key !== false) key = ele.text() || ele.val(); - if (!key) return; - - var target = ele - , targetSelector = ele.data("i18n-target"); - if (targetSelector) { - target = ele.find(targetSelector) || ele; - } - - if (!options && o.useDataAttrOptions === true) { - options = ele.data("i18n-options"); - } - options = options || {}; - - if (key.indexOf(';') >= 0) { - var keys = key.split(';'); - - $.each(keys, function(m, k) { - if (k !== '') parse(target, k, options); - }); - - } else { - parse(target, key, options); - } - - if (o.useDataAttrOptions === true) ele.data("i18n-options", options); - } - - // fn - $.fn.i18n = function (options) { - return this.each(function() { - // localize element itself - localize($(this), options); - - // localize childs - var elements = $(this).find('[' + o.selectorAttr + ']'); - elements.each(function() { - localize($(this), options); - }); - }); - }; - } - function applyReplacement(str, replacementHash, nestedKey, options) { - if (!str) return str; - - options = options || replacementHash; // first call uses replacement hash combined with options - if (str.indexOf(options.interpolationPrefix || o.interpolationPrefix) < 0) return str; - - var prefix = options.interpolationPrefix ? f.regexEscape(options.interpolationPrefix) : o.interpolationPrefixEscaped - , suffix = options.interpolationSuffix ? f.regexEscape(options.interpolationSuffix) : o.interpolationSuffixEscaped - , unEscapingSuffix = 'HTML'+suffix; - - f.each(replacementHash, function(key, value) { - var nextKey = nestedKey ? nestedKey + o.keyseparator + key : key; - if (typeof value === 'object' && value !== null) { - str = applyReplacement(str, value, nextKey, options); - } else { - if (options.escapeInterpolation || o.escapeInterpolation) { - str = str.replace(new RegExp([prefix, nextKey, unEscapingSuffix].join(''), 'g'), value); - str = str.replace(new RegExp([prefix, nextKey, suffix].join(''), 'g'), f.escape(value)); - } else { - str = str.replace(new RegExp([prefix, nextKey, suffix].join(''), 'g'), value); - } - // str = options.escapeInterpolation; - } - }); - return str; - } - - // append it to functions - f.applyReplacement = applyReplacement; - - function applyReuse(translated, options) { - var comma = ','; - var options_open = '{'; - var options_close = '}'; - - var opts = f.extend({}, options); - delete opts.postProcess; - - while (translated.indexOf(o.reusePrefix) != -1) { - replacementCounter++; - if (replacementCounter > o.maxRecursion) { break; } // safety net for too much recursion - var index_of_opening = translated.lastIndexOf(o.reusePrefix); - var index_of_end_of_closing = translated.indexOf(o.reuseSuffix, index_of_opening) + o.reuseSuffix.length; - var token = translated.substring(index_of_opening, index_of_end_of_closing); - var token_without_symbols = token.replace(o.reusePrefix, '').replace(o.reuseSuffix, ''); - - - if (token_without_symbols.indexOf(comma) != -1) { - var index_of_token_end_of_closing = token_without_symbols.indexOf(comma); - if (token_without_symbols.indexOf(options_open, index_of_token_end_of_closing) != -1 && token_without_symbols.indexOf(options_close, index_of_token_end_of_closing) != -1) { - var index_of_opts_opening = token_without_symbols.indexOf(options_open, index_of_token_end_of_closing); - var index_of_opts_end_of_closing = token_without_symbols.indexOf(options_close, index_of_opts_opening) + options_close.length; - try { - opts = f.extend(opts, JSON.parse(token_without_symbols.substring(index_of_opts_opening, index_of_opts_end_of_closing))); - token_without_symbols = token_without_symbols.substring(0, index_of_token_end_of_closing); - } catch (e) { - } - } - } - - var translated_token = _translate(token_without_symbols, opts); - translated = translated.replace(token, translated_token); - } - return translated; - } - - function hasContext(options) { - return (options.context && (typeof options.context == 'string' || typeof options.context == 'number')); - } - - function needsPlural(options) { - return (options.count !== undefined && typeof options.count != 'string' && options.count !== 1); - } - - function exists(key, options) { - options = options || {}; - - var notFound = _getDefaultValue(key, options) - , found = _find(key, options); - - return found !== undefined || found === notFound; - } - - function translate(key, options) { - options = options || {}; - - if (!initialized) { - f.log('i18next not finished initialization. you might have called t function before loading resources finished.') - return options.defaultValue || ''; - }; - replacementCounter = 0; - return _translate.apply(null, arguments); - } - - function _getDefaultValue(key, options) { - return (options.defaultValue !== undefined) ? options.defaultValue : key; - } - - function _injectSprintfProcessor() { - - var values = []; - - // mh: build array from second argument onwards - for (var i = 1; i < arguments.length; i++) { - values.push(arguments[i]); - } - - return { - postProcess: 'sprintf', - sprintf: values - }; - } - - function _translate(potentialKeys, options) { - if (options && typeof options !== 'object') { - if (o.shortcutFunction === 'sprintf') { - // mh: gettext like sprintf syntax found, automatically create sprintf processor - options = _injectSprintfProcessor.apply(null, arguments); - } else if (o.shortcutFunction === 'defaultValue') { - options = { - defaultValue: options - } - } - } else { - options = options || {}; - } - - if (potentialKeys === undefined || potentialKeys === null) return ''; - - if (typeof potentialKeys == 'string') { - potentialKeys = [potentialKeys]; - } - - var key = potentialKeys[0]; - - if (potentialKeys.length > 1) { - for (var i = 0; i < potentialKeys.length; i++) { - key = potentialKeys[i]; - if (exists(key, options)) { - break; - } - } - } - - var notFound = _getDefaultValue(key, options) - , found = _find(key, options) - , lngs = options.lng ? f.toLanguages(options.lng) : languages - , ns = options.ns || o.ns.defaultNs - , parts; - - // split ns and key - if (key.indexOf(o.nsseparator) > -1) { - parts = key.split(o.nsseparator); - ns = parts[0]; - key = parts[1]; - } - - if (found === undefined && o.sendMissing) { - if (options.lng) { - sync.postMissing(lngs[0], ns, key, notFound, lngs); - } else { - sync.postMissing(o.lng, ns, key, notFound, lngs); - } - } - - var postProcessor = options.postProcess || o.postProcess; - if (found !== undefined && postProcessor) { - if (postProcessors[postProcessor]) { - found = postProcessors[postProcessor](found, key, options); - } - } - - // process notFound if function exists - var splitNotFound = notFound; - if (notFound.indexOf(o.nsseparator) > -1) { - parts = notFound.split(o.nsseparator); - splitNotFound = parts[1]; - } - if (splitNotFound === key && o.parseMissingKey) { - notFound = o.parseMissingKey(notFound); - } - - if (found === undefined) { - notFound = applyReplacement(notFound, options); - notFound = applyReuse(notFound, options); - - if (postProcessor && postProcessors[postProcessor]) { - var val = _getDefaultValue(key, options); - found = postProcessors[postProcessor](val, key, options); - } - } - - return (found !== undefined) ? found : notFound; - } - - function _find(key, options) { - options = options || {}; - - var optionWithoutCount, translated - , notFound = _getDefaultValue(key, options) - , lngs = languages; - - if (!resStore) { return notFound; } // no resStore to translate from - - // CI mode - if (lngs[0].toLowerCase() === 'cimode') return notFound; - - // passed in lng - if (options.lng) { - lngs = f.toLanguages(options.lng); - - if (!resStore[lngs[0]]) { - var oldAsync = o.getAsync; - o.getAsync = false; - - i18n.sync.load(lngs, o, function(err, store) { - f.extend(resStore, store); - o.getAsync = oldAsync; - }); - } - } - - var ns = options.ns || o.ns.defaultNs; - if (key.indexOf(o.nsseparator) > -1) { - var parts = key.split(o.nsseparator); - ns = parts[0]; - key = parts[1]; - } - - if (hasContext(options)) { - optionWithoutCount = f.extend({}, options); - delete optionWithoutCount.context; - optionWithoutCount.defaultValue = o.contextNotFound; - - var contextKey = ns + o.nsseparator + key + '_' + options.context; - - translated = translate(contextKey, optionWithoutCount); - if (translated != o.contextNotFound) { - return applyReplacement(translated, { context: options.context }); // apply replacement for context only - } // else continue translation with original/nonContext key - } - - if (needsPlural(options)) { - optionWithoutCount = f.extend({}, options); - delete optionWithoutCount.count; - optionWithoutCount.defaultValue = o.pluralNotFound; - - var pluralKey = ns + o.nsseparator + key + o.pluralSuffix; - var pluralExtension = pluralExtensions.get(lngs[0], options.count); - if (pluralExtension >= 0) { - pluralKey = pluralKey + '_' + pluralExtension; - } else if (pluralExtension === 1) { - pluralKey = ns + o.nsseparator + key; // singular - } - - translated = translate(pluralKey, optionWithoutCount); - if (translated != o.pluralNotFound) { - return applyReplacement(translated, { - count: options.count, - interpolationPrefix: options.interpolationPrefix, - interpolationSuffix: options.interpolationSuffix - }); // apply replacement for count only - } // else continue translation with original/singular key - } - - var found; - var keys = key.split(o.keyseparator); - for (var i = 0, len = lngs.length; i < len; i++ ) { - if (found !== undefined) break; - - var l = lngs[i]; - - var x = 0; - var value = resStore[l] && resStore[l][ns]; - while (keys[x]) { - value = value && value[keys[x]]; - x++; - } - if (value !== undefined) { - var valueType = Object.prototype.toString.apply(value); - if (typeof value === 'string') { - value = applyReplacement(value, options); - value = applyReuse(value, options); - } else if (valueType === '[object Array]' && !o.returnObjectTrees && !options.returnObjectTrees) { - value = value.join('\n'); - value = applyReplacement(value, options); - value = applyReuse(value, options); - } else if (value === null && o.fallbackOnNull === true) { - value = undefined; - } else if (value !== null) { - if (!o.returnObjectTrees && !options.returnObjectTrees) { - if (o.objectTreeKeyHandler && typeof o.objectTreeKeyHandler == 'function') { - value = o.objectTreeKeyHandler(key, value, l, ns, options); - } else { - value = 'key \'' + ns + ':' + key + ' (' + l + ')\' ' + - 'returned an object instead of string.'; - f.log(value); - } - } else if (valueType !== '[object Number]' && valueType !== '[object Function]' && valueType !== '[object RegExp]') { - var copy = (valueType === '[object Array]') ? [] : {}; // apply child translation on a copy - f.each(value, function(m) { - copy[m] = _translate(ns + o.nsseparator + key + o.keyseparator + m, options); - }); - value = copy; - } - } - - if (typeof value === 'string' && value.trim() === '' && o.fallbackOnEmpty === true) - value = undefined; - - found = value; - } - } - - if (found === undefined && !options.isFallbackLookup && (o.fallbackToDefaultNS === true || (o.fallbackNS && o.fallbackNS.length > 0))) { - // set flag for fallback lookup - avoid recursion - options.isFallbackLookup = true; - - if (o.fallbackNS.length) { - - for (var y = 0, lenY = o.fallbackNS.length; y < lenY; y++) { - found = _find(o.fallbackNS[y] + o.nsseparator + key, options); - - if (found) { - /* compare value without namespace */ - var foundValue = found.indexOf(o.nsseparator) > -1 ? found.split(o.nsseparator)[1] : found - , notFoundValue = notFound.indexOf(o.nsseparator) > -1 ? notFound.split(o.nsseparator)[1] : notFound; - - if (foundValue !== notFoundValue) break; - } - } - } else { - found = _find(key, options); // fallback to default NS - } - } - - return found; - } - function detectLanguage() { - var detectedLng; - - // get from qs - var qsParm = []; - if (typeof window !== 'undefined') { - (function() { - var query = window.location.search.substring(1); - var parms = query.split('&'); - for (var i=0; i 0) { - var key = parms[i].substring(0,pos); - var val = parms[i].substring(pos+1); - qsParm[key] = val; - } - } - })(); - if (qsParm[o.detectLngQS]) { - detectedLng = qsParm[o.detectLngQS]; - } - } - - // get from cookie - if (!detectedLng && typeof document !== 'undefined' && o.useCookie ) { - var c = f.cookie.read(o.cookieName); - if (c) detectedLng = c; - } - - // get from navigator - if (!detectedLng && typeof navigator !== 'undefined') { - detectedLng = (navigator.language) ? navigator.language : navigator.userLanguage; - } - - return detectedLng; - } - var sync = { - - load: function(lngs, options, cb) { - if (options.useLocalStorage) { - sync._loadLocal(lngs, options, function(err, store) { - var missingLngs = []; - for (var i = 0, len = lngs.length; i < len; i++) { - if (!store[lngs[i]]) missingLngs.push(lngs[i]); - } - - if (missingLngs.length > 0) { - sync._fetch(missingLngs, options, function(err, fetched) { - f.extend(store, fetched); - sync._storeLocal(fetched); - - cb(null, store); - }); - } else { - cb(null, store); - } - }); - } else { - sync._fetch(lngs, options, function(err, store){ - cb(null, store); - }); - } - }, - - _loadLocal: function(lngs, options, cb) { - var store = {} - , nowMS = new Date().getTime(); - - if(window.localStorage) { - - var todo = lngs.length; - - f.each(lngs, function(key, lng) { - var local = window.localStorage.getItem('res_' + lng); - - if (local) { - local = JSON.parse(local); - - if (local.i18nStamp && local.i18nStamp + options.localStorageExpirationTime > nowMS) { - store[lng] = local; - } - } - - todo--; // wait for all done befor callback - if (todo === 0) cb(null, store); - }); - } - }, - - _storeLocal: function(store) { - if(window.localStorage) { - for (var m in store) { - store[m].i18nStamp = new Date().getTime(); - window.localStorage.setItem('res_' + m, JSON.stringify(store[m])); - } - } - return; - }, - - _fetch: function(lngs, options, cb) { - var ns = options.ns - , store = {}; - - if (!options.dynamicLoad) { - var todo = ns.namespaces.length * lngs.length - , errors; - - // load each file individual - f.each(ns.namespaces, function(nsIndex, nsValue) { - f.each(lngs, function(lngIndex, lngValue) { - - // Call this once our translation has returned. - var loadComplete = function(err, data) { - if (err) { - errors = errors || []; - errors.push(err); - } - store[lngValue] = store[lngValue] || {}; - store[lngValue][nsValue] = data; - - todo--; // wait for all done befor callback - if (todo === 0) cb(errors, store); - }; - - if(typeof options.customLoad == 'function'){ - // Use the specified custom callback. - options.customLoad(lngValue, nsValue, options, loadComplete); - } else { - //~ // Use our inbuilt sync. - sync._fetchOne(lngValue, nsValue, options, loadComplete); - } - }); - }); - } else { - // Call this once our translation has returned. - var loadComplete = function(err, data) { - cb(null, data); - }; - - if(typeof options.customLoad == 'function'){ - // Use the specified custom callback. - options.customLoad(lngs, ns.namespaces, options, loadComplete); - } else { - var url = applyReplacement(options.resGetPath, { lng: lngs.join('+'), ns: ns.namespaces.join('+') }); - // load all needed stuff once - f.ajax({ - url: url, - success: function(data, status, xhr) { - f.log('loaded: ' + url); - loadComplete(null, data); - }, - error : function(xhr, status, error) { - f.log('failed loading: ' + url); - loadComplete('failed loading resource.json error: ' + error); - }, - dataType: "json", - async : options.getAsync - }); - } - } - }, - - _fetchOne: function(lng, ns, options, done) { - var url = applyReplacement(options.resGetPath, { lng: lng, ns: ns }); - f.ajax({ - url: url, - success: function(data, status, xhr) { - f.log('loaded: ' + url); - done(null, data); - }, - error : function(xhr, status, error) { - if ((status && status == 200) || (xhr && xhr.status && xhr.status == 200)) { - // file loaded but invalid json, stop waste time ! - f.log('There is a typo in: ' + url); - } else if ((status && status == 404) || (xhr && xhr.status && xhr.status == 404)) { - f.log('Does not exist: ' + url); - } else { - var theStatus = status ? status : ((xhr && xhr.status) ? xhr.status : null); - f.log(theStatus + ' when loading ' + url); - } - - done(error, {}); - }, - dataType: "json", - async : options.getAsync - }); - }, - - postMissing: function(lng, ns, key, defaultValue, lngs) { - var payload = {}; - payload[key] = defaultValue; - - var urls = []; - - if (o.sendMissingTo === 'fallback' && o.fallbackLng[0] !== false) { - for (var i = 0; i < o.fallbackLng.length; i++) { - urls.push({lng: o.fallbackLng[i], url: applyReplacement(o.resPostPath, { lng: o.fallbackLng[i], ns: ns })}); - } - } else if (o.sendMissingTo === 'current' || (o.sendMissingTo === 'fallback' && o.fallbackLng[0] === false) ) { - urls.push({lng: lng, url: applyReplacement(o.resPostPath, { lng: lng, ns: ns })}); - } else if (o.sendMissingTo === 'all') { - for (var i = 0, l = lngs.length; i < l; i++) { - urls.push({lng: lngs[i], url: applyReplacement(o.resPostPath, { lng: lngs[i], ns: ns })}); - } - } - - for (var y = 0, len = urls.length; y < len; y++) { - var item = urls[y]; - f.ajax({ - url: item.url, - type: o.sendType, - data: payload, - success: function(data, status, xhr) { - f.log('posted missing key \'' + key + '\' to: ' + item.url); - - // add key to resStore - var keys = key.split('.'); - var x = 0; - var value = resStore[item.lng][ns]; - while (keys[x]) { - if (x === keys.length - 1) { - value = value[keys[x]] = defaultValue; - } else { - value = value[keys[x]] = value[keys[x]] || {}; - } - x++; - } - }, - error : function(xhr, status, error) { - f.log('failed posting missing key \'' + key + '\' to: ' + item.url); - }, - dataType: "json", - async : o.postAsync - }); - } - } - }; - // definition http://translate.sourceforge.net/wiki/l10n/pluralforms - var pluralExtensions = { - - rules: { - "ach": { - "name": "Acholi", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "af": { - "name": "Afrikaans", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ak": { - "name": "Akan", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "am": { - "name": "Amharic", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "an": { - "name": "Aragonese", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ar": { - "name": "Arabic", - "numbers": [ - 0, - 1, - 2, - 3, - 11, - 100 - ], - "plurals": function(n) { return Number(n===0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); } - }, - "arn": { - "name": "Mapudungun", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "ast": { - "name": "Asturian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ay": { - "name": "Aymar\u00e1", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "az": { - "name": "Azerbaijani", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "be": { - "name": "Belarusian", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "bg": { - "name": "Bulgarian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "bn": { - "name": "Bengali", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "bo": { - "name": "Tibetan", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "br": { - "name": "Breton", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "bs": { - "name": "Bosnian", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "ca": { - "name": "Catalan", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "cgg": { - "name": "Chiga", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "cs": { - "name": "Czech", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); } - }, - "csb": { - "name": "Kashubian", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "cy": { - "name": "Welsh", - "numbers": [ - 1, - 2, - 3, - 8 - ], - "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3); } - }, - "da": { - "name": "Danish", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "de": { - "name": "German", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "dz": { - "name": "Dzongkha", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "el": { - "name": "Greek", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "en": { - "name": "English", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "eo": { - "name": "Esperanto", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "es": { - "name": "Spanish", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "es_ar": { - "name": "Argentinean Spanish", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "et": { - "name": "Estonian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "eu": { - "name": "Basque", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "fa": { - "name": "Persian", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "fi": { - "name": "Finnish", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "fil": { - "name": "Filipino", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "fo": { - "name": "Faroese", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "fr": { - "name": "French", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "fur": { - "name": "Friulian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "fy": { - "name": "Frisian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ga": { - "name": "Irish", - "numbers": [ - 1, - 2, - 3, - 7, - 11 - ], - "plurals": function(n) { return Number(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4) ;} - }, - "gd": { - "name": "Scottish Gaelic", - "numbers": [ - 1, - 2, - 3, - 20 - ], - "plurals": function(n) { return Number((n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3); } - }, - "gl": { - "name": "Galician", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "gu": { - "name": "Gujarati", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "gun": { - "name": "Gun", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "ha": { - "name": "Hausa", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "he": { - "name": "Hebrew", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "hi": { - "name": "Hindi", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "hr": { - "name": "Croatian", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "hu": { - "name": "Hungarian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "hy": { - "name": "Armenian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ia": { - "name": "Interlingua", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "id": { - "name": "Indonesian", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "is": { - "name": "Icelandic", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n%10!=1 || n%100==11); } - }, - "it": { - "name": "Italian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ja": { - "name": "Japanese", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "jbo": { - "name": "Lojban", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "jv": { - "name": "Javanese", - "numbers": [ - 0, - 1 - ], - "plurals": function(n) { return Number(n !== 0); } - }, - "ka": { - "name": "Georgian", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "kk": { - "name": "Kazakh", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "km": { - "name": "Khmer", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "kn": { - "name": "Kannada", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ko": { - "name": "Korean", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "ku": { - "name": "Kurdish", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "kw": { - "name": "Cornish", - "numbers": [ - 1, - 2, - 3, - 4 - ], - "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3); } - }, - "ky": { - "name": "Kyrgyz", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "lb": { - "name": "Letzeburgesch", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ln": { - "name": "Lingala", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "lo": { - "name": "Lao", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "lt": { - "name": "Lithuanian", - "numbers": [ - 1, - 2, - 10 - ], - "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "lv": { - "name": "Latvian", - "numbers": [ - 1, - 2, - 0 - ], - "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n !== 0 ? 1 : 2); } - }, - "mai": { - "name": "Maithili", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "mfe": { - "name": "Mauritian Creole", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "mg": { - "name": "Malagasy", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "mi": { - "name": "Maori", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "mk": { - "name": "Macedonian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n==1 || n%10==1 ? 0 : 1); } - }, - "ml": { - "name": "Malayalam", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "mn": { - "name": "Mongolian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "mnk": { - "name": "Mandinka", - "numbers": [ - 0, - 1, - 2 - ], - "plurals": function(n) { return Number(0 ? 0 : n==1 ? 1 : 2); } - }, - "mr": { - "name": "Marathi", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ms": { - "name": "Malay", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "mt": { - "name": "Maltese", - "numbers": [ - 1, - 2, - 11, - 20 - ], - "plurals": function(n) { return Number(n==1 ? 0 : n===0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3); } - }, - "nah": { - "name": "Nahuatl", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "nap": { - "name": "Neapolitan", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "nb": { - "name": "Norwegian Bokmal", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ne": { - "name": "Nepali", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "nl": { - "name": "Dutch", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "nn": { - "name": "Norwegian Nynorsk", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "no": { - "name": "Norwegian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "nso": { - "name": "Northern Sotho", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "oc": { - "name": "Occitan", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "or": { - "name": "Oriya", - "numbers": [ - 2, - 1 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "pa": { - "name": "Punjabi", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "pap": { - "name": "Papiamento", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "pl": { - "name": "Polish", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "pms": { - "name": "Piemontese", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ps": { - "name": "Pashto", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "pt": { - "name": "Portuguese", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "pt_br": { - "name": "Brazilian Portuguese", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "rm": { - "name": "Romansh", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ro": { - "name": "Romanian", - "numbers": [ - 1, - 2, - 20 - ], - "plurals": function(n) { return Number(n==1 ? 0 : (n===0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); } - }, - "ru": { - "name": "Russian", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "sah": { - "name": "Yakut", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "sco": { - "name": "Scots", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "se": { - "name": "Northern Sami", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "si": { - "name": "Sinhala", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "sk": { - "name": "Slovak", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); } - }, - "sl": { - "name": "Slovenian", - "numbers": [ - 5, - 1, - 2, - 3 - ], - "plurals": function(n) { return Number(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); } - }, - "so": { - "name": "Somali", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "son": { - "name": "Songhay", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "sq": { - "name": "Albanian", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "sr": { - "name": "Serbian", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "su": { - "name": "Sundanese", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "sv": { - "name": "Swedish", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "sw": { - "name": "Swahili", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "ta": { - "name": "Tamil", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "te": { - "name": "Telugu", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "tg": { - "name": "Tajik", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "th": { - "name": "Thai", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "ti": { - "name": "Tigrinya", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "tk": { - "name": "Turkmen", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "tr": { - "name": "Turkish", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "tt": { - "name": "Tatar", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "ug": { - "name": "Uyghur", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "uk": { - "name": "Ukrainian", - "numbers": [ - 1, - 2, - 5 - ], - "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } - }, - "ur": { - "name": "Urdu", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "uz": { - "name": "Uzbek", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "vi": { - "name": "Vietnamese", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "wa": { - "name": "Walloon", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n > 1); } - }, - "wo": { - "name": "Wolof", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - }, - "yo": { - "name": "Yoruba", - "numbers": [ - 1, - 2 - ], - "plurals": function(n) { return Number(n != 1); } - }, - "zh": { - "name": "Chinese", - "numbers": [ - 1 - ], - "plurals": function(n) { return 0; } - } - }, - - // for demonstration only sl and ar is added but you can add your own pluralExtensions - addRule: function(lng, obj) { - pluralExtensions.rules[lng] = obj; - }, - - setCurrentLng: function(lng) { - if (!pluralExtensions.currentRule || pluralExtensions.currentRule.lng !== lng) { - var parts = lng.split('-'); - - pluralExtensions.currentRule = { - lng: lng, - rule: pluralExtensions.rules[parts[0]] - }; - } - }, - - get: function(lng, count) { - var parts = lng.split('-'); - - function getResult(l, c) { - var ext; - if (pluralExtensions.currentRule && pluralExtensions.currentRule.lng === lng) { - ext = pluralExtensions.currentRule.rule; - } else { - ext = pluralExtensions.rules[l]; - } - if (ext) { - var i = ext.plurals(c); - var number = ext.numbers[i]; - if (ext.numbers.length === 2 && ext.numbers[0] === 1) { - if (number === 2) { - number = -1; // regular plural - } else if (number === 1) { - number = 1; // singular - } - }//console.log(count + '-' + number); - return number; - } else { - return c === 1 ? '1' : '-1'; - } - } - - return getResult(parts[0], count); - } - - }; - var postProcessors = {}; - var addPostProcessor = function(name, fc) { - postProcessors[name] = fc; - }; - // sprintf support - var sprintf = (function() { - function get_type(variable) { - return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); - } - function str_repeat(input, multiplier) { - for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} - return output.join(''); - } - - var str_format = function() { - if (!str_format.cache.hasOwnProperty(arguments[0])) { - str_format.cache[arguments[0]] = str_format.parse(arguments[0]); - } - return str_format.format.call(null, str_format.cache[arguments[0]], arguments); - }; - - str_format.format = function(parse_tree, argv) { - var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; - for (i = 0; i < tree_length; i++) { - node_type = get_type(parse_tree[i]); - if (node_type === 'string') { - output.push(parse_tree[i]); - } - else if (node_type === 'array') { - match = parse_tree[i]; // convenience purposes only - if (match[2]) { // keyword argument - arg = argv[cursor]; - for (k = 0; k < match[2].length; k++) { - if (!arg.hasOwnProperty(match[2][k])) { - throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); - } - arg = arg[match[2][k]]; - } - } - else if (match[1]) { // positional argument (explicit) - arg = argv[match[1]]; - } - else { // positional argument (implicit) - arg = argv[cursor++]; - } - - if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { - throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); - } - switch (match[8]) { - case 'b': arg = arg.toString(2); break; - case 'c': arg = String.fromCharCode(arg); break; - case 'd': arg = parseInt(arg, 10); break; - case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; - case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; - case 'o': arg = arg.toString(8); break; - case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; - case 'u': arg = Math.abs(arg); break; - case 'x': arg = arg.toString(16); break; - case 'X': arg = arg.toString(16).toUpperCase(); break; - } - arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); - pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; - pad_length = match[6] - String(arg).length; - pad = match[6] ? str_repeat(pad_character, pad_length) : ''; - output.push(match[5] ? arg + pad : pad + arg); - } - } - return output.join(''); - }; - - str_format.cache = {}; - - str_format.parse = function(fmt) { - var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; - while (_fmt) { - if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { - parse_tree.push(match[0]); - } - else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { - parse_tree.push('%'); - } - else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { - if (match[2]) { - arg_names |= 1; - var field_list = [], replacement_field = match[2], field_match = []; - if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { - if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - } - else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - } - else { - throw('[sprintf] huh?'); - } - } - } - else { - throw('[sprintf] huh?'); - } - match[2] = field_list; - } - else { - arg_names |= 2; - } - if (arg_names === 3) { - throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); - } - parse_tree.push(match); - } - else { - throw('[sprintf] huh?'); - } - _fmt = _fmt.substring(match[0].length); - } - return parse_tree; - }; - - return str_format; - })(); - - var vsprintf = function(fmt, argv) { - argv.unshift(fmt); - return sprintf.apply(null, argv); - }; - - addPostProcessor("sprintf", function(val, key, opts) { - if (!opts.sprintf) return val; - - if (Object.prototype.toString.apply(opts.sprintf) === '[object Array]') { - return vsprintf(val, opts.sprintf); - } else if (typeof opts.sprintf === 'object') { - return sprintf(val, opts.sprintf); - } - - return val; - }); - // public api interface - i18n.init = init; - i18n.setLng = setLng; - i18n.preload = preload; - i18n.addResourceBundle = addResourceBundle; - i18n.removeResourceBundle = removeResourceBundle; - i18n.loadNamespace = loadNamespace; - i18n.loadNamespaces = loadNamespaces; - i18n.setDefaultNamespace = setDefaultNamespace; - i18n.t = translate; - i18n.translate = translate; - i18n.exists = exists; - i18n.detectLanguage = f.detectLanguage; - i18n.pluralExtensions = pluralExtensions; - i18n.sync = sync; - i18n.functions = f; - i18n.lng = lng; - i18n.addPostProcessor = addPostProcessor; - i18n.options = o; - -})(); \ No newline at end of file diff --git a/services/web/public/js/libs/ng-i18next/directive/directive.js b/services/web/public/js/libs/ng-i18next/directive/directive.js deleted file mode 100755 index ce5dedf262..0000000000 --- a/services/web/public/js/libs/ng-i18next/directive/directive.js +++ /dev/null @@ -1,185 +0,0 @@ -angular.module('jm.i18next').directive('ngI18next', ['$rootScope', '$i18next', '$compile', '$parse', '$interpolate', function ($rootScope, $i18next, $compile, $parse, $interpolate) { - - 'use strict'; - - console.log("running directive") - var watchUnregister; - - function parse(scope, element, key) { - - var attr = 'text', - attrs = [attr], - string, - i; - - // If there was a watched value, unregister it - if (watchUnregister) { - watchUnregister(); - } - - key = key.trim(); - - /* - * Check if we want to translate an attribute - */ - if (key.indexOf('[') === 0) { - - var parts = key.split(']'); - - // If there are more than two parts because of multiple "]", concatenate them again. - if (parts.length > 2) { - for (i = 2; i < parts.length; i++) { - parts[1] += ']' + parts[i]; - parts[i] = null; - } - } - - key = parts[1]; - attr = parts[0].substr(1, parts[0].length - 1); - - } - /* - * Cut of the ";" that might be at the end of the string - */ - if (key.indexOf(';') === key.length - 1) { - key = key.substr(0, key.length - 2).trim(); - } - /* - * If passing options, split attr - */ - if (attr.indexOf(':') >= 0) { - attrs = attr.split(':'); - attr = attrs[0]; - } else if (attr === 'i18next') { - attrs[1] = 'i18next'; - attr = 'text'; - } - - if (attr !== 'i18next' && attrs[1] !== 'i18next') { - - string = $i18next(key); - - } else { - - var options = {}, - strippedKey = key; - - if (key.indexOf('(') >= 0 && key.indexOf(')') >= 0) { - - var keys = key.split(')'); - - keys[0] = keys[0].substr(1, keys[0].length); - - if (keys.length > 2) { - - strippedKey = keys.pop(); - - options = $parse(keys.join(')'))(scope); - - } else { - - options = $parse(keys[0])(scope); - strippedKey = keys[1].trim(); - - } - - if (options.sprintf) { - options.postProcess = 'sprintf'; - } - - } - - string = $i18next(strippedKey, options); - - } - - if (attr === 'html') { - - element.empty().append(string); - - /* - * Now compile the content of the element and bind the variables to - * the scope - */ - $compile(element.contents())(scope); - - } else { - var insertText = element.text.bind(element); - - if (attr !== 'text') { - insertText = element.attr.bind(element, attr); - } - - watchUnregister = scope.$watch($interpolate(string), insertText); - insertText(string); - } - - if (!$rootScope.$$phase) { - $rootScope.$digest(); - } - } - - - function localize(scope, element, key) { - - if (key.indexOf(';') >= 0) { - - var keys = key.split(';'); - - for (var i = 0; i < keys.length; i++) { - if (keys[i] !== '') { - parse(scope, element, keys[i]); - } - } - - } else { - parse(scope, element, key); - } - - } - - return { - - // 'A': only as attribute - restrict: 'A', - - scope: false, - - link: function postLink(scope, element, attrs) { - - var translationValue; - - function observe(value) { - translationValue = value.replace(/^\s+|\s+$/g, ''); // RegEx removes whitespace - - if (translationValue === '') { - return setupWatcher(); - } - - localize(scope, element, translationValue); - } - - function setupWatcher() { - // Prevent from executing this method twice - if (setupWatcher.done) { - return; - } - - // interpolate is allowing to transform {{expr}} into text - var interpolation = $interpolate(element.html()); - - scope.$watch(interpolation, observe); - - setupWatcher.done = true; - } - - attrs.$observe('ngI18next', observe); - - scope.$on('i18nextLanguageChange', function () { - localize(scope, element, translationValue); - }); - } - - }; - -}]); diff --git a/services/web/public/js/libs/ng-i18next/filter/filter.js b/services/web/public/js/libs/ng-i18next/filter/filter.js deleted file mode 100755 index 0e3a144a94..0000000000 --- a/services/web/public/js/libs/ng-i18next/filter/filter.js +++ /dev/null @@ -1,12 +0,0 @@ -angular.module('jm.i18next').filter('i18next', ['$i18next', function ($i18next) { - - 'use strict'; - - console.log("running filter") - return function (string, options) { - - return $i18next(string, options); - - }; - -}]); diff --git a/services/web/public/js/libs/ng-i18next/index.js b/services/web/public/js/libs/ng-i18next/index.js deleted file mode 100644 index 0fcd4f424c..0000000000 --- a/services/web/public/js/libs/ng-i18next/index.js +++ /dev/null @@ -1,8 +0,0 @@ -(function() { - define(["libs/ng-i18next/provider", "libs/ng-i18next/directive/directive", "libs/ng-i18next/filter/filter"], function() { - - console.log("hello") - - }); - -}).call(this); diff --git a/services/web/public/js/libs/ng-i18next/provider.js b/services/web/public/js/libs/ng-i18next/provider.js deleted file mode 100755 index c10cadb659..0000000000 --- a/services/web/public/js/libs/ng-i18next/provider.js +++ /dev/null @@ -1,123 +0,0 @@ -angular.module('jm.i18next', ['ng']); -angular.module('jm.i18next').provider('$i18next', function () { - - 'use strict'; - console.log("Running provider") - - var self = this, - /** - * This will be our translation function (see code below) - */ - t = null, - translations = {}, - globalOptions = null, - triesToLoadI18next = 0; - - self.options = {}; - - self.$get = ['$rootScope', '$timeout', function ($rootScope, $timeout) { - - function init(options) { - - if (window.i18n) { - - window.i18n.init(options, function (localize) { - - translations = {}; - - t = localize; - - if (!$rootScope.$$phase) { - $rootScope.$digest(); - } - - $rootScope.$broadcast('i18nextLanguageChange'); - - }); - - } else { - - triesToLoadI18next++; - // only check 4 times for i18next - if (triesToLoadI18next < 5) { - - $timeout(function () { - init(options); - }, 400); - - } else { - throw new Error('[ng-i18next] Can\'t find i18next!'); - } - - } - } - - function optionsChange(newOptions, oldOptions) { - - $i18nextTanslate.debugMsg.push(['i18next options changed:', oldOptions, newOptions]); - - globalOptions = newOptions; - - init(globalOptions); - - } - - /** - * Translates `key` with given options and puts the translation into `translations`. - * @param {Boolean} hasOwnOptions hasOwnOptions means that we are passing options to - * $i18next so we can't use previous saved translation. - */ - function translate(key, options, hasOwnOptions) { - - var lng = options.lng || 'auto'; - - if (!translations[lng]) { - translations[lng] = {}; - } - - if (!t) { - translations[lng][key] = 'defaultLoadingValue' in options ? options.defaultLoadingValue : - 'defaultValue' in options ? options.defaultValue : - 'defaultLoadingValue' in globalOptions ? globalOptions.defaultLoadingValue : key; - } else if (!translations[lng][key] || hasOwnOptions) { - translations[lng][key] = t(key, options); - } - - } - - function $i18nextTanslate(key, options) { - - var optionsObj = options || {}, - mergedOptions = options ? angular.extend({}, optionsObj, options) : optionsObj; - - translate(key, mergedOptions, !!options); - - return (options && options.lng) ? translations[options.lng][key] : - !!optionsObj.lng ? translations[optionsObj.lng][key] : translations['auto'][key]; - - } - - $i18nextTanslate.debugMsg = []; - - $i18nextTanslate.options = self.options; - - if (self.options !== globalOptions) { - optionsChange(self.options, globalOptions); - } - - $i18nextTanslate.reInit = function () { - optionsChange(globalOptions, globalOptions); - }; - - $rootScope.$watch(function () { return $i18nextTanslate.options; }, function (newOptions, oldOptions) { - // Check whether there are new options and whether the new options are different from the old options. - if (!!newOptions && oldOptions !== newOptions) { - optionsChange(newOptions, oldOptions); - } - }, true); - - return $i18nextTanslate; - - }]; - -}); From 1e0652888ac6bf58433a14a37824749ba7210f07 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 4 Aug 2014 12:20:52 +0100 Subject: [PATCH 18/26] removed console.log --- .../web/app/coffee/Features/User/UserPagesController.coffee | 1 - services/web/app/coffee/infrastructure/ExpressLocals.coffee | 3 --- 2 files changed, 4 deletions(-) diff --git a/services/web/app/coffee/Features/User/UserPagesController.coffee b/services/web/app/coffee/Features/User/UserPagesController.coffee index b5c1330231..5ca6bade06 100644 --- a/services/web/app/coffee/Features/User/UserPagesController.coffee +++ b/services/web/app/coffee/Features/User/UserPagesController.coffee @@ -23,7 +23,6 @@ module.exports = new_email:req.query.new_email || "" loginPage : (req, res)-> - console.info req res.render 'user/login', title: 'login', redir: req.query.redir diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 0dbacd28ca..34035e66fe 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -50,9 +50,6 @@ module.exports = (app)-> next() app.use (req, res, next)-> - console.log req.i18n - console.log req.i18n.t("profile_complete_percentage", {percent:88}) - console.log req.i18n.translate("profile_complete_percentage", {percent:87}) res.locals.translate = req.i18n.translate next() From 2be22d7ca79033556ef1cf4fee68afabde77eb0d Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Mon, 4 Aug 2014 16:47:14 +0100 Subject: [PATCH 19/26] cookies work accross different subdomains, added translations middlewear into stack to render based on domain --- services/web/app/coffee/infrastructure/Server.coffee | 5 +++-- services/web/config/settings.defaults.coffee | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index 32f8c42fe5..c00e367eb6 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -18,7 +18,7 @@ oneDayInMilliseconds = 86400000 ReferalConnect = require('../Features/Referal/ReferalConnect') RedirectManager = require("./RedirectManager") OldAssetProxy = require("./OldAssetProxy") -translations = require "translations-sharelatex" +translations = require("translations-sharelatex").setup(Settings.subdomainLang) metrics.mongodb.monitor(Path.resolve(__dirname + "/../../../node_modules/mongojs/node_modules/mongodb"), logger) metrics.mongodb.monitor(Path.resolve(__dirname + "/../../../node_modules/mongoose/node_modules/mongodb"), logger) @@ -51,10 +51,12 @@ app.configure () -> app.use express.bodyParser(uploadDir: Settings.path.uploadFolder) app.use express.bodyParser(uploadDir: __dirname + "/../../../data/uploads") app.use translations.expressMiddlewear + app.use translations.setLangBasedOnDomainMiddlewear app.use cookieParser app.use express.session proxy: Settings.behindProxy cookie: + domain: Settings.cookieDomain maxAge: cookieSessionLength secure: Settings.secureCookie store: sessionStore @@ -73,7 +75,6 @@ app.configure () -> app.use ReferalConnect.use app.use express.methodOverride() - app.use translations.serverStaticFiles expressLocals(app) diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 8f28bf9fae..13a3b72a53 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -107,6 +107,11 @@ module.exports = # that are sent out, generated links, etc. siteUrl : 'http://localhost:3000' + # cooke domain + # use full domain for cookies to only be accesabble from that domain, + # replace subdomain with dot to have them accessable on all subdomains + # cookieDomain: ".sharelatex.dev" + # Same, but with http auth credentials. httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000' From 377acfaa560d910f0044eda205aafebafe2d6888 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 5 Aug 2014 11:15:17 +0100 Subject: [PATCH 20/26] added default lang of en-US and translations package does the set lang based on subdomain --- services/web/app/coffee/infrastructure/Server.coffee | 3 +-- services/web/config/settings.defaults.coffee | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index c00e367eb6..264360c81c 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -18,7 +18,7 @@ oneDayInMilliseconds = 86400000 ReferalConnect = require('../Features/Referal/ReferalConnect') RedirectManager = require("./RedirectManager") OldAssetProxy = require("./OldAssetProxy") -translations = require("translations-sharelatex").setup(Settings.subdomainLang) +translations = require("translations-sharelatex").setup(Settings.i18n) metrics.mongodb.monitor(Path.resolve(__dirname + "/../../../node_modules/mongojs/node_modules/mongodb"), logger) metrics.mongodb.monitor(Path.resolve(__dirname + "/../../../node_modules/mongoose/node_modules/mongodb"), logger) @@ -76,7 +76,6 @@ app.configure () -> app.use ReferalConnect.use app.use express.methodOverride() - expressLocals(app) app.configure 'production', -> diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 13a3b72a53..212cc3b926 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -139,6 +139,13 @@ module.exports = features: defaultFeatures }] + + # i18n + # ------ + # + i18n: + defaultLng:"en-US" + # Spelling languages # ------------------ # From 90395af6b83f2fc5729628b9faf37a9db122845a Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 5 Aug 2014 11:22:42 +0100 Subject: [PATCH 21/26] nav bar text is now translated but uses key as the string if it can't be translated --- services/web/app/views/layout/navbar.jade | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/web/app/views/layout/navbar.jade b/services/web/app/views/layout/navbar.jade index 488189f81f..7053a8562a 100644 --- a/services/web/app/views/layout/navbar.jade +++ b/services/web/app/views/layout/navbar.jade @@ -23,7 +23,7 @@ nav.navbar.navbar-default if item.dropdown li.dropdown(class=item.class) a.dropdown-toggle(href) - | !{item.text} + | !{translate(item.text)} b.caret ul.dropdown-menu each child in item.dropdown @@ -32,14 +32,14 @@ nav.navbar.navbar-default else li if child.url - a(href=child.url, class=child.class) !{child.text} + a(href=child.url, class=child.class) !{translate(child.text)} else - | !{child.text} + | !{translate(child.text)} else li(class=item.class) if item.url - a(href=item.url, class=item.class) !{item.text} + a(href=item.url, class=item.class) !{translate(item.text)} else - | !{item.text} + | !{translate(item.text)} From 9baea1edc10601a52629bc2d79ef3f71d2d4578f Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 5 Aug 2014 11:35:23 +0100 Subject: [PATCH 22/26] removed lang dropdown --- services/web/app/views/layout/navbar.jade | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/services/web/app/views/layout/navbar.jade b/services/web/app/views/layout/navbar.jade index 7053a8562a..00a08bdd46 100644 --- a/services/web/app/views/layout/navbar.jade +++ b/services/web/app/views/layout/navbar.jade @@ -7,16 +7,6 @@ nav.navbar.navbar-default .navbar-collapse.collapse(collapse="navCollapsed") - .dropdown - button.btn.btn-default.dropdown-toggle Lang - span.caret - ul.dropdown-menu - li - a(href="/?setLng=en-GB") british - a(href="/?setLng=en-US") USA - a(href="/?setLng=fr-FR") french - - ul.nav.navbar-nav.navbar-right each item in nav.header if ((item.only_when_logged_in && session && session.user) || (item.only_when_logged_out && (!session || !session.user)) || (!item.only_when_logged_out && !item.only_when_logged_in)) From 673def4f903bbf19dc57bc67ed69c8745511d2f8 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 6 Aug 2014 15:05:13 +0100 Subject: [PATCH 23/26] added different lang links to head for google --- services/web/app/coffee/infrastructure/ExpressLocals.coffee | 1 + services/web/app/views/layout.jade | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 34035e66fe..21f70fc55b 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -51,6 +51,7 @@ module.exports = (app)-> app.use (req, res, next)-> res.locals.translate = req.i18n.translate + res.locals.currentUrl = req.originalUrl next() app.use (req, res, next)-> diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index bf462d473d..5a60e494b6 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -13,6 +13,10 @@ html(itemscope, itemtype='http://schema.org/Product') link(rel='stylesheet', href='/stylesheets/style.css?fingerprint='+fingerprint('/stylesheets/style.css')) link(href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css",rel="stylesheet") + + each subdomainDetails in settings.i18n.subdomainLang + link(rel="alternate", href=subdomainDetails.url+currentUrl, hreflang=subdomainDetails.lngCode) + meta(itemprop="name" ,content="ShareLaTeX - Real Time Online LaTeX Collaborative Editor in Your Browser") meta(itemprop="description", content="Online LaTeX editor for collaborative editing, great for Maths or Sciences. You don't need to install LaTeX so it's great for beginners too.") meta(itemprop="image", content="https://www.sharelatex.com/favicon.ico") From 01b7f61e6bbf98015a157ed0f23ae002be4af447 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 6 Aug 2014 15:35:57 +0100 Subject: [PATCH 24/26] added check if other langs are enabled for put the header rel link in --- services/web/app/views/layout.jade | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index 5a60e494b6..e774e3f8aa 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -13,9 +13,9 @@ html(itemscope, itemtype='http://schema.org/Product') link(rel='stylesheet', href='/stylesheets/style.css?fingerprint='+fingerprint('/stylesheets/style.css')) link(href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css",rel="stylesheet") - - each subdomainDetails in settings.i18n.subdomainLang - link(rel="alternate", href=subdomainDetails.url+currentUrl, hreflang=subdomainDetails.lngCode) + if settings.i18n.subdomainLang + each subdomainDetails in settings.i18n.subdomainLang + link(rel="alternate", href=subdomainDetails.url+currentUrl, hreflang=subdomainDetails.lngCode) meta(itemprop="name" ,content="ShareLaTeX - Real Time Online LaTeX Collaborative Editor in Your Browser") meta(itemprop="description", content="Online LaTeX editor for collaborative editing, great for Maths or Sciences. You don't need to install LaTeX so it's great for beginners too.") From c961cea514b7cff05368cb0460bd6921ba133817 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 7 Aug 2014 13:56:04 +0100 Subject: [PATCH 25/26] fixed some bits --- .../web/app/coffee/Features/Errors/ErrorController.coffee | 2 +- services/web/app/views/project/editor.jade | 5 +---- services/web/app/views/project/editor/chat.jade | 3 +-- services/web/app/views/project/list/modals.jade | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/services/web/app/coffee/Features/Errors/ErrorController.coffee b/services/web/app/coffee/Features/Errors/ErrorController.coffee index 39e2375713..d0589ba5ed 100644 --- a/services/web/app/coffee/Features/Errors/ErrorController.coffee +++ b/services/web/app/coffee/Features/Errors/ErrorController.coffee @@ -2,4 +2,4 @@ module.exports = ErrorController = notFound: (req, res)-> res.statusCode = 404 res.render 'general/404', - title: "Page Not Found" \ No newline at end of file + title: "page_not_found" \ No newline at end of file diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade index 100610aa15..00e7db8654 100644 --- a/services/web/app/views/project/editor.jade +++ b/services/web/app/views/project/editor.jade @@ -28,10 +28,7 @@ block content strong #{translate("reconnecting")}... .div(ng-controller="SavingNotificationController") - .alert.alert-warning.small( - ng-repeat="(doc_id, state) in docSavingStatus" - ng-if="state.unsavedSeconds > 3" - ) #{translate("saving_notification_with_seconds", {docname:"{{ state.doc.name }}", seconds:"{{ state.unsavedSeconds }}"})} + .alert.alert-warning.small( ng-repeat="(doc_id, state) in docSavingStatus" ng-if="state.unsavedSeconds > 3") #{translate("saving_notification_with_seconds", {docname:"{{ state.doc.name }}", seconds:"{{ state.unsavedSeconds }}"})} include ./editor/left-menu diff --git a/services/web/app/views/project/editor/chat.jade b/services/web/app/views/project/editor/chat.jade index 64e6a1a132..e6f6249793 100644 --- a/services/web/app/views/project/editor/chat.jade +++ b/services/web/app/views/project/editor/chat.jade @@ -27,8 +27,7 @@ aside.chat( ng-controller="ChatMessageController" ng-class="{'self': message.user.id == user.id }" ) - div.date(ng-if="$index == 0 || (message.timestamp - chat.messages[$index - 1].timestamp) > 5 * 60 * 1000") - {{ message.timestamp | formatDate:'h:mm a' }} {{ message.timestamp | relativeDate }} + div.date(ng-if="$index == 0 || (message.timestamp - chat.messages[$index - 1].timestamp) > 5 * 60 * 1000") {{ message.timestamp | formatDate:'h:mm a' }} {{ message.timestamp | relativeDate }} span.avatar img(ng-src="{{message.user.gravatar_url}}?d=mm&s=50") div.message-wrapper diff --git a/services/web/app/views/project/list/modals.jade b/services/web/app/views/project/list/modals.jade index e4ea1e2542..ca609ae697 100644 --- a/services/web/app/views/project/list/modals.jade +++ b/services/web/app/views/project/list/modals.jade @@ -85,7 +85,7 @@ script(type='text/ng-template', id='cloneProjectModalTemplate') ng-click="clone()" ) span(ng-hide="state.inflight") #{translate("copy")} - span(ng-show="state.inflight") #{translate("coping")} ... + span(ng-show="state.inflight") #{translate("copying")} ... script(type='text/ng-template', id='newProjectModalTemplate') .modal-header From a337b8e848e7b6614145df837f561ad75be475b1 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 7 Aug 2014 14:31:03 +0100 Subject: [PATCH 26/26] fixed jade error in track changes --- services/web/app/views/project/editor/track-changes.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/views/project/editor/track-changes.jade b/services/web/app/views/project/editor/track-changes.jade index 0fe6b3ea42..c9505a5a53 100644 --- a/services/web/app/views/project/editor/track-changes.jade +++ b/services/web/app/views/project/editor/track-changes.jade @@ -112,7 +112,7 @@ div#trackChanges(ng-show="ui.view == 'track-changes'") .diff-deleted.text-centered( ng-show="trackChanges.diff.deleted" ) - p.text-serif #{translate("file_has_been_deleted", filename:"{{ trackChanges.diff.doc.name }} ")} + p.text-serif #{translate("file_has_been_deleted", {filename:"{{ trackChanges.diff.doc.name }} "})} p a.btn.btn-primary.btn-lg(