From 60af7766bfd34338e1cc3a77a7250a3e791180ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Sat, 10 Jul 2021 10:55:54 +0200 Subject: [PATCH] Switch tap:i18n to custom TAPi18n implementation --- .meteor/packages | 12 +- .meteor/versions | 19 +- client/components/activities/activities.js | 1 + client/components/boards/boardBody.js | 2 + client/components/boards/boardHeader.js | 2 + client/components/boards/boardsList.js | 3 +- client/components/cards/cardCustomFields.js | 2 + client/components/cards/cardDate.js | 2 + client/components/cards/cardDetails.js | 3 +- client/components/cards/cardTime.js | 2 + client/components/cards/checklists.js | 1 + client/components/cards/minicard.js | 2 + client/components/lists/list.js | 1 + client/components/lists/listBody.js | 1 + client/components/lists/listHeader.js | 2 + client/components/main/globalSearch.js | 1 + client/components/main/layouts.js | 128 +- .../components/rules/triggers/cardTriggers.js | 2 + client/components/settings/adminReports.js | 1 + client/components/settings/informationBody.js | 2 + client/components/settings/settingBody.js | 1 + client/components/sidebar/sidebar.js | 2 + client/components/sidebar/sidebarArchives.js | 2 + .../components/sidebar/sidebarCustomFields.js | 2 + client/components/users/userHeader.js | 106 +- client/config/blazeHelpers.js | 10 + client/lib/cardSearch.js | 1 + client/lib/datepicker.js | 3 + client/lib/filter.js | 4 +- client/lib/i18n.js | 28 +- client/lib/popup.js | 2 + config/accounts.js | 5 +- config/query-classes.js | 3 +- config/router.js | 22 +- imports/i18n/accounts.js | 63 + imports/i18n/blaze.js | 8 + {i18n => imports/i18n/data}/ar-EG.i18n.json | 0 {i18n => imports/i18n/data}/ar.i18n.json | 0 {i18n => imports/i18n/data}/bg.i18n.json | 0 {i18n => imports/i18n/data}/br.i18n.json | 0 {i18n => imports/i18n/data}/ca.i18n.json | 0 {i18n => imports/i18n/data}/cs.i18n.json | 0 {i18n => imports/i18n/data}/da.i18n.json | 0 {i18n => imports/i18n/data}/de-AT.i18n.json | 0 {i18n => imports/i18n/data}/de-CH.i18n.json | 0 {i18n => imports/i18n/data}/de.i18n.json | 0 {i18n => imports/i18n/data}/el-GR.i18n.json | 0 imports/i18n/data/el.i18n.json | 1148 +++++++ {i18n => imports/i18n/data}/en-DE.i18n.json | 0 {i18n => imports/i18n/data}/en-GB.i18n.json | 0 {i18n => imports/i18n/data}/en-IT.i18n.json | 0 {i18n => imports/i18n/data}/en.i18n.json | 0 {i18n => imports/i18n/data}/eo.i18n.json | 0 {i18n => imports/i18n/data}/es-AR.i18n.json | 0 {i18n => imports/i18n/data}/es-CL.i18n.json | 0 {i18n => imports/i18n/data}/es-LA.i18n.json | 0 {i18n => imports/i18n/data}/es-MX.i18n.json | 0 {i18n => imports/i18n/data}/es-PE.i18n.json | 0 {i18n => imports/i18n/data}/es-PY.i18n.json | 0 {i18n => imports/i18n/data}/es.i18n.json | 0 {i18n => imports/i18n/data}/et-EE.i18n.json | 0 {i18n => imports/i18n/data}/eu.i18n.json | 0 {i18n => imports/i18n/data}/fa-IR.i18n.json | 0 {i18n => imports/i18n/data}/fa.i18n.json | 0 {i18n => imports/i18n/data}/fi.i18n.json | 0 {i18n => imports/i18n/data}/fr-CH.i18n.json | 0 {i18n => imports/i18n/data}/fr.i18n.json | 0 {i18n => imports/i18n/data}/gl-ES.i18n.json | 0 {i18n => imports/i18n/data}/gl.i18n.json | 0 {i18n => imports/i18n/data}/gu-IN.i18n.json | 0 {i18n => imports/i18n/data}/he.i18n.json | 0 {i18n => imports/i18n/data}/hi-IN.i18n.json | 0 {i18n => imports/i18n/data}/hi.i18n.json | 0 {i18n => imports/i18n/data}/hr.i18n.json | 0 {i18n => imports/i18n/data}/hu.i18n.json | 0 {i18n => imports/i18n/data}/hy.i18n.json | 0 {i18n => imports/i18n/data}/id.i18n.json | 0 {i18n => imports/i18n/data}/ig.i18n.json | 0 {i18n => imports/i18n/data}/it.i18n.json | 0 {i18n => imports/i18n/data}/ja.i18n.json | 0 {i18n => imports/i18n/data}/ka.i18n.json | 0 {i18n => imports/i18n/data}/km.i18n.json | 0 {i18n => imports/i18n/data}/ko.i18n.json | 0 {i18n => imports/i18n/data}/lt.i18n.json | 0 {i18n => imports/i18n/data}/lv.i18n.json | 0 {i18n => imports/i18n/data}/mk.i18n.json | 0 {i18n => imports/i18n/data}/mn.i18n.json | 0 {i18n => imports/i18n/data}/ms-MY.i18n.json | 0 {i18n => imports/i18n/data}/nb.i18n.json | 0 {i18n => imports/i18n/data}/nl.i18n.json | 0 {i18n => imports/i18n/data}/oc.i18n.json | 0 {i18n => imports/i18n/data}/pa.i18n.json | 0 {i18n => imports/i18n/data}/pl.i18n.json | 0 {i18n => imports/i18n/data}/pt-BR.i18n.json | 0 {i18n => imports/i18n/data}/pt.i18n.json | 0 {i18n => imports/i18n/data}/ro.i18n.json | 0 {i18n => imports/i18n/data}/ru-UA.i18n.json | 0 {i18n => imports/i18n/data}/ru.i18n.json | 0 {i18n => imports/i18n/data}/sk.i18n.json | 0 {i18n => imports/i18n/data}/sl.i18n.json | 0 {i18n => imports/i18n/data}/sr.i18n.json | 0 {i18n => imports/i18n/data}/sv.i18n.json | 0 {i18n => imports/i18n/data}/sw.i18n.json | 0 {i18n => imports/i18n/data}/ta.i18n.json | 0 {i18n => imports/i18n/data}/th.i18n.json | 0 {i18n => imports/i18n/data}/tr.i18n.json | 0 .../i18n/data/ua-UA.i18n.json | 0 .../i18n/data/ua.i18n.json | 0 {i18n => imports/i18n/data}/vi.i18n.json | 0 {i18n => imports/i18n/data}/zh-CN.i18n.json | 0 {i18n => imports/i18n/data}/zh-HK.i18n.json | 0 {i18n => imports/i18n/data}/zh-TW.i18n.json | 0 imports/i18n/index.js | 14 + imports/i18n/languages.js | 470 +++ imports/i18n/moment.js | 13 + imports/i18n/tap.js | 60 + models/boards.js | 3 +- models/cards.js | 1 + models/exportExcel.js | 5 +- models/exportPDF.js | 1 + models/exporter.js | 4 +- models/server/ExporterExcel.js | 16 +- models/settings.js | 1 + models/trelloCreator.js | 3 + models/users.js | 1 + models/wekanCreator.js | 2 + package-lock.json | 58 +- package.json | 12 +- packages/wekan-tap-i18n/ChangeLog | 216 -- packages/wekan-tap-i18n/LICENSE | 21 - packages/wekan-tap-i18n/README.md | 808 ----- packages/wekan-tap-i18n/TODO | 3 - packages/wekan-tap-i18n/lib/globals.js | 10 - .../lib/plugin/compiler_configuration.coffee | 23 - .../lib/plugin/compilers/i18n.coffee | 18 - .../compilers/i18n.generic_compiler.coffee | 124 - .../lib/plugin/compilers/i18n.json.coffee | 4 - .../lib/plugin/compilers/i18n.yml.coffee | 4 - .../plugin/compilers/package-tap.i18n.coffee | 95 - .../plugin/compilers/project-tap.i18n.coffee | 122 - .../lib/plugin/compilers/share.coffee | 1 - .../lib/plugin/etc/language_names.js | 136 - .../helpers/compile_step_helpers.coffee | 23 - .../lib/plugin/helpers/helpers.coffee | 1 - .../lib/plugin/helpers/load_json.coffee | 25 - .../lib/plugin/helpers/load_yml.coffee | 25 - .../lib/tap_i18n/tap_i18n-client.coffee | 198 -- .../lib/tap_i18n/tap_i18n-common.coffee | 143 - .../lib/tap_i18n/tap_i18n-helpers.coffee | 1 - .../lib/tap_i18n/tap_i18n-init.coffee | 1 - .../lib/tap_i18n/tap_i18n-server.coffee | 85 - .../lib/tap_i18next/tap_i18next-1.7.3.js | 2748 ----------------- .../lib/tap_i18next/tap_i18next_init.js | 1 - packages/wekan-tap-i18n/package.js | 78 - server/migrations.js | 1 + server/notifications/email.js | 1 + server/notifications/outgoing.js | 2 + 157 files changed, 1991 insertions(+), 5190 deletions(-) create mode 100644 imports/i18n/accounts.js create mode 100644 imports/i18n/blaze.js rename {i18n => imports/i18n/data}/ar-EG.i18n.json (100%) rename {i18n => imports/i18n/data}/ar.i18n.json (100%) rename {i18n => imports/i18n/data}/bg.i18n.json (100%) rename {i18n => imports/i18n/data}/br.i18n.json (100%) rename {i18n => imports/i18n/data}/ca.i18n.json (100%) rename {i18n => imports/i18n/data}/cs.i18n.json (100%) rename {i18n => imports/i18n/data}/da.i18n.json (100%) rename {i18n => imports/i18n/data}/de-AT.i18n.json (100%) rename {i18n => imports/i18n/data}/de-CH.i18n.json (100%) rename {i18n => imports/i18n/data}/de.i18n.json (100%) rename {i18n => imports/i18n/data}/el-GR.i18n.json (100%) create mode 100644 imports/i18n/data/el.i18n.json rename {i18n => imports/i18n/data}/en-DE.i18n.json (100%) rename {i18n => imports/i18n/data}/en-GB.i18n.json (100%) rename {i18n => imports/i18n/data}/en-IT.i18n.json (100%) rename {i18n => imports/i18n/data}/en.i18n.json (100%) rename {i18n => imports/i18n/data}/eo.i18n.json (100%) rename {i18n => imports/i18n/data}/es-AR.i18n.json (100%) rename {i18n => imports/i18n/data}/es-CL.i18n.json (100%) rename {i18n => imports/i18n/data}/es-LA.i18n.json (100%) rename {i18n => imports/i18n/data}/es-MX.i18n.json (100%) rename {i18n => imports/i18n/data}/es-PE.i18n.json (100%) rename {i18n => imports/i18n/data}/es-PY.i18n.json (100%) rename {i18n => imports/i18n/data}/es.i18n.json (100%) rename {i18n => imports/i18n/data}/et-EE.i18n.json (100%) rename {i18n => imports/i18n/data}/eu.i18n.json (100%) rename {i18n => imports/i18n/data}/fa-IR.i18n.json (100%) rename {i18n => imports/i18n/data}/fa.i18n.json (100%) rename {i18n => imports/i18n/data}/fi.i18n.json (100%) rename {i18n => imports/i18n/data}/fr-CH.i18n.json (100%) rename {i18n => imports/i18n/data}/fr.i18n.json (100%) rename {i18n => imports/i18n/data}/gl-ES.i18n.json (100%) rename {i18n => imports/i18n/data}/gl.i18n.json (100%) rename {i18n => imports/i18n/data}/gu-IN.i18n.json (100%) rename {i18n => imports/i18n/data}/he.i18n.json (100%) rename {i18n => imports/i18n/data}/hi-IN.i18n.json (100%) rename {i18n => imports/i18n/data}/hi.i18n.json (100%) rename {i18n => imports/i18n/data}/hr.i18n.json (100%) rename {i18n => imports/i18n/data}/hu.i18n.json (100%) rename {i18n => imports/i18n/data}/hy.i18n.json (100%) rename {i18n => imports/i18n/data}/id.i18n.json (100%) rename {i18n => imports/i18n/data}/ig.i18n.json (100%) rename {i18n => imports/i18n/data}/it.i18n.json (100%) rename {i18n => imports/i18n/data}/ja.i18n.json (100%) rename {i18n => imports/i18n/data}/ka.i18n.json (100%) rename {i18n => imports/i18n/data}/km.i18n.json (100%) rename {i18n => imports/i18n/data}/ko.i18n.json (100%) rename {i18n => imports/i18n/data}/lt.i18n.json (100%) rename {i18n => imports/i18n/data}/lv.i18n.json (100%) rename {i18n => imports/i18n/data}/mk.i18n.json (100%) rename {i18n => imports/i18n/data}/mn.i18n.json (100%) rename {i18n => imports/i18n/data}/ms-MY.i18n.json (100%) rename {i18n => imports/i18n/data}/nb.i18n.json (100%) rename {i18n => imports/i18n/data}/nl.i18n.json (100%) rename {i18n => imports/i18n/data}/oc.i18n.json (100%) rename {i18n => imports/i18n/data}/pa.i18n.json (100%) rename {i18n => imports/i18n/data}/pl.i18n.json (100%) rename {i18n => imports/i18n/data}/pt-BR.i18n.json (100%) rename {i18n => imports/i18n/data}/pt.i18n.json (100%) rename {i18n => imports/i18n/data}/ro.i18n.json (100%) rename {i18n => imports/i18n/data}/ru-UA.i18n.json (100%) rename {i18n => imports/i18n/data}/ru.i18n.json (100%) rename {i18n => imports/i18n/data}/sk.i18n.json (100%) rename {i18n => imports/i18n/data}/sl.i18n.json (100%) rename {i18n => imports/i18n/data}/sr.i18n.json (100%) rename {i18n => imports/i18n/data}/sv.i18n.json (100%) rename {i18n => imports/i18n/data}/sw.i18n.json (100%) rename {i18n => imports/i18n/data}/ta.i18n.json (100%) rename {i18n => imports/i18n/data}/th.i18n.json (100%) rename {i18n => imports/i18n/data}/tr.i18n.json (100%) rename i18n/uk-UA.i18n.json => imports/i18n/data/ua-UA.i18n.json (100%) rename i18n/uk.i18n.json => imports/i18n/data/ua.i18n.json (100%) rename {i18n => imports/i18n/data}/vi.i18n.json (100%) rename {i18n => imports/i18n/data}/zh-CN.i18n.json (100%) rename {i18n => imports/i18n/data}/zh-HK.i18n.json (100%) rename {i18n => imports/i18n/data}/zh-TW.i18n.json (100%) create mode 100644 imports/i18n/index.js create mode 100644 imports/i18n/languages.js create mode 100644 imports/i18n/moment.js create mode 100644 imports/i18n/tap.js delete mode 100644 packages/wekan-tap-i18n/ChangeLog delete mode 100644 packages/wekan-tap-i18n/LICENSE delete mode 100644 packages/wekan-tap-i18n/README.md delete mode 100644 packages/wekan-tap-i18n/TODO delete mode 100644 packages/wekan-tap-i18n/lib/globals.js delete mode 100644 packages/wekan-tap-i18n/lib/plugin/compiler_configuration.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/compilers/i18n.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/compilers/i18n.generic_compiler.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/compilers/i18n.json.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/compilers/i18n.yml.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/compilers/package-tap.i18n.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/compilers/project-tap.i18n.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/compilers/share.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/etc/language_names.js delete mode 100644 packages/wekan-tap-i18n/lib/plugin/helpers/compile_step_helpers.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/helpers/helpers.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/helpers/load_json.coffee delete mode 100644 packages/wekan-tap-i18n/lib/plugin/helpers/load_yml.coffee delete mode 100644 packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-client.coffee delete mode 100644 packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-common.coffee delete mode 100644 packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-helpers.coffee delete mode 100644 packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-init.coffee delete mode 100644 packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-server.coffee delete mode 100644 packages/wekan-tap-i18n/lib/tap_i18next/tap_i18next-1.7.3.js delete mode 100644 packages/wekan-tap-i18n/lib/tap_i18next/tap_i18next_init.js delete mode 100644 packages/wekan-tap-i18n/package.js diff --git a/.meteor/packages b/.meteor/packages index 218cce0f0..2b46496ad 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -41,10 +41,11 @@ kadira:dochead mquandalle:autofocus ongoworks:speakingurl raix:handlebar-helpers -http@2.0.0 +http@2.0.0! # force new http package # UI components blaze +ostrio:i18n reactive-var@1.0.11 fortawesome:fontawesome mousetrap:mousetrap @@ -59,9 +60,7 @@ email@2.1.1 horka:swipebox dynamic-import@0.7.1 rzymek:fullcalendar -momentjs:moment@2.22.2 browser-policy-framing@1.1.0 -mquandalle:moment msavin:usercache # Keep stylus in 1.1.0, because building v2 takes extra 52 minutes. coagmano:stylus@1.1.0! @@ -124,8 +123,13 @@ pascoual:pdfkit lmieulet:meteor-coverage meteortesting:mocha aldeed:simple-schema -accounts-password@2.0.0 +accounts-password matb33:collection-hooks simple:json-routes kadira:flow-router spacebars +communitypackages:picker +useraccounts:core +useraccounts:unstyled +useraccounts:flow-routing +service-configuration diff --git a/.meteor/versions b/.meteor/versions index 5f85a4cf8..1db92d8e4 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -20,8 +20,7 @@ browser-policy-common@1.0.11 browser-policy-framing@1.1.0 caching-compiler@1.2.2 caching-html-compiler@1.2.1 -callback-hook@1.3.1 -cfs:http-methods@0.0.32 +callback-hook@1.4.0 check@1.3.1 coagmano:stylus@1.1.0 coffeescript@2.4.1 @@ -77,7 +76,6 @@ meteorhacks:aggregate@1.3.0 meteorhacks:collection-utils@1.2.0 meteorhacks:picker@1.0.3 meteorhacks:subs-manager@1.6.4 -meteorspark:util@0.2.0 meteortesting:browser-tests@0.2.0 meteortesting:mocha@0.6.0 minifier-css@1.5.4 @@ -100,15 +98,14 @@ mquandalle:collection-mutations@0.1.0 mquandalle:jade@0.4.9 mquandalle:jade-compiler@0.4.5 mquandalle:jquery-textcomplete@0.8.0_1 -mquandalle:jquery-ui-drag-drop-sort@0.2.0 -mquandalle:moment@1.0.1 mquandalle:mousetrap-bindglobal@0.0.1 -mrt:just-i18n@0.3.0 msavin:usercache@1.8.0 npm-mongo@3.9.1 observe-sequence@1.0.19 ongoworks:speakingurl@1.1.0 ordered-dict@1.1.0 +ostrio:cstorage@2.2.2 +ostrio:i18n@3.1.0 pascoual:pdfkit@1.0.7 peerlibrary:assert@0.3.0 peerlibrary:base-component@0.17.1 @@ -186,9 +183,8 @@ simple:json-routes@2.1.0 socket-stream-client@0.4.0 spacebars@1.2.0 spacebars-compiler@1.3.0 -standard-minifier-css@1.7.3 -standard-minifier-js@2.6.1 -tap:i18n@1.8.2 +standard-minifier-css@1.7.4 +standard-minifier-js@2.8.0 templates:tabs@2.3.0 templating@1.4.1 templating-compiler@1.4.1 @@ -199,7 +195,10 @@ twbs:bootstrap@3.3.6 ui@1.0.13 underscore@1.0.10 url@1.3.2 -webapp@1.11.1 +useraccounts:core@1.16.2 +useraccounts:flow-routing@1.15.0 +useraccounts:unstyled@1.14.2 +webapp@1.13.0 webapp-hashing@1.1.0 wekan-markdown@1.0.9 zimme:active-route@2.3.2 diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index cc5237097..4470f72de 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -1,4 +1,5 @@ import DOMPurify from 'dompurify'; +import { TAPi18n } from '/imports/i18n'; const activitiesPerPage = 500; diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 81846f2ee..d52404a86 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + const subManager = new SubsManager(); const { calculateIndex } = Utils; const swimlaneWhileSortingHeight = 150; diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index ed7d9490e..fb8a5ed5c 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + /* const DOWNCLS = 'fa-sort-down'; const UPCLS = 'fa-sort-up'; diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index a5b1ccb51..37665b8b9 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + const subManager = new SubsManager(); Template.boardListHeaderBar.events({ @@ -33,7 +35,6 @@ BlazeComponent.extendComponent({ } if (userLanguage) { TAPi18n.setLanguage(userLanguage); - T9n.setLanguage(userLanguage); } }, diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js index 7686bbeb9..6dafd8e4d 100644 --- a/client/components/cards/cardCustomFields.js +++ b/client/components/cards/cardCustomFields.js @@ -1,3 +1,5 @@ +import moment from 'moment'; +import { TAPi18n } from '/imports/i18n'; import { DatePicker } from '/client/lib/datepicker'; import Cards from '/models/cards'; diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 685f79e3a..ca3af61c0 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -1,3 +1,5 @@ +import moment from 'moment'; +import { TAPi18n } from '/imports/i18n'; import { DatePicker } from '/client/lib/datepicker'; Template.dateBadge.helpers({ diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 110e64bf6..d749d9b2a 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1,3 +1,5 @@ +import moment from 'moment'; +import { TAPi18n } from '/imports/i18n'; import { DatePicker } from '/client/lib/datepicker'; import Cards from '/models/cards'; import Boards from '/models/boards'; @@ -7,7 +9,6 @@ import Users from '/models/users'; import Lists from '/models/lists'; import CardComments from '/models/cardComments'; import { ALLOWED_COLORS } from '/config/const'; -import moment from 'moment'; import { UserAvatar } from '../users/userAvatar'; const subManager = new SubsManager(); diff --git a/client/components/cards/cardTime.js b/client/components/cards/cardTime.js index 984f048e1..b9a24c0a4 100644 --- a/client/components/cards/cardTime.js +++ b/client/components/cards/cardTime.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + BlazeComponent.extendComponent({ template() { return 'editCardSpentTime'; diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js index 568b76108..cb64c1a37 100644 --- a/client/components/cards/checklists.js +++ b/client/components/cards/checklists.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import Cards from '/models/cards'; import Boards from '/models/boards'; diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index 6a2057c34..e1a642ee0 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + // Template.cards.events({ // 'click .member': Popup.open('cardMember') // }); diff --git a/client/components/lists/list.js b/client/components/lists/list.js index 0f953ae36..7d3483b1e 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; require('/client/lib/jquery-ui.js') const { calculateIndex } = Utils; diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 3a368c344..6cf1ebb4b 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import { Spinner } from '/client/lib/spinner'; const subManager = new SubsManager(); diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index 547ba1bc8..95d05e28e 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + let listsColors; Meteor.startup(() => { listsColors = Lists.simpleSchema()._schema.color.allowedValues; diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js index f74393917..3a8a84b52 100644 --- a/client/components/main/globalSearch.js +++ b/client/components/main/globalSearch.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import { CardSearchPagedComponent } from '../../lib/cardSearch'; import Boards from '../../../models/boards'; import { Query, QueryErrors } from '../../../config/query-classes'; diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js index f78791102..e9b153b92 100644 --- a/client/components/main/layouts.js +++ b/client/components/main/layouts.js @@ -1,10 +1,6 @@ -BlazeLayout.setRoot('body'); +import { TAPi18n } from '/imports/i18n'; -const i18nTagToT9n = i18nTag => { - // t9n/i18n tags are same now, see: https://github.com/softwarerero/meteor-accounts-t9n/pull/129 - // but we keep this conversion function here, to be aware that that they are different system. - return i18nTag; -}; +BlazeLayout.setRoot('body'); let alreadyCheck = 1; let isCheckDone = false; @@ -60,11 +56,6 @@ Template.userFormsLayout.onRendered(() => { AccountsTemplates.state.form.keys, validator, ); - - const i18nTag = navigator.language; - if (i18nTag) { - T9n.setLanguage(i18nTagToT9n(i18nTag)); - } EscapeActions.executeAll(); }); @@ -89,11 +80,11 @@ Template.userFormsLayout.helpers({ getLegalNoticeWithWritTraduction(){ let spanLegalNoticeElt = $("#legalNoticeSpan"); if(spanLegalNoticeElt != null && spanLegalNoticeElt != undefined){ - spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {}, T9n.getLanguage() || 'en')); + spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {})); } let atLinkLegalNoticeElt = $("#legalNoticeAtLink"); if(atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined){ - atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {}, T9n.getLanguage() || 'en')); + atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {})); } return true; }, @@ -111,112 +102,27 @@ Template.userFormsLayout.helpers({ }, languages() { - return _.map(TAPi18n.getLanguages(), (lang, code) => { - const tag = code; - let name = lang.name; - if (lang.name === 'br') { - name = 'Brezhoneg'; - } else if (lang.name === 'ar-EG') { - // ar-EG = Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo) - name = 'مَصرى'; - } else if (lang.name === 'de-CH') { - name = 'Deutsch (Schweiz)'; - } else if (lang.name === 'de-AT') { - name = 'Deutsch (Österreich)'; - } else if (lang.name === 'en-DE') { - name = 'English (Germany)'; - } else if (lang.name === 'et-EE') { - name = 'eesti keel (Eesti)'; - } else if (lang.name === 'fa-IR') { - // fa-IR = Persian (Iran) - name = 'فارسی/پارسی (ایران‎)'; - } else if (lang.name === 'fr-BE') { - name = 'Français (Belgique)'; - } else if (lang.name === 'fr-CA') { - name = 'Français (Canada)'; - } else if (lang.name === 'fr-CH') { - name = 'Français (Schweiz)'; - } else if (lang.name === 'gu-IN') { - // gu-IN = Gurajati (India) - name = 'ગુજરાતી'; - } else if (lang.name === 'hi-IN') { - // hi-IN = Hindi (India) - name = 'हिंदी (भारत)'; - } else if (lang.name === 'ig') { - name = 'Igbo'; - } else if (lang.name === 'lv') { - name = 'Latviešu'; - } else if (lang.name === 'latviešu valoda') { - name = 'Latviešu'; - } else if (lang.name === 'ms-MY') { - // ms-MY = Malay (Malaysia) - name = 'بهاس ملايو'; - } else if (lang.name === 'en-IT') { - name = 'English (Italy)'; - } else if (lang.name === 'el-GR') { - // el-GR = Greek (Greece) - name = 'Ελληνικά (Ελλάδα)'; - } else if (lang.name === 'Español') { - name = 'español'; - } else if (lang.name === 'es_419') { - name = 'español de América Latina'; - } else if (lang.name === 'es-419') { - name = 'español de América Latina'; - } else if (lang.name === 'Español de América Latina') { - name = 'español de América Latina'; - } else if (lang.name === 'es-LA') { - name = 'español de América Latina'; - } else if (lang.name === 'Español de Argentina') { - name = 'español de Argentina'; - } else if (lang.name === 'Español de Chile') { - name = 'español de Chile'; - } else if (lang.name === 'Español de Colombia') { - name = 'español de Colombia'; - } else if (lang.name === 'Español de México') { - name = 'español de México'; - } else if (lang.name === 'es-PY') { - name = 'español de Paraguayo'; - } else if (lang.name === 'Español de Paraguayo') { - name = 'español de Paraguayo'; - } else if (lang.name === 'Español de Perú') { - name = 'español de Perú'; - } else if (lang.name === 'Español de Puerto Rico') { - name = 'español de Puerto Rico'; - } else if (lang.name === 'gl-ES') { - name = 'Galego (España)'; - } else if (lang.name === 'oc') { - name = 'Occitan'; - } else if (lang.name === 'ru-UA') { - name = 'Русский (Украина)'; - } else if (lang.name === 'st') { - name = 'Sãotomense'; - } else if (lang.name === 'uk-UA') { - name = 'українська (Україна)'; - } else if (lang.name === '繁体中文(台湾)') { - // Traditional Chinese (Taiwan) - name = '繁體中文(台灣)'; - } - return { tag, name }; - }).sort(function(a, b) { - if (a.name === b.name) { - return 0; - } else { - return a.name > b.name ? 1 : -1; - } - }); + return TAPi18n.getSupportedLanguages() + .map(({ isoCode, name }) => ({ tag: isoCode, name })) + .sort((a, b) => { + if (a.name === b.name) { + return 0; + } else { + return a.name > b.name ? 1 : -1; + } + }); }, isCurrentLanguage() { - const t9nTag = i18nTagToT9n(this.tag); - const curLang = T9n.getLanguage() || 'en'; - return t9nTag === curLang; + const curLang = TAPi18n.getLanguage(); + return this.tag === curLang; }, }); Template.userFormsLayout.events({ 'change .js-userform-set-language'(event) { - const i18nTag = $(event.currentTarget).val(); - T9n.setLanguage(i18nTagToT9n(i18nTag)); + const tag = $(event.currentTarget).val(); + TAPi18n.setLanguage(tag); event.preventDefault(); }, 'click #at-btn'(event, templateInstance) { diff --git a/client/components/rules/triggers/cardTriggers.js b/client/components/rules/triggers/cardTriggers.js index 8d4ec52e3..fbe064a76 100644 --- a/client/components/rules/triggers/cardTriggers.js +++ b/client/components/rules/triggers/cardTriggers.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + BlazeComponent.extendComponent({ onCreated() { this.subscribe('allRules'); diff --git a/client/components/settings/adminReports.js b/client/components/settings/adminReports.js index 6dcbb0fc4..341eb4e39 100644 --- a/client/components/settings/adminReports.js +++ b/client/components/settings/adminReports.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import { AttachmentStorage } from '/models/attachments'; import { CardSearchPagedComponent } from '/client/lib/cardSearch'; import SessionData from '/models/usersessiondata'; diff --git a/client/components/settings/informationBody.js b/client/components/settings/informationBody.js index 4775bce67..6bab8b3ac 100644 --- a/client/components/settings/informationBody.js +++ b/client/components/settings/informationBody.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + BlazeComponent.extendComponent({ onCreated() { this.info = new ReactiveVar({}); diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js index 71a286fdb..8d0797966 100644 --- a/client/components/settings/settingBody.js +++ b/client/components/settings/settingBody.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import { ALLOWED_WAIT_SPINNERS } from '/config/const'; BlazeComponent.extendComponent({ diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index a1943a8f0..ae42a3f8d 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + Sidebar = null; const defaultView = 'home'; diff --git a/client/components/sidebar/sidebarArchives.js b/client/components/sidebar/sidebarArchives.js index 32a23b0d0..d535d0822 100644 --- a/client/components/sidebar/sidebarArchives.js +++ b/client/components/sidebar/sidebarArchives.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + //archivedRequested = false; const subManager = new SubsManager(); diff --git a/client/components/sidebar/sidebarCustomFields.js b/client/components/sidebar/sidebarCustomFields.js index 0a35b08e6..61b1cb2d8 100644 --- a/client/components/sidebar/sidebarCustomFields.js +++ b/client/components/sidebar/sidebarCustomFields.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + BlazeComponent.extendComponent({ customFields() { return CustomFields.find({ diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index 28729d6f0..92f6f37eb 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + Template.headerUserBar.events({ 'click .js-open-header-member-menu': Popup.open('memberMenu'), 'click .js-change-avatar': Popup.open('changeAvatar'), @@ -256,101 +258,15 @@ Template.changePasswordPopup.onRendered(function() { Template.changeLanguagePopup.helpers({ languages() { - return _.map(TAPi18n.getLanguages(), (lang, code) => { - // Same code in /client/components/main/layouts.js - // TODO : Make code reusable - const tag = code; - let name = lang.name; - if (lang.name === 'br') { - name = 'Brezhoneg'; - } else if (lang.name === 'ar-EG') { - // ar-EG = Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo) - name = 'مَصرى'; - } else if (lang.name === 'de-CH') { - name = 'Deutsch (Schweiz)'; - } else if (lang.name === 'de-AT') { - name = 'Deutsch (Österreich)'; - } else if (lang.name === 'en-DE') { - name = 'English (Germany)'; - } else if (lang.name === 'et-EE') { - name = 'eesti keel (Eesti)'; - } else if (lang.name === 'fa-IR') { - // fa-IR = Persian (Iran) - name = 'فارسی/پارسی (ایران‎)'; - } else if (lang.name === 'fr-BE') { - name = 'Français (Belgique)'; - } else if (lang.name === 'fr-CA') { - name = 'Français (Canada)'; - } else if (lang.name === 'fr-CH') { - name = 'Français (Schweiz)'; - } else if (lang.name === 'gu-IN') { - // gu-IN = Gurajati (India) - name = 'ગુજરાતી'; - } else if (lang.name === 'hi-IN') { - // hi-IN = Hindi (India) - name = 'हिंदी (भारत)'; - } else if (lang.name === 'ig') { - name = 'Igbo'; - } else if (lang.name === 'lv') { - name = 'Latviešu'; - } else if (lang.name === 'latviešu valoda') { - name = 'Latviešu'; - } else if (lang.name === 'ms-MY') { - // ms-MY = Malay (Malaysia) - name = 'بهاس ملايو'; - } else if (lang.name === 'en-IT') { - name = 'English (Italy)'; - } else if (lang.name === 'el-GR') { - // el-GR = Greek (Greece) - name = 'Ελληνικά (Ελλάδα)'; - } else if (lang.name === 'Español') { - name = 'español'; - } else if (lang.name === 'es_419') { - name = 'español de América Latina'; - } else if (lang.name === 'es-419') { - name = 'español de América Latina'; - } else if (lang.name === 'Español de América Latina') { - name = 'español de América Latina'; - } else if (lang.name === 'es-LA') { - name = 'español de América Latina'; - } else if (lang.name === 'Español de Argentina') { - name = 'español de Argentina'; - } else if (lang.name === 'Español de Chile') { - name = 'español de Chile'; - } else if (lang.name === 'Español de Colombia') { - name = 'español de Colombia'; - } else if (lang.name === 'Español de México') { - name = 'español de México'; - } else if (lang.name === 'es-PY') { - name = 'español de Paraguayo'; - } else if (lang.name === 'Español de Paraguayo') { - name = 'español de Paraguayo'; - } else if (lang.name === 'Español de Perú') { - name = 'español de Perú'; - } else if (lang.name === 'Español de Puerto Rico') { - name = 'español de Puerto Rico'; - } else if (lang.name === 'gl-ES') { - name = 'Galego (España)'; - } else if (lang.name === 'oc') { - name = 'Occitan'; - } else if (lang.name === 'ru-UA') { - name = 'Русский (Украина)'; - } else if (lang.name === 'st') { - name = 'Sãotomense'; - } else if (lang.name === 'uk-UA') { - name = 'українська (Україна)'; - } else if (lang.name === '繁体中文(台湾)') { - // Traditional Chinese (Taiwan) - name = '繁體中文(台灣)'; - } - return { tag, name }; - }).sort(function(a, b) { - if (a.name === b.name) { - return 0; - } else { - return a.name > b.name ? 1 : -1; - } - }); + return TAPi18n.getSupportedLanguages() + .map(({ isoCode, name }) => ({ tag: isoCode, name })) + .sort((a, b) => { + if (a.name === b.name) { + return 0; + } else { + return a.name > b.name ? 1 : -1; + } + }); }, isCurrentLanguage() { diff --git a/client/config/blazeHelpers.js b/client/config/blazeHelpers.js index 22194f3bd..715803ef6 100644 --- a/client/config/blazeHelpers.js +++ b/client/config/blazeHelpers.js @@ -1,3 +1,7 @@ +import { Blaze } from 'meteor/blaze'; +import { Session } from 'meteor/session'; +import moment from 'moment'; + Blaze.registerHelper('currentBoard', () => { const ret = Utils.getCurrentBoard(); return ret; @@ -30,3 +34,9 @@ Blaze.registerHelper('isShowDesktopDragHandles', () => Blaze.registerHelper('isMiniScreenOrShowDesktopDragHandles', () => Utils.isMiniScreenOrShowDesktopDragHandles(), ); + +Blaze.registerHelper('moment', (...args) => { + args.pop(); // hash + const [date, format] = args; + return moment(date).format(format); +}); diff --git a/client/lib/cardSearch.js b/client/lib/cardSearch.js index 7fe74492b..28db79a79 100644 --- a/client/lib/cardSearch.js +++ b/client/lib/cardSearch.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import Cards from '../../models/cards'; import SessionData from '../../models/usersessiondata'; import {QueryDebug} from "../../config/query-classes"; diff --git a/client/lib/datepicker.js b/client/lib/datepicker.js index f57bcdaa3..7138c366b 100644 --- a/client/lib/datepicker.js +++ b/client/lib/datepicker.js @@ -1,3 +1,6 @@ +import { TAPi18n } from '/imports/i18n'; +import moment from 'moment'; + // Helper function to replace HH with H for 24 hours format, because H allows also single-digit hours function adjustedTimeFormat() { return moment diff --git a/client/lib/filter.js b/client/lib/filter.js index 283dbcd07..d1d5669e9 100644 --- a/client/lib/filter.js +++ b/client/lib/filter.js @@ -1,3 +1,5 @@ +import moment from 'moment'; + // Filtered view manager // We define local filter objects for each different type of field (SetFilter, // RangeFilter, dateFilter, etc.). We then define a global `Filter` object whose @@ -721,7 +723,7 @@ Filter = { isFilterActive = true; selectors.push(this.advanced._getMongoSelector()); } - + if(isFilterActive) { return { $or: selectors, diff --git a/client/lib/i18n.js b/client/lib/i18n.js index dbce251e9..5d809c17b 100644 --- a/client/lib/i18n.js +++ b/client/lib/i18n.js @@ -1,27 +1,21 @@ +import { TAPi18n } from '/imports/i18n'; + // We save the user language preference in the user profile, and use that to set // the language reactively. If the user is not connected we use the language // information provided by the browser, and default to english. Meteor.startup(() => { - TAPi18n.conf.i18n_files_route = Meteor._relativeToSiteRootUrl('/tap-i18n'); const currentUser = Meteor.user(); - let language; - if (currentUser) { - language = currentUser.profile && currentUser.profile.language; - } - - if (!language) { - if (navigator.languages) { - language = navigator.languages[0]; - } else { - language = navigator.language || navigator.userLanguage; - } - } - + // Select first available language + const [language] = [ + // User profile + currentUser?.profile?.language, + // Browser locale + navigator.languages?.at(0), + navigator.language, + navigator.userLanguage, + ].filter(Boolean); if (language) { TAPi18n.setLanguage(language); - // eslint-disable-next-line no-console - // console.log('language set!'); - T9n.setLanguage(language); } }); diff --git a/client/lib/popup.js b/client/lib/popup.js index 01f34f92a..9eb72b662 100644 --- a/client/lib/popup.js +++ b/client/lib/popup.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + window.Popup = new (class { constructor() { // The template we use to render popups diff --git a/config/accounts.js b/config/accounts.js index a2046d0f1..7f6c22161 100644 --- a/config/accounts.js +++ b/config/accounts.js @@ -1,4 +1,5 @@ -/* +import { TAPi18n } from '/imports/i18n'; + const passwordField = AccountsTemplates.removeField('password'); const emailField = AccountsTemplates.removeField('email'); @@ -89,5 +90,3 @@ if (Meteor.isServer) { }; }); } - -*/ diff --git a/config/query-classes.js b/config/query-classes.js index a1abec3a1..be3b098f8 100644 --- a/config/query-classes.js +++ b/config/query-classes.js @@ -1,3 +1,5 @@ +import moment from 'moment'; +import { TAPi18n } from '/imports/i18n'; import { OPERATOR_ASSIGNEE, OPERATOR_BOARD, @@ -46,7 +48,6 @@ import { PREDICATE_YEAR, } from './search-const'; import Boards from '../models/boards'; -import moment from 'moment'; export class QueryDebug { predicate = null; diff --git a/config/router.js b/config/router.js index cc1d2c5ea..e5b8fd273 100644 --- a/config/router.js +++ b/config/router.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + let previousPath; FlowRouter.triggers.exit([ ({ path }) => { @@ -7,7 +9,7 @@ FlowRouter.triggers.exit([ FlowRouter.route('/', { name: 'home', - //triggersEnter: [qAccountsTemplates.ensureSignedIn], + triggersEnter: [AccountsTemplates.ensureSignedIn], action() { Session.set('currentBoard', null); Session.set('currentList', null); @@ -31,7 +33,7 @@ FlowRouter.route('/', { FlowRouter.route('/public', { name: 'public', - //triggersEnter: [AccountsTemplates.ensureSignedIn], + triggersEnter: [AccountsTemplates.ensureSignedIn], action() { Session.set('currentBoard', null); Session.set('currentList', null); @@ -150,7 +152,7 @@ FlowRouter.route('/b/templates', { FlowRouter.route('/my-cards', { name: 'my-cards', - //triggersEnter: [AccountsTemplates.ensureSignedIn], + triggersEnter: [AccountsTemplates.ensureSignedIn], action() { Filter.reset(); Session.set('sortBy', ''); @@ -170,7 +172,7 @@ FlowRouter.route('/my-cards', { FlowRouter.route('/due-cards', { name: 'due-cards', - //triggersEnter: [AccountsTemplates.ensureSignedIn], + triggersEnter: [AccountsTemplates.ensureSignedIn], action() { Filter.reset(); Session.set('sortBy', ''); @@ -190,7 +192,7 @@ FlowRouter.route('/due-cards', { FlowRouter.route('/global-search', { name: 'global-search', - //triggersEnter: [AccountsTemplates.ensureSignedIn], + triggersEnter: [AccountsTemplates.ensureSignedIn], action() { Filter.reset(); Session.set('sortBy', ''); @@ -236,7 +238,7 @@ FlowRouter.route('/broken-cards', { FlowRouter.route('/import/:source', { name: 'import', - //triggersEnter: [AccountsTemplates.ensureSignedIn], + triggersEnter: [AccountsTemplates.ensureSignedIn], action(params) { if (Session.get('currentBoard')) { Session.set('fromBoard', Session.get('currentBoard')); @@ -261,7 +263,7 @@ FlowRouter.route('/import/:source', { FlowRouter.route('/setting', { name: 'setting', triggersEnter: [ - //AccountsTemplates.ensureSignedIn, + AccountsTemplates.ensureSignedIn, () => { Session.set('currentBoard', null); Session.set('currentList', null); @@ -286,7 +288,7 @@ FlowRouter.route('/setting', { FlowRouter.route('/information', { name: 'information', triggersEnter: [ - //AccountsTemplates.ensureSignedIn, + AccountsTemplates.ensureSignedIn, () => { Session.set('currentBoard', null); Session.set('currentList', null); @@ -310,7 +312,7 @@ FlowRouter.route('/information', { FlowRouter.route('/people', { name: 'people', triggersEnter: [ - //AccountsTemplates.ensureSignedIn, + AccountsTemplates.ensureSignedIn, () => { Session.set('currentBoard', null); Session.set('currentList', null); @@ -334,7 +336,7 @@ FlowRouter.route('/people', { FlowRouter.route('/admin-reports', { name: 'admin-reports', triggersEnter: [ - //AccountsTemplates.ensureSignedIn, + AccountsTemplates.ensureSignedIn, () => { Session.set('currentBoard', null); Session.set('currentList', null); diff --git a/imports/i18n/accounts.js b/imports/i18n/accounts.js new file mode 100644 index 000000000..124331dd6 --- /dev/null +++ b/imports/i18n/accounts.js @@ -0,0 +1,63 @@ +// Load all useraccounts translations at once +import { Tracker } from 'meteor/tracker'; +import { T9n } from 'meteor-accounts-t9n'; +import { TAPi18n } from './tap'; + +T9n.setTracker({ Tracker }); + +T9n.map('ar', require('meteor-accounts-t9n/build/ar').ar); +T9n.map('ca', require('meteor-accounts-t9n/build/ca').ca); +T9n.map('cs', require('meteor-accounts-t9n/build/cs').cs); +T9n.map('da', require('meteor-accounts-t9n/build/da').da); +T9n.map('de', require('meteor-accounts-t9n/build/de').de); +T9n.map('el', require('meteor-accounts-t9n/build/el').el); +T9n.map('en', require('meteor-accounts-t9n/build/en').en); +T9n.map('es', require('meteor-accounts-t9n/build/es').es); +T9n.map('es-ES', require('meteor-accounts-t9n/build/es_ES').es_ES); +T9n.map('es-ES-formal', require('meteor-accounts-t9n/build/es_ES_formal').es_ES_formal); +T9n.map('es-formal', require('meteor-accounts-t9n/build/es_formal').es_formal); +T9n.map('et', require('meteor-accounts-t9n/build/et').et); +T9n.map('fa', require('meteor-accounts-t9n/build/fa').fa); +T9n.map('fi', require('meteor-accounts-t9n/build/fi').fi); +T9n.map('fr', require('meteor-accounts-t9n/build/fr').fr); +T9n.map('fr-CA', require('meteor-accounts-t9n/build/fr_CA').fr_CA); +T9n.map('he', require('meteor-accounts-t9n/build/he').he); +T9n.map('hr', require('meteor-accounts-t9n/build/hr').hr); +T9n.map('hu', require('meteor-accounts-t9n/build/hu').hu); +T9n.map('id', require('meteor-accounts-t9n/build/id').id); +T9n.map('it', require('meteor-accounts-t9n/build/it').it); +T9n.map('ja', require('meteor-accounts-t9n/build/ja').ja); +T9n.map('kh', require('meteor-accounts-t9n/build/kh').kh); +T9n.map('ko', require('meteor-accounts-t9n/build/ko').ko); +T9n.map('nl', require('meteor-accounts-t9n/build/nl').nl); +T9n.map('no-NB', require('meteor-accounts-t9n/build/no_NB').no_NB); +T9n.map('pl', require('meteor-accounts-t9n/build/pl').pl); +T9n.map('pt', require('meteor-accounts-t9n/build/pt').pt); +T9n.map('pt-PT', require('meteor-accounts-t9n/build/pt_PT').pt_PT); +T9n.map('ro', require('meteor-accounts-t9n/build/ro').ro); +T9n.map('ru', require('meteor-accounts-t9n/build/ru').ru); +T9n.map('sk', require('meteor-accounts-t9n/build/sk').sk); +T9n.map('sl', require('meteor-accounts-t9n/build/sl').sl); +T9n.map('sv', require('meteor-accounts-t9n/build/sv').sv); +T9n.map('th', require('meteor-accounts-t9n/build/th').th); +T9n.map('tr', require('meteor-accounts-t9n/build/tr').tr); +T9n.map('uk', require('meteor-accounts-t9n/build/uk').uk); +T9n.map('vi', require('meteor-accounts-t9n/build/vi').vi); +T9n.map('zh-CN', require('meteor-accounts-t9n/build/zh_CN').zh_CN); +T9n.map('zh-HK', require('meteor-accounts-t9n/build/zh_HK').zh_HK); +T9n.map('zh-TW', require('meteor-accounts-t9n/build/zh_TW').zh_TW); + +// Reactively adjust useraccounts:core translations +Tracker.autorun(() => { + const language = TAPi18n.getLanguage(); + try { + T9n.setLanguage(language); + } catch (err) { + // Try to extract & set the language part only (e.g. "en" instead of "en-UK") + try { + T9n.setLanguage(language.split('-')[0]); + } catch (err) { + console.error(err); + } + } +}); diff --git a/imports/i18n/blaze.js b/imports/i18n/blaze.js new file mode 100644 index 000000000..fd8e393ce --- /dev/null +++ b/imports/i18n/blaze.js @@ -0,0 +1,8 @@ +import { Blaze } from 'meteor/blaze'; +import { TAPi18n } from './tap'; + +Blaze.registerHelper('_', (...args) => { + const { hash } = args.pop(); + const [key] = args.splice(0, 1); + return TAPi18n.__(key, { ...hash, sprintf: args }); +}); diff --git a/i18n/ar-EG.i18n.json b/imports/i18n/data/ar-EG.i18n.json similarity index 100% rename from i18n/ar-EG.i18n.json rename to imports/i18n/data/ar-EG.i18n.json diff --git a/i18n/ar.i18n.json b/imports/i18n/data/ar.i18n.json similarity index 100% rename from i18n/ar.i18n.json rename to imports/i18n/data/ar.i18n.json diff --git a/i18n/bg.i18n.json b/imports/i18n/data/bg.i18n.json similarity index 100% rename from i18n/bg.i18n.json rename to imports/i18n/data/bg.i18n.json diff --git a/i18n/br.i18n.json b/imports/i18n/data/br.i18n.json similarity index 100% rename from i18n/br.i18n.json rename to imports/i18n/data/br.i18n.json diff --git a/i18n/ca.i18n.json b/imports/i18n/data/ca.i18n.json similarity index 100% rename from i18n/ca.i18n.json rename to imports/i18n/data/ca.i18n.json diff --git a/i18n/cs.i18n.json b/imports/i18n/data/cs.i18n.json similarity index 100% rename from i18n/cs.i18n.json rename to imports/i18n/data/cs.i18n.json diff --git a/i18n/da.i18n.json b/imports/i18n/data/da.i18n.json similarity index 100% rename from i18n/da.i18n.json rename to imports/i18n/data/da.i18n.json diff --git a/i18n/de-AT.i18n.json b/imports/i18n/data/de-AT.i18n.json similarity index 100% rename from i18n/de-AT.i18n.json rename to imports/i18n/data/de-AT.i18n.json diff --git a/i18n/de-CH.i18n.json b/imports/i18n/data/de-CH.i18n.json similarity index 100% rename from i18n/de-CH.i18n.json rename to imports/i18n/data/de-CH.i18n.json diff --git a/i18n/de.i18n.json b/imports/i18n/data/de.i18n.json similarity index 100% rename from i18n/de.i18n.json rename to imports/i18n/data/de.i18n.json diff --git a/i18n/el-GR.i18n.json b/imports/i18n/data/el-GR.i18n.json similarity index 100% rename from i18n/el-GR.i18n.json rename to imports/i18n/data/el-GR.i18n.json diff --git a/imports/i18n/data/el.i18n.json b/imports/i18n/data/el.i18n.json new file mode 100644 index 000000000..7ab19efaa --- /dev/null +++ b/imports/i18n/data/el.i18n.json @@ -0,0 +1,1148 @@ +{ + "accept": "Αποδοχή", + "act-activity-notify": "Ειδοποίηση δραστηριότητας", + "act-addAttachment": "προσετέθη το συνημμένο __attachment__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-deleteAttachment": "διεγράφη το συνημμένο __attachment__ από την κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addSubtask": "προστέθηκε η υποεργασία __subtask__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addLabel": "Προστέθηκε η ετικέτα __label__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addedLabel": "Προστέθηκε η ετικέτα __label__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-removeLabel": "Διεγράφη η ετικέτα __label__ από την κάρτα __card__ της λίστας __list__ της λωρίδας __swimlane__ του πίνακα __board__", + "act-removedLabel": "Διεγράφη η ετικέτα __label__ από την κάρτα __card__ της λίστας __list__ της λωρίδας __swimlane__ του πίνακα __board__", + "act-addChecklist": "προστέθηκε η λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addChecklistItem": "προστέθηκε το στοιχείο __checklistItem__ στη λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-removeChecklist": "διεγράφη η λίστα ελέγχου - checklist __checklist__ από την κάρτα __card__ της λίστας __list__ της λωρίδας __swimlane__ του πίνακα __board__", + "act-removeChecklistItem": "διεγράφη το στοιχείο __checklistItem__ από τη λίστα ελέγχου - checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__\nεπιλέχθηκε το στοιχείο __checklistItem__ της λίστας ελέγχου - checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-uncheckedItem": "αποεπιλέχθηκε το στοιχείο __checklistItem__ της λίστας ελέγχου - checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-completeChecklist": "ολοκληρώθηκε η λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-uncompleteChecklist": "σημάνθηκε ως ημιτελής η λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addComment": "προσέθεσε σχόλιο στην κάρτα __card__: __comment__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-editComment": "μετέβαλε σχόλιο στην κάρτα __card__: __comment__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-deleteComment": "διεγράφη σχόλιο στην κάρτα __card__: __comment__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-createBoard": "δημιουργήθηκε ο πίνακας __board__", + "act-createSwimlane": "δημιουργήθηκε η λωρίδα __swimlane__ στον πίνακα __board__", + "act-createCard": "δημιουργήθηκε η κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-createCustomField": "δημιουργήθηκε το προσαρμοσμένο πεδίο __customField__ στον πίνακα __board__", + "act-deleteCustomField": "διεγράφη το προσαρμοσμένο πεδίο __customField__ στον πίνακα __board__", + "act-setCustomField": "υπέστη επεξεργασία η τιμή του προσαρμοσμένου πεδίου __customField__: __customFieldValue__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-createList": "προστέθηκε η λίστα __list__ στον πίνακα __board__", + "act-addBoardMember": "προστέθηκε το μέλος __member__ στον πίνακα __board__", + "act-archivedBoard": "Ο πίνακας __board__ μεταφέρθηκε στο Αρχείο", + "act-archivedCard": "Η κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__ μεταφέρθηκε στο Αρχείο", + "act-archivedList": "Η λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__ μεταφέρθηκε στο Αρχείο", + "act-archivedSwimlane": "Η λωρίδα __swimlane__ στον πίνακα __board__ μεταφέρθηκε στο Αρχείο", + "act-importBoard": "Εισήχθη ο πίνακας __board__", + "act-importCard": "εισήχθη η κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-importList": "εισήχθη η λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-joinMember": "προστέθηκε το μέλος __member__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-moveCard": "μετακίνησε την κάρτα __card__ στον πίνακα __board__ από τη λίστα __oldList__ της λωρίδας __oldSwimlane__ στη λίστα __list__ στη λωρίδα __swimlane__", + "act-moveCardToOtherBoard": "μετακίνησε την κάρτα __card__ από τη λίστα __oldList__ της λωρίδας __oldSwimlane__ του πίνακα __oldBoard__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-removeBoardMember": "αφαιρέθηκε το μέλος __member__ από τον πίνακα __board__", + "act-restoredCard": "επαναφορά της κάρτας __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-unjoinMember": "διαγραφή του μέλους __member__ από την κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Ενέργειες", + "activities": "Δραστηριότητες", + "activity": "Δραστηριότητα", + "activity-added": "προστέθηκε %s στο %s", + "activity-archived": "%s μετακινήθηκε στο Αρχείο", + "activity-attached": "επισυνάφθηκε %s στο %s", + "activity-created": "δημιουργήθηκε %s", + "activity-customfield-created": "δημιούργησε το προσαρμοσμένο πεδίο %s", + "activity-excluded": "εξαιρέθηκε %s από %s", + "activity-imported": "εισήχθη %s στο %s από %s", + "activity-imported-board": "εισήχθη %s από %s", + "activity-joined": "joined %s", + "activity-moved": "μετακινήθηκε το %s από %s στο %s", + "activity-on": "στό %s", + "activity-removed": "διεγράφη %s από %s", + "activity-sent": "εστάλη %s στο %s", + "activity-unjoined": "unjoined %s", + "activity-subtask-added": "προστέθηκε υποεργασία στο %s", + "activity-checked-item": "επιλέχθηκε %s στη λίστα ελέγχου - checlist %s του %s", + "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checklist-added": "added checklist to %s", + "activity-checklist-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-item-added": "προστέθηκε ένα στοιχείο λίστας ελέγου στη '%s' στο %s", + "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "add": "Προσθήκη", + "activity-checked-item-card": "επιλέχθηκε το %s στη λίστα ελέγχου - checklist %s", + "activity-unchecked-item-card": "αποεπιλέχθηκε το %s στη λίστα ελέγχου - checklist %s", + "activity-checklist-completed-card": "ολοκληρώθηκε η λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "επεξεργασία σχολίου %s", + "activity-deleteComment": "διεγράφη το σχόλιο %s", + "activity-receivedDate": "η ημερομηνία λήψης άλλαξε σε %s από %s", + "activity-startDate": "η ημερομηνία έναρξης άλλαξε σε %s από %s", + "activity-dueDate": "υπέστη επεξεργασία η τιμή της προθεσμίας σε %s από %s", + "activity-endDate": "η ημερομηνία λήξης άλλαξε σε %s από %s", + "add-attachment": "Προσθήκη Συνημμένου", + "add-board": "Προσθήκη Πίνακα", + "add-template": "Add Template", + "add-card": "Προσθήκη Κάρτας", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Προσθήκη Λωρίδας", + "add-subtask": "Προσθήκη Υποεργασίας", + "add-checklist": "Προσθήκη Λίστας ελέγχου", + "add-checklist-item": "Προσθήκη ενός στοιχείου στη λίστα ελέγχου - checklist", + "convertChecklistItemToCardPopup-title": "Convert to Card", + "add-cover": "Add Cover", + "add-label": "Προσθήκη Ετικέτας", + "add-list": "Προσθήκη Λίστας", + "add-members": "Προσθήκη Μελών", + "added": "Προστέθηκε", + "addMemberPopup-title": "Μέλη", + "memberPopup-title": "Ρυθμίσεις Μελών", + "admin": "Διαχειριστής", + "admin-desc": "Μπορεί να δει, να επεξεργαστεί κάρτες, να διαγράψει μέλη και να μεταβάλει τις ρυθμίσεις του πίνακα.", + "admin-announcement": "Ανακοίνωση", + "admin-announcement-active": "Ενεργή Ανακοίνωση που είναι ορατή σε όλο το σύστημα", + "admin-announcement-title": "Ανακοίνωση από το Διαχειριστή Συστήματος", + "all-boards": "Όλοι οι πίνακες", + "and-n-other-card": "Και __count__ επιπλέον κάρτα", + "and-n-other-card_plural": "Και __count__ επιπλέον κάρτες", + "apply": "Εφαρμογή", + "app-is-offline": "Φορτώνει, παρακαλώ περιμένετε. Η ανανέωση της σελίδας θα προκαλέσει απώλεια δεδομένων. Αν η φόρτωση δεν επιτύχει, παρακαλούμε ελέγξτε ότι ο server δεν έχει σταματήσει.", + "app-try-reconnect": "Try to reconnect.", + "archive": "Μετακίνηση στο Αρχείο", + "archive-all": "Μετακίνηση Όλων στο Αρχείο", + "archive-board": "Μετακίνηση Πίνακα στο Αρχείο", + "archive-card": "Μετακίνηση Κάρτας στο Αρχείο", + "archive-list": "Μετακίνηση Λίστας στο Αρχείο", + "archive-swimlane": "Μετακίνηση της Λωρίδας στο Αρχείο", + "archive-selection": "Μετακίνηση επιλογής στο Αρχείο", + "archiveBoardPopup-title": "Να μετακινηθεί ο Πίνακας στο Αρχείο;", + "archived-items": "Αρχείο", + "archived-boards": "Πίνακες στο Αρχείο", + "restore-board": "Επαναφορά Πίνακα", + "no-archived-boards": "Δεν υπάρχουν Πίνακες στο Αρχείο.", + "archives": "Αρχείο", + "template": "Πρότυπο", + "templates": "Πρότυπα", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Ανάθεση μέλους", + "attached": "επισυνάφθηκε", + "attachment": "Συνημμένο", + "attachment-delete-pop": "Η διαγραφή του συνημμένου είναι μόνιμη. Δεν υπάρχει δυνατότητα επαναφοράς.", + "attachmentDeletePopup-title": "Διαγραφή Συνημμένου;", + "attachments": "Συνημμένα", + "auto-watch": "Αυτόματη παρακολούθηση των πινάκων από τη στιγμή που δημιουργούνται.", + "avatar-too-big": "Το avatar είναι πολύ μεγάλο (μέγιστο 520KB)", + "back": "Πίσω", + "board-change-color": "Αλλαγή χρώματος", + "board-nb-stars": "%s αστέρια", + "board-not-found": "Ο πίνακας δε βρέθηκε", + "board-private-info": "Αυτός ο πίνακας θα είναι κρυφός.", + "board-public-info": "Αυτός ο πίνακας θα είναι δημόσιος.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Αλλαγή Φόντου Πίνακα", + "boardChangeTitlePopup-title": "Μετονομασία Πίνακα", + "boardChangeVisibilityPopup-title": "Αλλαγή Ορατότητας", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Ρυθμίσεις Πίνακα", + "boardChangeViewPopup-title": "Προβολή Πίνακα", + "boards": "Πίνακες", + "board-view": "Προβολή Πίνακα", + "board-view-cal": "Ημερολόγιο", + "board-view-swimlanes": "Λωρίδες", + "board-view-collapse": "Σύμπτυξη", + "board-view-gantt": "Διάγραμμα Gantt", + "board-view-lists": "Λίστες", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Ακύρωση", + "card-archived": "Αυτή η κάρτα μετακινήθηκε στο Αρχείο.", + "board-archived": "Αυτός ο πίνακας μετακινήθηκε στο Αρχείο.", + "card-comments-title": "Αυτή η κάρτα έχει %s σχόλιο.", + "card-delete-notice": "Η Διαγραφή είναι μόνιμη. Θα χάσετε όλες τις ενέργειες που σχετίζονται με αυτή την κάρτα.", + "card-delete-pop": "Όλες οι ενέργειες θα αφαιρεθούν από τη ροή δραστηριοτήτων και δε θα μπορείτε να ξανανοίξετε την κάρτα. Δεν υπάρχει δυνατότητα επαναφοράς.", + "card-delete-suggest-archive": "Μπορείτε να μετακινήσετε μια κάρτα στο Αρχείο για να την αφαιρέσετε από τον πίνακα και να διατηρήσετε τη δραστηριότητα.", + "card-archive-pop": "Card will not be visible at this list after archiving card.", + "card-archive-suggest-cancel": "You can later restore card from Archive.", + "card-due": "Έως", + "card-due-on": "Έως τις", + "card-spent": "Δαπανηθείς Χρόνος", + "card-edit-attachments": "Επεξεργασία συνημμένων", + "card-edit-custom-fields": "Επεξεργασία προσαρμοσμένων πεδίων", + "card-edit-labels": "Επεξεργασία ετικετών", + "card-edit-members": "Επεξεργασία μελών", + "card-labels-title": "Αλλαγή ετικετών για την κάρτα.", + "card-members-title": "Προσθήκη ή διαγραφή μελών του πίνακα από την κάρτα.", + "card-start": "Έναρξη", + "card-start-on": "Αρχίζει σε", + "cardAttachmentsPopup-title": "Συνημμένο Από", + "cardCustomField-datePopup-title": "Αλλαγή ημερομηνίας", + "cardCustomFieldsPopup-title": "Επεξεργασία προσαρμοσμένων πεδίων", + "cardStartVotingPopup-title": "Έναρξη ψηφοφορίας", + "positiveVoteMembersPopup-title": "Υποστηρικτές", + "negativeVoteMembersPopup-title": "Αντιτιθέμενοι", + "card-edit-voting": "Επεξεργασία ψηφοφορίας", + "editVoteEndDatePopup-title": "Αλλαγή της ημερομηνίας λήξης ψηφοφορίας", + "allowNonBoardMembers": "Επίτρεψε όλους τους συνδεδεμένους χρήστες", + "vote-question": "Ερώτηση ψηφοφορίας", + "vote-public": "Εμφάνισε ποιός ψήφισε τι", + "vote-for-it": "για αυτό", + "vote-against": "εναντίον", + "deleteVotePopup-title": "Διαγραφή ψήφου;", + "vote-delete-pop": "Μόνιμη Διαγραφή. Θα χάσετε όλες τις ενέργειες που σχετίζονται με αυτή την ψήφο.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Διαγραφή Κάρτας;", + "cardArchivePopup-title": "Archive Card?", + "cardDetailsActionsPopup-title": "Ενέργειες κάρτας", + "cardLabelsPopup-title": "Ετικέτες", + "cardMembersPopup-title": "Μέλοι", + "cardMorePopup-title": "Περισσότερα", + "cardTemplatePopup-title": "Δημιουργία προτύπου", + "cards": "Κάρτες", + "cards-count": "Κάρτες", + "cards-count-one": "Κάρτα", + "casSignIn": "Σύνδεση χρήστη με CAS", + "cardType-card": "Κάρτα", + "cardType-linkedCard": "Συνδεδεμένη Κάρτα", + "cardType-linkedBoard": "Συνδεδεμένος Πίνακας", + "change": "Αλλαγή", + "change-avatar": "Αλλαγή Avatar", + "change-password": "Αλλαγή Κωδικού", + "change-permissions": "Αλλαγή δικαιωμάτων", + "change-settings": "Αλλαγή Ρυθμίσεων", + "changeAvatarPopup-title": "Αλλαγή Avatar", + "changeLanguagePopup-title": "Αλλαγή Γλώσσας", + "changePasswordPopup-title": "Αλλαγή Κωδικού", + "changePermissionsPopup-title": "Αλλαγή Δικαιωμάτων", + "changeSettingsPopup-title": "Αλλαγή Ρυθμίσεων", + "subtasks": "Υποεργασίες", + "checklists": "Λίστες Ελέγχου - Checklists", + "click-to-star": "Κλικ για να προσθέσετε αστεράκι στον πίνακα", + "click-to-unstar": "Κλικ για να αφαιρέσετε αστεράκι από τον πίνακα", + "clipboard": "Clipboard ή drag & drop", + "close": "Κλείσιμο", + "close-board": "Κλείσιμο Πίνακα", + "close-board-pop": "Μελλοντικά μπορείτε να επαναφέρετε τον πίνακα κάνοντας κλικ στο κουμπί \"Αρχειοθέτηση\" από την αρχική επικεφαλίδα.", + "close-card": "Close Card", + "color-black": "μαύρο", + "color-blue": "μπλε", + "color-crimson": "βυσσινί", + "color-darkgreen": "σκούρο πράσινο", + "color-gold": "χρυσό", + "color-gray": "γκρι", + "color-green": "πράσινο", + "color-indigo": "λουλάκι", + "color-lime": "λάιμ", + "color-magenta": "ματζέντα", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "πορτοκαλί", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "ροζ", + "color-plum": "plum", + "color-purple": "μωβ", + "color-red": "κόκκινο", + "color-saddlebrown": "saddlebrown", + "color-silver": "ασημί", + "color-sky": "ουρανός", + "color-slateblue": "slateblue", + "color-white": "λευκό", + "color-yellow": "κίτρινο", + "unset-color": "Unset", + "comment": "Σχόλιο", + "comment-placeholder": "Συγγραφή Σχολίου", + "comment-only": "Μόνο σχόλιο", + "comment-only-desc": "Μπορεί μόνο να σχολιάζει σε κάρτες.", + "comment-delete": "Are you sure you want to delete the comment?", + "deleteCommentPopup-title": "Delete comment?", + "no-comments": "Χωρίς σχόλια", + "no-comments-desc": "Δε μπορεί να δει σχόλια και δραστηριότητες.", + "worker": "Worker", + "worker-desc": "Μπορεί μόνο να μετακινεί κάρτες, να αναθέτει μια κάρτα στον εαυτό του και να σχολιάζει.", + "computer": "Υπολογιστής", + "confirm-subtask-delete-dialog": "Είστε σίγουροι ότι θέλετε να σβήσετε την υποεργασία;", + "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", + "checklistDeletePopup-title": "Delete Checklist?", + "copy-card-link-to-clipboard": "Αντιγραφή του συνδέσμου της κάρτας στο clipboard", + "copy-text-to-clipboard": "Copy text to clipboard", + "linkCardPopup-title": "Σύνδεση Κάρτας", + "searchElementPopup-title": "Αναζήτηση", + "copyCardPopup-title": "Αντιγραφή Κάρτας", + "copyChecklistToManyCardsPopup-title": "Αντιγραφή του Προτύπου Λίστας Ελέγχου - Checklist σε πολλές Κάρτες", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Τίτλος πρώτης κάρτας\", \"description\":\"Περιγραφή πρώτης κάρτας\"}, {\"title\":\"Τίτλος δεύτερης κάρτας\",\"description\":\"Περιγραφή δεύτερης κάρτας\"},{\"title\":\"Τίτλος τελευταίας κάρτας\",\"description\":\"Περιγραφή τελευταίας κάρτας\"} ]", + "create": "Δημιουργία", + "createBoardPopup-title": "Δημιουργία Πίνακα", + "chooseBoardSourcePopup-title": "Εισαγωγή πίνακα", + "createLabelPopup-title": "Δημιουργία Ετικέτας", + "createCustomField": "Δημιουργία Πεδίου", + "createCustomFieldPopup-title": "Δημιουργία Πεδίου", + "current": "τρέχων", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Νόμισμα", + "custom-field-currency-option": "Κωδικός Νομίσματος", + "custom-field-date": "Ημερομηνία", + "custom-field-dropdown": "Λίστα επιλογών", + "custom-field-dropdown-none": "(κανένα)", + "custom-field-dropdown-options": "Επιλογές Λίστας", + "custom-field-dropdown-options-placeholder": "Πιέστε enter για να προσθέσετε περισσότερες επιλογές", + "custom-field-dropdown-unknown": "(άγνωστο)", + "custom-field-number": "Αριθμός", + "custom-field-text": "Κείμενο", + "custom-fields": "Προσαρμοσμένα Πεδία", + "date": "Ημερομηνία", + "decline": "Απόρριψη", + "default-avatar": "Default avatar", + "delete": "Διαγραφή", + "deleteCustomFieldPopup-title": "Διαγραφή Προσαρμοσμένου Πεδίου;", + "deleteLabelPopup-title": "Διαγραφή Ετικέτας;", + "description": "Περιγραφή", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Απόρριψη", + "done": "Ολοκληρώθηκε", + "download": "Λήψη", + "edit": "Επεξεργασία", + "edit-avatar": "Αλλαγή Avatar", + "edit-profile": "Επεξεργασία Προφίλ", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Αλλαγή ημερομηνίας έναρξης", + "editCardDueDatePopup-title": "Αλλαγή ημερομηνίας λήξης προθεσμίας", + "editCustomFieldPopup-title": "Επεξεργασία Πεδίου", + "addReactionPopup-title": "Add reaction", + "editCardSpentTimePopup-title": "Αλλαγή δαπανηθέντος χρόνου", + "editLabelPopup-title": "Αλλαγή Ετικέτας", + "editNotificationPopup-title": "Επεξεργασία Ειδοποίησης", + "editProfilePopup-title": "Επεξεργασία Προφίλ", + "email": "Email", + "email-enrollAccount-subject": "Ένας λογαριασμός δημιουργήθηκε για εσάς στο __siteName__", + "email-enrollAccount-text": "Χαίρετε __user__,\n\nΓια να ξεκινήσετε να χρησιμοποιείτε αυτή την υπηρεσία, απλώς κάνετε κλικ στον παρακάτω σύνδεσμο.\n\n__url__\n\nΕυχαριστούμε.", + "email-fail": "Η αποστολή του email απέτυχε", + "email-fail-text": "Σφάλμα κατά την αποστολή του email", + "email-invalid": "Μη έγκυρο email", + "email-invite": "Πρόσκληση μέσω Email", + "email-invite-subject": "__inviter__ σας έστειλε μια πρόσκληση", + "email-invite-text": "Αγαπητέ/ή __user__,\n\n__inviter__ σας προσκαλεί να λάβετε μέρος στον πίνακα \"__board__\" για να συνεργαστείτε.\n\nΠαρακαλούμε επιλέξτε τον παρακάτω σύνδεσμο:\n\n__url__\n\nΕυχαριστούμε.", + "email-resetPassword-subject": "Επαναφορά του κωδικού σας για το __siteName__", + "email-resetPassword-text": "Χαίρετε __user__,\n\nΓια να αλλάξετε τον κωδικό πρόσβασής σας, κάνετε κλικ στον παρακάτω σύνδεσμο.\n\n__url__\n\nΕυχαριστούμε.", + "email-sent": "Εστάλη Email", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Χαίρετε __user__,\n\nΓια να επιβεβαιώσετε το email που χρησιμοποιεί ο λογαριασμός σας, απλώς κάνετε κλικ στον παρακάτω σύνδεσμο.\n\n__url__\n\nΕυχαριστούμε.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "Αυτός ο πίνακας δεν υφίσταται", + "error-board-notAdmin": "Πρέπει να είστε διαχειριστής του πίνακα αυτού για να προβείτε σε αυτό", + "error-board-notAMember": "Πρέπει να είστε μέλος του πίνακα αυτού για να προβείτε σε αυτό", + "error-json-malformed": "Το κείμενο δεν είναι έγκυρο JSON", + "error-json-schema": "Τα JSON δεδομένα σας δεν περιλαμβάνουν την ορθή πληροφορία στην ορθή μορφοποίηση", + "error-csv-schema": "Το CSV(Comma Separated Values)/TSV (Tab Separated Values) σας δεν περιλαμβάνει την ορθή πληροφορία σε ορθή μορφοποίηση", + "error-list-doesNotExist": "Η λίστα δεν υπάρχει", + "error-user-doesNotExist": "Ο χρήστης δεν υπάρχει", + "error-user-notAllowSelf": "Δε μπορείτε να αυτοπροσκληθείτε", + "error-user-notCreated": "Ο χρήστης δε δημιουργήθηκε", + "error-username-taken": "Το όνομα χρήστη είναι ήδη κατειλημμένο", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Το email είναι ήδη κατειλημμένο", + "export-board": "Εξαγωγή πίνακα", + "export-board-json": "Εξαγωγή πίνακα σε JSON", + "export-board-csv": "Εξαγωγή πίνακα σε CSV", + "export-board-tsv": "Εξαγωγή πίνακα σε TSV", + "export-board-excel": "Εξαγωγή πίνακα σε Excel", + "user-can-not-export-excel": "Ο χρήστης δε μπορεί να εξάγει σε Excel", + "export-board-html": "Εξαγωγή πίνακα σε HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Εξαγωγή πίνακα", + "exportCardPopup-title": "Export card", + "sort": "Ταξινόμηση", + "sorted": "Sorted", + "remove-sort": "Remove sort", + "sort-desc": "Κάντε κλικ για να ταξινομήστε τη λίστα", + "list-sort-by": "Ταξινόμηση λίστας βάσει:", + "list-label-modifiedAt": "Τελευταία Προσπέλαση", + "list-label-title": "Ονομασία Λίστας", + "list-label-sort": "Η καθορισμένη σας Ταξινόμηση", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Φίλτρο", + "filter-cards": "Φιλτράρετε Κάρτες ή Λίστες", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Φιλτράρετε Λίστα βάσει Τίτλου", + "filter-clear": "Καθαρισμός φίλτρου", + "filter-labels-label": "Φίλτρο βάσει ετικέτας", + "filter-no-label": "Καμμία ετικέτα", + "filter-member-label": "Φίλτρο βάσει μέλους", + "filter-no-member": "Κανένα μέλος", + "filter-assignee-label": "Φίλτρο βάσει ανάθεσης μέλους", + "filter-no-assignee": "Κανένας ανατεθείς", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "Κανένα Προσαρμοσμένο Πεδίο", + "filter-show-archive": "Προβολή αρχειοθετημένων λιστών", + "filter-hide-empty": "Απόκρυψη άδειων λιστών", + "filter-on": "Φίλτρο Ενεργό", + "filter-on-desc": "Έχετε ενεργοποιημένο το φίλτρο καρτών σε αυτόν τον πίνακα. Κάνετε κλικ εδώ για να αλλάξετε το φίλτρο.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Άλλα φίλτρα", + "advanced-filter-label": "Φιλτράρισμα για Προχωρημένους", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Πλήρες Όνομα", + "header-logo-title": "Επιστροφή στη σελίδα των πινάκων σας.", + "hide-system-messages": "Απόκρυψη μηνυμάτων συστήματος", + "headerBarCreateBoardPopup-title": "Δημιουργία Πίνακα", + "home": "Αρχική", + "import": "Εισαγωγή", + "impersonate-user": "Impersonate user", + "link": "Σύνδεσμος", + "import-board": "Εισαγωγή πίνακα", + "import-board-c": "Εισαγωγή πίνακα", + "import-board-title-trello": "Εισαγωγή πίνακα από το Trello", + "import-board-title-wekan": "Εισαγωγή πίνακα από προηγούμενη εξαγωγή", + "import-board-title-csv": "Εισαγωγή πίνακα από CSV/TSV", + "from-trello": "Από το Trello", + "from-wekan": "Aπό προηγούμενη εξαγωγή", + "from-csv": "Από CSV/TSV", + "import-board-instruction-trello": "Στον πίνακα του Trello, πλοηγηθείτε στο 'Menu', έπειτα 'More', 'Print and Export', 'Export JSON' και αντιγράψτε το παραχθέν κείμενο.", + "import-board-instruction-csv": "Επικολλήστε τις Τιμές Διαχωρισμένες με Κόμμα (CSV)/ Τιμές Διαχωρισμένες με Tab (TSV) .", + "import-board-instruction-wekan": "Στον πίνακά σας, πλοηγηθείτε στο 'Menu', έπειτα 'Export board' και αντιγράψετε το κείμενο στο ληφθένα αρχείο.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Επικολλήστε τα ορθά JSON δεδομένα σας εδώ", + "import-csv-placeholder": "Επικολλήστε τα ορθά CSV/TSV δεδομένα σας εδώ", + "import-map-members": "Αντιστοίχιση μελών", + "import-members-map": "Ο εισαχθείς πίνακας έχει κάποια μέλη. Παρακαλούμε αντιστοιχίστε τα μέλη που θέλετε να εισάγετε στους χρήστες σας", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Αναθεώρηση αντιστοίχισης μελών", + "import-user-select": "Επιλέξτε τον ήδη υπάρχοντα χρήστη που επιθυμείτε να χρησιμοποιήσετε ως αυτό το μέλος", + "importMapMembersAddPopup-title": "Επιλογή μέλους", + "info": "Έκδοση", + "initials": "Αρχικά", + "invalid-date": "Λανθασμένη ημερομηνία", + "invalid-time": "Λανθασμένη ώρα", + "invalid-user": "Λανθασμένος χρήστης", + "joined": "joined", + "just-invited": "Μόλις προσκληθήκατε σε αυτόν τον πίνακα", + "keyboard-shortcuts": "Συντομεύσεις πληκτρολογίου", + "label-create": "Δημιουργία Ετικέτας", + "label-default": "%s ετικέτα (προεπιλογή)", + "label-delete-pop": "Δεν υπάρχει δυνατότητα επαναφοράς. Θα διαγραφεί η ετικέτα από όλες τις κάρτες και θα καταστραφεί το ιστορικό της.", + "labels": "Ετικέτες", + "language": "Γλώσσα", + "last-admin-desc": "Δε μπορείτε να μεταβάλετε ρόλους επειδή πρέπει να υπάρχει τουλάχιστον ένας διαχειριστής.", + "leave-board": "Αποχώρηση από Πίνακα", + "leave-board-pop": "Είστε σίγουροι ότι θέλετε να αποχωρήσετε από το __boardTitle__? Θα αφαιρεθείτε από όλες τις κάρτες αυτού του πίνακα.", + "leaveBoardPopup-title": "Αποχωρείτε απο τον Πίνακα;", + "link-card": "Σύνδεσμος σε αυτή την κάρτα", + "list-archive-cards": "Μεταφορά όλων των καρτών αυτής της λίστας στο Αρχείο", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Μεταφορά όλων των καρτών αυτής της λίστας", + "list-select-cards": "Επιλογή όλων των καρτών αυτής της λίστας", + "set-color-list": "Ρύθμιση Χρώματος", + "listActionPopup-title": "Ενέργειες Λίστας", + "settingsUserPopup-title": "Ρυθμίσεις Χρήστη", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Ενέργειες λωρίδας", + "swimlaneAddPopup-title": "Προσθήκη μιας Λωρίδας παρακάτω", + "listImportCardPopup-title": "Εισαγωγή μιας κάρτας Trello", + "listImportCardsTsvPopup-title": "Εισαγωγή Excel CSV/TSV", + "listMorePopup-title": "Περισσότερα", + "link-list": "Σύνδεσμος σε αυτή τη λίστα", + "list-delete-pop": "Όλες οι ενέργειες θα αφαιρεθούν από τη ροή δραστηριοτήτων και δε θα μπορείτε να ανακτήσετε τη λίστα. Δεν υπάρχει δυνατότητα επαναφοράς.", + "list-delete-suggest-archive": "Μπορείτε να μετακινήσετε μια λίστα στο Αρχείο για να την αφαιρέσετε από τον πίνακα και να διατηρήσετε τη δραστηριότητα.", + "lists": "Λίστες", + "swimlanes": "Λωρίδες", + "log-out": "Αποσύνδεση", + "log-in": "Σύνδεση", + "loginPopup-title": "Σύνδεση", + "memberMenuPopup-title": "Ρυθμίσεις Μελών", + "members": "Μέλοι", + "menu": "Μενού", + "move-selection": "Μετακίνηση επιλογής", + "moveCardPopup-title": "Μετακίνηση Κάρτας", + "moveCardToBottom-title": "Μετακίνηση στην Αρχή", + "moveCardToTop-title": "Μετακίνηση στο Τέλος", + "moveSelectionPopup-title": "Μετακίνηση επιλογής", + "multi-selection": "Πολλαπλή Επιλογή", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Πολλαπλή Επιλογή ενεργοποιημένη", + "muted": "Σίγαση", + "muted-info": "Δεν πρόκειται να ενημερωθείτε ποτέ για οποιεσδήποτε αλλαγές σε αυτόν τον πίνακα", + "my-boards": "Οι Πίνακες μου", + "name": "Όνομα", + "no-archived-cards": "Δεν υπάρχουν κάρτες στο Αρχείο.", + "no-archived-lists": "Δεν υπάρχουν λίστες στο Αρχείο.", + "no-archived-swimlanes": "Δεν υπάρχουν λωρίδες στο Αρχείο.", + "no-results": "Κανένα αποτέλεσμα", + "normal": "Κανονικό", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Η πρόσκληση δεν έχει λάβει αποδοχή ακόμη", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "προεραιτικό", + "or": "ή", + "page-maybe-private": "This page may be private. You may be able to view it by logging in.", + "page-not-found": "Η σελίδα δεν βρέθηκε.", + "password": "Κωδικός", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Συμμετέχει", + "preview": "Προεπισκόπηση", + "previewAttachedImagePopup-title": "Προεπισκόπηση", + "previewClipboardImagePopup-title": "Προεπισκόπηση", + "private": "Private", + "private-desc": "Ο πίνακας αυτός είναι προσωπικός. Μόνο άτομα που έχουν προστεθεί σε αυτόν τον πίνακα μπορούν να τον δουν και να τον μεταβάλλουν.", + "profile": "Προφίλ", + "public": "Δημόσιο", + "public-desc": "Αυτός ο πίνακας είναι δημόσιος. Είναι ορατός σε οποιονδήποτε κατέχει το σύνδεσμο προς αυτόν και θα εμφανίζεται σε μηχανές αναζήτησης όπως η Google. Ο πίνακας μπορεί να μεταβληθεί μόνο από άτομα που έχουν προστεθεί σε αυτόν.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Αφαίρεση Σκεπάσματος", + "remove-from-board": "Αφαίρεση από Πίνακα", + "remove-label": "Αφαίρεση Ετικέτας", + "listDeletePopup-title": "Διαγραφή Λίστας;", + "remove-member": "Αφαίρεση Μέλους", + "remove-member-from-card": "Αφαίρεση από την Κάρτα", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Αφαίρεση Μέλους;", + "rename": "Μετανομασία", + "rename-board": "Μετονομασία Πίνακα", + "restore": "Επαναφορά", + "save": "Αποθήκευση", + "search": "Αναζήτηση", + "rules": "Κανόνες", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Επιλέξτε Χρώμα", + "select-board": "Επιλογή Πίνακα", + "set-wip-limit-value": "Προσδιορισμός ορίου στο μέγιστο αριθμό εργασιών σε αυτή τη λίστα.", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Καθαρισμός φίλτρων", + "shortcut-close-dialog": "Κλείσιμο Διαλόγου", + "shortcut-filter-my-cards": "Φιλτράρισμα των καρτών μου", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Άνοιγμα Πλευρικού Μενού", + "sidebar-close": "Κλείσιμο Πλευρικού Μενού", + "signupPopup-title": "Δημιουργία Λογαριασμού", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Εγγραφή", + "team": "Ομάδα", + "this-board": "αυτόν τον πίνακα", + "this-card": "αυτή η κάρτα", + "spent-time-hours": "Δαπανηθείς Χρόνος (ώρες)", + "overtime-hours": "Υπερωρία (ώρες)", + "overtime": "Υπερωρία", + "has-overtime-cards": "Έχει κάρτες με υπερωρία", + "has-spenttime-cards": "Έχει κάρτες με δαπανηθέντα χρόνο", + "time": "Ώρα", + "title": "Τίτλος", + "tracking": "Καταγραφή", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Τύπος", + "unassign-member": "Απο-ανάθεση μέλους", + "unsaved-description": "Έχετε μια μη αποθηκευμένη περιγραφή.", + "unwatch": "Unwatch", + "upload": "Μεταφόρτωση", + "upload-avatar": "Μεταφόρτωση ενός avatar", + "uploaded-avatar": "Μεταφόρτωσε ένα avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Όνομα Χρήστη", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Παρακολούθηση", + "watching": "Παρακολουθούν", + "watching-info": "Θα ενημερωθείτε για οποιαδήποτε αλλαγή σε αυτόν τον πίνακα", + "welcome-board": "Πίνακας Καλωσορίσματος", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Πρότυπα Καρτών", + "list-templates-swimlane": "Πρότυπα Λίστας", + "board-templates-swimlane": "Πρότυπα Πινάκων", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Ρυθμίσεις", + "people": "Άνθρωποι", + "registration": "Εγγραφή", + "disable-self-registration": "Απενεργοποίηση Αυτό-Εγγραφής", + "invite": "Πρόσκληση", + "invite-people": "Πρόσκάλεσε Ανθρώπους", + "to-boards": "Στον πίνακα(ες)", + "email-addresses": "Email Διευθύνσεις", + "smtp-host-description": "Η διεύθυνση του SMTP server που διαχειρίζεται τα emails σας.", + "smtp-port-description": "Η πόρτα που ο SMTP server χρησιμοποιεί για τα εξερχόμενα emails.", + "smtp-tls-description": "Ενεργοποίηση υποστήριξης TLS για το SMTP server", + "smtp-host": "Διακομιστής SMTP (host)", + "smtp-port": "Πόρτα SMTP (Port)", + "smtp-username": "Όνομα Χρήστη", + "smtp-password": "Κωδικός", + "smtp-tls": "TLS υποστήριξη", + "send-from": "Από", + "send-smtp-test": "Στείλε ένα δοκιμαστικό email στον εαυτό σου", + "invitation-code": "Κωδικός Πρόσκλησης", + "email-invite-register-subject": "__inviter__ σας έστειλε μια πρόσκληση", + "email-invite-register-text": "Αγαπητέ __user__,\n\n__inviter__ σας προσκαλεί για να συμμετάσχετε στον πίνακα kanban.\n\nΠαρακαλούμε πιέστε την παρακάτω διεύθυνση:\n__url__\n\nΟ κωδικός πρόσκλησής σας είναι: __icode__\n\nΕυχαριστούμε.", + "email-smtp-test-subject": "SMTP Δοκιμαστικό Email", + "email-smtp-test-text": "Στείλατε επιτυχώς ένα email", + "error-invitation-code-not-exist": "Ο κωδικός πρόσκλησης δεν υπάρχει", + "error-notAuthorized": "Δεν έχετε εξουσιοδότηση για να δείτε αυτή τη σελίδα.", + "webhook-title": "Όνομα Webhook", + "webhook-token": "Token (Προεραιτικό για Αυθεντικοποίηση)", + "outgoing-webhooks": "Εξερχόμενα Webhooks", + "bidirectional-webhooks": "Αμφίδρομα Webhooks", + "outgoingWebhooksPopup-title": "Εξερχόμενα Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Απενεργοποίηση αυτού του Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "Νέο Εξερχόμενο Webhook", + "no-name": "(Άγνωστο)", + "Node_version": "Έκδοση Node", + "Meteor_version": "Έκδοση Meteor", + "MongoDB_version": "Έκδοση MongoDB", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog ενεργοποιημένο", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "ημέρες", + "hours": "ώρες", + "minutes": "λεπτά", + "seconds": "δευτερόλεπτα", + "show-field-on-card": "Προβολή αυτού του πεδίου στην κάρτα", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "showSum-field-on-list": "Show sum of fields at top of list", + "yes": "Ναι", + "no": "Όχι", + "accounts": "Λογαριασμοί", + "accounts-allowEmailChange": "Επίτρεψε Αλλαγή Email", + "accounts-allowUserNameChange": "Επίτρεψε Αλλαγή Ονόματος χρήστη", + "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", + "tableVisibilityMode": "Boards visibility", + "createdAt": "Δημιουργήθηκε στις", + "modifiedAt": "Modified at", + "verified": "Επιβεβαιώθηκε", + "active": "Ενεργό", + "card-received": "Ελήφθη", + "card-received-on": "Ελήφθη στις", + "card-end": "Τέλος", + "card-end-on": "Λήγει στις", + "editCardReceivedDatePopup-title": "Αλλαγή ημερομηνίας λήψης", + "editCardEndDatePopup-title": "Αλλαγή ημερομηνίας λήξης", + "setCardColorPopup-title": "Ρύθμιση χρώματος", + "setCardActionsColorPopup-title": "Επιλέξτε ένα χρώμα", + "setSwimlaneColorPopup-title": "Επιλέξτε ένα χρώμα", + "setListColorPopup-title": "Επιλέξτε ένα χρώμα", + "assigned-by": "Ανατέθηκε Από", + "requested-by": "Αιτήθηκε Από", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Διαγραφή Πίνακα;", + "delete-board": "Διαγραφή Πίνακα", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Προεπιλογή", + "queue": "Ουρά αναμονής", + "subtask-settings": "Ρυθμίσεις υποεργασιών (subtasks)", + "card-settings": "Ρυθμίσεις Κάρτας", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Ρυθμίσεις Κάρτας", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Μητρική κάρτα", + "source-board": "Κάρτα προέλευσης", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "διεγράφη ετικέτα '%s' από %s", + "activity-delete-attach": "διεγράφη ένα συνημμένο από %s", + "activity-added-label-card": "προσετέθη ετικέτα '%s'", + "activity-removed-label-card": "διεγράφη ετικέτα '%s'", + "activity-delete-attach-card": "διεγραφη ένα συνημμένο", + "activity-set-customfield": "ανάθεση τιμής του custom πεδίου '%s' στο '%s' στο %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Κανόνας", + "r-add-trigger": "Add trigger", + "r-add-action": "Προσθήκη ενέργειας", + "r-board-rules": "Κανόνες πίνακα", + "r-add-rule": "Προσθήκη κανόνα", + "r-view-rule": "Προβολή κανόνα", + "r-delete-rule": "Διαγραφή κανόνα", + "r-new-rule-name": "Νέος τίτλος κανόνα", + "r-no-rules": "Κανένας κανόνας", + "r-trigger": "Trigger", + "r-action": "Ενέργεια", + "r-when-a-card": "Όταν μία κάρτα", + "r-is": "είναι", + "r-is-moved": "έχει μετακινηθεί", + "r-added-to": "Προσετέθη στο", + "r-removed-from": "Διεγράφη από", + "r-the-board": "ο πίνακας", + "r-list": "λίστα", + "list": "List", + "set-filter": "Καθορισμός Φίλτρου", + "r-moved-to": "Μετακινήθηκε σε", + "r-moved-from": "Μετακινήθηκε από", + "r-archived": "Μετακινήθηκε στο Αρχείο", + "r-unarchived": "Επαναφέρθηκε από το Αρχείο", + "r-a-card": "μία κάρτα", + "r-when-a-label-is": "Όταν μια ετικέτα είναι", + "r-when-the-label": "Όταν η ετικέτα", + "r-list-name": "όνομα λίστα", + "r-when-a-member": "Όταν ένα μέλος είναι", + "r-when-the-member": "Όταν το μέλος", + "r-name": "όνομα", + "r-when-a-attach": "Όταν ένα συνημμένο", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Μετακίνηση στο Αρχείο", + "r-unarchive": "Επαναφορά από το Αρχείο", + "r-card": "card", + "r-add": "Προσθήκη", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Μετακίνηση κάρτας στην αρχή της λίστας της", + "r-d-move-to-top-spec": "Μετακίνηση κάρτας στην αρχή της λίστας", + "r-d-move-to-bottom-gen": "Μετακίνηση κάρτας στο τέλος της λίστας της", + "r-d-move-to-bottom-spec": "Μετακίνηση κάρτας στο τέλος της λίστας", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Μετακίνηση κάρτας στο Αρχείο", + "r-d-unarchive": "Επαναφορά κάρτας από το Αρχείο", + "r-d-add-label": "Προσθήκη ετικέτας", + "r-d-remove-label": "Αφαίρεση ετικέτας", + "r-create-card": "Δημιουργία νέας κάρτας", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Επιλογή στοιχείου", + "r-d-uncheck-one": "Αποεπιλογή στοιχείου", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Προσθήκη λίστας ελέγχου", + "r-d-remove-checklist": "Διαγραφή λίστας ελέγχου", + "r-by": "by", + "r-add-checklist": "Προσθήκη λίστας ελέγχου", + "r-with-items": "με στοιχεία", + "r-items-list": "στοιχείο1,στοιχείο2,στοιχείο3", + "r-add-swimlane": "Προσθήκη λωρίδας", + "r-swimlane-name": "ονομασία λωρίδας", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "Όταν η κάρτα μετακινηθεί σε μια άλλη λίστα", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after start", + "add-custom-html-before-body-end": "Add Custom HTML before end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "oidc-button-text": "Customize the OIDC button text", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Κάρτα", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "org-name-not-found": "Organization '%s' not found.", + "team-name-not-found": "Team '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "λίστα", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "operator-debug": "debug", + "operator-org": "org", + "operator-team": "team", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "predicate-selector": "selector", + "predicate-projection": "projection", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "operator-debug-invalid": "%s is not a valid debug predicate", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-org": "`__operator_org__:<display name|short name>` - cards belonging to a board assigned to organization *<name>*", + "globalSearch-instructions-operator-team": "`__operator_team__:<display name|short name>` - cards belonging to a board assigned to team *<name>*", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Αριθμός", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "boardsReportTitle": "Boards Report", + "cardsReportTitle": "Cards Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it", + "subject": "Subject", + "details": "Details", + "carbon-copy": "Carbon Copy (Cc:)", + "ticket": "Ticket", + "tickets": "Tickets", + "ticket-number": "Ticket Number", + "open": "Open", + "pending": "Pending", + "closed": "Closed", + "resolved": "Resolved", + "cancelled": "Cancelled", + "history": "History", + "request": "Request", + "requests": "Requests", + "help-request": "Help Request", + "editCardSortOrderPopup-title": "Change Sorting", + "cardDetailsPopup-title": "Card Details", + "add-teams": "Add teams", + "add-teams-label": "Added teams are displayed below:", + "remove-team-from-table": "Are you sure you want to remove this team from the board ?", + "confirm-btn": "Confirm", + "remove-btn": "Remove", + "filter-card-title-label": "Filter by card title", + "invite-people-success": "Invitation to register sent with success", + "invite-people-error": "Error while sending invitation to register", + "can-invite-if-same-mailDomainName": "Email domain name", + "to-create-teams-contact-admin": "To create teams, please contact the administrator.", + "Node_heap_total_heap_size": "Node heap: total heap size", + "Node_heap_total_heap_size_executable": "Node heap: total heap size executable", + "Node_heap_total_physical_size": "Node heap: total physical size", + "Node_heap_total_available_size": "Node heap: total available size", + "Node_heap_used_heap_size": "Node heap: used heap size", + "Node_heap_heap_size_limit": "Node heap: heap size limit", + "Node_heap_malloced_memory": "Node heap: malloced memory", + "Node_heap_peak_malloced_memory": "Node heap: peak malloced memory", + "Node_heap_does_zap_garbage": "Node heap: does zap garbage", + "Node_heap_number_of_native_contexts": "Node heap: number of native contexts", + "Node_heap_number_of_detached_contexts": "Node heap: number of detached contexts", + "Node_memory_usage_rss": "Node memory usage: resident set size", + "Node_memory_usage_heap_total": "Node memory usage: total size of the allocated heap", + "Node_memory_usage_heap_used": "Node memory usage: actual memory used", + "Node_memory_usage_external": "Node memory usage: external", + "add-organizations": "Add organizations", + "add-organizations-label": "Added organizations are displayed below:", + "remove-organization-from-board": "Are you sure you want to remove this organization from this board ?", + "to-create-organizations-contact-admin": "To create organizations, please contact administrator.", + "custom-legal-notice-link-url": "Custom legal notice page URL", + "acceptance_of_our_legalNotice": "By continuing, you accept our", + "legalNotice": "legal notice", + "copied": "Copied!", + "checklistActionsPopup-title": "Checklist Actions", + "moveChecklist": "Move Checklist", + "moveChecklistPopup-title": "Move Checklist", + "newlineBecomesNewChecklistItem": "Newline becomes new checklist item", + "copyChecklist": "Copy Checklist", + "copyChecklistPopup-title": "Copy Checklist", + "card-show-lists": "Card Show Lists" +} \ No newline at end of file diff --git a/i18n/en-DE.i18n.json b/imports/i18n/data/en-DE.i18n.json similarity index 100% rename from i18n/en-DE.i18n.json rename to imports/i18n/data/en-DE.i18n.json diff --git a/i18n/en-GB.i18n.json b/imports/i18n/data/en-GB.i18n.json similarity index 100% rename from i18n/en-GB.i18n.json rename to imports/i18n/data/en-GB.i18n.json diff --git a/i18n/en-IT.i18n.json b/imports/i18n/data/en-IT.i18n.json similarity index 100% rename from i18n/en-IT.i18n.json rename to imports/i18n/data/en-IT.i18n.json diff --git a/i18n/en.i18n.json b/imports/i18n/data/en.i18n.json similarity index 100% rename from i18n/en.i18n.json rename to imports/i18n/data/en.i18n.json diff --git a/i18n/eo.i18n.json b/imports/i18n/data/eo.i18n.json similarity index 100% rename from i18n/eo.i18n.json rename to imports/i18n/data/eo.i18n.json diff --git a/i18n/es-AR.i18n.json b/imports/i18n/data/es-AR.i18n.json similarity index 100% rename from i18n/es-AR.i18n.json rename to imports/i18n/data/es-AR.i18n.json diff --git a/i18n/es-CL.i18n.json b/imports/i18n/data/es-CL.i18n.json similarity index 100% rename from i18n/es-CL.i18n.json rename to imports/i18n/data/es-CL.i18n.json diff --git a/i18n/es-LA.i18n.json b/imports/i18n/data/es-LA.i18n.json similarity index 100% rename from i18n/es-LA.i18n.json rename to imports/i18n/data/es-LA.i18n.json diff --git a/i18n/es-MX.i18n.json b/imports/i18n/data/es-MX.i18n.json similarity index 100% rename from i18n/es-MX.i18n.json rename to imports/i18n/data/es-MX.i18n.json diff --git a/i18n/es-PE.i18n.json b/imports/i18n/data/es-PE.i18n.json similarity index 100% rename from i18n/es-PE.i18n.json rename to imports/i18n/data/es-PE.i18n.json diff --git a/i18n/es-PY.i18n.json b/imports/i18n/data/es-PY.i18n.json similarity index 100% rename from i18n/es-PY.i18n.json rename to imports/i18n/data/es-PY.i18n.json diff --git a/i18n/es.i18n.json b/imports/i18n/data/es.i18n.json similarity index 100% rename from i18n/es.i18n.json rename to imports/i18n/data/es.i18n.json diff --git a/i18n/et-EE.i18n.json b/imports/i18n/data/et-EE.i18n.json similarity index 100% rename from i18n/et-EE.i18n.json rename to imports/i18n/data/et-EE.i18n.json diff --git a/i18n/eu.i18n.json b/imports/i18n/data/eu.i18n.json similarity index 100% rename from i18n/eu.i18n.json rename to imports/i18n/data/eu.i18n.json diff --git a/i18n/fa-IR.i18n.json b/imports/i18n/data/fa-IR.i18n.json similarity index 100% rename from i18n/fa-IR.i18n.json rename to imports/i18n/data/fa-IR.i18n.json diff --git a/i18n/fa.i18n.json b/imports/i18n/data/fa.i18n.json similarity index 100% rename from i18n/fa.i18n.json rename to imports/i18n/data/fa.i18n.json diff --git a/i18n/fi.i18n.json b/imports/i18n/data/fi.i18n.json similarity index 100% rename from i18n/fi.i18n.json rename to imports/i18n/data/fi.i18n.json diff --git a/i18n/fr-CH.i18n.json b/imports/i18n/data/fr-CH.i18n.json similarity index 100% rename from i18n/fr-CH.i18n.json rename to imports/i18n/data/fr-CH.i18n.json diff --git a/i18n/fr.i18n.json b/imports/i18n/data/fr.i18n.json similarity index 100% rename from i18n/fr.i18n.json rename to imports/i18n/data/fr.i18n.json diff --git a/i18n/gl-ES.i18n.json b/imports/i18n/data/gl-ES.i18n.json similarity index 100% rename from i18n/gl-ES.i18n.json rename to imports/i18n/data/gl-ES.i18n.json diff --git a/i18n/gl.i18n.json b/imports/i18n/data/gl.i18n.json similarity index 100% rename from i18n/gl.i18n.json rename to imports/i18n/data/gl.i18n.json diff --git a/i18n/gu-IN.i18n.json b/imports/i18n/data/gu-IN.i18n.json similarity index 100% rename from i18n/gu-IN.i18n.json rename to imports/i18n/data/gu-IN.i18n.json diff --git a/i18n/he.i18n.json b/imports/i18n/data/he.i18n.json similarity index 100% rename from i18n/he.i18n.json rename to imports/i18n/data/he.i18n.json diff --git a/i18n/hi-IN.i18n.json b/imports/i18n/data/hi-IN.i18n.json similarity index 100% rename from i18n/hi-IN.i18n.json rename to imports/i18n/data/hi-IN.i18n.json diff --git a/i18n/hi.i18n.json b/imports/i18n/data/hi.i18n.json similarity index 100% rename from i18n/hi.i18n.json rename to imports/i18n/data/hi.i18n.json diff --git a/i18n/hr.i18n.json b/imports/i18n/data/hr.i18n.json similarity index 100% rename from i18n/hr.i18n.json rename to imports/i18n/data/hr.i18n.json diff --git a/i18n/hu.i18n.json b/imports/i18n/data/hu.i18n.json similarity index 100% rename from i18n/hu.i18n.json rename to imports/i18n/data/hu.i18n.json diff --git a/i18n/hy.i18n.json b/imports/i18n/data/hy.i18n.json similarity index 100% rename from i18n/hy.i18n.json rename to imports/i18n/data/hy.i18n.json diff --git a/i18n/id.i18n.json b/imports/i18n/data/id.i18n.json similarity index 100% rename from i18n/id.i18n.json rename to imports/i18n/data/id.i18n.json diff --git a/i18n/ig.i18n.json b/imports/i18n/data/ig.i18n.json similarity index 100% rename from i18n/ig.i18n.json rename to imports/i18n/data/ig.i18n.json diff --git a/i18n/it.i18n.json b/imports/i18n/data/it.i18n.json similarity index 100% rename from i18n/it.i18n.json rename to imports/i18n/data/it.i18n.json diff --git a/i18n/ja.i18n.json b/imports/i18n/data/ja.i18n.json similarity index 100% rename from i18n/ja.i18n.json rename to imports/i18n/data/ja.i18n.json diff --git a/i18n/ka.i18n.json b/imports/i18n/data/ka.i18n.json similarity index 100% rename from i18n/ka.i18n.json rename to imports/i18n/data/ka.i18n.json diff --git a/i18n/km.i18n.json b/imports/i18n/data/km.i18n.json similarity index 100% rename from i18n/km.i18n.json rename to imports/i18n/data/km.i18n.json diff --git a/i18n/ko.i18n.json b/imports/i18n/data/ko.i18n.json similarity index 100% rename from i18n/ko.i18n.json rename to imports/i18n/data/ko.i18n.json diff --git a/i18n/lt.i18n.json b/imports/i18n/data/lt.i18n.json similarity index 100% rename from i18n/lt.i18n.json rename to imports/i18n/data/lt.i18n.json diff --git a/i18n/lv.i18n.json b/imports/i18n/data/lv.i18n.json similarity index 100% rename from i18n/lv.i18n.json rename to imports/i18n/data/lv.i18n.json diff --git a/i18n/mk.i18n.json b/imports/i18n/data/mk.i18n.json similarity index 100% rename from i18n/mk.i18n.json rename to imports/i18n/data/mk.i18n.json diff --git a/i18n/mn.i18n.json b/imports/i18n/data/mn.i18n.json similarity index 100% rename from i18n/mn.i18n.json rename to imports/i18n/data/mn.i18n.json diff --git a/i18n/ms-MY.i18n.json b/imports/i18n/data/ms-MY.i18n.json similarity index 100% rename from i18n/ms-MY.i18n.json rename to imports/i18n/data/ms-MY.i18n.json diff --git a/i18n/nb.i18n.json b/imports/i18n/data/nb.i18n.json similarity index 100% rename from i18n/nb.i18n.json rename to imports/i18n/data/nb.i18n.json diff --git a/i18n/nl.i18n.json b/imports/i18n/data/nl.i18n.json similarity index 100% rename from i18n/nl.i18n.json rename to imports/i18n/data/nl.i18n.json diff --git a/i18n/oc.i18n.json b/imports/i18n/data/oc.i18n.json similarity index 100% rename from i18n/oc.i18n.json rename to imports/i18n/data/oc.i18n.json diff --git a/i18n/pa.i18n.json b/imports/i18n/data/pa.i18n.json similarity index 100% rename from i18n/pa.i18n.json rename to imports/i18n/data/pa.i18n.json diff --git a/i18n/pl.i18n.json b/imports/i18n/data/pl.i18n.json similarity index 100% rename from i18n/pl.i18n.json rename to imports/i18n/data/pl.i18n.json diff --git a/i18n/pt-BR.i18n.json b/imports/i18n/data/pt-BR.i18n.json similarity index 100% rename from i18n/pt-BR.i18n.json rename to imports/i18n/data/pt-BR.i18n.json diff --git a/i18n/pt.i18n.json b/imports/i18n/data/pt.i18n.json similarity index 100% rename from i18n/pt.i18n.json rename to imports/i18n/data/pt.i18n.json diff --git a/i18n/ro.i18n.json b/imports/i18n/data/ro.i18n.json similarity index 100% rename from i18n/ro.i18n.json rename to imports/i18n/data/ro.i18n.json diff --git a/i18n/ru-UA.i18n.json b/imports/i18n/data/ru-UA.i18n.json similarity index 100% rename from i18n/ru-UA.i18n.json rename to imports/i18n/data/ru-UA.i18n.json diff --git a/i18n/ru.i18n.json b/imports/i18n/data/ru.i18n.json similarity index 100% rename from i18n/ru.i18n.json rename to imports/i18n/data/ru.i18n.json diff --git a/i18n/sk.i18n.json b/imports/i18n/data/sk.i18n.json similarity index 100% rename from i18n/sk.i18n.json rename to imports/i18n/data/sk.i18n.json diff --git a/i18n/sl.i18n.json b/imports/i18n/data/sl.i18n.json similarity index 100% rename from i18n/sl.i18n.json rename to imports/i18n/data/sl.i18n.json diff --git a/i18n/sr.i18n.json b/imports/i18n/data/sr.i18n.json similarity index 100% rename from i18n/sr.i18n.json rename to imports/i18n/data/sr.i18n.json diff --git a/i18n/sv.i18n.json b/imports/i18n/data/sv.i18n.json similarity index 100% rename from i18n/sv.i18n.json rename to imports/i18n/data/sv.i18n.json diff --git a/i18n/sw.i18n.json b/imports/i18n/data/sw.i18n.json similarity index 100% rename from i18n/sw.i18n.json rename to imports/i18n/data/sw.i18n.json diff --git a/i18n/ta.i18n.json b/imports/i18n/data/ta.i18n.json similarity index 100% rename from i18n/ta.i18n.json rename to imports/i18n/data/ta.i18n.json diff --git a/i18n/th.i18n.json b/imports/i18n/data/th.i18n.json similarity index 100% rename from i18n/th.i18n.json rename to imports/i18n/data/th.i18n.json diff --git a/i18n/tr.i18n.json b/imports/i18n/data/tr.i18n.json similarity index 100% rename from i18n/tr.i18n.json rename to imports/i18n/data/tr.i18n.json diff --git a/i18n/uk-UA.i18n.json b/imports/i18n/data/ua-UA.i18n.json similarity index 100% rename from i18n/uk-UA.i18n.json rename to imports/i18n/data/ua-UA.i18n.json diff --git a/i18n/uk.i18n.json b/imports/i18n/data/ua.i18n.json similarity index 100% rename from i18n/uk.i18n.json rename to imports/i18n/data/ua.i18n.json diff --git a/i18n/vi.i18n.json b/imports/i18n/data/vi.i18n.json similarity index 100% rename from i18n/vi.i18n.json rename to imports/i18n/data/vi.i18n.json diff --git a/i18n/zh-CN.i18n.json b/imports/i18n/data/zh-CN.i18n.json similarity index 100% rename from i18n/zh-CN.i18n.json rename to imports/i18n/data/zh-CN.i18n.json diff --git a/i18n/zh-HK.i18n.json b/imports/i18n/data/zh-HK.i18n.json similarity index 100% rename from i18n/zh-HK.i18n.json rename to imports/i18n/data/zh-HK.i18n.json diff --git a/i18n/zh-TW.i18n.json b/imports/i18n/data/zh-TW.i18n.json similarity index 100% rename from i18n/zh-TW.i18n.json rename to imports/i18n/data/zh-TW.i18n.json diff --git a/imports/i18n/index.js b/imports/i18n/index.js new file mode 100644 index 000000000..cc42e80aa --- /dev/null +++ b/imports/i18n/index.js @@ -0,0 +1,14 @@ +import { TAPi18n } from './tap'; +import './accounts'; +import './moment'; + +if (Meteor.isClient) { + import './blaze'; +} + +export { TAPi18n }; + +(async () => { + await TAPi18n.init(); +})(); + diff --git a/imports/i18n/languages.js b/imports/i18n/languages.js new file mode 100644 index 000000000..48a421de6 --- /dev/null +++ b/imports/i18n/languages.js @@ -0,0 +1,470 @@ +export default { + "ar-EG": { + code: "ar", + isoCode: "ar-EG", + name: "مَصرى", + load: () => import('./data/ar-EG.i18n.json'), + }, + "ar": { + code: "ar", + isoCode: "ar", + name: "ar", + load: () => import('./data/ar.i18n.json'), + }, + "bg": { + code: "bg", + isoCode: "bg", + name: "bg", + load: () => import('./data/bg.i18n.json'), + }, + "br": { + code: "br", + isoCode: "br", + name: "Brezhoneg", + load: () => import('./data/xxx.i18n.json'), + }, + "ca": { + code: "ca", + isoCode: "ca", + name: "ca", + load: () => import('./data/ca.i18n.json'), + }, + "cs": { + code: "cs", + isoCode: "cs", + name: "cs", + load: () => import('./data/cs.i18n.json'), + }, + "da": { + code: "da", + isoCode: "da", + name: "da", + load: () => import('./data/da.i18n.json'), + }, + "de-AT": { + code: "de", + isoCode: "de-AT", + name: "Deutsch (Österreich)", + load: () => import('./data/de-AT.i18n.json'), + }, + "de-CH": { + code: "de", + isoCode: "de-CH", + name: "Deutsch (Schweiz)", + load: () => import('./data/de-CH.i18n.json'), + }, + "de": { + code: "de", + isoCode: "de", + name: "Deutsch", + load: () => import('./data/de.i18n.json'), + }, + "el-GR": { + code: "el", + isoCode: "el-GR", + name: "Ελληνικά (Ελλάδα)", + load: () => import('./data/el-GR.i18n.json'), + }, + "el": { + code: "el", + isoCode: "el", + name: "el", + load: () => import('./data/el.i18n.json'), + }, + "en-DE": { + code: "en", + isoCode: "en-DE", + name: "English (Germany)", + load: () => import('./data/en-DE.i18n.json'), + }, + "en-GB": { + code: "en", + isoCode: "en-GB", + name: "English (Great Britain)", + load: () => import('./data/en-GB.i18n.json'), + }, + "en-IT": { + code: "en", + isoCode: "en-IT", + name: "English (Italy)", + load: () => import('./data/en-IT.i18n.json'), + }, + "en": { + code: "en", + isoCode: "en", + name: "English", + load: () => import('./data/en.i18n.json'), + }, + "eo": { + code: "eo", + isoCode: "eo", + name: "eo", + load: () => import('./data/eo.i18n.json'), + }, + "es-AR": { + code: "es", + isoCode: "es-AR", + name: "español de Argentina", + load: () => import('./data/es-AR.i18n.json'), + }, + "es-CL": { + code: "es", + isoCode: "es-CL", + name: "español de Chile", + load: () => import('./data/es-CL.i18n.json'), + }, + "es-LA": { + code: "es", + isoCode: "es-LA", + name: "español de América Latina", + load: () => import('./data/es-LA.i18n.json'), + }, + "es-MX": { + code: "es", + isoCode: "es-MX", + name: "español de México", + load: () => import('./data/es-MX.i18n.json'), + }, + "es-PE": { + code: "es", + isoCode: "es-PE", + name: "español de Perú", + load: () => import('./data/es-PE.i18n.json'), + }, + "es-PY": { + code: "es", + isoCode: "es-PY", + name: "español de Paraguayo", + load: () => import('./data/es-PY.i18n.json'), + }, + "es": { + code: "es", + isoCode: "es", + name: "español", + load: () => import('./data/es.i18n.json'), + }, + "et-EE": { + code: "et", + isoCode: "et-EE", + name: "eesti keel (Eesti)", + load: () => import('./data/et-EE.i18n.json'), + }, + "eu": { + code: "eu", + isoCode: "eu", + name: "eu", + load: () => import('./data/eu.i18n.json'), + }, + "fa-IR": { + code: "fa", + isoCode: "fa-IR", + name: "فارسی/پارسی (ایران\u200e)", + load: () => import('./data/fa-IR.i18n.json'), + }, + "fa": { + code: "fa", + isoCode: "fa", + name: "fa", + load: () => import('./data/fa.i18n.json'), + }, + "fi-FI": { + code: "fi", + isoCode: "fi-FI", + name: "Suomi", + load: () => import('./data/fi-FI.i18n.json'), + }, + "fr-BE": { + code: "fr", + isoCode: "fr-BE", + name: "Français (Belgique)", + load: () => import('./data/fr-BE.i18n.json'), + }, + "fr-CA": { + code: "fr", + isoCode: "fr-CA", + name: "Français (Canada)", + load: () => import('./data/fr-CA.i18n.json'), + }, + "fr-CH": { + code: "fr", + isoCode: "fr-CH", + name: "Français (Schweiz)", + load: () => import('./data/fr-CH.i18n.json'), + }, + "fr": { + code: "fr", + isoCode: "fr", + name: "Français", + load: () => import('./data/fr.i18n.json'), + }, + "gl-ES": { + code: "gl", + isoCode: "gl-ES", + name: "Galego (España)", + load: () => import('./data/gl-ES.i18n.json'), + }, + "gl": { + code: "gl", + isoCode: "gl", + name: "Galego", + load: () => import('./data/gl.i18n.json'), + }, + "gu-IN": { + code: "gu", + isoCode: "gu-IN", + name: "ગુજરાતી", + load: () => import('./data/gu-IN.i18n.json'), + }, + "he": { + code: "he", + isoCode: "he", + name: "he", + load: () => import('./data/he.i18n.json'), + }, + "hi-IN": { + code: "hi", + isoCode: "hi-IN", + name: "हिंदी (भारत)", + load: () => import('./data/hi-IN.i18n.json'), + }, + "hi": { + code: "hi", + isoCode: "hi", + name: "hi", + load: () => import('./data/hi.i18n.json'), + }, + "hr": { + code: "hr", + isoCode: "hr", + name: "hr", + load: () => import('./data/hr.i18n.json'), + }, + "hu": { + code: "hu", + isoCode: "hu", + name: "hu", + load: () => import('./data/hu.i18n.json'), + }, + "hy": { + code: "hy", + isoCode: "hy", + name: "hy", + load: () => import('./data/hy.i18n.json'), + }, + "id": { + code: "id", + isoCode: "id", + name: "id", + load: () => import('./data/id.i18n.json'), + }, + "ig": { + code: "ig", + isoCode: "ig", + name: "Igbo", + load: () => import('./data/ig.i18n.json'), + }, + "it": { + code: "it", + isoCode: "it", + name: "it", + load: () => import('./data/it.i18n.json'), + }, + "ja": { + code: "ja", + isoCode: "ja", + name: "ja", + load: () => import('./data/ja.i18n.json'), + }, + "ka": { + code: "ka", + isoCode: "ka", + name: "ka", + load: () => import('./data/ka.i18n.json'), + }, + "km": { + code: "km", + isoCode: "km", + name: "km", + load: () => import('./data/km.i18n.json'), + }, + "ko": { + code: "ko", + isoCode: "ko", + name: "ko", + load: () => import('./data/ko.i18n.json'), + }, + "lt": { + code: "lt", + isoCode: "lt", + name: "lt", + load: () => import('./data/lt.i18n.json'), + }, + "lv": { + code: "lv", + isoCode: "lv", + name: "Latviešu", + load: () => import('./data/lv.i18n.json'), + }, + "mk": { + code: "mk", + isoCode: "mk", + name: "mk", + load: () => import('./data/mk.i18n.json'), + }, + "mn": { + code: "mn", + isoCode: "mn", + name: "mn", + load: () => import('./data/mn.i18n.json'), + }, + "ms-MY": { + code: "ms", + isoCode: "ms-MY", + name: "بهاس ملايو", + load: () => import('./data/ms-MY.i18n.json'), + }, + "nb": { + code: "nb", + isoCode: "nb", + name: "nb", + load: () => import('./data/nb.i18n.json'), + }, + "nl": { + code: "nl", + isoCode: "nl", + name: "nl", + load: () => import('./data/nl.i18n.json'), + }, + "oc": { + code: "oc", + isoCode: "oc", + name: "Occitan", + load: () => import('./data/oc.i18n.json'), + }, + "pa": { + code: "pa", + isoCode: "pa", + name: "pa", + load: () => import('./data/pa.i18n.json'), + }, + "pl": { + code: "pl", + isoCode: "pl", + name: "pl", + load: () => import('./data/pl.i18n.json'), + }, + "pt-BR": { + code: "pt", + isoCode: "pt-BR", + name: "pt-BR", + load: () => import('./data/pt-BR.i18n.json'), + }, + "pt": { + code: "pt", + isoCode: "pt", + name: "pt", + load: () => import('./data/pt.i18n.json'), + }, + "ro": { + code: "ro", + isoCode: "ro", + name: "ro", + load: () => import('./data/ro.i18n.json'), + }, + "ru-UA": { + code: "ru", + isoCode: "ru-UA", + name: "Русский (Украина)", + load: () => import('./data/ru-UA.i18n.json'), + }, + "ru": { + code: "ru", + isoCode: "ru", + name: "Русский", + load: () => import('./data/ru.i18n.json'), + }, + "sk": { + code: "sk", + isoCode: "sk", + name: "sk", + load: () => import('./data/sk.i18n.json'), + }, + "sl": { + code: "sl", + isoCode: "sl", + name: "sl", + load: () => import('./data/sl.i18n.json'), + }, + "sr": { + code: "sr", + isoCode: "sr", + name: "sr", + load: () => import('./data/sr.i18n.json'), + }, + "sv": { + code: "sv", + isoCode: "sv", + name: "sv", + load: () => import('./data/sv.i18n.json'), + }, + "sw": { + code: "sw", + isoCode: "sw", + name: "sw", + load: () => import('./data/sw.i18n.json'), + }, + "ta": { + code: "ta", + isoCode: "ta", + name: "ta", + load: () => import('./data/ta.i18n.json'), + }, + "th": { + code: "th", + isoCode: "th", + name: "th", + load: () => import('./data/th.i18n.json'), + }, + "tr": { + code: "tr", + isoCode: "tr", + name: "tr", + load: () => import('./data/tr.i18n.json'), + }, + "ua-UA": { + code: "ua", + isoCode: "ua-UA", + name: "українська (Україна)", + load: () => import('./data/ua-UA.i18n.json'), + }, + "ua": { + code: "ua", + isoCode: "ua", + name: "українська (Україна)", + load: () => import('./data/ua.i18n.json'), + }, + "vi": { + code: "vi", + isoCode: "vi", + name: "vi", + load: () => import('./data/vi.i18n.json'), + }, + "zh-CN": { + code: "zh", + isoCode: "zh-CN", + name: "zh-CN", + load: () => import('./data/zh-CN.i18n.json'), + }, + "zh-HK": { + code: "zh", + isoCode: "zh-HK", + name: "zh-HK", + load: () => import('./data/zh-HK.i18n.json'), + }, + "zh-TW": { + code: "zh", + isoCode: "zh-TW", + name: "繁體中文(台灣)", + load: () => import('./data/zh-TW.i18n.json'), + } +}; diff --git a/imports/i18n/moment.js b/imports/i18n/moment.js new file mode 100644 index 000000000..998e45f86 --- /dev/null +++ b/imports/i18n/moment.js @@ -0,0 +1,13 @@ +import { Tracker } from 'meteor/tracker'; +import moment from 'moment/min/moment-with-locales'; +import { TAPi18n } from './tap'; + +// Reactively adjust Moment.js translations +Tracker.autorun(() => { + const language = TAPi18n.getLanguage(); + try { + moment.locale(language); + } catch (err) { + console.error(err); + } +}); diff --git a/imports/i18n/tap.js b/imports/i18n/tap.js new file mode 100644 index 000000000..4fa632a84 --- /dev/null +++ b/imports/i18n/tap.js @@ -0,0 +1,60 @@ +import { Meteor } from 'meteor/meteor'; +import { ReactiveVar } from 'meteor/reactive-var'; +import i18next from 'i18next'; +import sprintf from 'i18next-sprintf-postprocessor'; +import languages from './languages'; + +const DEFAULT_NAMESPACE = 'translation'; +const DEFAULT_LANGUAGE = 'en'; + +// Carefully reproduced tap:i18n API +export const TAPi18n = { + i18n: null, + current: new ReactiveVar(DEFAULT_LANGUAGE), + async init() { + this.i18n = i18next.createInstance().use(sprintf); + await this.i18n.init({ + fallbackLng: DEFAULT_LANGUAGE, + cleanCode: true, + debug: Meteor.isDevelopment, + supportedLngs: Object.values(languages).map(({ isoCode }) => isoCode), + ns: DEFAULT_NAMESPACE, + defaultNs: DEFAULT_NAMESPACE, + postProcess: ["sprintf"], + // Default values to match tap:i18n behaviour + interpolation: { + prefix: '__', + suffix: '__', + escapeValue: false, + }, + resources: {}, + }); + // Load the current language data + await TAPi18n.loadLanguage(DEFAULT_LANGUAGE); + }, + getSupportedLanguages() { + return Object.values(languages).map(({ name, code, isoCode }) => ({ name, code, isoCode })); + }, + getLanguage() { + return this.current.get(); + }, + async loadLanguage(language) { + if (language in languages && 'load' in languages[language]) { + const data = await languages[language].load(); + this.i18n.addResourceBundle(language, DEFAULT_NAMESPACE, data); + } + }, + async setLanguage(language) { + await this.loadLanguage(language); + await this.i18n.changeLanguage(language); + this.current.set(language); + }, + // Return translation by key + __(key, options, language) { + this.current.dep.depend(); + return this.i18n.t(key, { + ...options, + lng: language, + }); + } +}; diff --git a/models/boards.js b/models/boards.js index 5ca2a8e5f..582d04789 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1,3 +1,5 @@ +import escapeForRegex from 'escape-string-regexp'; +import { TAPi18n } from '/imports/i18n'; import { ALLOWED_BOARD_COLORS, ALLOWED_COLORS, @@ -7,7 +9,6 @@ import { } from '/config/const'; import Users from "./users"; -const escapeForRegex = require('escape-string-regexp'); Boards = new Mongo.Collection('boards'); /** diff --git a/models/cards.js b/models/cards.js index 142b738a4..c17bc546a 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1,3 +1,4 @@ +import moment from 'moment'; import { ALLOWED_COLORS, TYPE_CARD, diff --git a/models/exportExcel.js b/models/exportExcel.js index f7f861141..dabc49526 100644 --- a/models/exportExcel.js +++ b/models/exportExcel.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import { runOnServer } from './runOnServer'; runOnServer(function() { @@ -49,12 +50,12 @@ runOnServer(function() { isAdmin: true, }); } - + let userLanguage = 'en'; if(user && user.profile){ userLanguage = user.profile.language } - + const exporterExcel = new ExporterExcel(boardId, userLanguage); if (exporterExcel.canExport(user) || impersonateDone) { if (impersonateDone) { diff --git a/models/exportPDF.js b/models/exportPDF.js index e698d2f52..eceffc9c9 100644 --- a/models/exportPDF.js +++ b/models/exportPDF.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import { runOnServer } from './runOnServer'; runOnServer(function() { diff --git a/models/exporter.js b/models/exporter.js index f05b86881..4aefa4153 100644 --- a/models/exporter.js +++ b/models/exporter.js @@ -1,4 +1,6 @@ +import moment from 'moment'; const Papa = require('papaparse'); +import { TAPi18n } from '/imports/i18n'; // exporter maybe is broken since Gridfs introduced, add fs and path export class Exporter { @@ -209,7 +211,7 @@ export class Exporter { delimiter: userDelimiter, header: true, newline: "\r\n", - skipEmptyLines: false, + skipEmptyLines: false, escapeFormulae: true, }; diff --git a/models/server/ExporterExcel.js b/models/server/ExporterExcel.js index 1f0736acf..e5656626f 100644 --- a/models/server/ExporterExcel.js +++ b/models/server/ExporterExcel.js @@ -1,3 +1,5 @@ +import moment from 'moment'; +import { TAPi18n } from '/imports/i18n'; import { createWorkbook } from './createWorkbook'; // exporter maybe is broken since Gridfs introduced, add fs and path @@ -432,7 +434,7 @@ class ExporterExcel { //add blank row ws.addRow().values = ['', '', '', '', '', '']; - + //add board description ws.addRow().values = [ TAPi18n.__('description','',this.userLanguage), @@ -442,7 +444,7 @@ class ExporterExcel { ws.mergeCells('B3:H3'); ws.getRow(3).height = 40; // In MS Excel, we can't use the AutoFit feature on a column that contains a cell merged with cells in other columns. - // Likewise, we can't use AutoFit on a row that contains a cell merged with cells in other rows. + // Likewise, we can't use AutoFit on a row that contains a cell merged with cells in other rows. ws.getRow(3).font = { name: TAPi18n.__('excel-font'), size: 10, @@ -459,7 +461,7 @@ class ExporterExcel { vertical: 'middle', }; cellCenter('A3'); - + //add blank row ws.addRow().values = ['', '', '', '', '', '']; @@ -494,7 +496,7 @@ class ExporterExcel { }, numFmt: 'yyyy/mm/dd hh:mm:ss', }; - + cellCenter('A5'); cellCenter('B5'); cellCenter('C5'); @@ -502,7 +504,7 @@ class ExporterExcel { cellCenter('E5'); cellLeft('F5'); ws.getRow(5).height = 20; - + allBorder('A5'); allBorder('B5'); allBorder('C5'); @@ -786,7 +788,7 @@ class ExporterExcel { }, }; } - + //add title line ws2.mergeCells('A1:F1'); ws2.getCell('A1').value = result.title; @@ -813,7 +815,7 @@ class ExporterExcel { TAPi18n.__('card','',this.userLanguage), TAPi18n.__('owner','',this.userLanguage), TAPi18n.__('createdAt','',this.userLanguage), - TAPi18n.__('last-modified-at','',this.userLanguage), + TAPi18n.__('last-modified-at','',this.userLanguage), ]; ws2.getRow(3).height = 20; ws2.getRow(3).font = { diff --git a/models/settings.js b/models/settings.js index 91cd1d183..e24804c3c 100644 --- a/models/settings.js +++ b/models/settings.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; //var nodemailer = require('nodemailer'); // Sandstorm context is detected using the METEOR_SETTINGS environment variable diff --git a/models/trelloCreator.js b/models/trelloCreator.js index 98179b81f..5f591c46a 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.js @@ -1,3 +1,6 @@ +import moment from 'moment'; +import { TAPi18n } from '/imports/i18n'; + const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); return moment(dateAsString, moment.ISO_8601).isValid(); diff --git a/models/users.js b/models/users.js index 331a801b4..b42f6fe39 100644 --- a/models/users.js +++ b/models/users.js @@ -1,5 +1,6 @@ //var nodemailer = require('nodemailer'); import { SyncedCron } from 'meteor/percolate:synced-cron'; +import { TAPi18n } from '/imports/i18n'; import ImpersonatedUsers from './impersonatedUsers'; // Sandstorm context is detected using the METEOR_SETTINGS environment variable diff --git a/models/wekanCreator.js b/models/wekanCreator.js index 310230e7c..7aaf7f6eb 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -1,3 +1,5 @@ +import moment from 'moment'; + const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); return moment(dateAsString, moment.ISO_8601).isValid(); diff --git a/package-lock.json b/package-lock.json index 14f4bb162..b34180672 100644 --- a/package-lock.json +++ b/package-lock.json @@ -449,6 +449,14 @@ "dev": true, "requires": { "sprintf-js": "~1.0.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + } } }, "asn1": { @@ -693,6 +701,14 @@ "type-detect": "^4.0.5" } }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "requires": { + "check-error": "^1.0.2" + } + }, "chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -721,8 +737,7 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, "clone": { "version": "2.1.2", @@ -1802,6 +1817,29 @@ } } }, + "i18next": { + "version": "21.6.16", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.16.tgz", + "integrity": "sha512-xJlzrVxG9CyAGsbMP1aKuiNr1Ed2m36KiTB7hjGMG2Zo4idfw3p9THUEu+GjBwIgEZ7F11ZbCzJcfv4uyfKNuw==", + "requires": { + "@babel/runtime": "^7.17.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", + "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "i18next-sprintf-postprocessor": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/i18next-sprintf-postprocessor/-/i18next-sprintf-postprocessor-0.2.2.tgz", + "integrity": "sha1-LkCfEENXk4Jpi2otpwzapVHWfqQ=" + }, "iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -2212,6 +2250,11 @@ "lodash.template": "^4.5.0" } }, + "meteor-accounts-t9n": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/meteor-accounts-t9n/-/meteor-accounts-t9n-2.6.0.tgz", + "integrity": "sha512-SBbbYWvFYvsxHVL+q6ZB8lT3rp2LSvfALD2V52H+MGH2IgJsevy0VtXRkRG0EsUewwOaDTIKBn9DlD8HQ3GSwg==" + }, "meteor-node-stubs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.1.0.tgz", @@ -3449,6 +3492,11 @@ } } }, + "sinon-chai": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", + "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==" + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3479,12 +3527,6 @@ "memory-pager": "^1.0.2" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, "string-width": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.0.tgz", diff --git a/package.json b/package.json index 04d022d97..32c03530a 100644 --- a/package.json +++ b/package.json @@ -28,13 +28,16 @@ "bcryptjs": "^2.4.3", "bson": "^4.4.1", "bunyan": "^1.8.15", - "core-js": "^2.6.12", - "dompurify": "^2.3.0", + "chai-as-promised": "^7.1.1", + "core-js": "^3.18.2", + "dompurify": "^2.3.2", "es6-promise": "^4.2.4", "escape-string-regexp": "^5.0.0", "exceljs": "^4.2.1", "fibers": "^5.0.0", "gridfs-stream": "https://github.com/wekan/gridfs-stream/tarball/master", + "i18next": "^21.6.16", + "i18next-sprintf-postprocessor": "^0.2.2", "jquery": "^2.2.4", "jquery-ui": "^1.13.0", "jquery-ui-touch-punch": "^0.2.3", @@ -42,6 +45,7 @@ "ldapjs": "^2.3.1", "markdown-it": "^12.3.2", "markdown-it-emoji": "^2.0.0", + "meteor-accounts-t9n": "^2.6.0", "meteor-node-stubs": "^1.1.0", "moment": "^2.29.1", "mongodb": "^3.7.3", @@ -50,7 +54,9 @@ "papaparse": "^5.3.1", "qs": "^6.10.1", "simpl-schema": "^1.12.0", - "source-map-support": "^0.5.19" + "sinon-chai": "^3.7.0", + "source-map-support": "^0.5.20", + "uuid": "^8.3.2" }, "meteor": { "testModule": "tests/main.js" diff --git a/packages/wekan-tap-i18n/ChangeLog b/packages/wekan-tap-i18n/ChangeLog deleted file mode 100644 index e58de9fd7..000000000 --- a/packages/wekan-tap-i18n/ChangeLog +++ /dev/null @@ -1,216 +0,0 @@ -2016.05.24, Version 1.8.1 - -* Avoid race condition in setLanguage method. Fixes #86, thanks @mpowaga - -2016.03.14, Version 1.8.0 - -* YAML translations files support, thanks @karfield @nscarcella - -2015.10.06, Version 1.7.0 - -* Ready for Meteor v1.2 - -2015.09.19, Version 1.6.1 - -* [BUGFIX] Fix compiler's output path, thanks @glyphing - -2015.07.24, Version 1.6.0 - -* Implement project-tap.i18n's preloaded_langs option -* Improve Chinese dialects names - -2015.07.06, Version 1.5.1 - -* Register server side translator for fallback language correctly, fix #82 - -2015.05.08, Version 1.5.0 - -* Make TAPi18n a constructor instead of object, inherit from EventEmitter -* Make the lang option alias to the lng option in tap:i18n translator functions -* Allow translation to a specific language on the client -* [BUGFIX] Allow pre Meteor.startup() server side translations, resolves #56 -* [BUGFIX] Allow 0 as first argument of sprintf args. fix #43 -* [MINOR] i18next: Fix plurals function for Mandinka @hamoid -* [MINOR] README: Added note about dot notation @SachaG -* [MINOR] Correct name for Ukrainina language @shkomg -* [MINOR] Travis CI: Stop testing node v0.8 - -2015.02.28, Version 1.4.1 - -* [bugfix] Under cordova ensure absoluteUrl have no trailing / - -2015.02.05, Version 1.4.0 - -* Add Cordova support (requires internet connection) - -2015.01.16, Version 1.3.2 - -* Use simple-schema 1.3.0 - -2015.01.14, Version 1.3.1 - -* [bugfix] getLanguages() should return languages defined as supported in - project-tap.i18n even if there is no *.i18n.json for these languages in - the project level -* [bugfix] getLanguages() shouldn't return non-supported languages Fix #38 -* Use cfs:http-methods@0.0.27 instead of tap:http-methods@0.0.23 - -2015.01.11, Version 1.3.0 - -* Fix language_names object keys, fix #36 -* Disable TAPi18next cookie feature that caused anomalies -* Introduce TAPi18n.loadTranslations - -2014.11.25, Version 1.2.1 - -* Register helpers with Template.template.helpers() -* [bugfix] fix the package templates detection (got broken by a Meteor API change) - -2014.11.25, Version 1.2.0 - -* package-tap.i18n: Introduce the namespace option - -2014.11.22, Version 1.1.1 - -* [bugfix] build plugin: don't assign _ var to global namespace. Thanks -@smeijer . #30 - -2014.11.18, Version 1.1.0 - -* Allow the use of comments in *.i18n.json files -* [MINOR] package.js: use METEOR@0.9.4, and the stable version of registerBuildPlugin -* [MINOR] fix a symbolic link -* [MINOR] add to the tree files that were missing due to wrong .gitignore - -2014.09.25, Version 1.0.7 - -* [bugfix] Packages non-fallback languages now load see issue #13 Thanks - to @francocatena . -* [bugfix] tap:i18n files of packages under /packages directory now load - correctly - -2014.09.24, Version 1.0.6, 304e1ee00e2916646bf672fd53ef5a6cceb69db4 - -* [bugfix] Project level: Make loading of tap:i18n files insensitive to order by - Registering template helpers as soon as tap:i18n gets enabled and not on - Meteor.ready() . Thanks to @danieljonce for reporting this issue - -2014.09.24, Version 1.0.5, df044dc3523d1ebb608f1e115a31539b4ba42742 - -* Project level: languages files should be common to server and client -* Fix unitests - add languages files to the tree -* Remove redundant output from i18n.json and project-tap.i18n compilers - -2014.09.19, Version 1.0.4, 4d843a11cf757ef6d2ff6534a1c6b757b91eedda - -* [bugfix] each language file should have its own output path - (this bug prevented multiple files per language in the app level) - -2014.09.16, Version 1.0.3, 888f3dd5066e320513a76f5bb1ce711a7a84478d - -* [bugfix] TAPi18n.getLanguages() format is now compatible with docs - -2014.09.10, Version 1.0.2, 66fcd441f3b6a107db0aa32c8f3ea9692c1c8c09 - -* [bugfix] compiler: fix error for invalid JSON - -2014.09.10, Version 1.0.1, 7f92b8fbd8ced171873b8afd70a808f51167e7ac - -* Allow project-tap.i18n's supported_languages to have a lang with no -translation files -* [bugfix] tap-i18n compiler: init compiler_configuration upon a rebuild - -2014.09.09, Version 1.0.0, 5964838f9ab085136d45899d38bb126958c3deda - -* Build plugin rewritten -* tap-i18n now fully support Meteor v0.9 -* Ready to be used with Cordova -* **New Features:** -* Server side internationalization is now supported. -* The template translation helper name (_) and the package translation function -name (__) are now customizable. -* Transparent bundling, no need for special procedures for deploying project -that uses tap-i18n. -* Language files and project-tap.i18n can now be located anywhere in the project tree. -* A project/package can now have more than one language file for the same -language. -* getLanguages() now works in both server and client. -* **Backward compatibility:** -* package i18n files now have to be added to both the client and the server -architectures, not only the client. -* The base language of a dialects are no longer added automatically as a supported -language. -* project-tap.i18n: languages_files_dir and build_files_path properties are now -obsolete. browser_path property renamed to cdn_path. -* package-tap.i18n: languages_files_dir is now obsolete. - -2014.08.30, Version 0.9.2, 742e44f659dfb7800d332bf4b2aa990e6f220d36 - -* Bugfix: Build plugin should consider projects with a *:i18n package as tap:i18n enabled -* Use tap:http-methods instead of raix:http-methods which isn't ready for v0.9.0 - -2014.08.30, Version 0.9.0, 118aa825e76165aac9df9f3153fbb8edc044a864 - -* tap-i18n is now tap:i18n -* Migrate to Meteor v0.9 - -2014.08.11, Version 0.8.0, a500ae5c5c6da2aa0ccd56bfe407bfa9c8a77b62 - -* [MINOR] package.js: only use single quotes to enclose strings -* Do not require having a file for the base language of a dialect -* Build plugin: make sure isString before removeFileTrailingSeparator - -2014.07.30, Version 0.7.0, 7c414420e65cb67a9e49896826542db1815a257c - -* Refactor build plugin. Fix a rare bug causing the proj base lang not to load -* build plugin: catch all build errors and use compileStep.error to report them -* Do not build/load supported languages with no translation files - -2014.06.26, Version 0.6.0, 7e9685ce75165a6a5998f6f4643490fc1e14c166 - -* Introduce {{languageTag}} -* README: add instuctions for deploying tap-i18n projects to *.meteor.com -* tap-i18n now works in Meteor bundles - -2014.06.14, Version 0.5.1, 71a9ad595e972998e16d7cbd60fed699127464c3 - -* Bugfix: Trigger buildFilesOnce for .i18n.json files for the os arch so - TAPi18n.conf will get set if there is no project-tap.i18n - -2014.06.13, Version 0.5.0, 0d7ea8c3ac8307b3d48efc6c8b80b1ce2dd1e8b1 - -* Unittests now work on mac -* Introduce TAPi18n.getLanguages() - -2014.06.12, Version 0.4.0, f583aa179b559d447519c61dcdb019f05a0b10a3 - -* README restructured -* Set lisence to: MIT - -2014.06.11, Version 0.3.0, ba328abf4e057b60c82cfb455d183fe1ff4605cd - -* Refresh the clients when .i18n.json files change -* bugfix: build files if i18n.json files change for case we don't have - project-tap.i18n to trigger build -* Show debug meesages only if globals.debug is true or env variable - TAP_I18N_DEBUG is "true" -* project-tap.i18n ignore default_browser_path if build_files_path is null -* Fixes to README - -2014.06.10, Version 0.2.0, 50bb1e9643e8233438ff7614bb79ca3dd575a3a8 - -* Implement project-level translations -* For enabled tap-i18n projects use the http-methods package as the default - mean for serving unified languages files instead of /public -* API change: Consider projects with no project-tap.i18n as tap-i18n enabled -* API change: By default, regard all the languages we find translations for as - supported_languages -* API change: No package-level default language - en is our fallback_language - everywhere -* Improve the unittesting framework -* Naming: Use TAPi18n and TAPi18next instead of TapI18n and TapI18next -* Bugfix: don't add an error object when throwing exception if error - -2014.05.22, Version 0.1.0, 676f50f0bea154596cacf44c34c352b09aa1d215 - -* tap-i18n first release diff --git a/packages/wekan-tap-i18n/LICENSE b/packages/wekan-tap-i18n/LICENSE deleted file mode 100644 index 8d0265994..000000000 --- a/packages/wekan-tap-i18n/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 TAPevents Asia Limited <accounts@tapevents.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/packages/wekan-tap-i18n/README.md b/packages/wekan-tap-i18n/README.md deleted file mode 100644 index 8219a6c58..000000000 --- a/packages/wekan-tap-i18n/README.md +++ /dev/null @@ -1,808 +0,0 @@ -# tap-i18n - -A comprehensive internationalization solution for Meteor - -### Internationalization for Meteor - -**tap-i18n** is a [Meteor](http://www.meteor.com) package that provides a comprehensive [i18n](http://www.i18nguy.com/origini18n.html) solution for Meteor apps and packages, -with the goal of standardizing the way package developers internationalize their -packages. - -[Watch a talk about tap:i18n & tap:i18n-db](https://www.youtube.com/watch?v=cu_dsoIc_0E) - -**Get involved in tap:i18n:** - -* [Mailing list](http://groups.google.com/d/forum/tap-i18n) -* [tap:i18n chat on Gitter](https://gitter.im/TAPevents/tap-i18n) -* [Roadmap](https://trello.com/b/w80JNkwf/tap-i18n-roadmap) -* [New features planning on Hackpad](https://hackpad.com/collection/DRzcZ7sBdZV) - -Developed by <a href="http://www.meteorspark.com"><img src="http://www.meteorspark.com/logo/logo-github.png" title="MeteorSpark" alt="MeteorSpark"></a> [Professional Meteor Services](http://www.meteorspark.com)<br/> for <a href="http://tapevents.com/"><img src="http://tapevents.com/wp-content/uploads/2015/02/TAPevents_logo_144px.png" title="TAPevents" alt="TAPevents" style='margin-top:10px'>  Leading Conference Technology</a>. - -**Related Packages:** - -* Check [tap:i18n-db](https://github.com/TAPevents/tap-i18n-db) for Meteor collections internationalization. -* Check [tap:i18n-ui](https://github.com/TAPevents/tap-i18n-ui) for bootstrap based UI components for tap:i18n. -* Check [tap:i18n-bundler](https://github.com/TAPevents/i18n-bundler) for Cordova & static file deployments. - -**Users of tap-i18n v0.9** and below, read [tap:i18n v1.0 New Features & Backward Compatibility](https://github.com/TAPevents/tap-i18n/wiki/tap:i18n-v1.0-New-Feautres-&-Backward-Compatibility) and update your app to v1.0 . - -## Contents - -- [Key Features](#key-features) -- [Quickstart](#quickstart) -- [Documentation & Examples](#documentation--examples) - - [TAPi18n API](#tapi18n-api) - - [The tap-i18n Helpers](#the-tap-i18n-helpers) - - [Languages Tags and Translations Prioritization](#languages-tags-and-translations-prioritization) - - [Structure of Languages Files](#structure-of-languages-files) - - [Configuring tap-i18n](#configuring-tap-i18n) - - [Disabling tap-i18n](#disabling-tap-i18n) - - [Using tap-i18n in Cordova apps](#using-tap-i18n-in-cordova-apps) -- [Developing Packages](#developing-packages) - - [tap-i18n Two Work Modes](#tap-i18n-two-work-modes) - - [Setup tap-i18n](#setup-tap-i18n) - - [Package Level tap-i18n Functions](#package-level-tap-i18n-functions) - - [Using tap-i18n in Your Package Templates](#using-tap-i18n-in-your-package-templates) -- [Unit Testing](#unit-testing) -- [License](#license) -- [Credits](#credits) - -## Key Features - -### All Encompassing - -tap-i18n is designed in a way that distinguishes the role of the package developer, that is, making the package available in multiple languages, from the role of the app developer which is to translate the app, but more importantly, to manage the app's internationalization aspects, such as: setting the supported languages for the project, setting the client language, configuring CDNs for language files, and so on. - -### Readable Syntax - -```handlebars -<div class="btn">{{_ "sign_up"}}</div> -``` - -### Advanced i18n - -tap-i18n uses [i18next v1.11](http://i18next.github.io/i18next/) as its internationalization engine and exposes all its capabilities to the Meteor's templates - variables, dialects, count/context aware keys, and more. - -**client/messages.html** - -```handlebars -<template name="messages_today"> - <p>{{_ "inbox_status" "Daniel" count=18}}</p> -</template> -``` - -**i18n/en.i18n.json** - -```json -{ - "inbox_status": "Hey, %s! You have received one new message today.", - "inbox_status_plural": "Hey, %s! You have received %s new messages today." -} -``` -See more examples below. - -### Transparent Namespacing - -You don't need to worry about domain prefixing or package conflicts when you translate your project or package. Behind the scenes we automatically generate scoped namespaces for you. - -### Ready to Scale - -* Translations are unified into a single JSON file per language that includes both package and project-level translations -* On-demand: translations are loaded only when they are needed -* 3rd Party CDN Support - - -## Quickstart - -**Step 1:** Install tap-i18n using meteor: - -```bash -$ meteor add tap:i18n -``` - -**Step 2:** Add translation helpers to your markup: - -**\*.html** - -```handlebars -<div>{{_ "hello"}}</div> -``` - -**Step 3:** Define translations in JSON or YAML format: - -**i18n/en.i18n.json** - -```json -{ "hello": "Hey there" } -``` - -**i18n/fr.i18n.json** - -```json -{ "hello": "Bonjour" } -``` - -**i18n/es.i18n.yml** - -```yaml -hello: Hola -``` - -Translations files should end with lang_tag.i18n.json/yml. - -You can split translations of a certain language to multiple files, we ignore -the prefixed text, e.g., we add the translations of menu.en.i18n.json in the -same way we add those of en.i18n.json . - -You can put languages files anywhere in your project tree, as long as they are -common to both your server and client - **do not put languages files under -/client, /server or /public**. - -Note: Languages files have to be saved in utf-8 encoding. - -**Step 4:** Initiate the client language on startup (optional) - -If you want the client to be served by a specific language on startup - -Assuming that you have a function getUserLanguage() that returns the language -for tag for the current user. - -```javascript -getUserLanguage = function () { - // Put here the logic for determining the user language - - return "fr"; -}; - -if (Meteor.isClient) { - Meteor.startup(function () { - Session.set("showLoadingIndicator", true); - - TAPi18n.setLanguage(getUserLanguage()) - .done(function () { - Session.set("showLoadingIndicator", false); - }) - .fail(function (error_message) { - // Handle the situation - console.log(error_message); - }); - }); -} -``` - -* If you won't set a language on startup your project will be served in the - fallback language: English -* You probably want to show a loading indicator until the language is ready (as - shown in the example), otherwise the templates in your projects will be in - English until the language will be ready - -## Documentation & Examples - -### TAPi18n API - -**TAPi18n.setLanguage(language\_tag) (Client)** - -Sets the client's translation language. - -Returns a jQuery deferred object that resolves if the language load -succeed and fails otherwise. - -**Notes:** - - * language\_tag has to be a supported Language. - * jQuery deferred docs: [jQuery Deferred](http://api.jquery.com/jQuery.Deferred/) - -**TAPi18n.getLanguage() (Client)** - -Returns the tag of the client's current language or null if -tap-i18n is not installed. - -If inside a reactive computation, invalidate the computation the next time the -client language get changed (by TAPi18n.setLanguage) - -**TAPi18n.getLanguages() (Anywhere)** - -Returns an object with all the supported languages and their names. - -A language is considred supported if it is in the supported_languages array of -the project-tap.i18n json. If supported_languages is null or not defined in -project-tap.i18n we consider all the languages we find *.i18n.json/yml files to as -supported. - -The returned object is in the following format: - -```javascript -{ - 'en': { - 'name':'English', // Local name - 'en':'English' // English name - }, - 'zh': { - 'name':'中文' // Local name - 'en':'Chinese' // English name - } - . - . - . -} -``` - -**TAPi18n.__(key, options, lang_tag=null) (Anywhere)** - -*If `lang_tag` is null:* - -Translates key to the current client's language. If inside a reactive -computation, invalidate the computation the next time the client language get -changed (by TAPi18n.setLanguage). - -*Otherwise:* - -Translates key to lang_tag. if you use `lang_tag` you should use `__` in a -reactive computation since the string will be translated to the current client -language if a translator to lang_tag is not ready in the client (if called for -the first time with that lang_tag, or until language data load from the server -finishes) and will get invalidated (trigger reactivity) when the translator to -that lang_tag is ready to be used to translate the key. - -Using `i18next.t` `lng` option or `lang`, which we made as alias to `lang` in -tap:i18n, is equivalent to setting the `lang_tag` attribute. - -The function is a proxy to the i18next.t() method. -Refer to the [documentation of i18next.t()](http://i18next.github.io/i18next/pages/doc_features.html) -to learn about its possible options. (Make sure you refer to i18next v1.11 documentation and not v2) - -**On the server**, TAPi18n.__ is not a reactive resource. You have to specify -the language tag you want to translate the key to. - -**TAPi18n.loadTranslations(translations, namespace="project") (Anywhere)** - -Use *translations* in addition or instead of the translations defined in the -i18n.json files. Translations defined by loadTranslations will have priority -over those defined in language files (i18n.json) of *namespace* (the project, -or package name). - -To enjoy [the benefits of tap:i18n](#key-features), you should use language -files to internationalize your project whenever you can. - -Legitimate cases for *loadTranslations* are: - -* Allowing users to change the project translations -* Changing translations of 3rd party packages that you don't want to fork (see - the Note below). - -Example: - -```javascript -TAPi18n.loadTranslations( - { - es: { - meteor_status_waiting: "Desconectado" - }, - fr: { - meteor_status_failed: "La connexion au serveur a échoué" - } - }, - "francocatena:status" -); -``` - -**Arguments:** - -* `translations`: An object of the following format: - -```javascript -{ - 'lang-tag': { - 'translation-key1': 'translation', - 'translation-key2': 'translation', - ... - }, - ... -} -``` - -* `namespace="project"`: The namespace you want to add the translations to. by - default translations are added to the project namespace, if you want to - change a package translation use the package name as the namespace like the - above example. - -**Notes:** - -* **Adding support to a new language in your app:** You can't use - *addTranslations* in order to add support to a new language, that is, to allow - users to change the interface language of the app to that language. In order - to start support a new language in your app, you'll have to either add a - language file to that language (*.i18n.json file) or add that languages to your - project-tap.i18n file. - -* **Translating a package that uses tap:i18n to another language**: If you want - to add a new language to a 3rd party package (and you can't get it's owner to - merge your pull request) consider introducing a "translation" package in which - package-tap.i18n has the "namespace" options set to the package you are - translating. That way you can translate with languages files instead of - *addTranslations* and share your translation package with others. - -### The tap-i18n Helpers - -### The \_ Helper - -To use tap-i18n to internationalize your templates you can use the \_ helper -that we set on the project's templates and on packages' templates for packages -that uses tap-i18n: - - {{_ "key" "sprintf_arg1" "sprintf_arg2" ... op1="option-value" op2="option-value" ... }} - -**You can customize the helper name, see "Configuring tap-i18n" section.** - -The translation files that will be used to translate key depends on the -template from which it is being used: -* If the helper is being used in a template that belongs to a package that uses - tap-i18n we'll always look for the translation in that package's translation - files. -* If the helper is being used in one of the project's templates we'll look for - the translation in the project's translation files (tap-i18n has to be - installed of course). - -**Usage Examples:** - -Assuming the client language is en. - -**Example 1:** Simple key: - - en.i18n.json: - ------------- - { - "click": "Click Here", - "html_key": "<b>BOLD</b>" - } - - page.html: - ---------- - <template name="x"> - {{_ "click"}} - {{{_ "html_key"}}} <!-- Note that we use triple {{{ for html rendering --> - </template> - - output: - ------- - Click Here - <b>BOLD</b> - -**Example 2:** Simple key specific language: - - en.i18n.json: - ------------- - { - "click": "Click Here" - } - - fr.i18n.json: - ------------- - { - "click": "Cliquez Ici" - } - - page.html (lng and lang options are the same in tap:i18n you can use both): - ---------- - <template name="x"> - {{_ "click" lang="fr"}} - </template> - - <template name="x"> - {{_ "click" lng="fr"}} - </template> - - output: - ------- - Cliquez Ici - -**Example 3:** Sprintf: - - en.i18n.json: - ------------- - { - "hello": "Hello %s, your last visit was on: %s" - } - - page.html: - ---------- - <template name="x"> - {{_ "hello" "Daniel" "2014-05-22"}} - </template> - - output: - ------- - Hello Daniel, your last visit was on: 2014-05-22 - -**Example 4:** Named variables and sprintf: - - en.i18n.json: - ------------- - { - "hello": "Hello __user_name__, your last visit was on: %s" - } - - page.html: - ---------- - <template name="x"> - {{_ "hello" "2014-05-22" user_name="Daniel"}} - </template> - - output: - ------- - Hello Daniel, your last visit was on: 2014-05-22 - -**Note:** Named variables have to be after all the sprintf parameters. - -**Example 5:** Named variables, sprintf, singular/plural: - - en.i18n.json: - ------------- - { - "inbox_status": "__username__, You have a new message (inbox last checked %s)", - "inbox_status_plural": "__username__, You have __count__ new messages (last checked %s)" - } - - page.html: - ---------- - <template name="x"> - {{_ "inbox_status" "2014-05-22" username="Daniel" count=1}} - {{_ "inbox_status" "2014-05-22" username="Chris" count=4}} - </template> - - output: - ------- - Daniel, You have a new message (inbox last checked 2014-05-22) - Chris, You have 4 new messages (last checked 2014-05-22) - -**Example 6:** Singular/plural, context: - - en.i18n.json: - ------------- - { - "actors_count": "There is one actor in the movie", - "actors_count_male": "There is one actor in the movie", - "actors_count_female": "There is one actress in the movie", - "actors_count_plural": "There are __count__ actors in the movie", - "actors_count_male_plural": "There are __count__ actors in the movie", - "actors_count_female_plural": "There are __count__ actresses in the movie", - } - - page.html: - ---------- - <template name="x"> - {{_ "actors_count" count=1 }} - {{_ "actors_count" count=1 context="male" }} - {{_ "actors_count" count=1 context="female" }} - {{_ "actors_count" count=2 }} - {{_ "actors_count" count=2 context="male" }} - {{_ "actors_count" count=2 context="female" }} - </template> - - output: - ------- - There is one actor in the movie - There is one actor in the movie - There is one actress in the movie - There are 2 actors in the movie - There are 2 actors in the movie - There are 2 actresses in the movie - -* Refer to the [documentation of i18next.t() v1.11](http://i18next.github.io/i18next/pages/doc_features.html) - to learn more about its possible options. (Make sure you refer to i18next v1.11 documentation and not v2) -* The translation will get updated automatically after calls to - TAPi18n.setLanguage(). - -### More helpers - -**{{languageTag}}:** - -The {{languageTag}} helper calls TAPi18n.getLanguage(). - -It's useful when you need to load assets depending on the current language, for -example: - -```handlebars -<template name="example"> - <img src="welcome_{{languageTag}}.png"> -</template> -``` - -### Languages Tags and Translations Prioritization - -We use the [IETF language tag system](http://en.wikipedia.org/wiki/IETF_language_tag) -for languages tagging. With it developers can refer to a certain language or -pick one of its dialects. - -Example: A developer can either refer to English in general using: "en" or to -use the Great Britain dialect with "en-GB". - -**If tap-i18n is install** we'll attempt to look for a translation of a certain -string in the following order: -* Language dialect, if specified ("pt-BR") -* Base language ("pt") -* Base English ("en") - -**Notes:** - -* We currently support only one dialect level. e.g. nan-Hant-TW is not - supported. -* "en-US" is the dialect we use for the base English translations "en". -* If tap-i18n is not installed, packages will be served in English, the fallback language. - -### Structure of Languages Files - -Languages files should be named: arbitrary.text.lang_tag.i18n.json . e.g., en.i18n.json, menu.pt-BR.i18n.json. - -You can have more than one file for the same language. - -You can put languages files anywhere in your project tree, as long as they are -common to both your server and client - **do not put languages files under -/client, /server or /public**. - -Example for languages files: - - en.i18n.json - { - "sky": "Sky", - "color": "Color" - } - - pt.i18n.json - { - "sky": "Céu", - "color": "Cor" - } - - fr.i18n.json - { - "sky": "Ciel" - } - - en-GB.i18n.json - { - "color": "Colour" - } - -* Do not use colons and periods (see note below) in translation keys. -* To avoid translation bugs all the keys in your package must be translated to - English ("en") which is the fallback language we use if tap-i18n is not installed, - or when we can't find a translation for a certain key. -* In the above example there is no need to translate "sky" in en-GB which is the - same in en. Remember that thanks to the Languages Tags and Translations - Prioritization (see above) if a translation for a certain key is the same for a - language and one of its dialects you don't need to translate it again in the - dialect file. -* The French file above have no translation for the color key above, it will - fallback to English. -* Check [i18next features documentation](http://i18next.github.io/i18next/pages/doc_features.html) for - more advanced translations structures you can use in your JSONs files (Such as - variables, plural form, etc.). (Make sure you refer to i18next v1.11 documentation and not v2) - -#### A note about dot notation - -Note that `{_ "foo.bar"}` will be looked under `{foo: {bar: "Hello World"}}`, and not under `"foo.bar"`. - -### Configuring tap-i18n - -To configure tap-i18n add to it a file named **project-tap.i18n**. - -This JSON can have the following properties. All of them are optional. The values bellow -are the defaults. - - project-root/project-tap.i18n - ----------------------------- - { - "helper_name": "_", - "supported_languages": null, - "i18n_files_route": "/tap-i18n", - "cdn_path": null, - "preloaded_langs": [] - } - -Options: - -**helper\_name:** the name for the templates' translation helper. - -**supported\_languages:** A list of languages tags you want to make available on -your project. If null, all the languages we'll find translation files for, in the -project, will be available. - -**build\_files\_path:** Can be an absolute path or relative to the project's root. If you change this value we assume you want to serve the files yourself (via cdn, or by other means) so we won't initiate the tap-i18n's built-in files server. Therefore if you set build\_files\_path you **must** set the browser\_path. - -**i18n\_files\_route:** The route in which the tap-i18n resources will be available in the project. - -**cdn\_path:** An alternative path from which you want tap-i18n resources to be loaded. Example: "http://cdn.example.com/tap-i18n". - -**preloaded_langs:** An array of languages tags. If isn't empty, a single synchronous ajax requrest will load the translation strings for all the languages tags listed. If you want to load all the supported languages set preloaded_langs to `["*"]` (`"*"` must be the first item of the array, the rest of the array will be ignored. `["zh-*"]` won't work). - -**Notes:** - -* We use AJAX to load the languages files so you'll have to set CORS on your CDN. - -### Disabling tap-i18n - -**Step 1:** Remove tap-i18n method calls from your project. - -**Step 2:** Remove tap-i18n package - -```bash -$ meteor remove tap:i18n -``` - -### Using tap-i18n in Cordova apps - -In order to use tap-i18n in a Cordova app you must set the `--server` flag -to your server's root url when building your project. - -```bash -$ meteor build --server="http://www.your-site-domain.com" -``` - -If your app should work when the user is offline, install the [tap:i18n-bundler](https://atmospherejs.com/tap/i18n-bundler) package and follow [its instructions](https://github.com/TAPevents/i18n-bundler#usage). - -## Developing Packages - -Though the decision to translate a package and to internationalize it is a -decision made by the **package** developer, the control over the -internationalization configurations are done by the **project** developer and -are global to all the packages within the project. - -Therefore if you wish to use tap-i18n to internationalize your Meteor -package your docs will have to refer projects developers that will use it to -the "Usage - Project Developers" section above to enable internationalization. -If the project developer won't enable tap-i18n your package will be served in -the fallback language English. - -### tap-i18n Two Work Modes - -tap-i18n can be used to internationalize projects and packages, but its -behavior is determined by whether or not it's installed on the project level. -We call these two work modes: *enabled* and *disabled*. - -When tap-i18n is disabled we don't unify the languages files that the packages -being used by the project uses, and serve all the packages in the fallback -language (English) - -### Setup tap-i18n - -In order to use tap-i18n to internationalize your package: - -**Step 1:** Add the package-tap.i18n configuration file: - -You can use empty file or an empty JSON object if you don't need to change them. - -The values below are the defaults. - - package_dir/package-tap.i18n - ---------------------------- - { - // The name for the translation function that - // will be available in package's namespace. - "translation_function_name": "__", - - // the name for the package templates' translation helper - "helper_name": "_", - - // directory for the translation files (without leading slash) - "languages_files_dir": "i18n", - - // tap:i18n automatically separates the translation strings of each package to a - // namespace dedicated to that package, which is used by the package's translation - // function and helper. Use the namespace option to set a custom namespace for - // the package. By using the name of another package you can use your package to - // add to that package or modify its translations. You can also set the namespace to - // "project" to add translations that will be available in the project level. - "namespace": null - } - -**Step 2:** Create your languages\_files\_dir: - -Example for the default languages\_files\_dir path and its structure: - - . - |--package_name - |----package.js - |----package-tap.i18n - |----i18n # Should be the same path as languages_files_dir option above - |------en.i18n.json - |------fr.i18n.json - |------pt.i18n.json - |------pt-BR.i18n.json - . - . - . - -NOTE: the file for the fallback language (`en.i18n.json`) **must** exist (it may be empty though). - -The leanest set up (for instance in a private package, where you keep the translations at the project level) is two empty files: `package-tap.i18n` and `i18n/en.i18n.json`. - -**Step 3:** Setup your package.js: - -Your package's package.js should be structured as follow: - - Package.on_use(function (api) { - api.use(["tap:i18n@1.0.7"], ["client", "server"]); - - . - . - . - - // You must load your package's package-tap.i18n before you load any - // template - api.add_files("package-tap.i18n", ["client", "server"]); - - // Templates loads (if any) - - // List your languages files so Meteor will watch them and rebuild your - // package as they change. - // You must load the languages files after you load your templates - - // otherwise the templates won't have the i18n capabilities (unless - // you'll register them with tap-i18n yourself, see below). - api.add_files([ - "i18n/en.i18n.json", - "i18n/fr.i18n.json", - "i18n/pt.i18n.json", - "i18n/pt-br.i18n.json" - ], ["client", "server"]); - }); - -Note: en, which is the fallback language, is the only language we integrate -into the clients bundle. All the other languages files will be loaded only -to the server bundle and will be served as part of the unified languages files, -that contain all the project's translations. - -### Package Level tap-i18n Functions - -The following functions are added to your package namespace by tap-i18n: - -**\_\_("key", options, lang_tag) (Anywhere)** - -Read documenation for `TAPi18n.__` above. - -**On the server**, TAPi18n.__ is not a reactive resource. You have to specify -the language tag you want to translate the key to. - -You can use package-tap.i18n to change the name of this function. - -**registerI18nHelper(template\_name) (Client)** - -**registerTemplate(template\_name) (Client) [obsolete alias, will be removed in future versions]** - -Register the \_ helper that maps to the \_\_ function for the -template with the given name. - -**Important:** As long as you load the package templates after you add package-tap.i18n -and before you start adding the languages files you won't need to register templates yourself. - -### Using tap-i18n in Your Package Templates - -See "The tap-i18n helper" section above. - -## Unit Testing - -See /unittest/test-packages/README.md . - -## License - -MIT - -## Author - -[Daniel Chcouri](http://theosp.github.io/) - -## Contributors - -* [Chris Hitchcott](https://github.com/hitchcott/) -* [Kevin Iamburg](http://www.slickdevelopment.com) -* [Abe Pazos](https://github.com/hamoid/) -* [@karfield](https://github.com/karfield/) -* [@nscarcella](https://github.com/nscarcella/) -* [@mpowaga](https://github.com/mpowaga/) - -## Credits - -* [i18next v1.11](http://i18next.github.io/i18next/) -* [simple-schema](https://github.com/aldeed/meteor-simple-schema) -* [http-methods](https://github.com/CollectionFS/Meteor-http-methods) diff --git a/packages/wekan-tap-i18n/TODO b/packages/wekan-tap-i18n/TODO deleted file mode 100644 index edc654412..000000000 --- a/packages/wekan-tap-i18n/TODO +++ /dev/null @@ -1,3 +0,0 @@ -TODO: - -* Add memcache layer to the integral file server diff --git a/packages/wekan-tap-i18n/lib/globals.js b/packages/wekan-tap-i18n/lib/globals.js deleted file mode 100644 index bc36ed4ca..000000000 --- a/packages/wekan-tap-i18n/lib/globals.js +++ /dev/null @@ -1,10 +0,0 @@ -// The globals object will be accessible to the build plugin, the server and -// the client - -globals = { - fallback_language: "en", - langauges_tags_regex: "([a-z]{2})(-[A-Z]{2})?", - project_translations_domain: "project", - browser_path: "/tap-i18n", - debug: false -}; diff --git a/packages/wekan-tap-i18n/lib/plugin/compiler_configuration.coffee b/packages/wekan-tap-i18n/lib/plugin/compiler_configuration.coffee deleted file mode 100644 index e3677703f..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/compiler_configuration.coffee +++ /dev/null @@ -1,23 +0,0 @@ -# Note: same compiler can be used to compile more then one package (at least in v0.9.x) - -share.compiler_configuration = - fallback_language: globals.fallback_language - packages: [] # Each time we compile package-tap.i18n we push "package_name:arch" to this array - templates_registered_for: [] # Each time we register a template we push "package_name:arch" to this array - default_project_conf_inserted_for: [] # Keeps track of the archs we've inserted the default project conf for. - # Default project conf is inserted by the *.i18.json compiler to be used - # in case the project has no project-tap.i18n - project_tap_i18n_loaded_for: [] # Keeps track of the archs we've loaded project_tap_i18n for - - tap_i18n_input_files: [] - registerInputFile: (compileStep) -> - input_file = "#{compileStep.arch}:#{compileStep._fullInputPath}" - if input_file in @tap_i18n_input_files - # A new build cycle - @packages = [] - @templates_registered_for = [] - @default_project_conf_inserted_for = [] - @project_tap_i18n_loaded_for = [] - @tap_i18n_input_files = [] - - @tap_i18n_input_files.push(input_file) diff --git a/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.coffee b/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.coffee deleted file mode 100644 index 37d6f3027..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.coffee +++ /dev/null @@ -1,18 +0,0 @@ -path = Npm.require "path" - -compilers = share.compilers - -Plugin.registerSourceHandler "i18n", (compileStep) -> - # Starting from Meteor v1.2 registerSourceHandler doesn't - # accept filenames as handlers. - # See: https://github.com/meteor/meteor/issues/3985 - # and: https://github.com/TAPevents/tap-i18n/issues/113 - # Below is a workaround until we refactor for v1.2 new - # build plugin API. - file_name = _.last compileStep.inputPath.split(path.sep) - - if file_name == "package-tap.i18n" - compilers.package_tap_i18n(compileStep) - - if file_name == "project-tap.i18n" - compilers.project_tap_i18n(compileStep) diff --git a/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.generic_compiler.coffee b/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.generic_compiler.coffee deleted file mode 100644 index 86199b861..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.generic_compiler.coffee +++ /dev/null @@ -1,124 +0,0 @@ -path = Npm.require "path" - -helpers = share.helpers -compilers = share.compilers -compiler_configuration = share.compiler_configuration - -compilers.generic_compiler = (extension, helper) -> (compileStep) -> - compiler_configuration.registerInputFile(compileStep) - input_path = compileStep._fullInputPath - - language = path.basename(input_path).split(".").slice(0, -2).pop() - if _.isUndefined(language) or _.isEmpty(language) - compileStep.error - message: "Language-tag is not specified for *.i18n.`#{extension}' file: `#{input_path}'", - sourcePath: input_path - return - - if not RegExp("^#{globals.langauges_tags_regex}$").test(language) - compileStep.error - message: "Can't recognise '#{language}' as a language-tag: `#{input_path}'", - sourcePath: input_path - return - - translations = helper input_path, compileStep - - package_name = if helpers.isPackage(compileStep) then compileStep.packageName else globals.project_translations_domain - output = - """ - var _ = Package.underscore._, - package_name = "#{package_name}", - namespace = "#{package_name}"; - - if (package_name != "#{globals.project_translations_domain}") { - namespace = TAPi18n.packages[package_name].namespace; - } - - """ - - # only for project - if not helpers.isPackage(compileStep) - if /^(client|server)/.test(compileStep.inputPath) - compileStep.error - message: "Languages files should be common to the server and the client. Do not put them under /client or /server .", - sourcePath: input_path - return - - # add the language names to TAPi18n.languages_names - language_name = [language, language] - if language_names[language]? - language_name = language_names[language] - - if language != globals.fallback_language - # the name for the fallback_language is part of the getProjectConfJs()'s output - output += - """ - TAPi18n.languages_names["#{language}"] = #{JSON.stringify language_name}; - - """ - - # If this is a project but project-tap.i18n haven't compiled yet add default project conf - # for case there is no project-tap.i18n defined in this project. - # Reminder: we don't require projects to have project-tap.i18n - if not(helpers.isDefaultProjectConfInserted(compileStep)) and \ - not(helpers.isProjectI18nLoaded(compileStep)) - output += share.getProjectConfJs(share.project_i18n_schema.clean {}) # defined in project-tap.i18n.coffee - - helpers.markDefaultProjectConfInserted(compileStep) - - - # if fallback_language -> integrate, otherwise add to TAPi18n.translations if server arch. - if language == compiler_configuration.fallback_language - output += - """ - // integrate the fallback language translations - translations = {}; - translations[namespace] = #{JSON.stringify translations}; - TAPi18n._loadLangFileObject("#{compiler_configuration.fallback_language}", translations); - - """ - - if compileStep.archMatches "os" - if language != compiler_configuration.fallback_language - output += - """ - if(_.isUndefined(TAPi18n.translations["#{language}"])) { - TAPi18n.translations["#{language}"] = {}; - } - - if(_.isUndefined(TAPi18n.translations["#{language}"][namespace])) { - TAPi18n.translations["#{language}"][namespace] = {}; - } - - _.extend(TAPi18n.translations["#{language}"][namespace], #{JSON.stringify translations}); - - """ - - output += - """ - TAPi18n._registerServerTranslator("#{language}", namespace); - - """ - - # register i18n helper for templates, only once per web arch, only for packages - if helpers.isPackage(compileStep) - if compileStep.archMatches("web") and helpers.getCompileStepArchAndPackage(compileStep) not in compiler_configuration.templates_registered_for - output += - """ - var package_templates = _.difference(_.keys(Template), non_package_templates); - - for (var i = 0; i < package_templates.length; i++) { - var package_template = package_templates[i]; - - registerI18nTemplate(package_template); - } - - """ - compiler_configuration.templates_registered_for.push helpers.getCompileStepArchAndPackage(compileStep) - - output_path = compileStep.rootOutputPath + compileStep.inputPath.replace new RegExp("`#{extension}'$"), "js" - compileStep.addJavaScript - path: output_path, - sourcePath: input_path, - data: output, - bare: false \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.json.coffee b/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.json.coffee deleted file mode 100644 index fe65e24a0..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.json.coffee +++ /dev/null @@ -1,4 +0,0 @@ -helpers = share.helpers -compilers = share.compilers -compilers.i18n_json = compilers.generic_compiler('json', helpers.loadJSON) -Plugin.registerSourceHandler "i18n.json", compilers.i18n_json \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.yml.coffee b/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.yml.coffee deleted file mode 100644 index ec7e7f793..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/compilers/i18n.yml.coffee +++ /dev/null @@ -1,4 +0,0 @@ -helpers = share.helpers -compilers = share.compilers -compilers.i18n_yml = compilers.generic_compiler('yml', helpers.loadYAML) -Plugin.registerSourceHandler "i18n.yml", compilers.i18n_yml \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/plugin/compilers/package-tap.i18n.coffee b/packages/wekan-tap-i18n/lib/plugin/compilers/package-tap.i18n.coffee deleted file mode 100644 index 45db1a679..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/compilers/package-tap.i18n.coffee +++ /dev/null @@ -1,95 +0,0 @@ -helpers = share.helpers -compilers = share.compilers -compiler_configuration = share.compiler_configuration - -schema = new SimpleSchema - translation_function_name: - type: String - defaultValue: "__" - label: "Translation Function Name" - optional: true - helper_name: - type: String - defaultValue: "_" - label: "Helper Name" - optional: true - namespace: - type: String - defaultValue: null - label: "Translations Namespace" - optional: true - -compilers.package_tap_i18n = (compileStep) -> - compiler_configuration.registerInputFile(compileStep) - input_path = compileStep._fullInputPath - - if helpers.isPackage(compileStep) - compileStep.error - message: "More than one package-tap.i18n found for package: #{compileStep.packageName}", - sourcePath: input_path - return - - if helpers.isProjectI18nLoaded(compileStep) - compileStep.error - message: "Can't compile package-tap.i18n if project-tap.i18n is present", - sourcePath: input_path - return - - if helpers.isDefaultProjectConfInserted(compileStep) - compileStep.error - message: "package-tap.i18n should be loaded before languages files (*.i18n.json)", - sourcePath: input_path - return - - helpers.markAsPackage(compileStep) - - package_tap_i18n = helpers.loadJSON input_path, compileStep - - if not package_tap_i18n? - package_tap_i18n = schema.clean {} - schema.clean package_tap_i18n - - try - check package_tap_i18n, schema - catch error - compileStep.error - message: "File `#{file_path}' is an invalid package-tap.i18n file (#{error})", - sourcePath: input_path - return - - package_name = compileStep.packageName - - if not package_tap_i18n.namespace? - package_tap_i18n.namespace = package_name - - namespace = package_tap_i18n.namespace - - package_i18n_js_file = - """ - TAPi18n.packages["#{package_name}"] = #{JSON.stringify(package_tap_i18n)}; - - // define package's translation function (proxy to the i18next) - #{package_tap_i18n.translation_function_name} = TAPi18n._getPackageI18nextProxy("#{namespace}"); - - """ - - if compileStep.archMatches "web" - package_i18n_js_file += - """ - // define the package's templates registrar - registerI18nTemplate = TAPi18n._getRegisterHelpersProxy("#{package_name}"); - registerTemplate = registerI18nTemplate; // XXX OBSOLETE, kept for backward compatibility will be removed in the future - - // Record the list of templates prior to package load - var _ = Package.underscore._; - non_package_templates = _.keys(Template); - - """ - - compileStep.addJavaScript - path: "package-i18n.js", - sourcePath: input_path, - data: package_i18n_js_file, - bare: false - -Plugin.registerSourceHandler "package-tap.i18n", compilers.package_tap_i18n \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/plugin/compilers/project-tap.i18n.coffee b/packages/wekan-tap-i18n/lib/plugin/compilers/project-tap.i18n.coffee deleted file mode 100644 index 0187d7161..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/compilers/project-tap.i18n.coffee +++ /dev/null @@ -1,122 +0,0 @@ -helpers = share.helpers -compilers = share.compilers -compiler_configuration = share.compiler_configuration - -share.project_i18n_schema = schema = new SimpleSchema - helper_name: - type: String - defaultValue: "_" - label: "Helper Name" - optional: true - supported_languages: - type: [String] - label: "Supported Languages" - defaultValue: null - optional: true - i18n_files_route: - type: String - label: "Unified languages files path" - defaultValue: globals.browser_path - optional: true - preloaded_langs: - type: [String] - label: "Preload languages" - defaultValue: [] - optional: true - cdn_path: - type: String - label: "Unified languages files path on CDN" - defaultValue: null - optional: true - -getProjectConfJs = share.getProjectConfJs = (conf) -> - fallback_language_name = language_names[globals.fallback_language] - - project_conf_js = """ - TAPi18n._enable(#{JSON.stringify(conf)}); - TAPi18n.languages_names["#{globals.fallback_language}"] = #{JSON.stringify fallback_language_name}; - - """ - - # If we get a list of supported languages we must make sure that we'll have a - # language name for each one of its languages. - # - # Though languages names are added for every language we find i18n.json file - # for (by the i18n.json compiler). We shouldn't rely on the existence of - # *.i18n.json file for each supported language, because a language might be - # defined as supported even when it has no i18n.json files (it's especially - # true when tap:i18n is used with tap:i18n-db) - if conf.supported_languages? - for lang_tag in conf.supported_languages - if language_names[lang_tag]? - project_conf_js += """ - TAPi18n.languages_names["#{lang_tag}"] = #{JSON.stringify language_names[lang_tag]}; - - """ - - return project_conf_js - -compilers.project_tap_i18n = (compileStep) -> - compiler_configuration.registerInputFile(compileStep) - input_path = compileStep._fullInputPath - - if helpers.isPackage(compileStep) - compileStep.error - message: "Can't load project-tap.i18n in a package: #{compileStep.packageName}", - sourcePath: input_path - return - - if helpers.isProjectI18nLoaded(compileStep) - compileStep.error - message: "Can't have more than one project-tap.i18n", - sourcePath: input_path - return - - project_tap_i18n = helpers.loadJSON input_path, compileStep - - if not project_tap_i18n? - project_tap_i18n = schema.clean {} - schema.clean project_tap_i18n - - try - check project_tap_i18n, schema - catch error - compileStep.error - message: "File `#{file_path}' is an invalid project-tap.i18n file (#{error})", - sourcePath: input_path - return - - project_i18n_js_file = getProjectConfJs project_tap_i18n - - if compileStep.archMatches("web") and not _.isEmpty project_tap_i18n.preloaded_langs - preloaded_langs = "all" - if project_tap_i18n.preloaded_langs[0] != "*" - preloaded_langs = project_tap_i18n.preloaded_langs.join(",") - - project_i18n_js_file += - """ - $.ajax({ - type: 'GET', - url: "#{project_tap_i18n.i18n_files_route}/multi/#{preloaded_langs}.json", - dataType: 'json', - success: function(data) { - for (lang_tag in data) { - TAPi18n._loadLangFileObject(lang_tag, data[lang_tag]); - TAPi18n._loaded_languages.push(lang_tag); - } - }, - data: {}, - async: false - }); - - """ - - helpers.markProjectI18nLoaded(compileStep) - - compileStep.addJavaScript - path: "project-i18n.js", - sourcePath: input_path, - data: project_i18n_js_file, - bare: false - -Plugin.registerSourceHandler "project-tap.i18n", compilers.project_tap_i18n \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/plugin/compilers/share.coffee b/packages/wekan-tap-i18n/lib/plugin/compilers/share.coffee deleted file mode 100644 index 6216e2839..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/compilers/share.coffee +++ /dev/null @@ -1 +0,0 @@ -share.compilers = {} \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/plugin/etc/language_names.js b/packages/wekan-tap-i18n/lib/plugin/etc/language_names.js deleted file mode 100644 index 43d72f450..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/etc/language_names.js +++ /dev/null @@ -1,136 +0,0 @@ -language_names = { - "af": ["Afrikaans","Afrikaans"], - "ak": ["Akan","Akan"], - "sq": ["Albanian","Shqip"], - "am": ["Amharic","አማርኛ"], - "ar": ["Arabic","العربية"], - "hy": ["Armenian","Հայերեն"], - "rup": ["Aromanian","Armãneashce"], - "as": ["Assamese","অসমীয়া"], - "az": ["Azerbaijani","Azərbaycan dili"], - "az-TR": ["Azerbaijani (Turkey)","Azərbaycan Türkcəsi"], - "ba": ["Bashkir","башҡорт теле"], - "eu": ["Basque","Euskara"], - "bel": ["Belarusian","Беларуская мова"], - "bn": ["Bengali","বাংলা"], - "bs": ["Bosnian","Bosanski"], - "bg": ["Bulgarian","Български"], - "mya": ["Burmese","ဗမာစာ"], - "ca": ["Catalan","Català"], - "bal": ["Catalan (Balear)","Català (Balear)"], - "zh": ["Chinese","中文"], - "zh-CN": ["Chinese (China)","简体中文"], - "zh-HK": ["Chinese (Hong Kong)","繁體中文(香港)"], - "zh-TW": ["Chinese (Taiwan)","繁體中文(台灣)"], - "co": ["Corsican","corsu"], - "hr": ["Croatian","Hrvatski"], - "cs": ["Czech","čeština‎"], - "da": ["Danish","Dansk"], - "nl": ["Dutch","Nederlands"], - "nl-BE": ["Dutch (Belgium)","Nederlands (België)"], - "en": ["English","English"], - "en-AU": ["English (Australia)","English (Australia)"], - "en-CA": ["English (Canada)","English (Canada)"], - "en-GB": ["English (UK)","English (UK)"], - "eo": ["Esperanto","Esperanto"], - "et": ["Estonian","Eesti"], - "fo": ["Faroese","føroyskt"], - "fi": ["Finnish","Suomi"], - "fr-BE": ["French (Belgium)","Français de Belgique"], - "fr": ["French (France)","Français"], - "fy": ["Frisian","Frysk"], - "fuc": ["Fulah","Pulaar"], - "gl": ["Galician","Galego"], - "ka": ["Georgian","ქართული"], - "de": ["German","Deutsch"], - "el": ["Greek","Ελληνικά"], - "gn": ["Guaraní","Avañe'ẽ"], - "haw": ["Hawaiian","Ōlelo Hawaiʻi"], - "haz": ["Hazaragi","هزاره گی"], - "he": ["Hebrew","עברית"], - "hi": ["Hindi","हिन्दी"], - "hu": ["Hungarian","Magyar"], - "is": ["Icelandic","Íslenska"], - "id": ["Indonesian","Bahasa Indonesia"], - "ga": ["Irish","Gaelige"], - "it": ["Italian","Italiano"], - "ja": ["Japanese","日本語"], - "jv": ["Javanese","Basa Jawa"], - "kn": ["Kannada","ಕನ್ನಡ"], - "kk": ["Kazakh","Қазақ тілі"], - "km": ["Khmer","ភាសាខ្មែរ"], - "rw": ["Kinyarwanda","Kinyarwanda"], - "ky": ["Kirghiz","кыргыз тили"], - "ko": ["Korean","한국어"], - "ckb": ["Kurdish (Sorani)","كوردی‎"], - "lo": ["Lao","ພາສາລາວ"], - "lv": ["Latvian","latviešu valoda"], - "li": ["Limburgish","Limburgs"], - "lt": ["Lithuanian","Lietuvių kalba"], - "lb": ["Luxembourgish","Lëtzebuergesch"], - "mk": ["Macedonian","македонски јазик"], - "mg": ["Malagasy","Malagasy"], - "ms": ["Malay","Bahasa Melayu"], - "ml": ["Malayalam","മലയാളം"], - "mr": ["Marathi","मराठी"], - "xmf": ["Mingrelian","მარგალური ნინა"], - "mn": ["Mongolian","Монгол"], - "me": ["Montenegrin","Crnogorski jezik"], - "ne": ["Nepali","नेपाली"], - "nb": ["Norwegian (Bokmål)","Norsk bokmål"], - "nn": ["Norwegian (Nynorsk)","Norsk nynorsk"], - "os": ["Ossetic","Ирон"], - "ps": ["Pashto","پښتو"], - "fa": ["Persian","فارسی"], - "fa-AF": ["Persian (Afghanistan)","فارسی (افغانستان"], - "pl": ["Polish","Polski"], - "pt-BR": ["Portuguese (Brazil)","Português do Brasil"], - "pt": ["Portuguese (Portugal)","Português"], - "pa": ["Punjabi","ਪੰਜਾਬੀ"], - "rhg": ["Rohingya","Rohingya"], - "ro": ["Romanian","Română"], - "ru": ["Russian","Русский"], - "rue": ["Rusyn","Русиньскый"], - "sah": ["Sakha","Sakha"], - "sa-IN": ["Sanskrit","भारतम्"], - "srd": ["Sardinian","sardu"], - "gd": ["Scottish Gaelic","Gàidhlig"], - "sr": ["Serbian","Српски језик"], - "sd": ["Sindhi","سندھ"], - "si": ["Sinhala","සිංහල"], - "sk": ["Slovak","Slovenčina"], - "sl": ["Slovenian","slovenščina"], - "so": ["Somali","Afsoomaali"], - "azb": ["South Azerbaijani","گؤنئی آذربایجان"], - "es-AR": ["Spanish (Argentina)","Español de Argentina"], - "es-CL": ["Spanish (Chile)","Español de Chile"], - "es-CO": ["Spanish (Colombia)","Español de Colombia"], - "es-MX": ["Spanish (Mexico)","Español de México"], - "es-PE": ["Spanish (Peru)","Español de Perú"], - "es-PR": ["Spanish (Puerto Rico)","Español de Puerto Rico"], - "es": ["Spanish (Spain)","Español"], - "es-VE": ["Spanish (Venezuela)","Español de Venezuela"], - "su": ["Sundanese","Basa Sunda"], - "sw": ["Swahili","Kiswahili"], - "sv": ["Swedish","Svenska"], - "gsw": ["Swiss German","Schwyzerdütsch"], - "tl": ["Tagalog","Tagalog"], - "tg": ["Tajik","тоҷикӣ"], - "ta": ["Tamil","தமிழ்"], - "ta-LK": ["Tamil (Sri Lanka)","தமிழ்"], - "tt": ["Tatar","Татар теле"], - "te": ["Telugu","తెలుగు"], - "th": ["Thai","ไทย"], - "bo": ["Tibetan","བོད་སྐད"], - "tir": ["Tigrinya","ትግርኛ"], - "tr": ["Turkish","Türkçe"], - "tuk": ["Turkmen","Türkmençe"], - "ua": ["Ukrainian","Українська"], - "ug": ["Uighur","Uyƣurqə"], - "uk": ["Ukrainian","Українська"], - "ur": ["Urdu","اردو"], - "uz": ["Uzbek","O‘zbekcha"], - "vi": ["Vietnamese","Tiếng Việt"], - "wa": ["Walloon","Walon"], - "cy": ["Welsh","Cymraeg"] -}; diff --git a/packages/wekan-tap-i18n/lib/plugin/helpers/compile_step_helpers.coffee b/packages/wekan-tap-i18n/lib/plugin/helpers/compile_step_helpers.coffee deleted file mode 100644 index 0ab5a6f58..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/helpers/compile_step_helpers.coffee +++ /dev/null @@ -1,23 +0,0 @@ -compiler_configuration = share.compiler_configuration - -_.extend share.helpers, - getCompileStepArchAndPackage: (compileStep) -> - "#{compileStep.packageName}:#{compileStep.arch}" - - markAsPackage: (compileStep) -> - compiler_configuration.packages.push @.getCompileStepArchAndPackage(compileStep) - - isPackage: (compileStep) -> - @.getCompileStepArchAndPackage(compileStep) in compiler_configuration.packages - - markProjectI18nLoaded: (compileStep) -> - compiler_configuration.project_tap_i18n_loaded_for.push @.getCompileStepArchAndPackage(compileStep) - - isProjectI18nLoaded: (compileStep) -> - @.getCompileStepArchAndPackage(compileStep) in compiler_configuration.project_tap_i18n_loaded_for - - markDefaultProjectConfInserted: (compileStep) -> - compiler_configuration.default_project_conf_inserted_for.push @.getCompileStepArchAndPackage(compileStep) - - isDefaultProjectConfInserted: (compileStep) -> - @.getCompileStepArchAndPackage(compileStep) in compiler_configuration.default_project_conf_inserted_for diff --git a/packages/wekan-tap-i18n/lib/plugin/helpers/helpers.coffee b/packages/wekan-tap-i18n/lib/plugin/helpers/helpers.coffee deleted file mode 100644 index 74aff3ad7..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/helpers/helpers.coffee +++ /dev/null @@ -1 +0,0 @@ -share.helpers = {} \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/plugin/helpers/load_json.coffee b/packages/wekan-tap-i18n/lib/plugin/helpers/load_json.coffee deleted file mode 100644 index 6ead9c015..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/helpers/load_json.coffee +++ /dev/null @@ -1,25 +0,0 @@ -fs = Npm.require 'fs' -JSON.minify = JSON.minify || Npm.require("node-json-minify") - -# loads a json from file_path -# -# returns undefined if file doesn't exist null if file is empty, parsed content otherwise -_.extend share.helpers, - loadJSON: (file_path, compileStep=null) -> - try # use try/catch to avoid the additional syscall to fs.existsSync - fstats = fs.statSync file_path - catch - return undefined - - if fstats.size == 0 - return null - - try - content = JSON.parse(JSON.minify(fs.readFileSync(file_path, "utf8"))) - catch error - if compileStep? - compileStep.error - message: "Can't load `#{file_path}' JSON", - sourcePath: compileStep._fullInputPath - - throw new Error "Can't load `#{file_path}' JSON" diff --git a/packages/wekan-tap-i18n/lib/plugin/helpers/load_yml.coffee b/packages/wekan-tap-i18n/lib/plugin/helpers/load_yml.coffee deleted file mode 100644 index 77f898c33..000000000 --- a/packages/wekan-tap-i18n/lib/plugin/helpers/load_yml.coffee +++ /dev/null @@ -1,25 +0,0 @@ -fs = Npm.require 'fs' -YAML = Npm.require 'yamljs' - -# loads a yml from file_path -# -# returns undefined if file doesn't exist null if file is empty, parsed content otherwise -_.extend share.helpers, - loadYAML: (file_path, compileStep=null) -> - try # use try/catch to avoid the additional syscall to fs.existsSync - fstats = fs.statSync file_path - catch - return undefined - - if fstats.size == 0 - return null - - try - content = YAML.load(file_path) - catch error - if compileStep? - compileStep.error - message: "Can't load `#{file_path}' YAML", - sourcePath: compileStep._fullInputPath - - throw new Error "Can't load `#{file_path}' YAML" diff --git a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-client.coffee b/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-client.coffee deleted file mode 100644 index 5437cb885..000000000 --- a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-client.coffee +++ /dev/null @@ -1,198 +0,0 @@ -_.extend TAPi18n.prototype, - _languageSpecificTranslators: null - _languageSpecificTranslatorsTrackers: null - - _getLanguageFilePath: (lang_tag) -> - if not @_enabled() - return null - - path = if @.conf.cdn_path? then @.conf.cdn_path else @.conf.i18n_files_route - path = path.replace /\/$/, "" - if Meteor.isCordova and path[0] == "/" - path = Meteor.absoluteUrl().replace(/\/+$/, "") + path - - "#{path}/#{lang_tag}.json" - - _loadLanguage: (languageTag) -> - # Load languageTag and its dependencies languages to TAPi18next if we - # haven't loaded them already. - # - # languageTag dependencies languages are: - # * The base language if languageTag is a dialect. - # * The fallback language (en) if we haven't loaded it already. - # - # Returns a deferred object that resolves with no arguments if all files - # loaded successfully to TAPi18next and rejects with array of error - # messages otherwise - # - # Example: - # TAPi18n._loadLanguage("pt-BR") - # .done(function () { - # console.log("languageLoaded successfully"); - # }) - # .fail(function (messages) { - # console.log("Couldn't load languageTag", messages); - # }) - # - # The above example will attempt to load pt-BR, pt and en - - dfd = new $.Deferred() - - if not @_enabled() - return dfd.reject "tap-i18n is not enabled in the project level, check tap-i18n README" - - project_languages = @_getProjectLanguages() - - if languageTag in project_languages - if languageTag not in @_loaded_languages - loadLanguageTag = => - jqXHR = $.getJSON(@_getLanguageFilePath(languageTag)) - - jqXHR.done (data) => - @_loadLangFileObject(languageTag, data) - - @_loaded_languages.push languageTag - - dfd.resolve() - - jqXHR.fail (xhr, error_code) => - dfd.reject("Couldn't load language '#{languageTag}' JSON: #{error_code}") - - directDependencyLanguageTag = if "-" in languageTag then languageTag.replace(/-.*/, "") else fallback_language - - # load dependency language if it is part of the project and not the fallback language - if languageTag != fallback_language and directDependencyLanguageTag in project_languages - dependencyLoadDfd = @_loadLanguage directDependencyLanguageTag - - dependencyLoadDfd.done => - # All dependencies loaded successfully - loadLanguageTag() - - dependencyLoadDfd.fail (message) => - dfd.reject("Loading process failed since dependency language - '#{directDependencyLanguageTag}' failed to load: " + message) - else - loadLanguageTag() - else - # languageTag loaded already - dfd.resolve() - else - dfd.reject(["Language #{languageTag} is not supported"]) - - return dfd.promise() - - _registerHelpers: (package_name, template) -> - if package_name != globals.project_translations_domain - tapI18nextProxy = @_getPackageI18nextProxy(@packages[package_name].namespace) - else - tapI18nextProxy = @_getPackageI18nextProxy(globals.project_translations_domain) - - underscore_helper = (key, args...) -> - options = (args.pop()).hash - if not _.isEmpty(args) - options.sprintf = args - - tapI18nextProxy(key, options) - - # template specific helpers - if package_name != globals.project_translations_domain - # {{_ }} - if Template[template]? and Template[template].helpers? - helpers = {} - helpers[@packages[package_name].helper_name] = underscore_helper - Template[template].helpers(helpers) - - # global helpers - else - # {{_ }} - UI.registerHelper @conf.helper_name, underscore_helper - - # {{languageTag}} - UI.registerHelper "languageTag", () => @getLanguage() - - return - - _getRegisterHelpersProxy: (package_name) -> - # A proxy to _registerHelpers where the package_name is fixed to package_name - (template) => - @_registerHelpers(package_name, template) - - _prepareLanguageSpecificTranslator: (lang_tag) -> - dfd = (new $.Deferred()).resolve().promise() - - if lang_tag of @_languageSpecificTranslatorsTrackers - return dfd - - @_languageSpecificTranslatorsTrackers[lang_tag] = new Tracker.Dependency - - if not(lang_tag of @_languageSpecificTranslators) - dfd = @_loadLanguage(lang_tag) - .done => - @_languageSpecificTranslators[lang_tag] = @_getSpecificLangTranslator(lang_tag) - - @_languageSpecificTranslatorsTrackers[lang_tag].changed() - - return dfd - - _getPackageI18nextProxy: (package_name) -> - # A proxy to TAPi18next.t where the namespace is preset to the package's - - (key, options, lang_tag=null) => - # Devs get confused and use lang option instead of lng option, make lang - # alias of lng - if options?.lang? and not options?.lng? - options.lng = options.lang - - if options?.lng? and not lang_tag? - lang_tag = options.lng - # Remove options.lng so we won't pass it to the regular TAPi18next - # before the language specific translator is ready to keep behavior - # consistent. - # - # If lang is actually ready before the language specifc translator is - # ready, TAPi18next will translate to lang_tag if we won't remove - # options.lng. - delete options.lng - - if lang_tag? - @_prepareLanguageSpecificTranslator(lang_tag) - - @_languageSpecificTranslatorsTrackers[lang_tag].depend() - - if lang_tag of @_languageSpecificTranslators - return @_languageSpecificTranslators[lang_tag] "#{TAPi18n._getPackageDomain(package_name)}:#{key}", options - else - return TAPi18next.t "#{TAPi18n._getPackageDomain(package_name)}:#{key}", options - - # If inside a reactive computation, we want to invalidate the computation if the client lang changes - @_language_changed_tracker.depend() - - - TAPi18next.t "#{TAPi18n._getPackageDomain(package_name)}:#{key}", options - - _onceEnabled: () -> - @_registerHelpers globals.project_translations_domain - - _abortPreviousSetLang: null - setLanguage: (lang_tag) -> - self = @ - - @_abortPreviousSetLang?() - - isAborted = false - @_abortPreviousSetLang = -> isAborted = true - - @_loadLanguage(lang_tag).then => - if not isAborted - TAPi18next.setLng(lang_tag) - - @_language_changed_tracker.changed() - Session.set @_loaded_lang_session_key, lang_tag - - getLanguage: -> - if not @._enabled() - return null - - session_lang = Session.get @_loaded_lang_session_key - - if session_lang? then session_lang else @._fallback_language diff --git a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-common.coffee b/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-common.coffee deleted file mode 100644 index 23bc6e59f..000000000 --- a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-common.coffee +++ /dev/null @@ -1,143 +0,0 @@ -fallback_language = globals.fallback_language - -TAPi18n = -> - EventEmitter.call @ - - @_fallback_language = fallback_language - - @_language_changed_tracker = new Tracker.Dependency - - @_loaded_languages = [fallback_language] # stores the loaded languages, the fallback language is loaded automatically - - @conf = null # If conf isn't null we assume that tap:i18n is enabled for the project. - # We assume conf is valid, we sterilize and validate it during the build process. - - @packages = {} # Stores the packages' package-tap.i18n jsons - - @languages_names = {} # Stores languages that we've found languages files for in the project dir. - # format: - # { - # lang_tag: [lang_name_in_english, lang_name_in_local_language] - # } - - @translations = {} # Stores the packages/project translations - Server side only - # fallback_language translations are not stored here - - - if Meteor.isClient - Session.set @_loaded_lang_session_key, null - - @_languageSpecificTranslators = {} - @_languageSpecificTranslatorsTrackers = {} - - if Meteor.isServer - @server_translators = {} - - Meteor.startup => - # If tap-i18n is enabled for that project - if @_enabled() - @_registerHTTPMethod() - - @__ = @_getPackageI18nextProxy(globals.project_translations_domain) - - TAPi18next.setLng fallback_language - - return @ - -Util.inherits TAPi18n, EventEmitter - -_.extend TAPi18n.prototype, - _loaded_lang_session_key: "TAPi18n::loaded_lang" - - _enable: (conf) -> - # tap:i18n gets enabled for a project once a conf file is set for it. - # It can be either a conf object that was set by project-tap.i18n file or - # a default conf, which is being added if the project has lang files - # (*.i18n.json) but not project-tap.i18n - @conf = conf - - @._onceEnabled() - - _onceEnabled: () -> - # The arch specific code can use this for procedures that should be performed once - # tap:i18n gets enabled (project conf file is being set) - return - - _enabled: -> - # read the comment of @conf - @conf? - - _getPackageDomain: (package_name) -> - package_name.replace(/:/g, "-") - - addResourceBundle: (lang_tag, package_name, translations) -> - TAPi18next.addResourceBundle(lang_tag, @_getPackageDomain(package_name), translations) - - _getSpecificLangTranslator: (lang) -> - current_lang = TAPi18next.lng() - - translator = null - TAPi18next.setLng lang, {fixLng: true}, (lang_translator) => - translator = lang_translator - - # Restore i18next lang that had been changed in the process of generating - # lang specific translator - TAPi18next.setLng current_lang - - return translator - - _getProjectLanguages: () -> - # Return an array of languages available for the current project - if @._enabled() - if _.isArray @.conf.supported_languages - return _.union([@._fallback_language], @.conf.supported_languages) - else - # If supported_languages is null, all the languages we found - # translations files to in the project level are considered supported. - # We use the @.languages_names array to tell which languages we found - # since for every i18n.json file we found in the project level we add - # an entry for its language to @.languages_names in the build process. - # - # We also know for certain that when tap-i18n is enabled the fallback - # lang is in @.languages_names - return _.keys @.languages_names - else - return [@._fallback_language] - - getLanguages: -> - if not @._enabled() - return null - - languages = {} - for lang_tag in @._getProjectLanguages() - languages[lang_tag] = - name: @.languages_names[lang_tag][1] - en: @.languages_names[lang_tag][0] - - languages - - _loadLangFileObject: (language_tag, data) -> - for package_name, package_keys of data - # Translations that are added by loadTranslations() have higher priority - package_keys = _.extend({}, package_keys, @_loadTranslations_cache[language_tag]?[package_name] or {}) - - @addResourceBundle(language_tag, package_name, package_keys) - - _loadTranslations_cache: {} - loadTranslations: (translations, namespace) -> - project_languages = @_getProjectLanguages() - - for language_tag, translation_keys of translations - if not @_loadTranslations_cache[language_tag]? - @_loadTranslations_cache[language_tag] = {} - - if not @_loadTranslations_cache[language_tag][namespace]? - @_loadTranslations_cache[language_tag][namespace] = {} - - _.extend(@_loadTranslations_cache[language_tag][namespace], translation_keys) - - @addResourceBundle(language_tag, namespace, translation_keys) - - if Meteor.isClient and @getLanguage() == language_tag - # Retranslate if session language updated - @_language_changed_tracker.changed() \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-helpers.coffee b/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-helpers.coffee deleted file mode 100644 index 74aff3ad7..000000000 --- a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-helpers.coffee +++ /dev/null @@ -1 +0,0 @@ -share.helpers = {} \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-init.coffee b/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-init.coffee deleted file mode 100644 index f51365a4e..000000000 --- a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-init.coffee +++ /dev/null @@ -1 +0,0 @@ -TAPi18n = new TAPi18n() \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-server.coffee b/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-server.coffee deleted file mode 100644 index 014e89d0d..000000000 --- a/packages/wekan-tap-i18n/lib/tap_i18n/tap_i18n-server.coffee +++ /dev/null @@ -1,85 +0,0 @@ -_.extend TAPi18n.prototype, - server_translators: null - - _registerServerTranslator: (lang_tag, package_name) -> - if @_enabled() - if not(lang_tag of @server_translators) - @server_translators[lang_tag] = @_getSpecificLangTranslator(lang_tag) - - # fallback language is integrated, and isn't part of @translations - if lang_tag != @_fallback_language - @addResourceBundle(lang_tag, package_name, @translations[lang_tag][package_name]) - - if not(@_fallback_language of @server_translators) - @server_translators[@_fallback_language] = @_getSpecificLangTranslator(@_fallback_language) - - _registerAllServerTranslators: () -> - for lang_tag in @_getProjectLanguages() - for package_name of @translations[lang_tag] - @_registerServerTranslator(lang_tag, package_name) - - _getPackageI18nextProxy: (package_name) -> - # A proxy to TAPi18next.t where the namespace is preset to the package's - (key, options, lang_tag=null) => - if not lang_tag? - # translate to fallback_language - return @server_translators[@_fallback_language] "#{@_getPackageDomain(package_name)}:#{key}", options - else if not(lang_tag of @server_translators) - console.log "Warning: language #{lang_tag} is not supported in this project, fallback language (#{@_fallback_language})" - return @server_translators[@_fallback_language] "#{@_getPackageDomain(package_name)}:#{key}", options - else - return @server_translators[lang_tag] "#{@_getPackageDomain(package_name)}:#{key}", options - - _registerHTTPMethod: -> - self = @ - - methods = {} - - if not self._enabled() - throw new Meteor.Error 500, "tap-i18n has to be enabled in order to register the HTTP method" - - methods["#{self.conf.i18n_files_route.replace(/\/$/, "")}/multi/:langs"] = - get: () -> - if not RegExp("^((#{globals.langauges_tags_regex},)*#{globals.langauges_tags_regex}|all).json$").test(@params.langs) - return @setStatusCode(401) - - langs = @params.langs.replace ".json", "" - - if langs == "all" - output = self.translations - else - output = {} - - langs = langs.split(",") - for lang_tag in langs - if lang_tag in self._getProjectLanguages() and \ - lang_tag != self._fallback_language # fallback language is integrated to the bundle - language_translations = self.translations[lang_tag] - - if language_translations? - output[lang_tag] = language_translations - - return JSON.stringify(output) - - methods["#{self.conf.i18n_files_route.replace(/\/$/, "")}/:lang"] = - get: () -> - if not RegExp("^#{globals.langauges_tags_regex}.json$").test(@params.lang) - return @setStatusCode(401) - - lang_tag = @params.lang.replace ".json", "" - - if lang_tag not in self._getProjectLanguages() or \ - lang_tag == self._fallback_language # fallback language is integrated to the bundle - return @setStatusCode(404) # not found - - language_translations = self.translations[lang_tag] - # returning {} if lang_tag is not in translations allows the project - # developer to force a language supporte with project-tap.i18n's - # supported_languages property, even if that language has no lang - # files. - return JSON.stringify(if language_translations? then language_translations else {}) - - HTTP.methods methods - - _onceEnabled: -> - @_registerAllServerTranslators() \ No newline at end of file diff --git a/packages/wekan-tap-i18n/lib/tap_i18next/tap_i18next-1.7.3.js b/packages/wekan-tap-i18n/lib/tap_i18next/tap_i18next-1.7.3.js deleted file mode 100644 index dbf4f810b..000000000 --- a/packages/wekan-tap-i18n/lib/tap_i18next/tap_i18next-1.7.3.js +++ /dev/null @@ -1,2748 +0,0 @@ -// tap_i18next is a copy of i18next that expose i18next to the global namespace -// under the name name TAPi18next instead of i18n to (1) avoid interfering with other -// Meteor packages that might use i18n with different configurations than we do -// or worse - (2) using a different version of i18next -// -// setJqueryExt is disabled by default in TAPi18next -// sprintf is a default postProcess in TAPi18next -// -// TAPi18next is set outside of the singleton builder to make it available in the -// package level - -// i18next, v1.7.3 -// Copyright (c)2014 Jan Mühlemann (jamuhl). -// Distributed under MIT license -// http://i18next.com - -// set TAPi18next outside of the singleton builder to make it available in the package level -TAPi18next = {}; -(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 - , 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 = TAPi18next; - } else { - if ($) { - $.TAPi18next = $.TAPi18next || TAPi18next; - } - - root.TAPi18next = root.TAPi18next || TAPi18next; - } - // 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: false, - defaultValueFromContent: true, - useDataAttrOptions: false, - cookieExpirationTime: undefined, - useCookie: true, - cookieName: 'TAPi18next', - cookieDomain: undefined, - - objectTreeKeyHandler: undefined, - postProcess: ["sprintf"], - 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 - TAPi18next.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) { - TAPi18next.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) TAPi18next.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.TAPi18next = 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) { - if (typeof options === 'undefined') { - 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 (typeof options !== "undefined" && options !== null && 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; - - TAPi18next.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<parms.length; i++) { - var pos = parms[i].indexOf('='); - if (pos > 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(n == 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 - TAPi18next.init = init; - TAPi18next.setLng = setLng; - TAPi18next.preload = preload; - TAPi18next.addResourceBundle = addResourceBundle; - TAPi18next.removeResourceBundle = removeResourceBundle; - TAPi18next.loadNamespace = loadNamespace; - TAPi18next.loadNamespaces = loadNamespaces; - TAPi18next.setDefaultNamespace = setDefaultNamespace; - TAPi18next.t = translate; - TAPi18next.translate = translate; - TAPi18next.exists = exists; - TAPi18next.detectLanguage = f.detectLanguage; - TAPi18next.pluralExtensions = pluralExtensions; - TAPi18next.sync = sync; - TAPi18next.functions = f; - TAPi18next.lng = lng; - TAPi18next.addPostProcessor = addPostProcessor; - TAPi18next.options = o; -})(); diff --git a/packages/wekan-tap-i18n/lib/tap_i18next/tap_i18next_init.js b/packages/wekan-tap-i18n/lib/tap_i18next/tap_i18next_init.js deleted file mode 100644 index 8c2010744..000000000 --- a/packages/wekan-tap-i18n/lib/tap_i18next/tap_i18next_init.js +++ /dev/null @@ -1 +0,0 @@ -TAPi18next.init({resStore: {}, fallbackLng: globals.fallback_language, useCookie: false}); diff --git a/packages/wekan-tap-i18n/package.js b/packages/wekan-tap-i18n/package.js deleted file mode 100644 index 3c947c00f..000000000 --- a/packages/wekan-tap-i18n/package.js +++ /dev/null @@ -1,78 +0,0 @@ -Package.describe({ - name: 'wekan-tap-i18n', - summary: 'A comprehensive internationalization solution for Meteor', - version: '1.8.1', - git: 'https://github.com/TAPevents/tap-i18n' -}); - -both = ['server', 'client']; -server = 'server'; -client = 'client'; - -Package.onUse(function (api) { - api.versionsFrom('0.9.4'); - - api.use('coffeescript', both); - api.use('underscore', both); - api.use('meteor', both); - - api.use('raix:eventemitter@0.1.1', both); - api.use('meteorspark:util@0.2.0', both); - - api.use('tracker', both); - api.use('session', client); - api.use('jquery', client); - api.use('templating', client); - - // load TAPi18n - api.addFiles('lib/globals.js', both); - - // load and init TAPi18next - api.addFiles('lib/tap_i18next/tap_i18next-1.7.3.js', both); - api.export('TAPi18next'); - api.addFiles('lib/tap_i18next/tap_i18next_init.js', both); - - api.addFiles('lib/tap_i18n/tap_i18n-helpers.coffee', both); - - // We use the bare option since we need TAPi18n in the package level and - // coffee adds vars to all (so without bare all vars are in the file level) - api.addFiles('lib/tap_i18n/tap_i18n-common.coffee', server); - api.addFiles('lib/tap_i18n/tap_i18n-common.coffee', client, {bare: true}); - - api.addFiles('lib/tap_i18n/tap_i18n-server.coffee', server); - api.addFiles('lib/tap_i18n/tap_i18n-client.coffee', client, {bare: true}); - - api.addFiles('lib/tap_i18n/tap_i18n-init.coffee', server); - api.addFiles('lib/tap_i18n/tap_i18n-init.coffee', client, {bare: true}); - - api.export('TAPi18n'); -}); - -Package.registerBuildPlugin({ - name: 'tap-i18n-compiler', - use: ['coffeescript', 'underscore', 'aldeed:simple-schema@1.3.0', 'check@1.0.3', 'templating'], - npmDependencies: { - "node-json-minify": "0.1.3-a", - "yamljs": "0.2.4" - }, - sources: [ - 'lib/globals.js', - - 'lib/plugin/etc/language_names.js', - - 'lib/plugin/compiler_configuration.coffee', - - 'lib/plugin/helpers/helpers.coffee', - 'lib/plugin/helpers/load_json.coffee', - 'lib/plugin/helpers/load_yml.coffee', - 'lib/plugin/helpers/compile_step_helpers.coffee', - - 'lib/plugin/compilers/share.coffee', - 'lib/plugin/compilers/i18n.coffee', - 'lib/plugin/compilers/project-tap.i18n.coffee', - 'lib/plugin/compilers/package-tap.i18n.coffee', - 'lib/plugin/compilers/i18n.generic_compiler.coffee', - 'lib/plugin/compilers/i18n.json.coffee', - 'lib/plugin/compilers/i18n.yml.coffee' - ] -}); diff --git a/server/migrations.js b/server/migrations.js index 08a5cc72f..cb1c91bee 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; import AccountSettings from '../models/accountSettings'; import TableVisibilityModeSettings from '../models/tableVisibilityModeSettings'; import Actions from '../models/actions'; diff --git a/server/notifications/email.js b/server/notifications/email.js index b696aa90b..301bd471e 100644 --- a/server/notifications/email.js +++ b/server/notifications/email.js @@ -1,3 +1,4 @@ +import { TAPi18n } from '/imports/i18n'; //var nodemailer = require('nodemailer'); // buffer each user's email text in a queue, then flush them in single email diff --git a/server/notifications/outgoing.js b/server/notifications/outgoing.js index 1de7824e6..aa99ff937 100644 --- a/server/notifications/outgoing.js +++ b/server/notifications/outgoing.js @@ -1,3 +1,5 @@ +import { TAPi18n } from '/imports/i18n'; + if (Meteor.isServer) { const postCatchError = Meteor.wrapAsync((url, options, resolve) => { HTTP.post(url, options, (err, res) => {