浏览代码

Merge branch 'GhassenRjab-devel' into devel

Import Wekan board. Thanks to GhassenRjab !
Lauri Ojansivu 8 年之前
父节点
当前提交
1aea465a23

+ 3 - 2
CHANGELOG.md

@@ -6,13 +6,14 @@ This release adds the following new features:
   [related fix](https://github.com/wekan/wekan/pull/1097);
   [related fix](https://github.com/wekan/wekan/pull/1097);
 * [When finished input of checklist item, open new checklist
 * [When finished input of checklist item, open new checklist
   item](https://github.com/wekan/wekan/pull/1099);
   item](https://github.com/wekan/wekan/pull/1099);
-* [Improve UI design of checklist items](https://github.com/wekan/wekan/pull/1108).
+* [Improve UI design of checklist items](https://github.com/wekan/wekan/pull/1108);
+* [Import Wekan board](https://github.com/wekan/wekan/pull/1117).
 
 
 and fixes the following bugs:
 and fixes the following bugs:
 
 
 * [Possible to add empty item to checklist](https://github.com/wekan/wekan/pull/1107).
 * [Possible to add empty item to checklist](https://github.com/wekan/wekan/pull/1107).
 
 
-Thanks to GitHub users nztqa and zarnifoulette for their contributions!
+Thanks to GitHub users GhassenRjab, nztqa and zarnifoulette for their contributions.
 
 
 # v0.27 2017-06-28 Wekan release
 # v0.27 2017-06-28 Wekan release
 
 

+ 7 - 1
client/components/boards/boardHeader.jade

@@ -191,8 +191,14 @@ template(name="createBoard")
     input.primary.wide(type="submit" value="{{_ 'create'}}")
     input.primary.wide(type="submit" value="{{_ 'create'}}")
     span.quiet
     span.quiet
       | {{_ 'or'}}
       | {{_ 'or'}}
-      a(href="{{pathFor 'import'}}") {{_ 'import-board'}}
+      a.js-import-board {{_ 'import-board'}}
 
 
+template(name="chooseBoardSource")
+  ul.pop-over-list
+    li
+      a(href="{{pathFor 'import/trello'}}") {{_ 'from-trello'}}
+    li
+      a(href="{{pathFor 'import/wekan'}}") {{_ 'from-wekan'}}
 
 
 template(name="boardChangeTitlePopup")
 template(name="boardChangeTitlePopup")
   form
   form

+ 7 - 0
client/components/boards/boardHeader.js

@@ -174,10 +174,17 @@ const CreateBoard = BlazeComponent.extendComponent({
       'click .js-change-visibility': this.toggleVisibilityMenu,
       'click .js-change-visibility': this.toggleVisibilityMenu,
       'click .js-import': Popup.open('boardImportBoard'),
       'click .js-import': Popup.open('boardImportBoard'),
       submit: this.onSubmit,
       submit: this.onSubmit,
+      'click .js-import-board': Popup.open('chooseBoardSource'),
     }];
     }];
   },
   },
 }).register('createBoardPopup');
 }).register('createBoardPopup');
 
 
+BlazeComponent.extendComponent({
+  template() {
+    return 'chooseBoardSource';
+  },
+}).register('chooseBoardSourcePopup');
+
 (class HeaderBarCreateBoard extends CreateBoard {
 (class HeaderBarCreateBoard extends CreateBoard {
   onSubmit(evt) {
   onSubmit(evt) {
     super.onSubmit(evt);
     super.onSubmit(evt);

+ 2 - 2
client/components/import/import.jade

@@ -2,7 +2,7 @@ template(name="importHeaderBar")
   h1
   h1
     a.back-btn(href="{{pathFor 'home'}}")
     a.back-btn(href="{{pathFor 'home'}}")
       i.fa.fa-chevron-left
       i.fa.fa-chevron-left
-    | {{_ 'import-board-title'}}
+    | {{_ title}}
 
 
 template(name="import")
 template(name="import")
   .wrapper
   .wrapper
@@ -12,7 +12,7 @@ template(name="import")
 
 
 template(name="importTextarea")
 template(name="importTextarea")
   form
   form
-    p: label(for='import-textarea') {{_ 'import-board-trello-instruction'}}
+    p: label(for='import-textarea') {{_ instruction}}
     textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus)
     textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus)
       | {{jsonText}}
       | {{jsonText}}
     input.primary.wide(type="submit" value="{{_ 'import'}}")
     input.primary.wide(type="submit" value="{{_ 'import'}}")

+ 32 - 16
client/components/import/import.js

@@ -1,3 +1,12 @@
+import trelloMembersMapper from './trelloMembersMapper';
+import wekanMembersMapper from './wekanMembersMapper';
+
+BlazeComponent.extendComponent({
+  title() {
+    return `import-board-title-${Session.get('importSource')}!`;
+  },
+}).register('importHeaderBar');
+
 BlazeComponent.extendComponent({
 BlazeComponent.extendComponent({
   onCreated() {
   onCreated() {
     this.error = new ReactiveVar('');
     this.error = new ReactiveVar('');
@@ -5,6 +14,7 @@ BlazeComponent.extendComponent({
     this._currentStepIndex = new ReactiveVar(0);
     this._currentStepIndex = new ReactiveVar(0);
     this.importedData = new ReactiveVar();
     this.importedData = new ReactiveVar();
     this.membersToMap = new ReactiveVar([]);
     this.membersToMap = new ReactiveVar([]);
+    this.importSource = Session.get('importSource');
   },
   },
 
 
   currentTemplate() {
   currentTemplate() {
@@ -27,7 +37,10 @@ BlazeComponent.extendComponent({
       const dataObject = JSON.parse(dataJson);
       const dataObject = JSON.parse(dataJson);
       this.setError('');
       this.setError('');
       this.importedData.set(dataObject);
       this.importedData.set(dataObject);
-      this._prepareAdditionalData(dataObject);
+      const membersToMap = this._prepareAdditionalData(dataObject);
+      // store members data and mapping in Session
+      // (we go deep and 2-way, so storing in data context is not a viable option)
+      this.membersToMap.set(membersToMap);
       this.nextStep();
       this.nextStep();
     } catch (e) {
     } catch (e) {
       this.setError('error-json-malformed');
       this.setError('error-json-malformed');
@@ -51,7 +64,10 @@ BlazeComponent.extendComponent({
       additionalData.membersMapping = mappingById;
       additionalData.membersMapping = mappingById;
     }
     }
     this.membersToMap.set([]);
     this.membersToMap.set([]);
-    Meteor.call('importTrelloBoard', this.importedData.get(), additionalData,
+    Meteor.call('importBoard',
+      this.importedData.get(),
+      additionalData,
+      this.importSource,
       (err, res) => {
       (err, res) => {
         if (err) {
         if (err) {
           this.setError(err.error);
           this.setError(err.error);
@@ -63,20 +79,16 @@ BlazeComponent.extendComponent({
   },
   },
 
 
   _prepareAdditionalData(dataObject) {
   _prepareAdditionalData(dataObject) {
-    // we will work on the list itself (an ordered array of objects) when a
-    // mapping is done, we add a 'wekan' field to the object representing the
-    // imported member
-    const membersToMap = dataObject.members;
-    // auto-map based on username
-    membersToMap.forEach((importedMember) => {
-      const wekanUser = Users.findOne({ username: importedMember.username });
-      if (wekanUser) {
-        importedMember.wekanId = wekanUser._id;
-      }
-    });
-    // store members data and mapping in Session
-    // (we go deep and 2-way, so storing in data context is not a viable option)
-    this.membersToMap.set(membersToMap);
+    const importSource = Session.get('importSource');
+    let membersToMap;
+    switch (importSource) {
+    case 'trello':
+      membersToMap = trelloMembersMapper.getMembersToMap(dataObject);
+      break;
+    case 'wekan':
+      membersToMap = wekanMembersMapper.getMembersToMap(dataObject);
+      break;
+    }
     return membersToMap;
     return membersToMap;
   },
   },
 
 
@@ -90,6 +102,10 @@ BlazeComponent.extendComponent({
     return 'importTextarea';
     return 'importTextarea';
   },
   },
 
 
+  instruction() {
+    return `import-board-instruction-${Session.get('importSource')}!`;
+  },
+
   events() {
   events() {
     return [{
     return [{
       submit(evt) {
       submit(evt) {

+ 14 - 0
client/components/import/trelloMembersMapper.js

@@ -0,0 +1,14 @@
+export function getMembersToMap(data) {
+  // we will work on the list itself (an ordered array of objects) when a
+  // mapping is done, we add a 'wekan' field to the object representing the
+  // imported member
+  const membersToMap = data.members;
+  // auto-map based on username
+  membersToMap.forEach((importedMember) => {
+    const wekanUser = Users.findOne({ username: importedMember.username });
+    if (wekanUser) {
+      importedMember.wekanId = wekanUser._id;
+    }
+  });
+  return membersToMap;
+}

+ 24 - 0
client/components/import/wekanMembersMapper.js

@@ -0,0 +1,24 @@
+export function getMembersToMap(data) {
+  // we will work on the list itself (an ordered array of objects) when a
+  // mapping is done, we add a 'wekan' field to the object representing the
+  // imported member
+  const membersToMap = data.members;
+  const users = data.users;
+  // auto-map based on username
+  membersToMap.forEach((importedMember) => {
+    importedMember.id = importedMember.userId;
+    delete importedMember.userId;
+    const user = users.filter((user) => {
+      return user._id === importedMember.id;
+    })[0];
+    if (user.profile && user.profile.fullname) {
+      importedMember.fullName = user.profile.fullname;
+    }
+    importedMember.username = user.username;
+    const wekanUser = Users.findOne({ username: importedMember.username });
+    if (wekanUser) {
+      importedMember.wekanId = wekanUser._id;
+    }
+  });
+  return membersToMap;
+}

+ 9 - 11
config/router.js

@@ -80,19 +80,16 @@ FlowRouter.route('/shortcuts', {
   },
   },
 });
 });
 
 
-FlowRouter.route('/import', {
+FlowRouter.route('/import/:source', {
   name: 'import',
   name: 'import',
-  triggersEnter: [
-    AccountsTemplates.ensureSignedIn,
-    () => {
-      Session.set('currentBoard', null);
-      Session.set('currentCard', null);
+  triggersEnter: [AccountsTemplates.ensureSignedIn],
+  action(params) {
+    Session.set('currentBoard', null);
+    Session.set('currentCard', null);
+    Session.set('importSource', params.source);
 
 
-      Filter.reset();
-      EscapeActions.executeAll();
-    },
-  ],
-  action() {
+    Filter.reset();
+    EscapeActions.executeAll();
     BlazeLayout.render('defaultLayout', {
     BlazeLayout.render('defaultLayout', {
       headerBar: 'importHeaderBar',
       headerBar: 'importHeaderBar',
       content: 'import',
       content: 'import',
@@ -132,6 +129,7 @@ const redirections = {
   '/boards': '/',
   '/boards': '/',
   '/boards/:id/:slug': '/b/:id/:slug',
   '/boards/:id/:slug': '/b/:id/:slug',
   '/boards/:id/:slug/:cardId': '/b/:id/:slug/:cardId',
   '/boards/:id/:slug/:cardId': '/b/:id/:slug/:cardId',
+  '/import': '/import/trello',
 };
 };
 
 
 _.each(redirections, (newPath, oldPath) => {
 _.each(redirections, (newPath, oldPath) => {

+ 8 - 3
i18n/ar.i18n.json

@@ -145,6 +145,7 @@
     "computer": "حاسوب",
     "computer": "حاسوب",
     "create": "إنشاء",
     "create": "إنشاء",
     "createBoardPopup-title": "إنشاء لوحة",
     "createBoardPopup-title": "إنشاء لوحة",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "إنشاء علامة",
     "createLabelPopup-title": "إنشاء علامة",
     "current": "الحالي",
     "current": "الحالي",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "إنشاء لوحة",
     "headerBarCreateBoardPopup-title": "إنشاء لوحة",
     "home": "الرئيسية",
     "home": "الرئيسية",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/br.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Krouiñ",
     "create": "Krouiñ",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/ca.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Ordinador",
     "computer": "Ordinador",
     "create": "Crea",
     "create": "Crea",
     "createBoardPopup-title": "Crea tauler",
     "createBoardPopup-title": "Crea tauler",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Crea etiqueta",
     "createLabelPopup-title": "Crea etiqueta",
     "current": "Actual",
     "current": "Actual",
     "date": "Data",
     "date": "Data",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Crea tauler",
     "headerBarCreateBoardPopup-title": "Crea tauler",
     "home": "Inici",
     "home": "Inici",
     "import": "importa",
     "import": "importa",
-    "import-board": "Importa des de Trello",
-    "import-board-title": "Importa tauler des de Trello",
-    "import-board-trello-instruction": "En el teu tauler Trello, ves a 'Menú', 'Més'.' Imprimir i Exportar', 'Exportar JSON', i copia el text resultant.",
+    "import-board": "import board",
+    "import-board-title-trello": "Importa tauler des de Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "En el teu tauler Trello, ves a 'Menú', 'Més'.' Imprimir i Exportar', 'Exportar JSON', i copia el text resultant.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Aferra codi JSON vàlid aquí",
     "import-json-placeholder": "Aferra codi JSON vàlid aquí",
     "import-map-members": "Mapeja el membres",
     "import-map-members": "Mapeja el membres",
     "import-members-map": "El tauler importat conté membres. Assigna els membres que vulguis importar a usuaris Wekan",
     "import-members-map": "El tauler importat conté membres. Assigna els membres que vulguis importar a usuaris Wekan",

+ 8 - 3
i18n/cs.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Počítač",
     "computer": "Počítač",
     "create": "Vytvořit",
     "create": "Vytvořit",
     "createBoardPopup-title": "Vytvořit tablo",
     "createBoardPopup-title": "Vytvořit tablo",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Vytvořit štítek",
     "createLabelPopup-title": "Vytvořit štítek",
     "current": "Aktuální",
     "current": "Aktuální",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Vytvořit tablo",
     "headerBarCreateBoardPopup-title": "Vytvořit tablo",
     "home": "Domů",
     "home": "Domů",
     "import": "Import",
     "import": "Import",
-    "import-board": "Importovat ze služby Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "Na svém Trello tablu, otevři 'Menu', pak 'More', 'Print and Export', 'Export JSON', a zkopíruj výsledný text",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "Na svém Trello tablu, otevři 'Menu', pak 'More', 'Print and Export', 'Export JSON', a zkopíruj výsledný text",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Sem vlož validní JSON data",
     "import-json-placeholder": "Sem vlož validní JSON data",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Toto importované tablo obsahuje několik členů. Namapuj členy z importu na uživatelské účty Wekan.",
     "import-members-map": "Toto importované tablo obsahuje několik členů. Namapuj členy z importu na uživatelské účty Wekan.",

+ 8 - 3
i18n/de.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Erstellen",
     "create": "Erstellen",
     "createBoardPopup-title": "Board erstellen",
     "createBoardPopup-title": "Board erstellen",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Label erstellen",
     "createLabelPopup-title": "Label erstellen",
     "current": "aktuell",
     "current": "aktuell",
     "date": "Datum",
     "date": "Datum",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Board erstellen",
     "headerBarCreateBoardPopup-title": "Board erstellen",
     "home": "Home",
     "home": "Home",
     "import": "Importieren",
     "import": "Importieren",
-    "import-board": "von Trello importieren",
-    "import-board-title": "Board von Trello importieren",
-    "import-board-trello-instruction": "Gehen Sie in ihrem Trello-Board auf 'Menü', dann 'Mehr', 'Drucken und Exportieren', 'JSON-Export' und kopieren Sie den dort angezeigten Text",
+    "import-board": "import board",
+    "import-board-title-trello": "Board von Trello importieren",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "Gehen Sie in ihrem Trello-Board auf 'Menü', dann 'Mehr', 'Drucken und Exportieren', 'JSON-Export' und kopieren Sie den dort angezeigten Text",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Fügen Sie die korrekten JSON-Daten hier ein",
     "import-json-placeholder": "Fügen Sie die korrekten JSON-Daten hier ein",
     "import-map-members": "Mitglieder zuordnen",
     "import-map-members": "Mitglieder zuordnen",
     "import-members-map": "Das importierte Board hat einige Mitglieder. Bitte ordnen Sie die Mitglieder, die importiert werden sollen, Wekan-Nutzern zu",
     "import-members-map": "Das importierte Board hat einige Mitglieder. Bitte ordnen Sie die Mitglieder, die importiert werden sollen, Wekan-Nutzern zu",

+ 8 - 3
i18n/en-GB.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Create",
     "create": "Create",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/en.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Create",
     "create": "Create",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/eo.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Komputilo",
     "computer": "Komputilo",
     "create": "Krei",
     "create": "Krei",
     "createBoardPopup-title": "Krei ",
     "createBoardPopup-title": "Krei ",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Dato",
     "date": "Dato",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Krei ",
     "headerBarCreateBoardPopup-title": "Krei ",
     "home": "Hejmo",
     "home": "Hejmo",
     "import": "Importi",
     "import": "Importi",
-    "import-board": "Importi de Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/es.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Ordenador",
     "computer": "Ordenador",
     "create": "Crear",
     "create": "Crear",
     "createBoardPopup-title": "Crear tablero",
     "createBoardPopup-title": "Crear tablero",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Crear etiqueta",
     "createLabelPopup-title": "Crear etiqueta",
     "current": "actual",
     "current": "actual",
     "date": "Fecha",
     "date": "Fecha",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Crear tablero",
     "headerBarCreateBoardPopup-title": "Crear tablero",
     "home": "Inicio",
     "home": "Inicio",
     "import": "Importar",
     "import": "Importar",
-    "import-board": "importar desde Trello",
-    "import-board-title": "Importar tablero desde Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board": "import board",
+    "import-board-title-trello": "Importar tablero desde Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Mapa de miembros",
     "import-map-members": "Mapa de miembros",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/eu.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Ordenagailua",
     "computer": "Ordenagailua",
     "create": "Sortu",
     "create": "Sortu",
     "createBoardPopup-title": "Sortu arbela",
     "createBoardPopup-title": "Sortu arbela",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Sortu etiketa",
     "createLabelPopup-title": "Sortu etiketa",
     "current": "unekoa",
     "current": "unekoa",
     "date": "Data",
     "date": "Data",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Sortu arbela",
     "headerBarCreateBoardPopup-title": "Sortu arbela",
     "home": "Hasiera",
     "home": "Hasiera",
     "import": "Inportatu",
     "import": "Inportatu",
-    "import-board": "Inportatu Trellotik",
-    "import-board-title": "Inportatu arbela Trellotik",
-    "import-board-trello-instruction": "Zure Trello arbelean, aukeratu 'Menu\", 'More', 'Print and Export', 'Export JSON', eta kopiatu jasotako testua hemen.",
+    "import-board": "import board",
+    "import-board-title-trello": "Inportatu arbela Trellotik",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "Zure Trello arbelean, aukeratu 'Menu\", 'More', 'Print and Export', 'Export JSON', eta kopiatu jasotako testua hemen.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Isatsi baliozko JSON datuak hemen",
     "import-json-placeholder": "Isatsi baliozko JSON datuak hemen",
     "import-map-members": "Kideen mapa",
     "import-map-members": "Kideen mapa",
     "import-members-map": "Inportatu duzun arbela kide batzuk ditu, mesedez lotu inportatu nahi dituzun kideak Wekan erabiltzaileekin",
     "import-members-map": "Inportatu duzun arbela kide batzuk ditu, mesedez lotu inportatu nahi dituzun kideak Wekan erabiltzaileekin",

+ 8 - 3
i18n/fa.i18n.json

@@ -145,6 +145,7 @@
     "computer": "رایانه",
     "computer": "رایانه",
     "create": "ایجاد",
     "create": "ایجاد",
     "createBoardPopup-title": "ایجاد تخته",
     "createBoardPopup-title": "ایجاد تخته",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "ایجاد برچسب",
     "createLabelPopup-title": "ایجاد برچسب",
     "current": "جاری",
     "current": "جاری",
     "date": "تاریخ",
     "date": "تاریخ",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "ایجاد تخته",
     "headerBarCreateBoardPopup-title": "ایجاد تخته",
     "home": "خانه",
     "home": "خانه",
     "import": "وارد کردن",
     "import": "وارد کردن",
-    "import-board": "وارد کردن از Trello",
-    "import-board-title": "وارد کردن تخته ها از Trello",
-    "import-board-trello-instruction": "در Trello-ی خود  به 'Menu'، 'More'، 'Print'، 'Export to JSON رفته و متن نهایی را دراینجا وارد نمایید.",
+    "import-board": "import board",
+    "import-board-title-trello": "وارد کردن تخته ها از Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "در Trello-ی خود  به 'Menu'، 'More'، 'Print'، 'Export to JSON رفته و متن نهایی را دراینجا وارد نمایید.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "اطلاعات Json معتبر خود را اینجا وارد کنید.",
     "import-json-placeholder": "اطلاعات Json معتبر خود را اینجا وارد کنید.",
     "import-map-members": "نگاشت اعضا",
     "import-map-members": "نگاشت اعضا",
     "import-members-map": "تعدادی عضو در تخته وارد شده می باشد. لطفا کاربرانی که باید وارد نرم افزار بشوند را مشخص کنید.",
     "import-members-map": "تعدادی عضو در تخته وارد شده می باشد. لطفا کاربرانی که باید وارد نرم افزار بشوند را مشخص کنید.",

+ 8 - 3
i18n/fi.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Tietokone",
     "computer": "Tietokone",
     "create": "Luo",
     "create": "Luo",
     "createBoardPopup-title": "Luo taulu",
     "createBoardPopup-title": "Luo taulu",
+    "chooseBoardSourcePopup-title": "Tuo taulu",
     "createLabelPopup-title": "Luo tunniste",
     "createLabelPopup-title": "Luo tunniste",
     "current": "nykyinen",
     "current": "nykyinen",
     "date": "Päivämäärä",
     "date": "Päivämäärä",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Luo taulu",
     "headerBarCreateBoardPopup-title": "Luo taulu",
     "home": "Koti",
     "home": "Koti",
     "import": "Tuo",
     "import": "Tuo",
-    "import-board": "tuo Trellosta",
-    "import-board-title": "Tuo taulu Trellosta",
-    "import-board-trello-instruction": "Trello taulullasi, mene 'Menu', sitten 'More', 'Print and Export', 'Export JSON', ja kopioi tuloksena saamasi teksti",
+    "import-board": "tuo taulu",
+    "import-board-title-trello": "Tuo taulu Trellosta",
+    "import-board-title-wekan": "Tuo taulu Wekanista",
+    "from-trello": "Trellosta",
+    "from-wekan": "Wekanista",
+    "import-board-instruction-trello": "Trello taulullasi, mene 'Menu', sitten 'More', 'Print and Export', 'Export JSON', ja kopioi tuloksena saamasi teksti",
+    "import-board-instruction-wekan": "Wekan taulullasi, mene 'Valikko', sitten 'Vie taulu', ja kopioi teksti ladatusta tiedostosta.",
     "import-json-placeholder": "Liitä kelvollinen JSON tietosi tähän",
     "import-json-placeholder": "Liitä kelvollinen JSON tietosi tähän",
     "import-map-members": "Vastaavat jäsenet",
     "import-map-members": "Vastaavat jäsenet",
     "import-members-map": "Tuomallasi taululla on muutamia jäseniä. Ole hyvä ja valitse tuomiasi jäseniä vastaavat  Wekan käyttäjät",
     "import-members-map": "Tuomallasi taululla on muutamia jäseniä. Ole hyvä ja valitse tuomiasi jäseniä vastaavat  Wekan käyttäjät",

+ 8 - 3
i18n/fr.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Ordinateur",
     "computer": "Ordinateur",
     "create": "Créer",
     "create": "Créer",
     "createBoardPopup-title": "Créer un tableau",
     "createBoardPopup-title": "Créer un tableau",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Créer une étiquette",
     "createLabelPopup-title": "Créer une étiquette",
     "current": "actuel",
     "current": "actuel",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Créer un tableau",
     "headerBarCreateBoardPopup-title": "Créer un tableau",
     "home": "Accueil",
     "home": "Accueil",
     "import": "Importer",
     "import": "Importer",
-    "import-board": "Importer depuis Trello",
-    "import-board-title": "Importer le tableau depuis Trello",
-    "import-board-trello-instruction": "Dans votre tableau Trello, allez sur 'Menu', puis sur 'Plus', 'Imprimer et exporter', 'Exporter en JSON' et copiez le texte du résultat",
+    "import-board": "import board",
+    "import-board-title-trello": "Importer le tableau depuis Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "Dans votre tableau Trello, allez sur 'Menu', puis sur 'Plus', 'Imprimer et exporter', 'Exporter en JSON' et copiez le texte du résultat",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Collez ici les données JSON valides",
     "import-json-placeholder": "Collez ici les données JSON valides",
     "import-map-members": "Faire correspondre aux membres",
     "import-map-members": "Faire correspondre aux membres",
     "import-members-map": "Le tableau que vous venez d'importer contient des membres. Veuillez associer les membres que vous souhaitez importer à des utilisateurs de Wekan.",
     "import-members-map": "Le tableau que vous venez d'importer contient des membres. Veuillez associer les membres que vous souhaitez importer à des utilisateurs de Wekan.",

+ 8 - 3
i18n/he.i18n.json

@@ -145,6 +145,7 @@
     "computer": "מחשב",
     "computer": "מחשב",
     "create": "יצירה",
     "create": "יצירה",
     "createBoardPopup-title": "יצירת לוח",
     "createBoardPopup-title": "יצירת לוח",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "יצירת תווית",
     "createLabelPopup-title": "יצירת תווית",
     "current": "נוכחי",
     "current": "נוכחי",
     "date": "תאריך",
     "date": "תאריך",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "יצירת לוח",
     "headerBarCreateBoardPopup-title": "יצירת לוח",
     "home": "בית",
     "home": "בית",
     "import": "ייבוא",
     "import": "ייבוא",
-    "import-board": "ייבוא מטרלו",
-    "import-board-title": "ייבוא לוח מטרלו",
-    "import-board-trello-instruction": "בלוח הטרלו שלך, עליך ללחוץ על 'Menu', ואז על 'More',‏ 'Print and Export',‏ 'Export JSON' ולהעתיק את הטקסט שנוצר.",
+    "import-board": "import board",
+    "import-board-title-trello": "ייבוא לוח מטרלו",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "בלוח הטרלו שלך, עליך ללחוץ על 'Menu', ואז על 'More',‏ 'Print and Export',‏ 'Export JSON' ולהעתיק את הטקסט שנוצר.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "יש להדביק את נתוני ה־JSON התקינים לכאן",
     "import-json-placeholder": "יש להדביק את נתוני ה־JSON התקינים לכאן",
     "import-map-members": "מיפוי חברים",
     "import-map-members": "מיפוי חברים",
     "import-members-map": "הלוחות המיובאים שלך מכילים חברים. נא למפות את החברים שברצונך לייבא למשתמשי Wekan",
     "import-members-map": "הלוחות המיובאים שלך מכילים חברים. נא למפות את החברים שברצונך לייבא למשתמשי Wekan",

+ 8 - 3
i18n/hu.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Számítógép",
     "computer": "Számítógép",
     "create": "Létrehoz",
     "create": "Létrehoz",
     "createBoardPopup-title": "Új tábla",
     "createBoardPopup-title": "Új tábla",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Új cimke",
     "createLabelPopup-title": "Új cimke",
     "current": "aktuális",
     "current": "aktuális",
     "date": "Dátum",
     "date": "Dátum",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Új tábla",
     "headerBarCreateBoardPopup-title": "Új tábla",
     "home": "Kezdőlap",
     "home": "Kezdőlap",
     "import": "Importálás",
     "import": "Importálás",
-    "import-board": "importálás a Trello-ról",
-    "import-board-title": "Tábla importálása a Trello-ról",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Tábla importálása a Trello-ról",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Tagok megjelenítése",
     "import-map-members": "Tagok megjelenítése",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/id.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Komputer",
     "computer": "Komputer",
     "create": "Buat",
     "create": "Buat",
     "createBoardPopup-title": "Buat Panel",
     "createBoardPopup-title": "Buat Panel",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Buat Label",
     "createLabelPopup-title": "Buat Label",
     "current": "sekarang",
     "current": "sekarang",
     "date": "Tanggal",
     "date": "Tanggal",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Buat Panel",
     "headerBarCreateBoardPopup-title": "Buat Panel",
     "home": "Beranda",
     "home": "Beranda",
     "import": "Impor",
     "import": "Impor",
-    "import-board": "Impor dari Trello",
-    "import-board-title": "Impor panel dari Trello",
-    "import-board-trello-instruction": "Di panel Trello anda, ke 'Menu', terus 'More', 'Print and Export','Export JSON', dan salin hasilnya",
+    "import-board": "import board",
+    "import-board-title-trello": "Impor panel dari Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "Di panel Trello anda, ke 'Menu', terus 'More', 'Print and Export','Export JSON', dan salin hasilnya",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Tempelkan data JSON yang sah disini",
     "import-json-placeholder": "Tempelkan data JSON yang sah disini",
     "import-map-members": "Petakan partisipan",
     "import-map-members": "Petakan partisipan",
     "import-members-map": "Panel yang anda impor punya partisipan. Silahkan petakan anggota yang anda ingin impor ke user [Wekan]",
     "import-members-map": "Panel yang anda impor punya partisipan. Silahkan petakan anggota yang anda ingin impor ke user [Wekan]",

+ 8 - 3
i18n/it.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Crea",
     "create": "Crea",
     "createBoardPopup-title": "Crea bacheca",
     "createBoardPopup-title": "Crea bacheca",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Crea etichetta",
     "createLabelPopup-title": "Crea etichetta",
     "current": "corrente",
     "current": "corrente",
     "date": "Data",
     "date": "Data",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Crea bacheca",
     "headerBarCreateBoardPopup-title": "Crea bacheca",
     "home": "Home",
     "home": "Home",
     "import": "Importa",
     "import": "Importa",
-    "import-board": "importa da Trello",
-    "import-board-title": "Importa una bacheca da Trello",
-    "import-board-trello-instruction": "Nella tua bacheca Trello vai a 'Menu', poi 'Altro', 'Stampa ed esporta', 'Esporta JSON', e copia il testo che compare.",
+    "import-board": "import board",
+    "import-board-title-trello": "Importa una bacheca da Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "Nella tua bacheca Trello vai a 'Menu', poi 'Altro', 'Stampa ed esporta', 'Esporta JSON', e copia il testo che compare.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Incolla un JSON valido qui",
     "import-json-placeholder": "Incolla un JSON valido qui",
     "import-map-members": "Mappatura dei membri",
     "import-map-members": "Mappatura dei membri",
     "import-members-map": "La bacheca che hai importato ha alcuni membri. Per favore scegli i membri che vuoi vengano importati negli utenti di Wekan",
     "import-members-map": "La bacheca che hai importato ha alcuni membri. Per favore scegli i membri che vuoi vengano importati negli utenti di Wekan",

+ 8 - 3
i18n/ja.i18n.json

@@ -145,6 +145,7 @@
     "computer": "コンピューター",
     "computer": "コンピューター",
     "create": "作成",
     "create": "作成",
     "createBoardPopup-title": "ボードの作成",
     "createBoardPopup-title": "ボードの作成",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "ラベルの作成",
     "createLabelPopup-title": "ラベルの作成",
     "current": "現在",
     "current": "現在",
     "date": "日付",
     "date": "日付",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "ボードの作成",
     "headerBarCreateBoardPopup-title": "ボードの作成",
     "home": "ホーム",
     "home": "ホーム",
     "import": "インポート",
     "import": "インポート",
-    "import-board": "Trelloからインポート",
-    "import-board-title": "Trelloからボードをインポート",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board": "import board",
+    "import-board-title-trello": "Trelloからボードをインポート",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "メンバーを紐付け",
     "import-map-members": "メンバーを紐付け",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 32 - 27
i18n/ko.i18n.json

@@ -38,14 +38,14 @@
     "activity-unjoined": "%s에서 멤버 해제",
     "activity-unjoined": "%s에서 멤버 해제",
     "activity-checklist-added": "%s에 체크리스트를 추가함",
     "activity-checklist-added": "%s에 체크리스트를 추가함",
     "add": "추가",
     "add": "추가",
-    "add-attachment": "Add Attachment",
-    "add-board": "Add Board",
-    "add-card": "Add Card",
-    "add-checklist": "Add Checklist",
+    "add-attachment": "첨부파일 추가",
+    "add-board": "보드 추가",
+    "add-card": "카드 추가",
+    "add-checklist": "체크리스트 추가",
     "add-checklist-item": "체크리스트에 항목 추가",
     "add-checklist-item": "체크리스트에 항목 추가",
     "add-cover": "커버 추가",
     "add-cover": "커버 추가",
-    "add-label": "Add Label",
-    "add-list": "Add List",
+    "add-label": "라벨 추가",
+    "add-list": "리스트 추가",
     "add-members": "멤버 추가",
     "add-members": "멤버 추가",
     "added": "추가됨",
     "added": "추가됨",
     "addMemberPopup-title": "멤버",
     "addMemberPopup-title": "멤버",
@@ -60,13 +60,13 @@
     "archive-all": "모두 보관",
     "archive-all": "모두 보관",
     "archive-board": "보드 저장소 보관",
     "archive-board": "보드 저장소 보관",
     "archive-card": "카드 저장소 보관",
     "archive-card": "카드 저장소 보관",
-    "archive-list": "Archive List",
+    "archive-list": "리스트 저장소 보관",
     "archive-selection": "저장소 선택",
     "archive-selection": "저장소 선택",
     "archiveBoardPopup-title": "보드를 저장소에 보관하시겠습니까?",
     "archiveBoardPopup-title": "보드를 저장소에 보관하시겠습니까?",
     "archived-items": "보관된 아이템",
     "archived-items": "보관된 아이템",
-    "archived-boards": "Archived Boards",
-    "restore-board": "Restore Board",
-    "no-archived-boards": "No Archived Boards.",
+    "archived-boards": "저장소 보관된 보드들",
+    "restore-board": "보드 복구",
+    "no-archived-boards": "보관된 보드가 없습니다.",
     "archives": "저장소",
     "archives": "저장소",
     "assign-member": "멤버 지정",
     "assign-member": "멤버 지정",
     "attached": "첨부됨",
     "attached": "첨부됨",
@@ -74,8 +74,8 @@
     "attachment-delete-pop": "영구 첨부파일을 삭제합니다. 되돌릴 수 없습니다.",
     "attachment-delete-pop": "영구 첨부파일을 삭제합니다. 되돌릴 수 없습니다.",
     "attachmentDeletePopup-title": "첨부 파일을 삭제합니까?",
     "attachmentDeletePopup-title": "첨부 파일을 삭제합니까?",
     "attachments": "첨부 파일",
     "attachments": "첨부 파일",
-    "auto-watch": "Automatically watch boards when they are created",
-    "avatar-too-big": "The avatar is too large (70KB max)",
+    "auto-watch": "생성한 보드를 자동으로 감시합니다.",
+    "avatar-too-big": "아바타 파일이 너무 큽니다. (최대 70KB)",
     "back": "뒤로",
     "back": "뒤로",
     "board-change-color": "보드 색 변경",
     "board-change-color": "보드 색 변경",
     "board-nb-stars": "%s개의 별",
     "board-nb-stars": "%s개의 별",
@@ -139,12 +139,13 @@
     "color-sky": "스카이",
     "color-sky": "스카이",
     "color-yellow": "옐로우",
     "color-yellow": "옐로우",
     "comment": "댓글",
     "comment": "댓글",
-    "comment-placeholder": "Write Comment",
-    "comment-only": "Comment only",
+    "comment-placeholder": "댓글 입력",
+    "comment-only": "댓글만 입력 가능",
     "comment-only-desc": "카드에 댓글만 달수 있습니다.",
     "comment-only-desc": "카드에 댓글만 달수 있습니다.",
     "computer": "내 컴퓨터",
     "computer": "내 컴퓨터",
     "create": "생성",
     "create": "생성",
     "createBoardPopup-title": "보드 생성",
     "createBoardPopup-title": "보드 생성",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "라벨 생성",
     "createLabelPopup-title": "라벨 생성",
     "current": "경향",
     "current": "경향",
     "date": "날짜",
     "date": "날짜",
@@ -186,7 +187,7 @@
     "error-json-schema": "JSON 데이터에 정보가 올바른 형식으로 포함되어 있지 않습니다.",
     "error-json-schema": "JSON 데이터에 정보가 올바른 형식으로 포함되어 있지 않습니다.",
     "error-list-doesNotExist": "목록이 없습니다.",
     "error-list-doesNotExist": "목록이 없습니다.",
     "error-user-doesNotExist": "멤버의 정보가 없습니다.",
     "error-user-doesNotExist": "멤버의 정보가 없습니다.",
-    "error-user-notAllowSelf": "You can not invite yourself",
+    "error-user-notAllowSelf": "자기 자신을 초대할 수 없습니다.",
     "error-user-notCreated": "유저가 생성되지 않았습니다.",
     "error-user-notCreated": "유저가 생성되지 않았습니다.",
     "error-username-taken": "중복된 아이디 입니다.",
     "error-username-taken": "중복된 아이디 입니다.",
     "export-board": "보드 내보내기",
     "export-board": "보드 내보내기",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "보드 생성",
     "headerBarCreateBoardPopup-title": "보드 생성",
     "home": "홈",
     "home": "홈",
     "import": "가져오기",
     "import": "가져오기",
-    "import-board": "Trello에서 가져오기",
-    "import-board-title": "Trello에서 보드 가져오기",
-    "import-board-trello-instruction": "Trello 게시판에서 'Menu' -> 'More' -> 'Print and Export', 'Export JSON' 선택하여 텍스트 결과값 복사",
+    "import-board": "import board",
+    "import-board-title-trello": "Trello에서 보드 가져오기",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "Trello 게시판에서 'Menu' -> 'More' -> 'Print and Export', 'Export JSON' 선택하여 텍스트 결과값 복사",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "유효한 JSON 데이터를 여기에 붙여 넣으십시오.",
     "import-json-placeholder": "유효한 JSON 데이터를 여기에 붙여 넣으십시오.",
     "import-map-members": "보드 멤버들",
     "import-map-members": "보드 멤버들",
     "import-members-map": "가져온 보드에는 멤버가 있습니다. 원하는 멤버를 Wekan 멤버로 매핑하세요.",
     "import-members-map": "가져온 보드에는 멤버가 있습니다. 원하는 멤버를 Wekan 멤버로 매핑하세요.",
@@ -234,9 +239,9 @@
     "listActionPopup-title": "동작 목록",
     "listActionPopup-title": "동작 목록",
     "listImportCardPopup-title": "Trello 카드 가져 오기",
     "listImportCardPopup-title": "Trello 카드 가져 오기",
     "listMorePopup-title": "더보기",
     "listMorePopup-title": "더보기",
-    "link-list": "Link to this list",
-    "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
-    "list-delete-suggest-archive": "You can archive a list to remove it from the board and preserve the activity.",
+    "link-list": "이 리스트에 링크",
+    "list-delete-pop": "모든 작업이 활동내역에서 제거되며 리스트를 복구 할 수 없습니다. 실행 취소는 불가능 합니다.",
+    "list-delete-suggest-archive": "리스트를 보관하여 보드에서 삭제하고 활동내역을 보존 할 수 있습니다.",
     "lists": "목록들",
     "lists": "목록들",
     "log-out": "로그아웃",
     "log-out": "로그아웃",
     "log-in": "로그인",
     "log-in": "로그인",
@@ -281,8 +286,8 @@
     "quick-access-description": "여기에 바로 가기를 추가하려면 보드에 별 표시를 체크하세요.",
     "quick-access-description": "여기에 바로 가기를 추가하려면 보드에 별 표시를 체크하세요.",
     "remove-cover": "커버 제거",
     "remove-cover": "커버 제거",
     "remove-from-board": "보드에서 제거",
     "remove-from-board": "보드에서 제거",
-    "remove-label": "Remove Label",
-    "listDeletePopup-title": "Delete List ?",
+    "remove-label": "라벨 제거",
+    "listDeletePopup-title": "리스트를 삭제합니까?",
     "remove-member": "멤버 제거",
     "remove-member": "멤버 제거",
     "remove-member-from-card": "카드에서 제거",
     "remove-member-from-card": "카드에서 제거",
     "remove-member-pop": "__boardTitle__에서 __name__(__username__) 을 제거합니까? 이 보드의 모든 카드에서 제거됩니다. 해당 내용을 __name__(__username__) 은(는) 알림으로 받게됩니다.",
     "remove-member-pop": "__boardTitle__에서 __name__(__username__) 을 제거합니까? 이 보드의 모든 카드에서 제거됩니다. 해당 내용을 __name__(__username__) 은(는) 알림으로 받게됩니다.",
@@ -292,7 +297,7 @@
     "restore": "복구",
     "restore": "복구",
     "save": "저장",
     "save": "저장",
     "search": "검색",
     "search": "검색",
-    "select-color": "Select Color",
+    "select-color": "색 선택",
     "shortcut-assign-self": "현재 카드에 자신을 지정하세요.",
     "shortcut-assign-self": "현재 카드에 자신을 지정하세요.",
     "shortcut-autocomplete-emoji": "이모티콘 자동완성",
     "shortcut-autocomplete-emoji": "이모티콘 자동완성",
     "shortcut-autocomplete-members": "멤버 자동완성",
     "shortcut-autocomplete-members": "멤버 자동완성",
@@ -344,16 +349,16 @@
     "email-addresses": "이메일 주소",
     "email-addresses": "이메일 주소",
     "smtp-host-description": "이메일을 처리하는 SMTP 서버의 주소입니다.",
     "smtp-host-description": "이메일을 처리하는 SMTP 서버의 주소입니다.",
     "smtp-port-description": "SMTP 서버가 보내는 전자 메일에 사용하는 포트입니다.",
     "smtp-port-description": "SMTP 서버가 보내는 전자 메일에 사용하는 포트입니다.",
-    "smtp-tls-description": "Enable TLS support for SMTP server",
+    "smtp-tls-description": "SMTP 서버에 TLS 지원 사용",
     "smtp-host": "SMTP 호스트",
     "smtp-host": "SMTP 호스트",
     "smtp-port": "SMTP 포트",
     "smtp-port": "SMTP 포트",
     "smtp-username": "사용자 이름",
     "smtp-username": "사용자 이름",
     "smtp-password": "암호",
     "smtp-password": "암호",
-    "smtp-tls": "TLS support",
+    "smtp-tls": "TLS 지원",
     "send-from": "보낸 사람",
     "send-from": "보낸 사람",
     "invitation-code": "초대 코드",
     "invitation-code": "초대 코드",
     "email-invite-register-subject": "\"__inviter__ 님이 당신에게 초대장을 보냈습니다.",
     "email-invite-register-subject": "\"__inviter__ 님이 당신에게 초대장을 보냈습니다.",
     "email-invite-register-text": "\"__user__ 님, \n\n__inviter__ 님이 Wekan 보드에 협업을 위하여 초대합니다.\n\n아래 링크를 클릭해주세요 : \n__url__\n\n그리고 초대 코드는 __icode__ 입니다.\n\n감사합니다.",
     "email-invite-register-text": "\"__user__ 님, \n\n__inviter__ 님이 Wekan 보드에 협업을 위하여 초대합니다.\n\n아래 링크를 클릭해주세요 : \n__url__\n\n그리고 초대 코드는 __icode__ 입니다.\n\n감사합니다.",
     "error-invitation-code-not-exist": "초대 코드가 존재하지 않습니다.",
     "error-invitation-code-not-exist": "초대 코드가 존재하지 않습니다.",
-    "error-notAuthorized": "You are not authorized to view this page."
+    "error-notAuthorized": "이 페이지를 볼 수있는 권한이 없습니다."
 }
 }

+ 8 - 3
i18n/nb.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Create",
     "create": "Create",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/pl.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Komputer",
     "computer": "Komputer",
     "create": "Utwórz",
     "create": "Utwórz",
     "createBoardPopup-title": "Utwórz tablicę",
     "createBoardPopup-title": "Utwórz tablicę",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Utwórz etykietę",
     "createLabelPopup-title": "Utwórz etykietę",
     "current": "obecny",
     "current": "obecny",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Utwórz tablicę",
     "headerBarCreateBoardPopup-title": "Utwórz tablicę",
     "home": "Strona główna",
     "home": "Strona główna",
     "import": "Importu",
     "import": "Importu",
-    "import-board": "zaimportuj z Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "W twojej tablicy na Trello, idź do 'Menu', następnie 'More', 'Print and Export', 'Export JSON' i skopiuj wynik",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "W twojej tablicy na Trello, idź do 'Menu', następnie 'More', 'Print and Export', 'Export JSON' i skopiuj wynik",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Wklej twój JSON tutaj",
     "import-json-placeholder": "Wklej twój JSON tutaj",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Twoje zaimportowane tablice mają kilku członków. Proszę wybierz członków których chcesz zaimportować do Wekan",
     "import-members-map": "Twoje zaimportowane tablice mają kilku członków. Proszę wybierz członków których chcesz zaimportować do Wekan",

+ 8 - 3
i18n/pt-BR.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computador",
     "computer": "Computador",
     "create": "Criar",
     "create": "Criar",
     "createBoardPopup-title": "Criar Quadro",
     "createBoardPopup-title": "Criar Quadro",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Criar Etiqueta",
     "createLabelPopup-title": "Criar Etiqueta",
     "current": "atual",
     "current": "atual",
     "date": "Data",
     "date": "Data",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Criar Quadro",
     "headerBarCreateBoardPopup-title": "Criar Quadro",
     "home": "Início",
     "home": "Início",
     "import": "Importar",
     "import": "Importar",
-    "import-board": "Importar do Trello",
-    "import-board-title": "Importar board do Trello",
-    "import-board-trello-instruction": "No seu quadro do Trello, vá em 'Menu', depois em 'Mais', 'Imprimir e Exportar', 'Exportar JSON', então copie o texto emitido",
+    "import-board": "import board",
+    "import-board-title-trello": "Importar board do Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "No seu quadro do Trello, vá em 'Menu', depois em 'Mais', 'Imprimir e Exportar', 'Exportar JSON', então copie o texto emitido",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Cole seus dados JSON válidos aqui",
     "import-json-placeholder": "Cole seus dados JSON válidos aqui",
     "import-map-members": "Mapear membros",
     "import-map-members": "Mapear membros",
     "import-members-map": "O seu quadro importado tem alguns membros. Por favor determine os membros que você deseja importar para os usuários Wekan",
     "import-members-map": "O seu quadro importado tem alguns membros. Por favor determine os membros que você deseja importar para os usuários Wekan",

+ 8 - 3
i18n/ro.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Create",
     "create": "Create",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/ru.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Загрузить с компьютера",
     "computer": "Загрузить с компьютера",
     "create": "Создать",
     "create": "Создать",
     "createBoardPopup-title": "Создать доску",
     "createBoardPopup-title": "Создать доску",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Создать метку",
     "createLabelPopup-title": "Создать метку",
     "current": "Текущий",
     "current": "Текущий",
     "date": "Дата",
     "date": "Дата",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Создать доску",
     "headerBarCreateBoardPopup-title": "Создать доску",
     "home": "Главная",
     "home": "Главная",
     "import": "Импорт",
     "import": "Импорт",
-    "import-board": "Импорт с Trello",
-    "import-board-title": "Импортировать доску из Trello",
-    "import-board-trello-instruction": "На вашей Trello доске нажмите “Menu” - “More” - “Print and export - “Export JSON” и скопируйте полученный текст",
+    "import-board": "import board",
+    "import-board-title-trello": "Импортировать доску из Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "На вашей Trello доске нажмите “Menu” - “More” - “Print and export - “Export JSON” и скопируйте полученный текст",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Вставьте JSON сюда",
     "import-json-placeholder": "Вставьте JSON сюда",
     "import-map-members": "Карта пользователей",
     "import-map-members": "Карта пользователей",
     "import-members-map": "Вы ипортировали доску с пользователями. Пожалуйста, составьте карту пользователей, которых вы хотите импортировать в Wekan пользователей",
     "import-members-map": "Вы ипортировали доску с пользователями. Пожалуйста, составьте карту пользователей, которых вы хотите импортировать в Wekan пользователей",

+ 8 - 3
i18n/sr.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Create",
     "create": "Create",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Datum",
     "date": "Datum",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Uvezi tablu iz Trella",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board": "import board",
+    "import-board-title-trello": "Uvezi tablu iz Trella",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Mapiraj članove",
     "import-map-members": "Mapiraj članove",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/sv.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Dator",
     "computer": "Dator",
     "create": "Skapa",
     "create": "Skapa",
     "createBoardPopup-title": "Skapa anslagstavla",
     "createBoardPopup-title": "Skapa anslagstavla",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Skapa etikett",
     "createLabelPopup-title": "Skapa etikett",
     "current": "aktuell",
     "current": "aktuell",
     "date": "Datum",
     "date": "Datum",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Skapa anslagstavla",
     "headerBarCreateBoardPopup-title": "Skapa anslagstavla",
     "home": "Hem",
     "home": "Hem",
     "import": "Importera",
     "import": "Importera",
-    "import-board": "Importera från Trello",
-    "import-board-title": "Importera anslagstavla från Trello",
-    "import-board-trello-instruction": "I din Trello-anslagstavla, gå till 'Meny', sedan 'Mera', 'Skriv ut och exportera', 'Exportera JSON' och kopiera den resulterande text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Importera anslagstavla från Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "I din Trello-anslagstavla, gå till 'Meny', sedan 'Mera', 'Skriv ut och exportera', 'Exportera JSON' och kopiera den resulterande text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Klistra in giltigt JSON data här",
     "import-json-placeholder": "Klistra in giltigt JSON data här",
     "import-map-members": "Kartlägg medlemmar",
     "import-map-members": "Kartlägg medlemmar",
     "import-members-map": "Din importerade anslagstavla har några medlemmar. Kartlägg medlemmarna som du vill importera till Wekan-användare",
     "import-members-map": "Din importerade anslagstavla har några medlemmar. Kartlägg medlemmarna som du vill importera till Wekan-användare",

+ 8 - 3
i18n/ta.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Create",
     "create": "Create",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/th.i18n.json

@@ -145,6 +145,7 @@
     "computer": "คอมพิวเตอร์",
     "computer": "คอมพิวเตอร์",
     "create": "สร้าง",
     "create": "สร้าง",
     "createBoardPopup-title": "สร้างบอร์ด",
     "createBoardPopup-title": "สร้างบอร์ด",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "สร้างป้ายกำกับ",
     "createLabelPopup-title": "สร้างป้ายกำกับ",
     "current": "ปัจจุบัน",
     "current": "ปัจจุบัน",
     "date": "วันที่",
     "date": "วันที่",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "สร้างบอร์ด",
     "headerBarCreateBoardPopup-title": "สร้างบอร์ด",
     "home": "หน้าหลัก",
     "home": "หน้าหลัก",
     "import": "นำเข้า",
     "import": "นำเข้า",
-    "import-board": "นำเข้าจาก Trello",
-    "import-board-title": "นำเข้าบอร์ดจาก Trello",
-    "import-board-trello-instruction": "ใน Trello ของคุณให้ไปที่ 'Menu' และไปที่ More -> Print and Export -> Export JSON และคัดลอกข้อความจากนั้น",
+    "import-board": "import board",
+    "import-board-title-trello": "นำเข้าบอร์ดจาก Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "ใน Trello ของคุณให้ไปที่ 'Menu' และไปที่ More -> Print and Export -> Export JSON และคัดลอกข้อความจากนั้น",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "วางข้อมูล JSON ที่ถูกต้องของคุณที่นี่",
     "import-json-placeholder": "วางข้อมูล JSON ที่ถูกต้องของคุณที่นี่",
     "import-map-members": "แผนที่สมาชิก",
     "import-map-members": "แผนที่สมาชิก",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 33 - 28
i18n/tr.i18n.json

@@ -1,6 +1,6 @@
 {
 {
     "accept": "Kabul Et",
     "accept": "Kabul Et",
-    "act-activity-notify": "[Wekan] Aktivite Bildirimi",
+    "act-activity-notify": "[Wekan] Etkinlik Bildirimi",
     "act-addAttachment": "__card__ kartına __attachment__ dosyasını ekledi",
     "act-addAttachment": "__card__ kartına __attachment__ dosyasını ekledi",
     "act-addComment": "__card__ kartına bir yorum bırakıt: __comment__",
     "act-addComment": "__card__ kartına bir yorum bırakıt: __comment__",
     "act-createBoard": "__board__ panosunu oluşturdu",
     "act-createBoard": "__board__ panosunu oluşturdu",
@@ -38,14 +38,14 @@
     "activity-unjoined": "%s içinden ayrıldı",
     "activity-unjoined": "%s içinden ayrıldı",
     "activity-checklist-added": "%s içine liste ekledi",
     "activity-checklist-added": "%s içine liste ekledi",
     "add": "Ekle",
     "add": "Ekle",
-    "add-attachment": "Add Attachment",
-    "add-board": "Add Board",
-    "add-card": "Add Card",
-    "add-checklist": "Add Checklist",
-    "add-checklist-item": "Listeye yeni bir eleman ekle",
+    "add-attachment": "Ek Ekle",
+    "add-board": "Pano Ekle",
+    "add-card": "Kart Ekle",
+    "add-checklist": "Yapılacak Listesi Ekle",
+    "add-checklist-item": "Yapılacak listes yeni bir eleman ekle",
     "add-cover": "Kapak resmi ekle",
     "add-cover": "Kapak resmi ekle",
-    "add-label": "Add Label",
-    "add-list": "Add List",
+    "add-label": "Etiket Ekle",
+    "add-list": "Liste Ekle",
     "add-members": "Üye ekle",
     "add-members": "Üye ekle",
     "added": "Eklendi",
     "added": "Eklendi",
     "addMemberPopup-title": "Üyeler",
     "addMemberPopup-title": "Üyeler",
@@ -59,23 +59,23 @@
     "archive": "Arşivle",
     "archive": "Arşivle",
     "archive-all": "Tümünü Arşivle",
     "archive-all": "Tümünü Arşivle",
     "archive-board": "Panoyu Arşivle",
     "archive-board": "Panoyu Arşivle",
-    "archive-card": "Kartı arşivle",
-    "archive-list": "Archive List",
+    "archive-card": "Kartı Arşivle",
+    "archive-list": "Listeyi Arşivle",
     "archive-selection": "Seçimi arşivle",
     "archive-selection": "Seçimi arşivle",
     "archiveBoardPopup-title": "Pano arşivlensin mi?",
     "archiveBoardPopup-title": "Pano arşivlensin mi?",
     "archived-items": "Arşivlenmiş Öğeler",
     "archived-items": "Arşivlenmiş Öğeler",
-    "archived-boards": "Archived Boards",
-    "restore-board": "Restore Board",
-    "no-archived-boards": "No Archived Boards.",
+    "archived-boards": "Arşivlenmiş Panolar",
+    "restore-board": "Panoyu Tekrar Yükle",
+    "no-archived-boards": "Arşivlenmiş Pano Yok.",
     "archives": "Arşiv",
     "archives": "Arşiv",
     "assign-member": "Üye ata",
     "assign-member": "Üye ata",
-    "attached": "dosya eklendi",
+    "attached": "dosya(sı) eklendi",
     "attachment": "Ek Dosya",
     "attachment": "Ek Dosya",
-    "attachment-delete-pop": "Ek dosya silme işlemi kalıcıdır. Geri dönüşü yok",
-    "attachmentDeletePopup-title": "Ek Dosya Silinsin Mi?",
-    "attachments": "Ek Dosyalar",
+    "attachment-delete-pop": "Ek silme işlemi kalıcıdır ve geri alınamaz.",
+    "attachmentDeletePopup-title": "Ek Silinsin Mi?",
+    "attachments": "Ekler",
     "auto-watch": "Automatically watch boards when they are created",
     "auto-watch": "Automatically watch boards when they are created",
-    "avatar-too-big": "The avatar is too large (70KB max)",
+    "avatar-too-big": "Avatar boyutu çok büyük (En fazla 70KB olabilir)",
     "back": "Geri",
     "back": "Geri",
     "board-change-color": "Renk değiştir",
     "board-change-color": "Renk değiştir",
     "board-nb-stars": "%s yıldız",
     "board-nb-stars": "%s yıldız",
@@ -124,27 +124,28 @@
     "checklists": "Doğrulama Listeleri",
     "checklists": "Doğrulama Listeleri",
     "click-to-star": "Bu panoyu yıldızlamak için tıkla.",
     "click-to-star": "Bu panoyu yıldızlamak için tıkla.",
     "click-to-unstar": "Bu panunun yıldızını kaldırmak için tıkla.",
     "click-to-unstar": "Bu panunun yıldızını kaldırmak için tıkla.",
-    "clipboard": "Panodan (cliboard) veya sürükle ve bırak ile",
+    "clipboard": "Panodan (clipboard) veya sürükle ve bırak ile",
     "close": "Kapat",
     "close": "Kapat",
     "close-board": "Panoyu kapat",
     "close-board": "Panoyu kapat",
     "close-board-pop": "You will be able to restore the board by clicking the “Archives” button from the home header.",
     "close-board-pop": "You will be able to restore the board by clicking the “Archives” button from the home header.",
     "color-black": "siyah",
     "color-black": "siyah",
     "color-blue": "mavi",
     "color-blue": "mavi",
     "color-green": "yeşil",
     "color-green": "yeşil",
-    "color-lime": "çim",
+    "color-lime": "misket limonu",
     "color-orange": "turuncu",
     "color-orange": "turuncu",
     "color-pink": "pembe",
     "color-pink": "pembe",
     "color-purple": "mor",
     "color-purple": "mor",
     "color-red": "kırmızı",
     "color-red": "kırmızı",
     "color-sky": "açık mavi",
     "color-sky": "açık mavi",
     "color-yellow": "sarı",
     "color-yellow": "sarı",
-    "comment": "Yorum Gönder",
-    "comment-placeholder": "Write Comment",
-    "comment-only": "Comment only",
+    "comment": "Yorum",
+    "comment-placeholder": "Yorum Yaz",
+    "comment-only": "Sadece yorum",
     "comment-only-desc": "Sadece kartlara yorum yazabilir.",
     "comment-only-desc": "Sadece kartlara yorum yazabilir.",
     "computer": "Bilgisayar",
     "computer": "Bilgisayar",
     "create": "Oluştur",
     "create": "Oluştur",
     "createBoardPopup-title": "Pano Oluşturma",
     "createBoardPopup-title": "Pano Oluşturma",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Etiket Oluşturma",
     "createLabelPopup-title": "Etiket Oluşturma",
     "current": "mevcut",
     "current": "mevcut",
     "date": "Tarih",
     "date": "Tarih",
@@ -186,7 +187,7 @@
     "error-json-schema": "Girdiğin JSON metni tüm bilgileri doğru biçimde barındırmıyor",
     "error-json-schema": "Girdiğin JSON metni tüm bilgileri doğru biçimde barındırmıyor",
     "error-list-doesNotExist": "Liste bulunamadı",
     "error-list-doesNotExist": "Liste bulunamadı",
     "error-user-doesNotExist": "Kullanıcı bulunamadı",
     "error-user-doesNotExist": "Kullanıcı bulunamadı",
-    "error-user-notAllowSelf": "You can not invite yourself",
+    "error-user-notAllowSelf": "Kendi kendini davet edemezsin",
     "error-user-notCreated": "Bu üye oluşturulmadı",
     "error-user-notCreated": "Bu üye oluşturulmadı",
     "error-username-taken": "Kullanıcı adı zaten alınmış",
     "error-username-taken": "Kullanıcı adı zaten alınmış",
     "export-board": "Panoyu dışarı aktar",
     "export-board": "Panoyu dışarı aktar",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Pano Oluşturma",
     "headerBarCreateBoardPopup-title": "Pano Oluşturma",
     "home": "Ana Sayfa",
     "home": "Ana Sayfa",
     "import": "İçeri aktar",
     "import": "İçeri aktar",
-    "import-board": "Trello'dan içeri aktar",
-    "import-board-title": "Trello'dan panoları içeri aktarır",
-    "import-board-trello-instruction": "Trello panonuzda, 'Menü'ye gidip, ardıdan, 'daha fazlası' na tıklayın, 'Yazdır ve Çıktı al' sayfasından 'JSON biçiminde çıktı al' diyip, çıkan metni buraya kopyalayın.",
+    "import-board": "import board",
+    "import-board-title-trello": "Trello'dan panoyu içeri aktar",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "Trello panonuzda, 'Menü'ye gidip, ardıdan, 'daha fazlası' na tıklayın, 'Yazdır ve Çıktı al' sayfasından 'JSON biçiminde çıktı al' diyip, çıkan metni buraya kopyalayın.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Geçerli JSON verisini buraya yapıştırın",
     "import-json-placeholder": "Geçerli JSON verisini buraya yapıştırın",
     "import-map-members": "Üyeleri eşleştirme",
     "import-map-members": "Üyeleri eşleştirme",
     "import-members-map": "İçe aktardığın panoda bazı kullanıcılar var. Lütfen bu kullanıcıları Wekan panosundaki kullanıcılarla eşleştirin.",
     "import-members-map": "İçe aktardığın panoda bazı kullanıcılar var. Lütfen bu kullanıcıları Wekan panosundaki kullanıcılarla eşleştirin.",
@@ -216,7 +221,7 @@
     "info": "Bilgiler",
     "info": "Bilgiler",
     "initials": "İlk Harfleri",
     "initials": "İlk Harfleri",
     "invalid-date": "Geçersiz tarih",
     "invalid-date": "Geçersiz tarih",
-    "joined": "katılma",
+    "joined": "katıl",
     "just-invited": "Bu panoya şimdi davet edildin.",
     "just-invited": "Bu panoya şimdi davet edildin.",
     "keyboard-shortcuts": "Klavye kısayolları",
     "keyboard-shortcuts": "Klavye kısayolları",
     "label-create": "Etiket Oluşturma",
     "label-create": "Etiket Oluşturma",

+ 8 - 3
i18n/uk.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Create",
     "create": "Create",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/vi.i18n.json

@@ -145,6 +145,7 @@
     "computer": "Computer",
     "computer": "Computer",
     "create": "Create",
     "create": "Create",
     "createBoardPopup-title": "Create Board",
     "createBoardPopup-title": "Create Board",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "Create Label",
     "createLabelPopup-title": "Create Label",
     "current": "current",
     "current": "current",
     "date": "Date",
     "date": "Date",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "Create Board",
     "headerBarCreateBoardPopup-title": "Create Board",
     "home": "Home",
     "home": "Home",
     "import": "Import",
     "import": "Import",
-    "import-board": "import from Trello",
-    "import-board-title": "Import board from Trello",
-    "import-board-trello-instruction": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board": "import board",
+    "import-board-title-trello": "Import board from Trello",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-json-placeholder": "Paste your valid JSON data here",
     "import-map-members": "Map members",
     "import-map-members": "Map members",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",
     "import-members-map": "Your imported board has some members. Please map the members you want to import to Wekan users",

+ 8 - 3
i18n/zh-CN.i18n.json

@@ -145,6 +145,7 @@
     "computer": "从本机上传",
     "computer": "从本机上传",
     "create": "创建",
     "create": "创建",
     "createBoardPopup-title": "创建看板",
     "createBoardPopup-title": "创建看板",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "创建标签",
     "createLabelPopup-title": "创建标签",
     "current": "当前",
     "current": "当前",
     "date": "日期",
     "date": "日期",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "创建看板",
     "headerBarCreateBoardPopup-title": "创建看板",
     "home": "首页",
     "home": "首页",
     "import": "导入",
     "import": "导入",
-    "import-board": "从 Trello 导入",
-    "import-board-title": "从Trello导入看板",
-    "import-board-trello-instruction": "在你的Trello看板中,点击“菜单”,然后选择“更多”,“打印与导出”,“导出为 JSON” 并拷贝结果文本",
+    "import-board": "import board",
+    "import-board-title-trello": "从Trello导入看板",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "在你的Trello看板中,点击“菜单”,然后选择“更多”,“打印与导出”,“导出为 JSON” 并拷贝结果文本",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "粘贴您有效的 JSON 数据至此",
     "import-json-placeholder": "粘贴您有效的 JSON 数据至此",
     "import-map-members": "映射成员",
     "import-map-members": "映射成员",
     "import-members-map": "您导入的看板有一些成员。请将您想导入的成员映射到 Wekan 用户。",
     "import-members-map": "您导入的看板有一些成员。请将您想导入的成员映射到 Wekan 用户。",

+ 8 - 3
i18n/zh-TW.i18n.json

@@ -145,6 +145,7 @@
     "computer": "從本機上傳",
     "computer": "從本機上傳",
     "create": "建立",
     "create": "建立",
     "createBoardPopup-title": "建立看板",
     "createBoardPopup-title": "建立看板",
+    "chooseBoardSourcePopup-title": "Import board",
     "createLabelPopup-title": "建立標籤",
     "createLabelPopup-title": "建立標籤",
     "current": "目前",
     "current": "目前",
     "date": "日期",
     "date": "日期",
@@ -204,9 +205,13 @@
     "headerBarCreateBoardPopup-title": "建立看板",
     "headerBarCreateBoardPopup-title": "建立看板",
     "home": "首頁",
     "home": "首頁",
     "import": "匯入",
     "import": "匯入",
-    "import-board": "匯入 Trello 資料",
-    "import-board-title": "匯入在 Trello 的看板",
-    "import-board-trello-instruction": "在你的Trello看板中,點選“功能表”,然後選擇“更多”,“列印與匯出”,“匯出為 JSON” 並拷貝結果文本",
+    "import-board": "import board",
+    "import-board-title-trello": "匯入在 Trello 的看板",
+    "import-board-title-wekan": "Import board from Wekan",
+    "from-trello": "From Trello",
+    "from-wekan": "From Wekan",
+    "import-board-instruction-trello": "在你的Trello看板中,點選“功能表”,然後選擇“更多”,“列印與匯出”,“匯出為 JSON” 並拷貝結果文本",
+    "import-board-instruction-wekan": "In your Wekan board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
     "import-json-placeholder": "貼上您有效的 JSON 資料至此",
     "import-json-placeholder": "貼上您有效的 JSON 資料至此",
     "import-map-members": "複製成員",
     "import-map-members": "複製成員",
     "import-members-map": "您匯入的看板有一些成員。請將您想匯入的成員映射到 Wekan 使用者。",
     "import-members-map": "您匯入的看板有一些成員。請將您想匯入的成員映射到 Wekan 使用者。",

+ 17 - 496
models/import.js

@@ -1,507 +1,28 @@
-const DateString = Match.Where(function (dateAsString) {
-  check(dateAsString, String);
-  return moment(dateAsString, moment.ISO_8601).isValid();
-});
-
-class TrelloCreator {
-  constructor(data) {
-    // we log current date, to use the same timestamp for all our actions.
-    // this helps to retrieve all elements performed by the same import.
-    this._nowDate = new Date();
-    // The object creation dates, indexed by Trello id
-    // (so we only parse actions once!)
-    this.createdAt = {
-      board: null,
-      cards: {},
-      lists: {},
-    };
-    // The object creator Trello Id, indexed by the object Trello id
-    // (so we only parse actions once!)
-    this.createdBy = {
-      cards: {}, // only cards have a field for that
-    };
-
-    // Map of labels Trello ID => Wekan ID
-    this.labels = {};
-    // Map of lists Trello ID => Wekan ID
-    this.lists = {};
-    // Map of cards Trello ID => Wekan ID
-    this.cards = {};
-    // The comments, indexed by Trello card id (to map when importing cards)
-    this.comments = {};
-    // the members, indexed by Trello member id => Wekan user ID
-    this.members = data.membersMapping ? data.membersMapping : {};
-
-    // maps a trelloCardId to an array of trelloAttachments
-    this.attachments = {};
-  }
-
-  /**
-   * If dateString is provided,
-   * return the Date it represents.
-   * If not, will return the date when it was first called.
-   * This is useful for us, as we want all import operations to
-   * have the exact same date for easier later retrieval.
-   *
-   * @param {String} dateString a properly formatted Date
-   */
-  _now(dateString) {
-    if(dateString) {
-      return new Date(dateString);
-    }
-    if(!this._nowDate) {
-      this._nowDate = new Date();
-    }
-    return this._nowDate;
-  }
-
-  /**
-   * if trelloUserId is provided and we have a mapping,
-   * return it.
-   * Otherwise return current logged user.
-   * @param trelloUserId
-   * @private
-     */
-  _user(trelloUserId) {
-    if(trelloUserId && this.members[trelloUserId]) {
-      return this.members[trelloUserId];
-    }
-    return Meteor.userId();
-  }
-
-  checkActions(trelloActions) {
-    check(trelloActions, [Match.ObjectIncluding({
-      data: Object,
-      date: DateString,
-      type: String,
-    })]);
-    // XXX we could perform more thorough checks based on action type
-  }
-
-  checkBoard(trelloBoard) {
-    check(trelloBoard, Match.ObjectIncluding({
-      closed: Boolean,
-      name: String,
-      prefs: Match.ObjectIncluding({
-        // XXX refine control by validating 'background' against a list of
-        // allowed values (is it worth the maintenance?)
-        background: String,
-        permissionLevel: Match.Where((value) => {
-          return ['org', 'private', 'public'].indexOf(value)>= 0;
-        }),
-      }),
-    }));
-  }
-
-  checkCards(trelloCards) {
-    check(trelloCards, [Match.ObjectIncluding({
-      closed: Boolean,
-      dateLastActivity: DateString,
-      desc: String,
-      idLabels: [String],
-      idMembers: [String],
-      name: String,
-      pos: Number,
-    })]);
-  }
-
-  checkLabels(trelloLabels) {
-    check(trelloLabels, [Match.ObjectIncluding({
-      // XXX refine control by validating 'color' against a list of allowed
-      // values (is it worth the maintenance?)
-      color: String,
-      name: String,
-    })]);
-  }
-
-  checkLists(trelloLists) {
-    check(trelloLists, [Match.ObjectIncluding({
-      closed: Boolean,
-      name: String,
-    })]);
-  }
-
-  checkChecklists(trelloChecklists) {
-    check(trelloChecklists, [Match.ObjectIncluding({
-      idBoard: String,
-      idCard: String,
-      name: String,
-      checkItems: [Match.ObjectIncluding({
-        state: String,
-        name: String,
-      })],
-    })]);
-  }
-
-  // You must call parseActions before calling this one.
-  createBoardAndLabels(trelloBoard) {
-    const boardToCreate = {
-      archived: trelloBoard.closed,
-      color: this.getColor(trelloBoard.prefs.background),
-      // very old boards won't have a creation activity so no creation date
-      createdAt: this._now(this.createdAt.board),
-      labels: [],
-      members: [{
-        userId: Meteor.userId(),
-        isAdmin: true,
-        isActive: true,
-        isCommentOnly: false,
-      }],
-      permission: this.getPermission(trelloBoard.prefs.permissionLevel),
-      slug: getSlug(trelloBoard.name) || 'board',
-      stars: 0,
-      title: trelloBoard.name,
-    };
-    // now add other members
-    if(trelloBoard.memberships) {
-      trelloBoard.memberships.forEach((trelloMembership) => {
-        const trelloId = trelloMembership.idMember;
-        // do we have a mapping?
-        if(this.members[trelloId]) {
-          const wekanId = this.members[trelloId];
-          // do we already have it in our list?
-          const wekanMember = boardToCreate.members.find((wekanMember) => wekanMember.userId === wekanId);
-          if(wekanMember) {
-            // we're already mapped, but maybe with lower rights
-            if(!wekanMember.isAdmin) {
-              wekanMember.isAdmin = this.getAdmin(trelloMembership.memberType);
-            }
-          } else {
-            boardToCreate.members.push({
-              userId: wekanId,
-              isAdmin: this.getAdmin(trelloMembership.memberType),
-              isActive: true,
-              isCommentOnly: false,
-            });
-          }
-        }
-      });
-    }
-    trelloBoard.labels.forEach((label) => {
-      const labelToCreate = {
-        _id: Random.id(6),
-        color: label.color,
-        name: label.name,
-      };
-      // We need to remember them by Trello ID, as this is the only ref we have
-      // when importing cards.
-      this.labels[label.id] = labelToCreate._id;
-      boardToCreate.labels.push(labelToCreate);
-    });
-    const boardId = Boards.direct.insert(boardToCreate);
-    Boards.direct.update(boardId, {$set: {modifiedAt: this._now()}});
-    // log activity
-    Activities.direct.insert({
-      activityType: 'importBoard',
-      boardId,
-      createdAt: this._now(),
-      source: {
-        id: trelloBoard.id,
-        system: 'Trello',
-        url: trelloBoard.url,
-      },
-      // We attribute the import to current user,
-      // not the author from the original object.
-      userId: this._user(),
-    });
-    return boardId;
-  }
-
-  /**
-   * Create the Wekan cards corresponding to the supplied Trello cards,
-   * as well as all linked data: activities, comments, and attachments
-   * @param trelloCards
-   * @param boardId
-   * @returns {Array}
-   */
-  createCards(trelloCards, boardId) {
-    const result = [];
-    trelloCards.forEach((card) => {
-      const cardToCreate = {
-        archived: card.closed,
-        boardId,
-        // very old boards won't have a creation activity so no creation date
-        createdAt: this._now(this.createdAt.cards[card.id]),
-        dateLastActivity: this._now(),
-        description: card.desc,
-        listId: this.lists[card.idList],
-        sort: card.pos,
-        title: card.name,
-        // we attribute the card to its creator if available
-        userId: this._user(this.createdBy.cards[card.id]),
-        dueAt: card.due ? this._now(card.due) : null,
-      };
-      // add labels
-      if (card.idLabels) {
-        cardToCreate.labelIds = card.idLabels.map((trelloId) => {
-          return this.labels[trelloId];
-        });
-      }
-      // add members {
-      if(card.idMembers) {
-        const wekanMembers = [];
-        // we can't just map, as some members may not have been mapped
-        card.idMembers.forEach((trelloId) => {
-          if(this.members[trelloId]) {
-            const wekanId = this.members[trelloId];
-            // we may map multiple Trello members to the same wekan user
-            // in which case we risk adding the same user multiple times
-            if(!wekanMembers.find((wId) => wId === wekanId)){
-              wekanMembers.push(wekanId);
-            }
-          }
-          return true;
-        });
-        if(wekanMembers.length>0) {
-          cardToCreate.members = wekanMembers;
-        }
-      }
-      // insert card
-      const cardId = Cards.direct.insert(cardToCreate);
-      // keep track of Trello id => WeKan id
-      this.cards[card.id] = cardId;
-      // log activity
-      Activities.direct.insert({
-        activityType: 'importCard',
-        boardId,
-        cardId,
-        createdAt: this._now(),
-        listId: cardToCreate.listId,
-        source: {
-          id: card.id,
-          system: 'Trello',
-          url: card.url,
-        },
-        // we attribute the import to current user,
-        // not the author of the original card
-        userId: this._user(),
-      });
-      // add comments
-      const comments = this.comments[card.id];
-      if (comments) {
-        comments.forEach((comment) => {
-          const commentToCreate = {
-            boardId,
-            cardId,
-            createdAt: this._now(comment.date),
-            text: comment.data.text,
-            // we attribute the comment to the original author, default to current user
-            userId: this._user(comment.idMemberCreator),
-          };
-          // dateLastActivity will be set from activity insert, no need to
-          // update it ourselves
-          const commentId = CardComments.direct.insert(commentToCreate);
-          Activities.direct.insert({
-            activityType: 'addComment',
-            boardId: commentToCreate.boardId,
-            cardId: commentToCreate.cardId,
-            commentId,
-            createdAt: this._now(commentToCreate.createdAt),
-            // we attribute the addComment (not the import)
-            // to the original author - it is needed by some UI elements.
-            userId: commentToCreate.userId,
-          });
-        });
-      }
-      const attachments = this.attachments[card.id];
-      const trelloCoverId = card.idAttachmentCover;
-      if (attachments) {
-        attachments.forEach((att) => {
-          const file = new FS.File();
-          // Simulating file.attachData on the client generates multiple errors
-          // - HEAD returns null, which causes exception down the line
-          // - the template then tries to display the url to the attachment which causes other errors
-          // so we make it server only, and let UI catch up once it is done, forget about latency comp.
-          if(Meteor.isServer) {
-            file.attachData(att.url, function (error) {
-              file.boardId = boardId;
-              file.cardId = cardId;
-              if (error) {
-                throw(error);
-              } else {
-                const wekanAtt = Attachments.insert(file, () => {
-                  // we do nothing
-                });
-                //
-                if(trelloCoverId === att.id) {
-                  Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}});
-                }
-              }
-            });
-          }
-          // todo XXX set cover - if need be
-        });
-      }
-      result.push(cardId);
-    });
-    return result;
-  }
-
-  // Create labels if they do not exist and load this.labels.
-  createLabels(trelloLabels, board) {
-    trelloLabels.forEach((label) => {
-      const color = label.color;
-      const name = label.name;
-      const existingLabel = board.getLabel(name, color);
-      if (existingLabel) {
-        this.labels[label.id] = existingLabel._id;
-      } else {
-        const idLabelCreated = board.pushLabel(name, color);
-        this.labels[label.id] = idLabelCreated;
-      }
-    });
-  }
-
-  createLists(trelloLists, boardId) {
-    trelloLists.forEach((list) => {
-      const listToCreate = {
-        archived: list.closed,
-        boardId,
-        // We are being defensing here by providing a default date (now) if the
-        // creation date wasn't found on the action log. This happen on old
-        // Trello boards (eg from 2013) that didn't log the 'createList' action
-        // we require.
-        createdAt: this._now(this.createdAt.lists[list.id]),
-        title: list.name,
-      };
-      const listId = Lists.direct.insert(listToCreate);
-      Lists.direct.update(listId, {$set: {'updatedAt': this._now()}});
-      this.lists[list.id] = listId;
-      // log activity
-      Activities.direct.insert({
-        activityType: 'importList',
-        boardId,
-        createdAt: this._now(),
-        listId,
-        source: {
-          id: list.id,
-          system: 'Trello',
-        },
-        // We attribute the import to current user,
-        // not the creator of the original object
-        userId: this._user(),
-      });
-    });
-  }
-
-  createChecklists(trelloChecklists) {
-    trelloChecklists.forEach((checklist) => {
-      // Create the checklist
-      const checklistToCreate = {
-        cardId: this.cards[checklist.idCard],
-        title: checklist.name,
-        createdAt: this._now(),
-      };
-      const checklistId = Checklists.direct.insert(checklistToCreate);
-      // Now add the items to the checklist
-      const itemsToCreate = [];
-      checklist.checkItems.forEach((item) => {
-        itemsToCreate.push({
-          _id: checklistId + itemsToCreate.length,
-          title: item.name,
-          isFinished: item.state === 'complete',
-        });
-      });
-      Checklists.direct.update(checklistId, {$set: {items: itemsToCreate}});
-    });
-  }
-
-  getAdmin(trelloMemberType) {
-    return trelloMemberType === 'admin';
-  }
-
-  getColor(trelloColorCode) {
-    // trello color name => wekan color
-    const mapColors = {
-      'blue': 'belize',
-      'orange': 'pumpkin',
-      'green': 'nephritis',
-      'red': 'pomegranate',
-      'purple': 'wisteria',
-      'pink': 'pomegranate',
-      'lime': 'nephritis',
-      'sky': 'belize',
-      'grey': 'midnight',
-    };
-    const wekanColor = mapColors[trelloColorCode];
-    return wekanColor || Boards.simpleSchema()._schema.color.allowedValues[0];
-  }
-
-  getPermission(trelloPermissionCode) {
-    if (trelloPermissionCode === 'public') {
-      return 'public';
-    }
-    // Wekan does NOT have organization level, so we default both 'private' and
-    // 'org' to private.
-    return 'private';
-  }
-
-  parseActions(trelloActions) {
-    trelloActions.forEach((action) => {
-      if (action.type === 'addAttachmentToCard') {
-        // We have to be cautious, because the attachment could have been removed later.
-        // In that case Trello still reports its addition, but removes its 'url' field.
-        // So we test for that
-        const trelloAttachment = action.data.attachment;
-        if(trelloAttachment.url) {
-          // we cannot actually create the Wekan attachment, because we don't yet
-          // have the cards to attach it to, so we store it in the instance variable.
-          const trelloCardId = action.data.card.id;
-          if(!this.attachments[trelloCardId]) {
-            this.attachments[trelloCardId] = [];
-          }
-          this.attachments[trelloCardId].push(trelloAttachment);
-        }
-      } else if (action.type === 'commentCard') {
-        const id = action.data.card.id;
-        if (this.comments[id]) {
-          this.comments[id].push(action);
-        } else {
-          this.comments[id] = [action];
-        }
-      } else if (action.type === 'createBoard') {
-        this.createdAt.board = action.date;
-      } else if (action.type === 'createCard') {
-        const cardId = action.data.card.id;
-        this.createdAt.cards[cardId] = action.date;
-        this.createdBy.cards[cardId] = action.idMemberCreator;
-      } else if (action.type === 'createList') {
-        const listId = action.data.list.id;
-        this.createdAt.lists[listId] = action.date;
-      }
-    });
-  }
-}
+import { TrelloCreator } from './trelloCreator';
+import { WekanCreator } from './wekanCreator';
 
 
 Meteor.methods({
 Meteor.methods({
-  importTrelloBoard(trelloBoard, data) {
-    const trelloCreator = new TrelloCreator(data);
+  importBoard(board, data, importSource) {
+    check(board, Object);
+    check(data, Object);
+    check(importSource, String);
+    let creator;
+    switch (importSource) {
+    case 'trello':
+      creator = new TrelloCreator(data);
+      break;
+    case 'wekan':
+      creator = new WekanCreator(data);
+      break;
+    }
 
 
     // 1. check all parameters are ok from a syntax point of view
     // 1. check all parameters are ok from a syntax point of view
-    try {
-      check(data, {
-        membersMapping: Match.Optional(Object),
-      });
-      trelloCreator.checkActions(trelloBoard.actions);
-      trelloCreator.checkBoard(trelloBoard);
-      trelloCreator.checkLabels(trelloBoard.labels);
-      trelloCreator.checkLists(trelloBoard.lists);
-      trelloCreator.checkCards(trelloBoard.cards);
-      trelloCreator.checkChecklists(trelloBoard.checklists);
-    } catch (e) {
-      throw new Meteor.Error('error-json-schema');
-    }
+    creator.check(board);
 
 
     // 2. check parameters are ok from a business point of view (exist &
     // 2. check parameters are ok from a business point of view (exist &
     // authorized) nothing to check, everyone can import boards in their account
     // authorized) nothing to check, everyone can import boards in their account
 
 
     // 3. create all elements
     // 3. create all elements
-    trelloCreator.parseActions(trelloBoard.actions);
-    const boardId = trelloCreator.createBoardAndLabels(trelloBoard);
-    trelloCreator.createLists(trelloBoard.lists, boardId);
-    trelloCreator.createCards(trelloBoard.cards, boardId);
-    trelloCreator.createChecklists(trelloBoard.checklists);
-    // XXX add members
-    return boardId;
+    return creator.create(board);
   },
   },
 });
 });

+ 500 - 0
models/trelloCreator.js

@@ -0,0 +1,500 @@
+const DateString = Match.Where(function (dateAsString) {
+  check(dateAsString, String);
+  return moment(dateAsString, moment.ISO_8601).isValid();
+});
+
+export class TrelloCreator {
+  constructor(data) {
+    // we log current date, to use the same timestamp for all our actions.
+    // this helps to retrieve all elements performed by the same import.
+    this._nowDate = new Date();
+    // The object creation dates, indexed by Trello id
+    // (so we only parse actions once!)
+    this.createdAt = {
+      board: null,
+      cards: {},
+      lists: {},
+    };
+    // The object creator Trello Id, indexed by the object Trello id
+    // (so we only parse actions once!)
+    this.createdBy = {
+      cards: {}, // only cards have a field for that
+    };
+
+    // Map of labels Trello ID => Wekan ID
+    this.labels = {};
+    // Map of lists Trello ID => Wekan ID
+    this.lists = {};
+    // Map of cards Trello ID => Wekan ID
+    this.cards = {};
+    // The comments, indexed by Trello card id (to map when importing cards)
+    this.comments = {};
+    // the members, indexed by Trello member id => Wekan user ID
+    this.members = data.membersMapping ? data.membersMapping : {};
+
+    // maps a trelloCardId to an array of trelloAttachments
+    this.attachments = {};
+  }
+
+  /**
+   * If dateString is provided,
+   * return the Date it represents.
+   * If not, will return the date when it was first called.
+   * This is useful for us, as we want all import operations to
+   * have the exact same date for easier later retrieval.
+   *
+   * @param {String} dateString a properly formatted Date
+   */
+  _now(dateString) {
+    if(dateString) {
+      return new Date(dateString);
+    }
+    if(!this._nowDate) {
+      this._nowDate = new Date();
+    }
+    return this._nowDate;
+  }
+
+  /**
+   * if trelloUserId is provided and we have a mapping,
+   * return it.
+   * Otherwise return current logged user.
+   * @param trelloUserId
+   * @private
+     */
+  _user(trelloUserId) {
+    if(trelloUserId && this.members[trelloUserId]) {
+      return this.members[trelloUserId];
+    }
+    return Meteor.userId();
+  }
+
+  checkActions(trelloActions) {
+    check(trelloActions, [Match.ObjectIncluding({
+      data: Object,
+      date: DateString,
+      type: String,
+    })]);
+    // XXX we could perform more thorough checks based on action type
+  }
+
+  checkBoard(trelloBoard) {
+    check(trelloBoard, Match.ObjectIncluding({
+      closed: Boolean,
+      name: String,
+      prefs: Match.ObjectIncluding({
+        // XXX refine control by validating 'background' against a list of
+        // allowed values (is it worth the maintenance?)
+        background: String,
+        permissionLevel: Match.Where((value) => {
+          return ['org', 'private', 'public'].indexOf(value)>= 0;
+        }),
+      }),
+    }));
+  }
+
+  checkCards(trelloCards) {
+    check(trelloCards, [Match.ObjectIncluding({
+      closed: Boolean,
+      dateLastActivity: DateString,
+      desc: String,
+      idLabels: [String],
+      idMembers: [String],
+      name: String,
+      pos: Number,
+    })]);
+  }
+
+  checkLabels(trelloLabels) {
+    check(trelloLabels, [Match.ObjectIncluding({
+      // XXX refine control by validating 'color' against a list of allowed
+      // values (is it worth the maintenance?)
+      color: String,
+      name: String,
+    })]);
+  }
+
+  checkLists(trelloLists) {
+    check(trelloLists, [Match.ObjectIncluding({
+      closed: Boolean,
+      name: String,
+    })]);
+  }
+
+  checkChecklists(trelloChecklists) {
+    check(trelloChecklists, [Match.ObjectIncluding({
+      idBoard: String,
+      idCard: String,
+      name: String,
+      checkItems: [Match.ObjectIncluding({
+        state: String,
+        name: String,
+      })],
+    })]);
+  }
+
+  // You must call parseActions before calling this one.
+  createBoardAndLabels(trelloBoard) {
+    const boardToCreate = {
+      archived: trelloBoard.closed,
+      color: this.getColor(trelloBoard.prefs.background),
+      // very old boards won't have a creation activity so no creation date
+      createdAt: this._now(this.createdAt.board),
+      labels: [],
+      members: [{
+        userId: Meteor.userId(),
+        isAdmin: true,
+        isActive: true,
+        isCommentOnly: false,
+      }],
+      permission: this.getPermission(trelloBoard.prefs.permissionLevel),
+      slug: getSlug(trelloBoard.name) || 'board',
+      stars: 0,
+      title: trelloBoard.name,
+    };
+    // now add other members
+    if(trelloBoard.memberships) {
+      trelloBoard.memberships.forEach((trelloMembership) => {
+        const trelloId = trelloMembership.idMember;
+        // do we have a mapping?
+        if(this.members[trelloId]) {
+          const wekanId = this.members[trelloId];
+          // do we already have it in our list?
+          const wekanMember = boardToCreate.members.find((wekanMember) => wekanMember.userId === wekanId);
+          if(wekanMember) {
+            // we're already mapped, but maybe with lower rights
+            if(!wekanMember.isAdmin) {
+              wekanMember.isAdmin = this.getAdmin(trelloMembership.memberType);
+            }
+          } else {
+            boardToCreate.members.push({
+              userId: wekanId,
+              isAdmin: this.getAdmin(trelloMembership.memberType),
+              isActive: true,
+              isCommentOnly: false,
+            });
+          }
+        }
+      });
+    }
+    trelloBoard.labels.forEach((label) => {
+      const labelToCreate = {
+        _id: Random.id(6),
+        color: label.color,
+        name: label.name,
+      };
+      // We need to remember them by Trello ID, as this is the only ref we have
+      // when importing cards.
+      this.labels[label.id] = labelToCreate._id;
+      boardToCreate.labels.push(labelToCreate);
+    });
+    const boardId = Boards.direct.insert(boardToCreate);
+    Boards.direct.update(boardId, {$set: {modifiedAt: this._now()}});
+    // log activity
+    Activities.direct.insert({
+      activityType: 'importBoard',
+      boardId,
+      createdAt: this._now(),
+      source: {
+        id: trelloBoard.id,
+        system: 'Trello',
+        url: trelloBoard.url,
+      },
+      // We attribute the import to current user,
+      // not the author from the original object.
+      userId: this._user(),
+    });
+    return boardId;
+  }
+
+  /**
+   * Create the Wekan cards corresponding to the supplied Trello cards,
+   * as well as all linked data: activities, comments, and attachments
+   * @param trelloCards
+   * @param boardId
+   * @returns {Array}
+   */
+  createCards(trelloCards, boardId) {
+    const result = [];
+    trelloCards.forEach((card) => {
+      const cardToCreate = {
+        archived: card.closed,
+        boardId,
+        // very old boards won't have a creation activity so no creation date
+        createdAt: this._now(this.createdAt.cards[card.id]),
+        dateLastActivity: this._now(),
+        description: card.desc,
+        listId: this.lists[card.idList],
+        sort: card.pos,
+        title: card.name,
+        // we attribute the card to its creator if available
+        userId: this._user(this.createdBy.cards[card.id]),
+        dueAt: card.due ? this._now(card.due) : null,
+      };
+      // add labels
+      if (card.idLabels) {
+        cardToCreate.labelIds = card.idLabels.map((trelloId) => {
+          return this.labels[trelloId];
+        });
+      }
+      // add members {
+      if(card.idMembers) {
+        const wekanMembers = [];
+        // we can't just map, as some members may not have been mapped
+        card.idMembers.forEach((trelloId) => {
+          if(this.members[trelloId]) {
+            const wekanId = this.members[trelloId];
+            // we may map multiple Trello members to the same wekan user
+            // in which case we risk adding the same user multiple times
+            if(!wekanMembers.find((wId) => wId === wekanId)){
+              wekanMembers.push(wekanId);
+            }
+          }
+          return true;
+        });
+        if(wekanMembers.length>0) {
+          cardToCreate.members = wekanMembers;
+        }
+      }
+      // insert card
+      const cardId = Cards.direct.insert(cardToCreate);
+      // keep track of Trello id => WeKan id
+      this.cards[card.id] = cardId;
+      // log activity
+      Activities.direct.insert({
+        activityType: 'importCard',
+        boardId,
+        cardId,
+        createdAt: this._now(),
+        listId: cardToCreate.listId,
+        source: {
+          id: card.id,
+          system: 'Trello',
+          url: card.url,
+        },
+        // we attribute the import to current user,
+        // not the author of the original card
+        userId: this._user(),
+      });
+      // add comments
+      const comments = this.comments[card.id];
+      if (comments) {
+        comments.forEach((comment) => {
+          const commentToCreate = {
+            boardId,
+            cardId,
+            createdAt: this._now(comment.date),
+            text: comment.data.text,
+            // we attribute the comment to the original author, default to current user
+            userId: this._user(comment.idMemberCreator),
+          };
+          // dateLastActivity will be set from activity insert, no need to
+          // update it ourselves
+          const commentId = CardComments.direct.insert(commentToCreate);
+          Activities.direct.insert({
+            activityType: 'addComment',
+            boardId: commentToCreate.boardId,
+            cardId: commentToCreate.cardId,
+            commentId,
+            createdAt: this._now(commentToCreate.createdAt),
+            // we attribute the addComment (not the import)
+            // to the original author - it is needed by some UI elements.
+            userId: commentToCreate.userId,
+          });
+        });
+      }
+      const attachments = this.attachments[card.id];
+      const trelloCoverId = card.idAttachmentCover;
+      if (attachments) {
+        attachments.forEach((att) => {
+          const file = new FS.File();
+          // Simulating file.attachData on the client generates multiple errors
+          // - HEAD returns null, which causes exception down the line
+          // - the template then tries to display the url to the attachment which causes other errors
+          // so we make it server only, and let UI catch up once it is done, forget about latency comp.
+          if(Meteor.isServer) {
+            file.attachData(att.url, function (error) {
+              file.boardId = boardId;
+              file.cardId = cardId;
+              if (error) {
+                throw(error);
+              } else {
+                const wekanAtt = Attachments.insert(file, () => {
+                  // we do nothing
+                });
+                //
+                if(trelloCoverId === att.id) {
+                  Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}});
+                }
+              }
+            });
+          }
+          // todo XXX set cover - if need be
+        });
+      }
+      result.push(cardId);
+    });
+    return result;
+  }
+
+  // Create labels if they do not exist and load this.labels.
+  createLabels(trelloLabels, board) {
+    trelloLabels.forEach((label) => {
+      const color = label.color;
+      const name = label.name;
+      const existingLabel = board.getLabel(name, color);
+      if (existingLabel) {
+        this.labels[label.id] = existingLabel._id;
+      } else {
+        const idLabelCreated = board.pushLabel(name, color);
+        this.labels[label.id] = idLabelCreated;
+      }
+    });
+  }
+
+  createLists(trelloLists, boardId) {
+    trelloLists.forEach((list) => {
+      const listToCreate = {
+        archived: list.closed,
+        boardId,
+        // We are being defensing here by providing a default date (now) if the
+        // creation date wasn't found on the action log. This happen on old
+        // Trello boards (eg from 2013) that didn't log the 'createList' action
+        // we require.
+        createdAt: this._now(this.createdAt.lists[list.id]),
+        title: list.name,
+      };
+      const listId = Lists.direct.insert(listToCreate);
+      Lists.direct.update(listId, {$set: {'updatedAt': this._now()}});
+      this.lists[list.id] = listId;
+      // log activity
+      Activities.direct.insert({
+        activityType: 'importList',
+        boardId,
+        createdAt: this._now(),
+        listId,
+        source: {
+          id: list.id,
+          system: 'Trello',
+        },
+        // We attribute the import to current user,
+        // not the creator of the original object
+        userId: this._user(),
+      });
+    });
+  }
+
+  createChecklists(trelloChecklists) {
+    trelloChecklists.forEach((checklist) => {
+      // Create the checklist
+      const checklistToCreate = {
+        cardId: this.cards[checklist.idCard],
+        title: checklist.name,
+        createdAt: this._now(),
+      };
+      const checklistId = Checklists.direct.insert(checklistToCreate);
+      // Now add the items to the checklist
+      const itemsToCreate = [];
+      checklist.checkItems.forEach((item) => {
+        itemsToCreate.push({
+          _id: checklistId + itemsToCreate.length,
+          title: item.name,
+          isFinished: item.state === 'complete',
+        });
+      });
+      Checklists.direct.update(checklistId, {$set: {items: itemsToCreate}});
+    });
+  }
+
+  getAdmin(trelloMemberType) {
+    return trelloMemberType === 'admin';
+  }
+
+  getColor(trelloColorCode) {
+    // trello color name => wekan color
+    const mapColors = {
+      'blue': 'belize',
+      'orange': 'pumpkin',
+      'green': 'nephritis',
+      'red': 'pomegranate',
+      'purple': 'wisteria',
+      'pink': 'pomegranate',
+      'lime': 'nephritis',
+      'sky': 'belize',
+      'grey': 'midnight',
+    };
+    const wekanColor = mapColors[trelloColorCode];
+    return wekanColor || Boards.simpleSchema()._schema.color.allowedValues[0];
+  }
+
+  getPermission(trelloPermissionCode) {
+    if (trelloPermissionCode === 'public') {
+      return 'public';
+    }
+    // Wekan does NOT have organization level, so we default both 'private' and
+    // 'org' to private.
+    return 'private';
+  }
+
+  parseActions(trelloActions) {
+    trelloActions.forEach((action) => {
+      if (action.type === 'addAttachmentToCard') {
+        // We have to be cautious, because the attachment could have been removed later.
+        // In that case Trello still reports its addition, but removes its 'url' field.
+        // So we test for that
+        const trelloAttachment = action.data.attachment;
+        if(trelloAttachment.url) {
+          // we cannot actually create the Wekan attachment, because we don't yet
+          // have the cards to attach it to, so we store it in the instance variable.
+          const trelloCardId = action.data.card.id;
+          if(!this.attachments[trelloCardId]) {
+            this.attachments[trelloCardId] = [];
+          }
+          this.attachments[trelloCardId].push(trelloAttachment);
+        }
+      } else if (action.type === 'commentCard') {
+        const id = action.data.card.id;
+        if (this.comments[id]) {
+          this.comments[id].push(action);
+        } else {
+          this.comments[id] = [action];
+        }
+      } else if (action.type === 'createBoard') {
+        this.createdAt.board = action.date;
+      } else if (action.type === 'createCard') {
+        const cardId = action.data.card.id;
+        this.createdAt.cards[cardId] = action.date;
+        this.createdBy.cards[cardId] = action.idMemberCreator;
+      } else if (action.type === 'createList') {
+        const listId = action.data.list.id;
+        this.createdAt.lists[listId] = action.date;
+      }
+    });
+  }
+
+  check(board) {
+    try {
+      // check(data, {
+      //   membersMapping: Match.Optional(Object),
+      // });
+      this.checkActions(board.actions);
+      this.checkBoard(board);
+      this.checkLabels(board.labels);
+      this.checkLists(board.lists);
+      this.checkCards(board.cards);
+      this.checkChecklists(board.checklists);
+    } catch (e) {
+      throw new Meteor.Error('error-json-schema');
+    }
+  }
+
+  create(board) {
+    this.parseActions(board.actions);
+    const boardId = this.createBoardAndLabels(board);
+    this.createLists(board.lists, boardId);
+    this.createCards(board.cards, boardId);
+    this.createChecklists(board.checklists);
+    // XXX add members
+    return boardId;
+  }
+}

+ 476 - 0
models/wekanCreator.js

@@ -0,0 +1,476 @@
+const DateString = Match.Where(function (dateAsString) {
+  check(dateAsString, String);
+  return moment(dateAsString, moment.ISO_8601).isValid();
+});
+
+export class WekanCreator {
+  constructor(data) {
+    // we log current date, to use the same timestamp for all our actions.
+    // this helps to retrieve all elements performed by the same import.
+    this._nowDate = new Date();
+    // The object creation dates, indexed by Wekan id
+    // (so we only parse actions once!)
+    this.createdAt = {
+      board: null,
+      cards: {},
+      lists: {},
+    };
+    // The object creator Wekan Id, indexed by the object Wekan id
+    // (so we only parse actions once!)
+    this.createdBy = {
+      cards: {}, // only cards have a field for that
+    };
+
+    // Map of labels Wekan ID => Wekan ID
+    this.labels = {};
+    // Map of lists Wekan ID => Wekan ID
+    this.lists = {};
+    // Map of cards Wekan ID => Wekan ID
+    this.cards = {};
+    // The comments, indexed by Wekan card id (to map when importing cards)
+    this.comments = {};
+    // the members, indexed by Wekan member id => Wekan user ID
+    this.members = data.membersMapping ? data.membersMapping : {};
+
+    // maps a wekanCardId to an array of wekanAttachments
+    this.attachments = {};
+  }
+
+  /**
+   * If dateString is provided,
+   * return the Date it represents.
+   * If not, will return the date when it was first called.
+   * This is useful for us, as we want all import operations to
+   * have the exact same date for easier later retrieval.
+   *
+   * @param {String} dateString a properly formatted Date
+   */
+  _now(dateString) {
+    if(dateString) {
+      return new Date(dateString);
+    }
+    if(!this._nowDate) {
+      this._nowDate = new Date();
+    }
+    return this._nowDate;
+  }
+
+  /**
+   * if wekanUserId is provided and we have a mapping,
+   * return it.
+   * Otherwise return current logged user.
+   * @param wekanUserId
+   * @private
+     */
+  _user(wekanUserId) {
+    if(wekanUserId && this.members[wekanUserId]) {
+      return this.members[wekanUserId];
+    }
+    return Meteor.userId();
+  }
+
+  checkActivities(wekanActivities) {
+    check(wekanActivities, [Match.ObjectIncluding({
+      userId: String,
+      activityType: String,
+      createdAt: DateString,
+    })]);
+    // XXX we could perform more thorough checks based on action type
+  }
+
+  checkBoard(wekanBoard) {
+    check(wekanBoard, Match.ObjectIncluding({
+      archived: Boolean,
+      title: String,
+      // XXX refine control by validating 'color' against a list of
+      // allowed values (is it worth the maintenance?)
+      color: String,
+      permission: Match.Where((value) => {
+        return ['private', 'public'].indexOf(value)>= 0;
+      }),
+    }));
+  }
+
+  checkCards(wekanCards) {
+    check(wekanCards, [Match.ObjectIncluding({
+      archived: Boolean,
+      dateLastActivity: DateString,
+      labelIds: [String],
+      members: [String],
+      title: String,
+      sort: Number,
+    })]);
+  }
+
+  checkLabels(wekanLabels) {
+    check(wekanLabels, [Match.ObjectIncluding({
+      // XXX refine control by validating 'color' against a list of allowed
+      // values (is it worth the maintenance?)
+      color: String,
+      name: String,
+    })]);
+  }
+
+  checkLists(wekanLists) {
+    check(wekanLists, [Match.ObjectIncluding({
+      archived: Boolean,
+      title: String,
+    })]);
+  }
+
+  // checkChecklists(wekanChecklists) {
+  //   check(wekanChecklists, [Match.ObjectIncluding({
+  //     idBoard: String,
+  //     idCard: String,
+  //     name: String,
+  //     checkItems: [Match.ObjectIncluding({
+  //       state: String,
+  //       name: String,
+  //     })],
+  //   })]);
+  // }
+
+  // You must call parseActions before calling this one.
+  createBoardAndLabels(wekanBoard) {
+    const boardToCreate = {
+      archived: wekanBoard.archived,
+      color: wekanBoard.color,
+      // very old boards won't have a creation activity so no creation date
+      createdAt: this._now(wekanBoard.createdAt),
+      labels: [],
+      members: [{
+        userId: Meteor.userId(),
+        isAdmin: true,
+        isActive: true,
+        isCommentOnly: false,
+      }],
+      permission: wekanBoard.permission,
+      slug: getSlug(wekanBoard.title) || 'board',
+      stars: 0,
+      title: wekanBoard.title,
+    };
+    // now add other members
+    if(wekanBoard.members) {
+      wekanBoard.members.forEach((wekanMember) => {
+        const wekanId = wekanMember.userId;
+        // do we have a mapping?
+        if(this.members[wekanId]) {
+          const wekanId = this.members[wekanId];
+          // do we already have it in our list?
+          const wekanMember = boardToCreate.members.find((wekanMember) => wekanMember.userId === wekanId);
+          if(!wekanMember) {
+            boardToCreate.members.push({
+              userId: wekanId,
+              isAdmin: wekanMember.isAdmin,
+              isActive: true,
+              isCommentOnly: false,
+            });
+          }
+        }
+      });
+    }
+    wekanBoard.labels.forEach((label) => {
+      const labelToCreate = {
+        _id: Random.id(6),
+        color: label.color,
+        name: label.name,
+      };
+      // We need to remember them by Wekan ID, as this is the only ref we have
+      // when importing cards.
+      this.labels[label._id] = labelToCreate._id;
+      boardToCreate.labels.push(labelToCreate);
+    });
+    const boardId = Boards.direct.insert(boardToCreate);
+    Boards.direct.update(boardId, {$set: {modifiedAt: this._now()}});
+    // log activity
+    Activities.direct.insert({
+      activityType: 'importBoard',
+      boardId,
+      createdAt: this._now(),
+      source: {
+        id: wekanBoard.id,
+        system: 'Wekan',
+      },
+      // We attribute the import to current user,
+      // not the author from the original object.
+      userId: this._user(),
+    });
+    return boardId;
+  }
+
+  /**
+   * Create the Wekan cards corresponding to the supplied Wekan cards,
+   * as well as all linked data: activities, comments, and attachments
+   * @param wekanCards
+   * @param boardId
+   * @returns {Array}
+   */
+  createCards(wekanCards, boardId) {
+    const result = [];
+    wekanCards.forEach((card) => {
+      const cardToCreate = {
+        archived: card.archived,
+        boardId,
+        // very old boards won't have a creation activity so no creation date
+        createdAt: this._now(this.createdAt.cards[card._id]),
+        dateLastActivity: this._now(),
+        description: card.description,
+        listId: this.lists[card.listId],
+        sort: card.sort,
+        title: card.title,
+        // we attribute the card to its creator if available
+        userId: this._user(this.createdBy.cards[card._id]),
+        dueAt: card.dueAt ? this._now(card.dueAt) : null,
+      };
+      // add labels
+      if (card.labelIds) {
+        cardToCreate.labelIds = card.labelIds.map((wekanId) => {
+          return this.labels[wekanId];
+        });
+      }
+      // add members {
+      if(card.members) {
+        const wekanMembers = [];
+        // we can't just map, as some members may not have been mapped
+        card.members.forEach((sourceMemberId) => {
+          if(this.members[sourceMemberId]) {
+            const wekanId = this.members[sourceMemberId];
+            // we may map multiple Wekan members to the same wekan user
+            // in which case we risk adding the same user multiple times
+            if(!wekanMembers.find((wId) => wId === wekanId)){
+              wekanMembers.push(wekanId);
+            }
+          }
+          return true;
+        });
+        if(wekanMembers.length>0) {
+          cardToCreate.members = wekanMembers;
+        }
+      }
+      // insert card
+      const cardId = Cards.direct.insert(cardToCreate);
+      // keep track of Wekan id => WeKan id
+      this.cards[card.id] = cardId;
+      // log activity
+      Activities.direct.insert({
+        activityType: 'importCard',
+        boardId,
+        cardId,
+        createdAt: this._now(),
+        listId: cardToCreate.listId,
+        source: {
+          id: card._id,
+          system: 'Wekan',
+        },
+        // we attribute the import to current user,
+        // not the author of the original card
+        userId: this._user(),
+      });
+      // add comments
+      const comments = this.comments[card._id];
+      if (comments) {
+        comments.forEach((comment) => {
+          const commentToCreate = {
+            boardId,
+            cardId,
+            createdAt: this._now(comment.date),
+            text: comment.text,
+            // we attribute the comment to the original author, default to current user
+            userId: this._user(comment.userId),
+          };
+          // dateLastActivity will be set from activity insert, no need to
+          // update it ourselves
+          const commentId = CardComments.direct.insert(commentToCreate);
+          Activities.direct.insert({
+            activityType: 'addComment',
+            boardId: commentToCreate.boardId,
+            cardId: commentToCreate.cardId,
+            commentId,
+            createdAt: this._now(commentToCreate.createdAt),
+            // we attribute the addComment (not the import)
+            // to the original author - it is needed by some UI elements.
+            userId: commentToCreate.userId,
+          });
+        });
+      }
+      const attachments = this.attachments[card._id];
+      const wekanCoverId = card.coverId;
+      if (attachments) {
+        attachments.forEach((att) => {
+          const file = new FS.File();
+          // Simulating file.attachData on the client generates multiple errors
+          // - HEAD returns null, which causes exception down the line
+          // - the template then tries to display the url to the attachment which causes other errors
+          // so we make it server only, and let UI catch up once it is done, forget about latency comp.
+          if(Meteor.isServer) {
+            file.attachData(att.url, function (error) {
+              file.boardId = boardId;
+              file.cardId = cardId;
+              if (error) {
+                throw(error);
+              } else {
+                const wekanAtt = Attachments.insert(file, () => {
+                  // we do nothing
+                });
+                //
+                if(wekanCoverId === att._id) {
+                  Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}});
+                }
+              }
+            });
+          }
+          // todo XXX set cover - if need be
+        });
+      }
+      result.push(cardId);
+    });
+    return result;
+  }
+
+  // Create labels if they do not exist and load this.labels.
+  createLabels(wekanLabels, board) {
+    wekanLabels.forEach((label) => {
+      const color = label.color;
+      const name = label.name;
+      const existingLabel = board.getLabel(name, color);
+      if (existingLabel) {
+        this.labels[label.id] = existingLabel._id;
+      } else {
+        const idLabelCreated = board.pushLabel(name, color);
+        this.labels[label.id] = idLabelCreated;
+      }
+    });
+  }
+
+  createLists(wekanLists, boardId) {
+    wekanLists.forEach((list) => {
+      const listToCreate = {
+        archived: list.archived,
+        boardId,
+        // We are being defensing here by providing a default date (now) if the
+        // creation date wasn't found on the action log. This happen on old
+        // Wekan boards (eg from 2013) that didn't log the 'createList' action
+        // we require.
+        createdAt: this._now(this.createdAt.lists[list.id]),
+        title: list.title,
+      };
+      const listId = Lists.direct.insert(listToCreate);
+      Lists.direct.update(listId, {$set: {'updatedAt': this._now()}});
+      this.lists[list._id] = listId;
+      // log activity
+      Activities.direct.insert({
+        activityType: 'importList',
+        boardId,
+        createdAt: this._now(),
+        listId,
+        source: {
+          id: list._id,
+          system: 'Wekan',
+        },
+        // We attribute the import to current user,
+        // not the creator of the original object
+        userId: this._user(),
+      });
+    });
+  }
+
+  // createChecklists(wekanChecklists) {
+  //   wekanChecklists.forEach((checklist) => {
+  //     // Create the checklist
+  //     const checklistToCreate = {
+  //       cardId: this.cards[checklist.cardId],
+  //       title: checklist.title,
+  //       createdAt: this._now(),
+  //     };
+  //     const checklistId = Checklists.direct.insert(checklistToCreate);
+  //     // Now add the items to the checklist
+  //     const itemsToCreate = [];
+  //     checklist.checkItems.forEach((item) => {
+  //       itemsToCreate.push({
+  //         _id: checklistId + itemsToCreate.length,
+  //         title: item.title,
+  //         isFinished: item.isFinished,
+  //       });
+  //     });
+  //     Checklists.direct.update(checklistId, {$set: {items: itemsToCreate}});
+  //   });
+  // }
+
+  parseActivities(wekanBoard) {
+    wekanBoard.activities.forEach((activity) => {
+      switch (activity.activityType) {
+      case 'addAttachment': {
+        // We have to be cautious, because the attachment could have been removed later.
+        // In that case Wekan still reports its addition, but removes its 'url' field.
+        // So we test for that
+        const wekanAttachment = wekanBoard.attachments.filter((attachment) => {
+          return attachment._id === activity.attachmentId;
+        })[0];
+        if(wekanAttachment.url) {
+          // we cannot actually create the Wekan attachment, because we don't yet
+          // have the cards to attach it to, so we store it in the instance variable.
+          const wekanCardId = activity.cardId;
+          if(!this.attachments[wekanCardId]) {
+            this.attachments[wekanCardId] = [];
+          }
+          this.attachments[wekanCardId].push(wekanAttachment);
+        }
+        break;
+      }
+      case 'addComment': {
+        const wekanComment = wekanBoard.comments.filter((comment) => {
+          return comment._id === activity.commentId;
+        })[0];
+        const id = activity.cardId;
+        if (!this.comments[id]) {
+          this.comments[id] = [];
+        }
+        this.comments[id].push(wekanComment);
+        break;
+      }
+      case 'createBoard': {
+        this.createdAt.board = activity.createdAt;
+        break;
+      }
+      case 'createCard': {
+        const cardId = activity.cardId;
+        this.createdAt.cards[cardId] = activity.createdAt;
+        this.createdBy.cards[cardId] = activity.userId;
+        break;
+      }
+      case 'createList': {
+        const listId = activity.listId;
+        this.createdAt.lists[listId] = activity.createdAt;
+        break;
+      }}
+    });
+  }
+
+  check(board) {
+    try {
+      // check(data, {
+      //   membersMapping: Match.Optional(Object),
+      // });
+      this.checkActivities(board.activities);
+      this.checkBoard(board);
+      this.checkLabels(board.labels);
+      this.checkLists(board.lists);
+      this.checkCards(board.cards);
+      // Checklists are not exported yet
+      // this.checkChecklists(board.checklists);
+    } catch (e) {
+      throw new Meteor.Error('error-json-schema');
+    }
+  }
+
+  create(board) {
+    this.parseActivities(board);
+    const boardId = this.createBoardAndLabels(board);
+    this.createLists(board.lists, boardId);
+    this.createCards(board.cards, boardId);
+    // Checklists are not exported yet
+    // this.createChecklists(board.checklists);
+    // XXX add members
+    return boardId;
+  }
+}