Browse Source

Merge branch 'develop' of github.com:tracim/tracim_v2 into feature/refactor_content_type_and_content_status

Guénaël Muller 5 years ago
parent
commit
75b93f272a
92 changed files with 878 additions and 496 deletions
  1. 74 0
      README_traduction.md
  2. 4 4
      backend/tracim_backend/models/applications.py
  3. 1 1
      backend/tracim_backend/models/contents.py
  4. 1 1
      backend/tracim_backend/models/context_models.py
  5. 1 1
      backend/tracim_backend/tests/__init__.py
  6. 9 9
      backend/tracim_backend/tests/functional/test_contents.py
  7. 5 6
      backend/tracim_backend/tests/functional/test_system.py
  8. 4 4
      backend/tracim_backend/tests/functional/test_user.py
  9. 111 46
      backend/tracim_backend/tests/functional/test_workspaces.py
  10. 9 5
      backend/tracim_backend/views/core_api/schemas.py
  11. 23 0
      build_full_frontend.sh
  12. 1 1
      frontend/dist/appInterface.js
  13. 16 0
      frontend/i18next.scanner.js
  14. 23 0
      frontend/i18next.scanner/en/translation.json
  15. 23 0
      frontend/i18next.scanner/fr/translation.json
  16. 2 0
      frontend/package.json
  17. 24 17
      frontend/src/action-creator.sync.js
  18. 5 2
      frontend/src/appFactory.js
  19. 1 1
      frontend/src/component/FlashMessage.jsx
  20. 1 1
      frontend/src/component/Footer.jsx
  21. 4 4
      frontend/src/component/Header/MenuActionListItem/DropdownLang.jsx
  22. 4 3
      frontend/src/component/Header/MenuActionListItem/MenuProfil.jsx
  23. 5 4
      frontend/src/component/Header/MenuActionListItem/Notification.jsx
  24. 1 1
      frontend/src/component/Header/MenuActionListItem/Search.jsx
  25. 7 6
      frontend/src/component/Workspace/BtnExtandedAction.jsx
  26. 3 3
      frontend/src/component/Workspace/ContentItemHeader.jsx
  27. 1 1
      frontend/src/component/Workspace/Folder.jsx
  28. 1 1
      frontend/src/component/Workspace/OpenContentApp.jsx
  29. 5 2
      frontend/src/container/Dashboard.jsx
  30. 13 8
      frontend/src/container/Header.jsx
  31. 1 1
      frontend/src/container/Login.jsx
  32. 1 1
      frontend/src/container/Sidebar.jsx
  33. 1 1
      frontend/src/css/Generic.styl
  34. 0 1
      frontend/src/css/Header.styl
  35. 3 3
      frontend/src/css/Login.styl
  36. 1 1
      frontend/src/css/Workspace.styl
  37. 19 9
      frontend/src/i18n.js
  38. 0 0
      frontend/src/img/flag_en.png
  39. 0 0
      frontend/src/img/flag_fr.png
  40. 2 2
      frontend/src/reducer/app.js
  41. 2 2
      frontend/src/reducer/contentType.js
  42. 3 5
      frontend/src/reducer/flashMessage.js
  43. 16 5
      frontend/src/reducer/lang.js
  44. 2 2
      frontend/src/reducer/timezone.js
  45. 14 5
      frontend/src/reducer/user.js
  46. 5 3
      frontend/src/reducer/workspaceContent.js
  47. 6 4
      frontend/src/reducer/workspaceList.js
  48. 0 41
      frontend/src/translate/en.js
  49. 0 41
      frontend/src/translate/fr.js
  50. 17 0
      frontend_app_html-document/build_html-document.sh
  51. 1 1
      frontend_app_html-document/dist/index.html
  52. 14 0
      frontend_app_html-document/i18next.scanner.js
  53. 5 0
      frontend_app_html-document/i18next.scanner/en/translation.json
  54. 5 0
      frontend_app_html-document/i18next.scanner/fr/translation.json
  55. 4 2
      frontend_app_html-document/package.json
  56. 0 13
      frontend_app_html-document/rebuild_html-document.sh
  57. 5 5
      frontend_app_html-document/src/component/HtmlDocument.jsx
  58. 27 9
      frontend_app_html-document/src/container/HtmlDocument.jsx
  59. 31 5
      frontend_app_html-document/src/container/PopupCreateHtmlDocument.jsx
  60. 5 5
      frontend_app_html-document/src/css/index.styl
  61. 17 4
      frontend_app_html-document/src/helper.js
  62. 1 11
      frontend_app_html-document/src/i18n.js
  63. 1 1
      frontend_app_html-document/src/index.js
  64. 0 9
      frontend_app_html-document/src/translate/en.js
  65. 0 9
      frontend_app_html-document/src/translate/fr.js
  66. 17 0
      frontend_app_thread/build_thread.sh
  67. 14 0
      frontend_app_thread/i18next.scanner.js
  68. 4 0
      frontend_app_thread/i18next.scanner/en/translation.json
  69. 4 0
      frontend_app_thread/i18next.scanner/fr/translation.json
  70. 2 0
      frontend_app_thread/package.json
  71. 0 13
      frontend_app_thread/rebuild_thread.sh
  72. 38 5
      frontend_app_thread/src/container/PopupCreateThread.jsx
  73. 16 0
      frontend_app_thread/src/container/Thread.jsx
  74. 2 2
      frontend_app_thread/src/helper.js
  75. 1 11
      frontend_app_thread/src/i18n.js
  76. 1 1
      frontend_app_thread/src/index.js
  77. 0 9
      frontend_app_thread/src/translate/en.js
  78. 0 9
      frontend_app_thread/src/translate/fr.js
  79. 9 0
      frontend_lib/i18next.scanner.js
  80. 1 0
      frontend_lib/i18next.scanner/en/translation.json
  81. 1 0
      frontend_lib/i18next.scanner/fr/translation.json
  82. 3 1
      frontend_lib/package.json
  83. 3 2
      frontend_lib/src/component/CardPopup/CardPopupCreateContent.jsx
  84. 6 7
      frontend_lib/src/component/Input/SelectStatus/SelectStatus.jsx
  85. 8 11
      frontend_lib/src/component/Timeline/Comment.jsx
  86. 1 1
      frontend_lib/src/component/Timeline/Timeline.jsx
  87. 3 6
      frontend_lib/src/component/Timeline/Timeline.styl
  88. 84 84
      frontend_lib/src/component/Timeline/debugData.js
  89. 8 0
      frontend_lib/src/helper.js
  90. 5 1
      frontend_lib/src/index.js
  91. 40 0
      i18next.option.js
  92. 16 0
      install_frontend_dependencies.sh

+ 74 - 0
README_traduction.md View File

@@ -0,0 +1,74 @@
1
+# Tracim internationalization
2
+
3
+## How to for Frontend part
4
+
5
+In each frontend repo (frontend, frontend_app_..., frontend_lib), there is a folder i18next.scanner that holds every translation files in JSON.
6
+
7
+___
8
+
9
+### I have found a translation error, how do I fix it ?
10
+
11
+**If the error is in any language other than english:**
12
+
13
+a) you can edit the values of the json files.
14
+
15
+b) Then commit/push your changes
16
+
17
+**If the error is in en.json:**
18
+
19
+1) You must find the key in the according .jsx file of that same repo.
20
+
21
+2) Fix the error
22
+ 
23
+3) Rebuild the translation files with:
24
+
25
+`npm run build-translation`
26
+
27
+This will add your new key in the translation files and remove the old one.
28
+
29
+4) Add translations for your new key in other .json files.
30
+
31
+5) commit/push your changes
32
+
33
+___
34
+
35
+### I have found an untranslated key in a language, how do I fix it ?
36
+
37
+It means you have found an english text even though you have selected another language. 
38
+
39
+Do task a) and b) in the according .json file, in folder i18next.scanner.
40
+
41
+___
42
+
43
+### I have found an untranslated key in a language but the key does not appear in the .json file.
44
+
45
+Do step 3).
46
+
47
+If the key still isn't in the .json file, it means the text in the .jsx file does not implement the translation process.
48
+
49
+So you must:
50
+
51
+I) Find the according .jsx file that have your untranslated key
52
+
53
+II) wrap your untranslated key in the translation function `t`:
54
+
55
+Exemple: `<div>My untranslated key</div>` will become `<div>{this.props.t('My untranslated key')}</div>`
56
+
57
+III) Check that `t` in available in your component, meanings your component must be wraped in the `translate()` higher order function
58
+
59
+``` javascript
60
+import React from 'react'
61
+import { translate } from 'react-i18next'
62
+
63
+class MyComponent extends React.Component {
64
+  render () {
65
+    return (<div>{this.props.t('My untranslated key')}</div>)
66
+  }
67
+}
68
+
69
+export default translate()(MyComponent)
70
+```
71
+
72
+IV) You can destruct `t` from `this.props` in the `render()` like it is done in most components.
73
+
74
+V) Do steps 3), 4), 5)

+ 4 - 4
backend/tracim_backend/models/applications.py View File

@@ -50,7 +50,7 @@ calendar = Application(
50 50
 
51 51
 thread = Application(
52 52
     label='Threads',
53
-    slug='contents/threads',
53
+    slug='contents/thread',
54 54
     fa_icon='comments-o',
55 55
     hexcolor='#ad4cf9',
56 56
     is_active=True,
@@ -61,7 +61,7 @@ thread = Application(
61 61
 
62 62
 _file = Application(
63 63
     label='Files',
64
-    slug='contents/files',
64
+    slug='contents/file',
65 65
     fa_icon='paperclip',
66 66
     hexcolor='#FF9900',
67 67
     is_active=True,
@@ -81,12 +81,12 @@ markdownpluspage = Application(
81 81
 
82 82
 html_documents = Application(
83 83
     label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
84
-    slug='contents/html-documents',
84
+    slug='contents/html-document',
85 85
     fa_icon='file-text-o',
86 86
     hexcolor='#3f52e3',
87 87
     is_active=True,
88 88
     config={},
89
-    main_route='/#/workspaces/{workspace_id}/contents?type=html-documents',
89
+    main_route='/#/workspaces/{workspace_id}/contents?type=html-document',
90 90
 )
91 91
 # TODO - G.M - 08-06-2018 - This is hardcoded lists of app, make this dynamic.
92 92
 # List of applications

+ 1 - 1
backend/tracim_backend/models/contents.py View File

@@ -159,7 +159,7 @@ markdownpluspage_type = ContentType(
159 159
 )
160 160
 
161 161
 html_documents_type = ContentType(
162
-    slug='html-documents',
162
+    slug='html-document',
163 163
     fa_icon=html_documents.fa_icon,
164 164
     hexcolor=html_documents.hexcolor,
165 165
     label='Text Document',

+ 1 - 1
backend/tracim_backend/models/context_models.py View File

@@ -291,7 +291,7 @@ class ContentCreation(object):
291 291
     ) -> None:
292 292
         self.label = label
293 293
         self.content_type = content_type
294
-        self.parent_id = parent_id
294
+        self.parent_id = parent_id or None
295 295
 
296 296
 
297 297
 class CommentCreation(object):

+ 1 - 1
backend/tracim_backend/tests/__init__.py View File

@@ -50,7 +50,7 @@ def set_html_document_slug_to_legacy(session_factory) -> None:
50 50
     )
51 51
     content_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'page').filter(ContentRevisionRO.content_id == 6)  # nopep8
52 52
     assert content_query.count() == 0
53
-    html_documents_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'html-documents')  # nopep8
53
+    html_documents_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'html-document')  # nopep8
54 54
     html_documents_query.update({ContentRevisionRO.type: 'page'})
55 55
     transaction.commit()
56 56
     assert content_query.count() > 0

+ 9 - 9
backend/tracim_backend/tests/functional/test_contents.py View File

@@ -50,7 +50,7 @@ class TestHtmlDocuments(FunctionalTest):
50 50
             status=200
51 51
         )
52 52
         content = res.json_body
53
-        assert content['content_type'] == 'html-documents'
53
+        assert content['content_type'] == 'html-document'
54 54
         assert content['content_id'] == 6
55 55
         assert content['is_archived'] is False
56 56
         assert content['is_deleted'] is False
@@ -91,7 +91,7 @@ class TestHtmlDocuments(FunctionalTest):
91 91
             status=200
92 92
         )
93 93
         content = res.json_body
94
-        assert content['content_type'] == 'html-documents'
94
+        assert content['content_type'] == 'html-document'
95 95
         assert content['content_id'] == 6
96 96
         assert content['is_archived'] is False
97 97
         assert content['is_deleted'] is False
@@ -254,7 +254,7 @@ class TestHtmlDocuments(FunctionalTest):
254 254
             status=200
255 255
         )
256 256
         content = res.json_body
257
-        assert content['content_type'] == 'html-documents'
257
+        assert content['content_type'] == 'html-document'
258 258
         assert content['content_id'] == 6
259 259
         assert content['is_archived'] is False
260 260
         assert content['is_deleted'] is False
@@ -281,7 +281,7 @@ class TestHtmlDocuments(FunctionalTest):
281 281
             status=200
282 282
         )
283 283
         content = res.json_body
284
-        assert content['content_type'] == 'html-documents'
284
+        assert content['content_type'] == 'html-document'
285 285
         assert content['content_id'] == 6
286 286
         assert content['is_archived'] is False
287 287
         assert content['is_deleted'] is False
@@ -323,7 +323,7 @@ class TestHtmlDocuments(FunctionalTest):
323 323
         revisions = res.json_body
324 324
         assert len(revisions) == 3
325 325
         revision = revisions[0]
326
-        assert revision['content_type'] == 'html-documents'
326
+        assert revision['content_type'] == 'html-document'
327 327
         assert revision['content_id'] == 6
328 328
         assert revision['is_archived'] is False
329 329
         assert revision['is_deleted'] is False
@@ -345,7 +345,7 @@ class TestHtmlDocuments(FunctionalTest):
345 345
         assert revision['author']['avatar_url'] is None
346 346
         assert revision['author']['public_name'] == 'Global manager'
347 347
         revision = revisions[1]
348
-        assert revision['content_type'] == 'html-documents'
348
+        assert revision['content_type'] == 'html-document'
349 349
         assert revision['content_id'] == 6
350 350
         assert revision['is_archived'] is False
351 351
         assert revision['is_deleted'] is False
@@ -367,7 +367,7 @@ class TestHtmlDocuments(FunctionalTest):
367 367
         assert revision['author']['avatar_url'] is None
368 368
         assert revision['author']['public_name'] == 'Global manager'
369 369
         revision = revisions[2]
370
-        assert revision['content_type'] == 'html-documents'
370
+        assert revision['content_type'] == 'html-document'
371 371
         assert revision['content_id'] == 6
372 372
         assert revision['is_archived'] is False
373 373
         assert revision['is_deleted'] is False
@@ -410,7 +410,7 @@ class TestHtmlDocuments(FunctionalTest):
410 410
             status=200
411 411
         )
412 412
         content = res.json_body
413
-        assert content['content_type'] == 'html-documents'
413
+        assert content['content_type'] == 'html-document'
414 414
         assert content['content_id'] == 6
415 415
         assert content['status'] == 'open'
416 416
 
@@ -427,7 +427,7 @@ class TestHtmlDocuments(FunctionalTest):
427 427
             status=200
428 428
         )
429 429
         content = res.json_body
430
-        assert content['content_type'] == 'html-documents'
430
+        assert content['content_type'] == 'html-document'
431 431
         assert content['content_id'] == 6
432 432
         assert content['status'] == 'closed-deprecated'
433 433
 

+ 5 - 6
backend/tracim_backend/tests/functional/test_system.py View File

@@ -5,6 +5,7 @@ from tracim_backend.tests import FunctionalTest
5 5
 Tests for /api/v2/system subpath endpoints.
6 6
 """
7 7
 
8
+
8 9
 class TestApplicationEndpoint(FunctionalTest):
9 10
     """
10 11
     Tests for /api/v2/system/applications
@@ -25,7 +26,7 @@ class TestApplicationEndpoint(FunctionalTest):
25 26
         res = res.json_body
26 27
         application = res[0]
27 28
         assert application['label'] == "Text Documents"
28
-        assert application['slug'] == 'contents/html-documents'
29
+        assert application['slug'] == 'contents/html-document'
29 30
         assert application['fa_icon'] == 'file-text-o'
30 31
         assert application['hexcolor'] == '#3f52e3'
31 32
         assert application['is_active'] is True
@@ -39,14 +40,14 @@ class TestApplicationEndpoint(FunctionalTest):
39 40
         assert 'config' in application
40 41
         application = res[2]
41 42
         assert application['label'] == "Files"
42
-        assert application['slug'] == 'contents/files'
43
+        assert application['slug'] == 'contents/file'
43 44
         assert application['fa_icon'] == 'paperclip'
44 45
         assert application['hexcolor'] == '#FF9900'
45 46
         assert application['is_active'] is True
46 47
         assert 'config' in application
47 48
         application = res[3]
48 49
         assert application['label'] == "Threads"
49
-        assert application['slug'] == 'contents/threads'
50
+        assert application['slug'] == 'contents/thread'
50 51
         assert application['fa_icon'] == 'comments-o'
51 52
         assert application['hexcolor'] == '#ad4cf9'
52 53
         assert application['is_active'] is True
@@ -122,9 +123,7 @@ class TestContentsTypesEndpoint(FunctionalTest):
122 123
         assert content_type['creation_label'] == 'Create a Markdown document'
123 124
         assert 'available_statuses' in content_type
124 125
         assert len(content_type['available_statuses']) == 4
125
-
126
-        content_type = res[4]
127
-        assert content_type['slug'] == 'html-documents'
126
+        assert content_type['slug'] == 'html-document'
128 127
         assert content_type['fa_icon'] == 'file-text-o'
129 128
         assert content_type['hexcolor'] == '#3f52e3'
130 129
         assert content_type['label'] == 'Text Document'

+ 4 - 4
backend/tracim_backend/tests/functional/test_user.py View File

@@ -762,9 +762,9 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
762 762
         assert sidebar_entry['fa_icon'] == "th"
763 763
 
764 764
         sidebar_entry = workspace['sidebar_entries'][2]
765
-        assert sidebar_entry['slug'] == 'contents/html-documents'
765
+        assert sidebar_entry['slug'] == 'contents/html-document'
766 766
         assert sidebar_entry['label'] == 'Text Documents'
767
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-documents'  # nopep8
767
+        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-document'  # nopep8
768 768
         assert sidebar_entry['hexcolor'] == "#3f52e3"
769 769
         assert sidebar_entry['fa_icon'] == "file-text-o"
770 770
 
@@ -776,14 +776,14 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
776 776
         assert sidebar_entry['fa_icon'] == "file-code-o"
777 777
 
778 778
         sidebar_entry = workspace['sidebar_entries'][4]
779
-        assert sidebar_entry['slug'] == 'contents/files'
779
+        assert sidebar_entry['slug'] == 'contents/file'
780 780
         assert sidebar_entry['label'] == 'Files'
781 781
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
782 782
         assert sidebar_entry['hexcolor'] == "#FF9900"
783 783
         assert sidebar_entry['fa_icon'] == "paperclip"
784 784
 
785 785
         sidebar_entry = workspace['sidebar_entries'][5]
786
-        assert sidebar_entry['slug'] == 'contents/threads'
786
+        assert sidebar_entry['slug'] == 'contents/thread'
787 787
         assert sidebar_entry['label'] == 'Threads'
788 788
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
789 789
         assert sidebar_entry['hexcolor'] == "#ad4cf9"

+ 111 - 46
backend/tracim_backend/tests/functional/test_workspaces.py View File

@@ -58,9 +58,9 @@ class TestWorkspaceEndpoint(FunctionalTest):
58 58
         assert sidebar_entry['fa_icon'] == "th"
59 59
 
60 60
         sidebar_entry = workspace['sidebar_entries'][2]
61
-        assert sidebar_entry['slug'] == 'contents/html-documents'
61
+        assert sidebar_entry['slug'] == 'contents/html-document'
62 62
         assert sidebar_entry['label'] == 'Text Documents'
63
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-documents'  # nopep8
63
+        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-document'  # nopep8
64 64
         assert sidebar_entry['hexcolor'] == "#3f52e3"
65 65
         assert sidebar_entry['fa_icon'] == "file-text-o"
66 66
 
@@ -72,14 +72,14 @@ class TestWorkspaceEndpoint(FunctionalTest):
72 72
         assert sidebar_entry['fa_icon'] == "file-code-o"
73 73
 
74 74
         sidebar_entry = workspace['sidebar_entries'][4]
75
-        assert sidebar_entry['slug'] == 'contents/files'
75
+        assert sidebar_entry['slug'] == 'contents/file'
76 76
         assert sidebar_entry['label'] == 'Files'
77 77
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
78 78
         assert sidebar_entry['hexcolor'] == "#FF9900"
79 79
         assert sidebar_entry['fa_icon'] == "paperclip"
80 80
 
81 81
         sidebar_entry = workspace['sidebar_entries'][5]
82
-        assert sidebar_entry['slug'] == 'contents/threads'
82
+        assert sidebar_entry['slug'] == 'contents/thread'
83 83
         assert sidebar_entry['label'] == 'Threads'
84 84
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
85 85
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
@@ -643,7 +643,7 @@ class TestWorkspaceContents(FunctionalTest):
643 643
         assert content['show_in_ui'] is True
644 644
         assert content['slug'] == 'tools'
645 645
         assert content['status'] == 'open'
646
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
646
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
647 647
         assert content['workspace_id'] == 1
648 648
         content = res[1]
649 649
         assert content['content_id'] == 2
@@ -655,11 +655,11 @@ class TestWorkspaceContents(FunctionalTest):
655 655
         assert content['show_in_ui'] is True
656 656
         assert content['slug'] == 'menus'
657 657
         assert content['status'] == 'open'
658
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
658
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
659 659
         assert content['workspace_id'] == 1
660 660
         content = res[2]
661 661
         assert content['content_id'] == 11
662
-        assert content['content_type'] == 'html-documents'
662
+        assert content['content_type'] == 'html-document'
663 663
         assert content['is_archived'] is False
664 664
         assert content['is_deleted'] is False
665 665
         assert content['label'] == 'Current Menu'
@@ -667,7 +667,7 @@ class TestWorkspaceContents(FunctionalTest):
667 667
         assert content['show_in_ui'] is True
668 668
         assert content['slug'] == 'current-menu'
669 669
         assert content['status'] == 'open'
670
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
670
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
671 671
         assert content['workspace_id'] == 1
672 672
 
673 673
     def test_api__get_workspace_content__ok_200__get_default_html_documents(self):
@@ -682,14 +682,14 @@ class TestWorkspaceContents(FunctionalTest):
682 682
             )
683 683
         )
684 684
         params = {
685
-            'content_type': 'html-documents',
685
+            'content_type': 'html-document',
686 686
         }
687 687
         res = self.testapp.get('/api/v2/workspaces/1/contents', status=200, params=params).json_body   # nopep8
688 688
         assert len(res) == 1
689 689
         content = res[0]
690 690
         assert content
691 691
         assert content['content_id'] == 11
692
-        assert content['content_type'] == 'html-documents'
692
+        assert content['content_type'] == 'html-document'
693 693
         assert content['is_archived'] is False
694 694
         assert content['is_deleted'] is False
695 695
         assert content['label'] == 'Current Menu'
@@ -697,7 +697,7 @@ class TestWorkspaceContents(FunctionalTest):
697 697
         assert content['show_in_ui'] is True
698 698
         assert content['slug'] == 'current-menu'
699 699
         assert content['status'] == 'open'
700
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
700
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
701 701
         assert content['workspace_id'] == 1
702 702
 
703 703
     # Root related
@@ -727,7 +727,7 @@ class TestWorkspaceContents(FunctionalTest):
727 727
         # TODO - G.M - 30-05-2018 - Check this test
728 728
         assert len(res) == 4
729 729
         content = res[1]
730
-        assert content['content_type'] == 'html-documents'
730
+        assert content['content_type'] == 'html-document'
731 731
         assert content['content_id'] == 15
732 732
         assert content['is_archived'] is False
733 733
         assert content['is_deleted'] is False
@@ -736,11 +736,11 @@ class TestWorkspaceContents(FunctionalTest):
736 736
         assert content['show_in_ui'] is True
737 737
         assert content['slug'] == 'new-fruit-salad'
738 738
         assert content['status'] == 'open'
739
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
739
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
740 740
         assert content['workspace_id'] == 3
741 741
 
742 742
         content = res[2]
743
-        assert content['content_type'] == 'html-documents'
743
+        assert content['content_type'] == 'html-document'
744 744
         assert content['content_id'] == 16
745 745
         assert content['is_archived'] is True
746 746
         assert content['is_deleted'] is False
@@ -749,11 +749,11 @@ class TestWorkspaceContents(FunctionalTest):
749 749
         assert content['show_in_ui'] is True
750 750
         assert content['slug'].startswith('fruit-salad')
751 751
         assert content['status'] == 'open'
752
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
752
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
753 753
         assert content['workspace_id'] == 3
754 754
 
755 755
         content = res[3]
756
-        assert content['content_type'] == 'html-documents'
756
+        assert content['content_type'] == 'html-document'
757 757
         assert content['content_id'] == 17
758 758
         assert content['is_archived'] is False
759 759
         assert content['is_deleted'] is True
@@ -762,7 +762,7 @@ class TestWorkspaceContents(FunctionalTest):
762 762
         assert content['show_in_ui'] is True
763 763
         assert content['slug'].startswith('bad-fruit-salad')
764 764
         assert content['status'] == 'open'
765
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
765
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
766 766
         assert content['workspace_id'] == 3
767 767
 
768 768
     def test_api__get_workspace_content__ok_200__get_all_root_content(self):
@@ -790,7 +790,7 @@ class TestWorkspaceContents(FunctionalTest):
790 790
         # TODO - G.M - 30-05-2018 - Check this test
791 791
         assert len(res) == 4
792 792
         content = res[1]
793
-        assert content['content_type'] == 'html-documents'
793
+        assert content['content_type'] == 'html-document'
794 794
         assert content['content_id'] == 15
795 795
         assert content['is_archived'] is False
796 796
         assert content['is_deleted'] is False
@@ -799,11 +799,11 @@ class TestWorkspaceContents(FunctionalTest):
799 799
         assert content['show_in_ui'] is True
800 800
         assert content['slug'] == 'new-fruit-salad'
801 801
         assert content['status'] == 'open'
802
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
802
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
803 803
         assert content['workspace_id'] == 3
804 804
 
805 805
         content = res[2]
806
-        assert content['content_type'] == 'html-documents'
806
+        assert content['content_type'] == 'html-document'
807 807
         assert content['content_id'] == 16
808 808
         assert content['is_archived'] is True
809 809
         assert content['is_deleted'] is False
@@ -812,11 +812,11 @@ class TestWorkspaceContents(FunctionalTest):
812 812
         assert content['show_in_ui'] is True
813 813
         assert content['slug'].startswith('fruit-salad')
814 814
         assert content['status'] == 'open'
815
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
815
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
816 816
         assert content['workspace_id'] == 3
817 817
 
818 818
         content = res[3]
819
-        assert content['content_type'] == 'html-documents'
819
+        assert content['content_type'] == 'html-document'
820 820
         assert content['content_id'] == 17
821 821
         assert content['is_archived'] is False
822 822
         assert content['is_deleted'] is True
@@ -825,7 +825,7 @@ class TestWorkspaceContents(FunctionalTest):
825 825
         assert content['show_in_ui'] is True
826 826
         assert content['slug'].startswith('bad-fruit-salad')
827 827
         assert content['status'] == 'open'
828
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
828
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
829 829
         assert content['workspace_id'] == 3
830 830
 
831 831
     def test_api__get_workspace_content__ok_200__get_only_active_root_content(self):  # nopep8
@@ -853,7 +853,7 @@ class TestWorkspaceContents(FunctionalTest):
853 853
         # TODO - G.M - 30-05-2018 - Check this test
854 854
         assert len(res) == 2
855 855
         content = res[1]
856
-        assert content['content_type'] == 'html-documents'
856
+        assert content['content_type'] == 'html-document'
857 857
         assert content['content_id'] == 15
858 858
         assert content['is_archived'] is False
859 859
         assert content['is_deleted'] is False
@@ -862,7 +862,7 @@ class TestWorkspaceContents(FunctionalTest):
862 862
         assert content['show_in_ui'] is True
863 863
         assert content['slug'] == 'new-fruit-salad'
864 864
         assert content['status'] == 'open'
865
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
865
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
866 866
         assert content['workspace_id'] == 3
867 867
 
868 868
     def test_api__get_workspace_content__ok_200__get_only_archived_root_content(self):  # nopep8
@@ -889,7 +889,7 @@ class TestWorkspaceContents(FunctionalTest):
889 889
         ).json_body   # nopep8
890 890
         assert len(res) == 1
891 891
         content = res[0]
892
-        assert content['content_type'] == 'html-documents'
892
+        assert content['content_type'] == 'html-document'
893 893
         assert content['content_id'] == 16
894 894
         assert content['is_archived'] is True
895 895
         assert content['is_deleted'] is False
@@ -898,7 +898,7 @@ class TestWorkspaceContents(FunctionalTest):
898 898
         assert content['show_in_ui'] is True
899 899
         assert content['slug'].startswith('fruit-salad')
900 900
         assert content['status'] == 'open'
901
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
901
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
902 902
         assert content['workspace_id'] == 3
903 903
 
904 904
     def test_api__get_workspace_content__ok_200__get_only_deleted_root_content(self):  # nopep8
@@ -927,7 +927,7 @@ class TestWorkspaceContents(FunctionalTest):
927 927
 
928 928
         assert len(res) == 1
929 929
         content = res[0]
930
-        assert content['content_type'] == 'html-documents'
930
+        assert content['content_type'] == 'html-document'
931 931
         assert content['content_id'] == 17
932 932
         assert content['is_archived'] is False
933 933
         assert content['is_deleted'] is True
@@ -936,7 +936,7 @@ class TestWorkspaceContents(FunctionalTest):
936 936
         assert content['show_in_ui'] is True
937 937
         assert content['slug'].startswith('bad-fruit-salad')
938 938
         assert content['status'] == 'open'
939
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
939
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
940 940
         assert content['workspace_id'] == 3
941 941
 
942 942
     def test_api__get_workspace_content__ok_200__get_nothing_root_content(self):
@@ -1058,7 +1058,7 @@ class TestWorkspaceContents(FunctionalTest):
1058 1058
         assert content['show_in_ui'] is True
1059 1059
         assert content['slug'] == 'test-thread'
1060 1060
         assert content['status'] == 'open'
1061
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1061
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1062 1062
         assert content['workspace_id'] == 1
1063 1063
 
1064 1064
     def test_api__get_workspace_content__ok_200__get_all_filter_content_html_and_legacy_page(self):  # nopep8
@@ -1130,7 +1130,7 @@ class TestWorkspaceContents(FunctionalTest):
1130 1130
             'show_archived': 1,
1131 1131
             'show_deleted': 1,
1132 1132
             'show_active': 1,
1133
-            'content_type': 'html-documents',
1133
+            'content_type': 'html-document',
1134 1134
         }
1135 1135
         self.testapp.authorization = (
1136 1136
             'Basic',
@@ -1146,7 +1146,7 @@ class TestWorkspaceContents(FunctionalTest):
1146 1146
         ).json_body
1147 1147
         assert len(res) == 2
1148 1148
         content = res[0]
1149
-        assert content['content_type'] == 'html-documents'
1149
+        assert content['content_type'] == 'html-document'
1150 1150
         assert content['content_id']
1151 1151
         assert content['is_archived'] is False
1152 1152
         assert content['is_deleted'] is False
@@ -1155,10 +1155,10 @@ class TestWorkspaceContents(FunctionalTest):
1155 1155
         assert content['show_in_ui'] is True
1156 1156
         assert content['slug'] == 'test-page'
1157 1157
         assert content['status'] == 'open'
1158
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1158
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1159 1159
         assert content['workspace_id'] == 1
1160 1160
         content = res[1]
1161
-        assert content['content_type'] == 'html-documents'
1161
+        assert content['content_type'] == 'html-document'
1162 1162
         assert content['content_id']
1163 1163
         assert content['is_archived'] is False
1164 1164
         assert content['is_deleted'] is False
@@ -1167,7 +1167,7 @@ class TestWorkspaceContents(FunctionalTest):
1167 1167
         assert content['show_in_ui'] is True
1168 1168
         assert content['slug'] == 'test-html-page'
1169 1169
         assert content['status'] == 'open'
1170
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1170
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1171 1171
         assert content['workspace_id'] == 1
1172 1172
         assert res[0]['content_id'] != res[1]['content_id']
1173 1173
 
@@ -1196,7 +1196,7 @@ class TestWorkspaceContents(FunctionalTest):
1196 1196
         ).json_body   # nopep8
1197 1197
         assert len(res) == 3
1198 1198
         content = res[0]
1199
-        assert content['content_type'] == 'html-documents'
1199
+        assert content['content_type'] == 'html-document'
1200 1200
         assert content['content_id'] == 12
1201 1201
         assert content['is_archived'] is False
1202 1202
         assert content['is_deleted'] is False
@@ -1205,11 +1205,11 @@ class TestWorkspaceContents(FunctionalTest):
1205 1205
         assert content['show_in_ui'] is True
1206 1206
         assert content['slug'] == 'new-fruit-salad'
1207 1207
         assert content['status'] == 'open'
1208
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1208
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1209 1209
         assert content['workspace_id'] == 2
1210 1210
 
1211 1211
         content = res[1]
1212
-        assert content['content_type'] == 'html-documents'
1212
+        assert content['content_type'] == 'html-document'
1213 1213
         assert content['content_id'] == 13
1214 1214
         assert content['is_archived'] is True
1215 1215
         assert content['is_deleted'] is False
@@ -1218,11 +1218,11 @@ class TestWorkspaceContents(FunctionalTest):
1218 1218
         assert content['show_in_ui'] is True
1219 1219
         assert content['slug'].startswith('fruit-salad')
1220 1220
         assert content['status'] == 'open'
1221
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1221
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1222 1222
         assert content['workspace_id'] == 2
1223 1223
 
1224 1224
         content = res[2]
1225
-        assert content['content_type'] == 'html-documents'
1225
+        assert content['content_type'] == 'html-document'
1226 1226
         assert content['content_id'] == 14
1227 1227
         assert content['is_archived'] is False
1228 1228
         assert content['is_deleted'] is True
@@ -1231,7 +1231,7 @@ class TestWorkspaceContents(FunctionalTest):
1231 1231
         assert content['show_in_ui'] is True
1232 1232
         assert content['slug'].startswith('bad-fruit-salad')
1233 1233
         assert content['status'] == 'open'
1234
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1234
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1235 1235
         assert content['workspace_id'] == 2
1236 1236
 
1237 1237
     def test_api__get_workspace_content__ok_200__get_only_active_folder_content(self):  # nopep8
@@ -1267,7 +1267,7 @@ class TestWorkspaceContents(FunctionalTest):
1267 1267
         assert content['show_in_ui'] is True
1268 1268
         assert content['slug'] == 'new-fruit-salad'
1269 1269
         assert content['status'] == 'open'
1270
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1270
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1271 1271
         assert content['workspace_id'] == 2
1272 1272
 
1273 1273
     def test_api__get_workspace_content__ok_200__get_only_archived_folder_content(self):  # nopep8
@@ -1294,7 +1294,7 @@ class TestWorkspaceContents(FunctionalTest):
1294 1294
         ).json_body   # nopep8
1295 1295
         assert len(res) == 1
1296 1296
         content = res[0]
1297
-        assert content['content_type'] == 'html-documents'
1297
+        assert content['content_type'] == 'html-document'
1298 1298
         assert content['content_id'] == 13
1299 1299
         assert content['is_archived'] is True
1300 1300
         assert content['is_deleted'] is False
@@ -1303,7 +1303,7 @@ class TestWorkspaceContents(FunctionalTest):
1303 1303
         assert content['show_in_ui'] is True
1304 1304
         assert content['slug'].startswith('fruit-salad')
1305 1305
         assert content['status'] == 'open'
1306
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1306
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1307 1307
         assert content['workspace_id'] == 2
1308 1308
 
1309 1309
     def test_api__get_workspace_content__ok_200__get_only_deleted_folder_content(self):  # nopep8
@@ -1331,7 +1331,7 @@ class TestWorkspaceContents(FunctionalTest):
1331 1331
 
1332 1332
         assert len(res) == 1
1333 1333
         content = res[0]
1334
-        assert content['content_type'] == 'html-documents'
1334
+        assert content['content_type'] == 'html-document'
1335 1335
         assert content['content_id'] == 14
1336 1336
         assert content['is_archived'] is False
1337 1337
         assert content['is_deleted'] is True
@@ -1340,7 +1340,7 @@ class TestWorkspaceContents(FunctionalTest):
1340 1340
         assert content['show_in_ui'] is True
1341 1341
         assert content['slug'].startswith('bad-fruit-salad')
1342 1342
         assert content['status'] == 'open'
1343
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1343
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1344 1344
         assert content['workspace_id'] == 2
1345 1345
 
1346 1346
     def test_api__get_workspace_content__ok_200__get_nothing_folder_content(self):  # nopep8
@@ -1436,6 +1436,7 @@ class TestWorkspaceContents(FunctionalTest):
1436 1436
             )
1437 1437
         )
1438 1438
         params = {
1439
+            'parent_id': None,
1439 1440
             'label': 'GenericCreatedContent',
1440 1441
             'content_type': 'markdownpage',
1441 1442
         }
@@ -1466,6 +1467,70 @@ class TestWorkspaceContents(FunctionalTest):
1466 1467
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1467 1468
         assert res.json_body in active_contents
1468 1469
 
1470
+    def test_api__post_content_create_generic_content__ok_200__no_parent_id_param(self) -> None:  # nopep8
1471
+        """
1472
+        Create generic content
1473
+        """
1474
+        self.testapp.authorization = (
1475
+            'Basic',
1476
+            (
1477
+                'admin@admin.admin',
1478
+                'admin@admin.admin'
1479
+            )
1480
+        )
1481
+        params = {
1482
+            'label': 'GenericCreatedContent',
1483
+            'content_type': 'markdownpage',
1484
+        }
1485
+        res = self.testapp.post_json(
1486
+            '/api/v2/workspaces/1/contents',
1487
+            params=params,
1488
+            status=200
1489
+        )
1490
+        assert res
1491
+        assert res.json_body
1492
+        assert res.json_body['status'] == 'open'
1493
+        assert res.json_body['content_id']
1494
+        assert res.json_body['content_type'] == 'markdownpage'
1495
+        assert res.json_body['is_archived'] is False
1496
+        assert res.json_body['is_deleted'] is False
1497
+        assert res.json_body['workspace_id'] == 1
1498
+        assert res.json_body['slug'] == 'genericcreatedcontent'
1499
+        assert res.json_body['parent_id'] is None
1500
+        assert res.json_body['show_in_ui'] is True
1501
+        assert res.json_body['sub_content_types']
1502
+        params_active = {
1503
+            'parent_id': 0,
1504
+            'show_archived': 0,
1505
+            'show_deleted': 0,
1506
+            'show_active': 1,
1507
+        }
1508
+        # INFO - G.M - 2018-06-165 - Verify if new content is correctly created
1509
+        active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1510
+        assert res.json_body in active_contents
1511
+
1512
+    def test_api__post_content_create_generic_content__err_400__parent_id_0(self) -> None:  # nopep8
1513
+        """
1514
+        Create generic content
1515
+        """
1516
+        self.testapp.authorization = (
1517
+            'Basic',
1518
+            (
1519
+                'admin@admin.admin',
1520
+                'admin@admin.admin'
1521
+            )
1522
+        )
1523
+        params = {
1524
+            'parent_id': 0,
1525
+            'label': 'GenericCreatedContent',
1526
+            'content_type': 'markdownpage',
1527
+        }
1528
+        res = self.testapp.post_json(
1529
+            '/api/v2/workspaces/1/contents',
1530
+            params=params,
1531
+            status=400
1532
+        )
1533
+
1469 1534
     def test_api__post_content_create_generic_content__ok_200__in_folder(self) -> None:  # nopep8
1470 1535
         """
1471 1536
         Create generic content in folder

+ 9 - 5
backend/tracim_backend/views/core_api/schemas.py View File

@@ -645,17 +645,20 @@ class ContentCreationSchema(marshmallow.Schema):
645 645
         description='Title of the content to create'
646 646
     )
647 647
     content_type = marshmallow.fields.String(
648
-        example='html-documents',
649
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),  # nopep8
648
+        example='html-document',
649
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
650 650
     )
651 651
     parent_id = marshmallow.fields.Integer(
652 652
         example=35,
653
-        description='content_id of parent content, if content should be placed in a folder, this should be folder content_id.'
653
+        description='content_id of parent content, if content should be placed in a folder, this should be folder content_id.', # nopep8
654
+        allow_none=True,
655
+        default=None,
656
+        validate=Range(min=1, error="Value must be positive"),
654 657
     )
655 658
 
656 659
 
657 660
     @post_load
658
-    def make_content_filter(self, data):
661
+    def make_content_creation(self, data):
659 662
         return ContentCreation(**data)
660 663
 
661 664
 
@@ -677,7 +680,7 @@ class ContentDigestSchema(marshmallow.Schema):
677 680
     )
678 681
     label = marshmallow.fields.Str(example='Intervention Report 12')
679 682
     content_type = marshmallow.fields.Str(
680
-        example='html-documents',
683
+        example='html-document',
681 684
         validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
682 685
     )
683 686
     sub_content_types = marshmallow.fields.List(
@@ -716,6 +719,7 @@ class ReadStatusSchema(marshmallow.Schema):
716 719
 # Content
717 720
 #####
718 721
 
722
+
719 723
 class ContentSchema(ContentDigestSchema):
720 724
     current_revision_id = marshmallow.fields.Int(example=12)
721 725
     created = marshmallow.fields.DateTime(

+ 23 - 0
build_full_frontend.sh View File

@@ -9,23 +9,46 @@ fi
9 9
 
10 10
 echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}it also assumes your webpack dev server of frontend is running"
11 11
 
12
+# Tracim Lib
13
+
12 14
 log "cd frontend_lib"
13 15
 cd frontend_lib
14 16
 log "npm run buildtracimlib$windoz"
15 17
 npm run buildtracimlib$windoz
16 18
 cd -
17 19
 
20
+# app Html Document
21
+
18 22
 log "cd frontend_app_html-document"
19 23
 cd frontend_app_html-document
24
+
20 25
 log "npm run build$windoz # for frontend_app_html-document"
21 26
 npm run build$windoz
27
+
22 28
 log "cp dist/html-document.app.js"
23 29
 cp dist/html-document.app.js ../frontend/dist/app
30
+
31
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/tml-document_en_translation.json"
32
+cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json
33
+
34
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json"
35
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json
24 36
 cd -
25 37
 
38
+# app Thread
39
+
26 40
 log "cd frontend_app_thread"
27 41
 cd frontend_app_thread
42
+
28 43
 log "npm run build$windoz # for frontend_app_thread"
29 44
 npm run build$windoz
45
+
30 46
 log "cp dist/thread.app.js"
31 47
 cp dist/thread.app.js ../frontend/dist/app
48
+
49
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json"
50
+cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json
51
+
52
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json"
53
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json
54
+cd -

+ 1 - 1
frontend/dist/appInterface.js View File

@@ -3,7 +3,7 @@
3 3
 
4 4
   getSelectedApp = name => {
5 5
     switch (name) {
6
-      case 'html-documents':
6
+      case 'html-document':
7 7
         return appHtmlDocument
8 8
       case 'thread':
9 9
         return appThread

+ 16 - 0
frontend/i18next.scanner.js View File

@@ -0,0 +1,16 @@
1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+// this script is run by npm run build-translation and generate i18next.scanner/**/*.json
7
+
8
+// --------------------
9
+// 2018/07/27 - currently, last version is 2.6.5 but a bug is spaming log with errors. So I'm using 2.6.1
10
+// this issue seems related : https://github.com/i18next/i18next-scanner/issues/88
11
+// --------------------
12
+
13
+vfs.src(['./src/**/*.jsx'])
14
+  // .pipe(sort()) // Sort files in stream by path
15
+  .pipe(scanner(option))
16
+  .pipe(vfs.dest('./i18next.scanner'))

+ 23 - 0
frontend/i18next.scanner/en/translation.json View File

@@ -0,0 +1,23 @@
1
+{
2
+  "Create your own collaborative workspaces on trac.im": "Create your own collaborative workspaces on trac.im",
3
+  "Copyright 2013 - 2017": "Copyright 2013 - 2017",
4
+  "Disconnection error": "Disconnection error",
5
+  "Error": "Error",
6
+  "Email or password invalid": "Email or password invalid",
7
+  "Type": "Type",
8
+  "Status": "Status",
9
+  "Create in folder...": "Create in folder...",
10
+  "Search...": "Search...",
11
+  "Create a workspace": "Create a workspace",
12
+  "Title": "Title",
13
+  "Change": "Change",
14
+  "Move": "Move",
15
+  "Download": "Download",
16
+  "Archive": "Archive",
17
+  "Delete": "Delete",
18
+  "My Account": "My Account",
19
+  "Logout": "Logout",
20
+  "Notification": "Notification",
21
+  "Archive Topic": "Archive Topic",
22
+  "Deleted File": "Deleted File"
23
+}

+ 23 - 0
frontend/i18next.scanner/fr/translation.json View File

@@ -0,0 +1,23 @@
1
+{
2
+  "Create your own collaborative workspaces on trac.im": "Créer votre propre espace de travail sur trac.im",
3
+  "Copyright 2013 - 2017": "Copyright 2013 - 2017",
4
+  "Disconnection error": "Erreur de déconnexion",
5
+  "Error": "Erreur",
6
+  "Email or password invalid": "Email ou mot de passe invalid",
7
+  "Type": "Type",
8
+  "Status": "Statut",
9
+  "Create in folder...": "Créer dans le dossier ...",
10
+  "Search...": "Rechercher ...",
11
+  "Create a workspace": "Créer un espace de travail",
12
+  "Title": "Titre",
13
+  "Change": "Modifier",
14
+  "Move": "Déplacer",
15
+  "Download": "Télécharger",
16
+  "Archive": "Archiver",
17
+  "Delete": "Supprimer",
18
+  "My Account": "Mon Compte",
19
+  "Logout": "Se Déconnecter",
20
+  "Notification": "Notification",
21
+  "Archive Topic": "Conversation archivée",
22
+  "Deleted File": "Fichier supprimé"
23
+}

+ 2 - 0
frontend/package.json View File

@@ -10,6 +10,7 @@
10 10
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -- webpack-dev-server --watch --colors --inline --hot --progress",
11 11
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
12 12
     "build": "NODE_ENV=production webpack -p",
13
+    "build-translation": "node i18next.scanner.js",
13 14
     "test": "echo \"Error: no test specified\" && exit 1"
14 15
   },
15 16
   "author": "",
@@ -52,6 +53,7 @@
52 53
     "whatwg-fetch": "^2.0.3"
53 54
   },
54 55
   "devDependencies": {
56
+    "i18next-scanner": "^2.6.1",
55 57
     "json-server": "^0.12.0",
56 58
     "webpack-dashboard": "^1.0.2",
57 59
     "webpack-dev-server": "^2.9.2"

+ 24 - 17
frontend/src/action-creator.sync.js View File

@@ -1,44 +1,51 @@
1
+export const SET = 'Set'
2
+export const UPDATE = 'Update'
3
+export const ADD = 'Add'
4
+export const REMOVE = 'Remove'
5
+
1 6
 export const TIMEZONE = 'Timezone'
2
-export const setTimezone = timezone => ({ type: `Set/${TIMEZONE}`, timezone })
7
+export const setTimezone = timezone => ({ type: `${SET}/${TIMEZONE}`, timezone })
3 8
 
4 9
 export const FLASH_MESSAGE = 'FlashMessage'
5 10
 export const newFlashMessage = (msgText = '', msgType = 'info', msgDelay = 5000) => dispatch => {
6 11
   msgDelay !== 0 && window.setTimeout(() => dispatch(removeFlashMessage(msgText)), msgDelay)
7 12
   return dispatch(addFlashMessage({message: msgText, type: msgType}))
8 13
 }
9
-export const addFlashMessage = msg => ({ type: `Add/${FLASH_MESSAGE}`, msg })
10
-export const removeFlashMessage = msg => ({ type: `Remove/${FLASH_MESSAGE}`, msg })
14
+export const addFlashMessage = msg => ({ type: `${ADD}/${FLASH_MESSAGE}`, msg })
15
+export const removeFlashMessage = msg => ({ type: `${REMOVE}/${FLASH_MESSAGE}`, msg })
11 16
 
17
+export const USER = 'User'
12 18
 export const USER_LOGIN = 'User/Login'
13 19
 export const USER_LOGOUT = 'User/Logout'
14 20
 export const USER_DATA = 'User/Data'
15 21
 export const USER_ROLE = 'User/Role'
16 22
 export const USER_CONNECTED = 'User/Connected'
17 23
 export const USER_DISCONNECTED = 'User/Disconnected'
18
-export const setUserConnected = user => ({ type: `Set/${USER_CONNECTED}`, user })
19
-export const setUserDisconnected = () => ({ type: `Set/${USER_DISCONNECTED}` })
20
-export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data: userData })
21
-export const setUserRole = userRole => ({ type: `Set/${USER_ROLE}`, userRole }) // this actually update workspaceList state
24
+export const USER_LANG = 'User/Lang'
25
+export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
26
+export const setUserDisconnected = () => ({ type: `${SET}/${USER}/Disconnected` })
27
+export const updateUserData = userData => ({ type: `${UPDATE}/${USER}/Data`, data: userData })
28
+export const setUserRole = userRole => ({ type: `${SET}/${USER}/Role`, userRole }) // this actually update workspaceList state
29
+export const setUserLang = lang => ({ type: `${SET}/${USER}/Lang`, lang })
22 30
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
23
-  ({ type: `Update/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
31
+  ({ type: `${UPDATE}/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
24 32
 
25 33
 export const WORKSPACE = 'Workspace'
26
-export const setWorkspaceContent = (workspaceContent, filterStr = '') => ({ type: `Set/${WORKSPACE}/Content`, workspaceContent, filterStr })
27
-export const updateWorkspaceFilter = filterList => ({ type: `Update/${WORKSPACE}/Filter`, filterList })
34
+export const setWorkspaceContent = (workspaceContent, filterStr = '') => ({ type: `${SET}/${WORKSPACE}/Content`, workspaceContent, filterStr })
35
+export const updateWorkspaceFilter = filterList => ({ type: `${UPDATE}/${WORKSPACE}/Filter`, filterList })
28 36
 
29 37
 export const FOLDER = 'Folder'
30
-export const setFolderData = (folderId, content) => ({ type: `Set/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
38
+export const setFolderData = (folderId, content) => ({ type: `${SET}/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
31 39
 
32 40
 export const WORKSPACE_LIST = 'WorkspaceList'
33
-export const updateWorkspaceListData = workspaceList => ({ type: `Update/${WORKSPACE_LIST}`, workspaceList })
34
-export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `Set/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
41
+export const updateWorkspaceListData = workspaceList => ({ type: `${UPDATE}/${WORKSPACE_LIST}`, workspaceList })
42
+export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
35 43
 
36 44
 export const APP_LIST = 'App/List'
37
-export const setAppList = appList => ({ type: `Set/${APP_LIST}`, appList })
45
+export const setAppList = appList => ({ type: `${SET}/${APP_LIST}`, appList })
38 46
 
39 47
 export const CONTENT_TYPE_LIST = 'ContentType/List'
40
-export const setContentTypeList = contentTypeList => ({ type: `Set/${CONTENT_TYPE_LIST}`, contentTypeList })
48
+export const setContentTypeList = contentTypeList => ({ type: `${SET}/${CONTENT_TYPE_LIST}`, contentTypeList })
41 49
 
42 50
 export const LANG = 'Lang'
43
-export const updateLangList = langList => ({ type: `Update/${LANG}`, langList })
44
-export const setLangActive = langId => ({ type: `Set/${LANG}/Active`, langId })
51
+export const updateLangList = langList => ({ type: `${UPDATE}/${LANG}`, langList })

+ 5 - 2
frontend/src/appFactory.js View File

@@ -1,5 +1,6 @@
1 1
 import React from 'react'
2 2
 import { FETCH_CONFIG } from './helper.js'
3
+import i18n from './i18n.js'
3 4
 
4 5
 export function appFactory (WrappedComponent) {
5 6
   return class AppFactory extends React.Component {
@@ -10,7 +11,8 @@ export function appFactory (WrappedComponent) {
10 11
         domContainer: 'appContainer',
11 12
         apiUrl: FETCH_CONFIG.apiUrl,
12 13
         mockApiUrl: FETCH_CONFIG.mockApiUrl,
13
-        apiHeader: FETCH_CONFIG.headers
14
+        apiHeader: FETCH_CONFIG.headers,
15
+        translation: i18n.store.data
14 16
       },
15 17
       content
16 18
     })
@@ -22,7 +24,8 @@ export function appFactory (WrappedComponent) {
22 24
         domContainer: 'popupCreateContentContainer',
23 25
         apiUrl: FETCH_CONFIG.apiUrl,
24 26
         mockApiUrl: FETCH_CONFIG.mockApiUrl,
25
-        apiHeader: FETCH_CONFIG.headers // should this be used by app ? right now, apps have their own headers
27
+        apiHeader: FETCH_CONFIG.headers, // should this be used by app ? right now, apps have their own headers
28
+        translation: i18n.store.data
26 29
       },
27 30
       idWorkspace,
28 31
       idFolder: idFolder === 'null' ? null : idFolder

+ 1 - 1
frontend/src/component/FlashMessage.jsx View File

@@ -22,7 +22,7 @@ const FlashMessage = props => {
22 22
 
23 23
               <div className='flashmessage__container__content__text'>
24 24
                 <div className='flashmessage__container__content__text__title'>
25
-                  {props.t('FlashMessage.error')}
25
+                  {props.t('Error')}
26 26
                 </div>
27 27
                 <div className='flashmessage__container__content__text__paragraph'>
28 28
                   {props.flashMessage[0].message}

+ 1 - 1
frontend/src/component/Footer.jsx View File

@@ -7,7 +7,7 @@ const Footer = ({ t }) => {
7 7
   return (
8 8
     <footer className='footer text-right'>
9 9
       <div className='footer__text'>
10
-        {t('Footer.marketing_msg')} - {t('Footer.copyright')}
10
+        {t('Create your own collaborative workspaces on trac.im')} - {t('Copyright 2013 - 2017')}
11 11
       </div>
12 12
       <img className='footer__logo' src={logoFooter} />
13 13
     </footer>

+ 4 - 4
frontend/src/component/Header/MenuActionListItem/DropdownLang.jsx View File

@@ -2,7 +2,6 @@ import React from 'react'
2 2
 import PropTypes from 'prop-types'
3 3
 
4 4
 const DropdownLang = props => {
5
-  const activeLang = props.langList.find(l => l.active) || {id: 'fr', name: 'Français', src: '', active: true}
6 5
   return (
7 6
     <li className='header__menu__rightside__itemlanguage'>
8 7
       <div className='header__menu__rightside__itemlanguage__languagedropdown dropdown'>
@@ -14,12 +13,12 @@ const DropdownLang = props => {
14 13
           aria-haspopup='true'
15 14
           aria-expanded='false'
16 15
         >
17
-          <img className='languagedropdown__btnlanguage__imgselected' src={activeLang.src} />
16
+          <img className='languagedropdown__btnlanguage__imgselected' src={props.langList.find(l => l.id === props.idLangActive).icon} />
18 17
         </button>
19 18
         <div className='languagedropdown__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
20
-          { props.langList.map((l, i) => l.active === false &&
19
+          { props.langList.filter(l => l.id !== props.idLangActive).map((l, i) =>
21 20
             <div className='subdropdown__link dropdown-item' onClick={() => props.onChangeLang(l.id)} key={i}>
22
-              <img className='subdropdown__flag' src={l.src} />
21
+              <img className='subdropdown__flag' src={l.icon} />
23 22
             </div>
24 23
           )}
25 24
         </div>
@@ -31,5 +30,6 @@ export default DropdownLang
31 30
 
32 31
 DropdownLang.propTypes = {
33 32
   langList: PropTypes.array.isRequired,
33
+  idLangActive: PropTypes.string.isRequired,
34 34
   onChangeLang: PropTypes.func.isRequired
35 35
 }

+ 4 - 3
frontend/src/component/Header/MenuActionListItem/MenuProfil.jsx View File

@@ -2,6 +2,7 @@ import React from 'react'
2 2
 import { Link } from 'react-router-dom'
3 3
 import PropTypes from 'prop-types'
4 4
 import { PAGE } from '../../../helper.js'
5
+import { translate } from 'react-i18next'
5 6
 
6 7
 const MenuProfil = props => {
7 8
   return props.user.logged
@@ -17,12 +18,12 @@ const MenuProfil = props => {
17 18
           <div className='profilgroup__setting dropdown-menu' aria-labelledby='dropdownMenuButton'>
18 19
             <Link className='setting__link dropdown-item' to={PAGE.ACCOUNT}>
19 20
               <i className='fa fa-fw fa-user-o mr-2' />
20
-              Mon compte
21
+              {props.t('My Account')}
21 22
             </Link>
22 23
             {/* <div className='setting__link dropdown-item'>Mot de passe</div> */}
23 24
             <div className='setting__link dropdown-item' onClick={props.onClickLogout}>
24 25
               <i className='fa fa-fw fa-sign-out mr-2' />
25
-              Se déconnecter
26
+              {props.t('Logout')}
26 27
             </div>
27 28
           </div>
28 29
         </div>
@@ -30,7 +31,7 @@ const MenuProfil = props => {
30 31
     )
31 32
     : ''
32 33
 }
33
-export default MenuProfil
34
+export default translate()(MenuProfil)
34 35
 
35 36
 MenuProfil.propTypes = {
36 37
   user: PropTypes.object.isRequired,

+ 5 - 4
frontend/src/component/Header/MenuActionListItem/Notification.jsx View File

@@ -1,4 +1,5 @@
1 1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2 3
 // import PropTypes from 'prop-types'
3 4
 
4 5
 const Notification = props => {
@@ -13,18 +14,18 @@ const Notification = props => {
13 14
           aria-haspopup='true'
14 15
           aria-expanded='false'
15 16
         >
16
-          Notification
17
+          {props.t('Notification')}
17 18
         </button>
18 19
         <div className='timeline__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
19 20
           <div className='timeline__subdropdown__text dropdown-item' >
20
-            Conversation archivé
21
+            {props.t('Archive Topic')}
21 22
           </div>
22 23
           <div className='timeline__subdropdown__text dropdown-item' >
23
-            Fichier supprimé
24
+            {props.t('Deleted File')}
24 25
           </div>
25 26
         </div>
26 27
       </div>
27 28
     </li>
28 29
   )
29 30
 }
30
-export default Notification
31
+export default translate()(Notification)

+ 1 - 1
frontend/src/component/Header/MenuActionListItem/Search.jsx View File

@@ -9,7 +9,7 @@ const Search = props => {
9 9
         <input
10 10
           type='text'
11 11
           className='search__input form-control'
12
-          placeholder={`${props.t('Header.Search')}...`}
12
+          placeholder={`${props.t('Search...')}`}
13 13
           aria-describedby='headerInputSearch'
14 14
           onChange={props.onChangeInput}
15 15
         />

+ 7 - 6
frontend/src/component/Workspace/BtnExtandedAction.jsx View File

@@ -1,5 +1,6 @@
1 1
 import React from 'react'
2 2
 import PropTypes from 'prop-types'
3
+import { translate } from 'react-i18next'
3 4
 
4 5
 const ExtandedAction = props => {
5 6
   return (
@@ -22,7 +23,7 @@ const ExtandedAction = props => {
22 23
             <i className='fa fa-fw fa-pencil' />
23 24
           </div>
24 25
           <div className='subdropdown__item__text'>
25
-            Modifier
26
+            {props.t('Change')}
26 27
           </div>
27 28
         </div>
28 29
 
@@ -31,7 +32,7 @@ const ExtandedAction = props => {
31 32
             <i className='fa fa-fw fa-arrows-alt' />
32 33
           </div>
33 34
           <div className='subdropdown__item__text'>
34
-            Déplacer
35
+            {props.t('Move')}
35 36
           </div>
36 37
         </div>
37 38
 
@@ -40,7 +41,7 @@ const ExtandedAction = props => {
40 41
             <i className='fa fa-fw fa-download' />
41 42
           </div>
42 43
           <div className='subdropdown__item__text'>
43
-            Télécharger
44
+            {props.t('Download')}
44 45
           </div>
45 46
         </div> */ }
46 47
 
@@ -49,7 +50,7 @@ const ExtandedAction = props => {
49 50
             <i className='fa fa-fw fa-archive' />
50 51
           </div>
51 52
           <div className='subdropdown__item__text'>
52
-            Archiver
53
+            {props.t('Archive')}
53 54
           </div>
54 55
         </div>
55 56
 
@@ -58,7 +59,7 @@ const ExtandedAction = props => {
58 59
             <i className='fa fa-fw fa-trash-o' />
59 60
           </div>
60 61
           <div className='subdropdown__item__text'>
61
-            Supprimer
62
+            {props.t('Delete')}
62 63
           </div>
63 64
         </div>
64 65
 
@@ -67,7 +68,7 @@ const ExtandedAction = props => {
67 68
   )
68 69
 }
69 70
 
70
-export default ExtandedAction
71
+export default translate()(ExtandedAction)
71 72
 
72 73
 ExtandedAction.propTypes = {
73 74
   onClickExtendedAction: PropTypes.object.isRequired

+ 3 - 3
frontend/src/component/Workspace/ContentItemHeader.jsx View File

@@ -5,13 +5,13 @@ const FileItemHeader = props => {
5 5
   return (
6 6
     <div className='content__header'>
7 7
       <div className='content__header__type'>
8
-        {props.t('FileItemHeader.type')}
8
+        {props.t('Type')}
9 9
       </div>
10 10
       <div className='content__header__name'>
11
-        {props.t('FileItemHeader.document_name')}
11
+        {props.t('Title')}
12 12
       </div>
13 13
       <div className='content__header__status'>
14
-        {props.t('FileItemHeader.status')}
14
+        {props.t('Status')}
15 15
       </div>
16 16
     </div>
17 17
   )

+ 1 - 1
frontend/src/component/Workspace/Folder.jsx View File

@@ -65,7 +65,7 @@ class Folder extends React.Component {
65 65
                 aria-expanded='false'
66 66
                 onClick={e => e.stopPropagation()}
67 67
               >
68
-                {t('Folder.create')} ...
68
+                {t('Create in folder...')}
69 69
               </button>
70 70
 
71 71
               <div className='addbtn__subdropdown dropdown-menu' aria-labelledby='dropdownMenuButton'>

+ 1 - 1
frontend/src/component/Workspace/OpenContentApp.jsx View File

@@ -22,7 +22,7 @@ export class OpenContentApp extends React.Component {
22 22
 
23 23
       if (appOpenedType === contentToOpen.type) { // app already open
24 24
         GLOBAL_dispatchEvent({
25
-          type: `${contentToOpen.type}_reloadContent`, // handled by html-documents:src/container/HtmlDocument.jsx
25
+          type: `${contentToOpen.type}_reloadContent`, // handled by html-document:src/container/HtmlDocument.jsx
26 26
           data: contentToOpen
27 27
         })
28 28
       } else { // open another app

+ 5 - 2
frontend/src/container/Dashboard.jsx View File

@@ -414,7 +414,10 @@ class Dashboard extends React.Component {
414 414
                         </li>
415 415
                       </ul>
416 416
 
417
-                      <div className='dashboard__memberlist__btnadd'>
417
+                      <div
418
+                        className='dashboard__memberlist__btnadd'
419
+                        onClick={this.handleToggleNewMemberDashboard}
420
+                      >
418 421
                         <div className='dashboard__memberlist__btnadd__button'>
419 422
                           <div className='dashboard__memberlist__btnadd__button__avatar'>
420 423
                             <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
@@ -423,7 +426,7 @@ class Dashboard extends React.Component {
423 426
                           </div>
424 427
                           <div
425 428
                             className='dashboard__memberlist__btnadd__button__text'
426
-                            onClick={this.handleToggleNewMemberDashboard}
429
+
427 430
                           >
428 431
                              Ajouter un membre
429 432
                           </div>

+ 13 - 8
frontend/src/container/Header.jsx View File

@@ -1,6 +1,8 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
3 4
 import i18n from '../i18n.js'
5
+import appFactory from '../appFactory.js'
4 6
 import { translate } from 'react-i18next'
5 7
 import Cookies from 'js-cookie'
6 8
 import Logo from '../component/Header/Logo.jsx'
@@ -14,13 +16,13 @@ import MenuActionListItemNotification from '../component/Header/MenuActionListIt
14 16
 import logoHeader from '../img/logo-tracim.png'
15 17
 import {
16 18
   newFlashMessage,
17
-  setLangActive,
19
+  setUserLang,
18 20
   setUserDisconnected
19 21
 } from '../action-creator.sync.js'
20 22
 import {
21 23
   postUserLogout
22 24
 } from '../action-creator.async.js'
23
-import { COOKIE } from '../helper.js'
25
+import { COOKIE, PAGE } from '../helper.js'
24 26
 
25 27
 class Header extends React.Component {
26 28
   handleClickLogo = () => {}
@@ -32,15 +34,16 @@ class Header extends React.Component {
32 34
   handleChangeInput = e => this.setState({inputSearchValue: e.target.value})
33 35
   handleClickSubmit = () => {}
34 36
 
35
-  handleChangeLang = langId => {
36
-    this.props.dispatch(setLangActive(langId))
37
-    i18n.changeLanguage(langId)
37
+  handleChangeLang = idLang => {
38
+    this.props.dispatch(setUserLang(idLang))
39
+    i18n.changeLanguage(idLang)
40
+    this.props.emitEventApp('allApp_changeLang', idLang)
38 41
   }
39 42
 
40 43
   handleClickHelp = () => {}
41 44
 
42 45
   handleClickLogout = async () => {
43
-    const { dispatch, t } = this.props
46
+    const { history, dispatch, t } = this.props
44 47
 
45 48
     const fetchPostUserLogout = await dispatch(postUserLogout())
46 49
     if (fetchPostUserLogout.status === 204) {
@@ -48,8 +51,9 @@ class Header extends React.Component {
48 51
       Cookies.remove(COOKIE.USER_AUTH)
49 52
 
50 53
       dispatch(setUserDisconnected())
54
+      history.push(PAGE.LOGIN)
51 55
     } else {
52
-      dispatch(newFlashMessage(t('Login.logout_error', 'danger')))
56
+      dispatch(newFlashMessage(t('Disconnection error', 'danger')))
53 57
     }
54 58
   }
55 59
 
@@ -82,6 +86,7 @@ class Header extends React.Component {
82 86
 
83 87
               <MenuActionListItemDropdownLang
84 88
                 langList={lang}
89
+                idLangActive={user.lang}
85 90
                 onChangeLang={this.handleChangeLang}
86 91
               />
87 92
 
@@ -104,4 +109,4 @@ class Header extends React.Component {
104 109
 }
105 110
 
106 111
 const mapStateToProps = ({ lang, user }) => ({ lang, user })
107
-export default connect(mapStateToProps)(translate()(Header))
112
+export default withRouter(connect(mapStateToProps)(translate()(appFactory(Header))))

+ 1 - 1
frontend/src/container/Login.jsx View File

@@ -57,7 +57,7 @@ class Login extends React.Component {
57 57
 
58 58
       history.push(PAGE.HOME)
59 59
     } else if (fetchPostUserLogin.status === 400) {
60
-      dispatch(newFlashMessage(t('Login.fail'), 'danger'))
60
+      dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
61 61
     }
62 62
   }
63 63
 

+ 1 - 1
frontend/src/container/Sidebar.jsx View File

@@ -86,7 +86,7 @@ class Sidebar extends React.Component {
86 86
 
87 87
             <div className='sidebar__btnnewworkspace'>
88 88
               <button className='sidebar__btnnewworkspace__btn btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover mb-5'>
89
-                {t('Sidebar.create_new_workspace')}
89
+                {t('Create a workspace')}
90 90
               </button>
91 91
             </div>
92 92
 

+ 1 - 1
frontend/src/css/Generic.styl View File

@@ -237,4 +237,4 @@ a
237 237
       margin-bottom 15px
238 238
       border 1px solid grey
239 239
       border-radius 10px
240
-      padding 15px 25px
240
+      padding 15px

+ 0 - 1
frontend/src/css/Header.styl View File

@@ -40,7 +40,6 @@
40 40
         background-color transparent
41 41
         cursor pointer
42 42
       &__itemlanguage
43
-        display none
44 43
         &__languagedropdown
45 44
           .languagedropdown
46 45
             &__btnlanguage

+ 3 - 3
frontend/src/css/Login.styl View File

@@ -65,10 +65,10 @@
65 65
         width 130px
66 66
   &__footer
67 67
     position fixed
68
-    top 95%
69
-    left calc(50% - 152px) // 152px => width of the text / 2
68
+    bottom 2%
69
+    left calc(50% - 155px) // 155px => width of the text / 2
70 70
     &__text
71
-      width 305px
71
+      width 310px
72 72
       font-size 17px
73 73
 
74 74
 @media (min-width: min-lg) and (max-width: max-lg)

+ 1 - 1
frontend/src/css/Workspace.styl View File

@@ -8,7 +8,7 @@
8 8
     &__button
9 9
       display flex
10 10
       justify-content flex-end
11
-      margin 50px 15px 0 0
11
+      margin 45px 15px 45px 0
12 12
       width 100%
13 13
       &__btnaddworkspace
14 14
         margin-bottom 50px

+ 19 - 9
frontend/src/i18n.js View File

@@ -1,29 +1,39 @@
1 1
 import i18n from 'i18next'
2 2
 import { reactI18nextModule } from 'react-i18next'
3
-import { langFr, langEn } from 'tracim_frontend_lib'
4
-import fr from './translate/fr.js'
5
-import en from './translate/en.js'
3
+import { frLib, enLib } from 'tracim_frontend_lib'
4
+import en from '../i18next.scanner/en/translation.json'
5
+import fr from '../i18next.scanner/fr/translation.json'
6
+
7
+// get translation files of apps
8
+// theses files are generated by build_appname.sh
9
+const htmlDocEnTranslation = require('../dist/app/html-document_en_translation.json')
10
+const htmlDocFrTranslation = require('../dist/app/html-document_fr_translation.json')
6 11
 
7 12
 i18n
8 13
   .use(reactI18nextModule)
9 14
   .init({
10
-    fallbackLng: 'fr',
15
+    fallbackLng: 'en',
11 16
     // have a common namespace used around the full app
12 17
     ns: ['translation'], // namespace
13 18
     defaultNS: 'translation',
14 19
     debug: true,
15
-    // interpolation: {
16
-    //   escapeValue: false, // not needed for react!!
17
-    // },
18 20
     react: {
19 21
       wait: true
20 22
     },
21 23
     resources: {
22 24
       en: {
23
-        translation: {...langEn.translation, ...en.translation}
25
+        translation: {
26
+          ...enLib, // fronted_lib
27
+          ...en, // frontend
28
+          ...htmlDocEnTranslation // html-document
29
+        }
24 30
       },
25 31
       fr: {
26
-        translation: {...langFr.translation, ...fr.translation}
32
+        translation: {
33
+          ...frLib, // fronted_lib
34
+          ...fr, // frontend
35
+          ...htmlDocFrTranslation // html-document
36
+        }
27 37
       }
28 38
     }
29 39
   })

frontend/src/img/drapeau-en.png → frontend/src/img/flag_en.png View File


frontend/src/img/drapeau-fr.png → frontend/src/img/flag_fr.png View File


+ 2 - 2
frontend/src/reducer/app.js View File

@@ -1,8 +1,8 @@
1
-import { APP_LIST } from '../action-creator.sync.js'
1
+import { SET, APP_LIST } from '../action-creator.sync.js'
2 2
 
3 3
 export default function app (state = [], action) {
4 4
   switch (action.type) {
5
-    case `Set/${APP_LIST}`:
5
+    case `${SET}/${APP_LIST}`:
6 6
       return action.appList.map(a => ({
7 7
         label: a.label,
8 8
         slug: a.slug,

+ 2 - 2
frontend/src/reducer/contentType.js View File

@@ -1,8 +1,8 @@
1
-import { CONTENT_TYPE_LIST } from '../action-creator.sync.js'
1
+import { SET, CONTENT_TYPE_LIST } from '../action-creator.sync.js'
2 2
 
3 3
 export function contentType (state = [], action) {
4 4
   switch (action.type) {
5
-    case `Set/${CONTENT_TYPE_LIST}`:
5
+    case `${SET}/${CONTENT_TYPE_LIST}`:
6 6
       return action.contentTypeList.map(ct => ({
7 7
         label: ct.label,
8 8
         slug: ct.slug,

+ 3 - 5
frontend/src/reducer/flashMessage.js View File

@@ -1,16 +1,14 @@
1
-import {
2
-  FLASH_MESSAGE
3
-} from '../action-creator.sync.js'
1
+import { ADD, REMOVE, FLASH_MESSAGE } from '../action-creator.sync.js'
4 2
 
5 3
 export default function flashMessage (state = [], action) {
6 4
   switch (action.type) {
7
-    case `Add/${FLASH_MESSAGE}`:
5
+    case `${ADD}/${FLASH_MESSAGE}`:
8 6
       return [...state, {
9 7
         message: action.msg.message,
10 8
         type: action.msg.type || 'info' // may be info, success, danger
11 9
       }]
12 10
 
13
-    case `Remove/${FLASH_MESSAGE}`:
11
+    case `${REMOVE}/${FLASH_MESSAGE}`:
14 12
       return state.filter(fm => fm.message === action.message)
15 13
 
16 14
     default:

+ 16 - 5
frontend/src/reducer/lang.js View File

@@ -1,12 +1,23 @@
1
-import { LANG } from '../action-creator.sync.js'
1
+import { UPDATE, LANG } from '../action-creator.sync.js'
2
+import flagEn from '../img/flag_en.png'
3
+import flagFr from '../img/flag_fr.png'
2 4
 
3
-export function lang (state = [], action) {
5
+const defaultLang = [{
6
+  id: 'en',
7
+  icon: flagEn
8
+}, {
9
+  id: 'fr',
10
+  icon: flagFr
11
+}]
12
+
13
+export function lang (state = defaultLang, action) {
4 14
   switch (action.type) {
5
-    case `Update/${LANG}`:
15
+    case `${UPDATE}/${LANG}`:
6 16
       return action.langList
7 17
 
8
-    case `Set/${LANG}/Active`:
9
-      return state.map(l => ({...l, active: l.id === action.langId}))
18
+    // Côme - 2018/07/30 - deprecated, lang active is saved in user reducer
19
+    // case `Set/${LANG}/Active`:
20
+    //   return state.map(l => ({...l, active: l.id === action.langId}))
10 21
 
11 22
     default:
12 23
       return state

+ 2 - 2
frontend/src/reducer/timezone.js View File

@@ -1,8 +1,8 @@
1
-import { TIMEZONE } from '../action-creator.sync.js'
1
+import { SET, TIMEZONE } from '../action-creator.sync.js'
2 2
 
3 3
 export function timezone (state = [], action) {
4 4
   switch (action.type) {
5
-    case `Set/${TIMEZONE}`:
5
+    case `${SET}/${TIMEZONE}`:
6 6
       return action.timezone
7 7
 
8 8
     default:

+ 14 - 5
frontend/src/reducer/user.js View File

@@ -1,12 +1,16 @@
1 1
 import {
2
+  SET,
3
+  UPDATE,
2 4
   USER_CONNECTED,
3 5
   USER_DISCONNECTED,
4
-  USER_DATA
6
+  USER_DATA,
7
+  USER_LANG
5 8
 } from '../action-creator.sync.js'
6 9
 
7 10
 const defaultUser = {
8 11
   user_id: -1,
9 12
   logged: null, // null avoid to be redirected to /login while whoami ep has not responded yet
13
+  auth: '',
10 14
   timezone: '',
11 15
   profile: {
12 16
     id: 1,
@@ -17,23 +21,28 @@ const defaultUser = {
17 21
   caldav_url: null,
18 22
   avatar_url: null,
19 23
   created: '',
20
-  display_name: ''
24
+  public_name: '',
25
+  lang: 'en' // @FIXME Côme - 2018/07/30 - remove this line when api returns the lang (https://github.com/tracim/tracim/issues/734)
21 26
 }
22 27
 
23 28
 export default function user (state = defaultUser, action) {
24 29
   switch (action.type) {
25
-    case `Set/${USER_CONNECTED}`:
30
+    case `${SET}/${USER_CONNECTED}`:
26 31
       return {
32
+        ...state,
27 33
         ...action.user,
28 34
         avatar_url: 'https://www.algoo.fr/static/images/people_images/PERSO_SEUL.png' // @FIXME use avatar from api when db handles it
29 35
       }
30 36
 
31
-    case `Set/${USER_DISCONNECTED}`:
37
+    case `${SET}/${USER_DISCONNECTED}`:
32 38
       return defaultUser
33 39
 
34
-    case `Update/${USER_DATA}`:
40
+    case `${UPDATE}/${USER_DATA}`:
35 41
       return {...state, ...action.data}
36 42
 
43
+    case `${SET}/${USER_LANG}`:
44
+      return {...state, lang: action.lang}
45
+
37 46
     default:
38 47
       return state
39 48
   }

+ 5 - 3
frontend/src/reducer/workspaceContent.js View File

@@ -1,11 +1,13 @@
1 1
 import {
2
+  SET,
3
+  UPDATE,
2 4
   WORKSPACE,
3 5
   FOLDER
4 6
 } from '../action-creator.sync.js'
5 7
 
6 8
 export default function workspace (state = [], action) {
7 9
   switch (action.type) {
8
-    case `Set/${WORKSPACE}/Content`:
10
+    case `${SET}/${WORKSPACE}/Content`:
9 11
       return action.workspaceContent.map(wsc => ({
10 12
         id: wsc.content_id,
11 13
         label: wsc.label,
@@ -20,10 +22,10 @@ export default function workspace (state = [], action) {
20 22
         subContentTypeSlug: wsc.sub_content_type_slug
21 23
       }))
22 24
 
23
-    case `Update/${WORKSPACE}/Filter`: // not used anymore ?
25
+    case `${UPDATE}/${WORKSPACE}/Filter`: // not used anymore ?
24 26
       return {...state, filter: action.filterList}
25 27
 
26
-    case `Set/${WORKSPACE}/${FOLDER}/Content`:
28
+    case `${SET}/${WORKSPACE}/${FOLDER}/Content`:
27 29
       const setFolderContent = (contentItem, action) => {
28 30
         if (contentItem.id === action.folderId) return {...contentItem, content: action.content}
29 31
 

+ 6 - 4
frontend/src/reducer/workspaceList.js View File

@@ -1,4 +1,6 @@
1 1
 import {
2
+  SET,
3
+  UPDATE,
2 4
   WORKSPACE_LIST,
3 5
   USER_ROLE
4 6
 } from '../action-creator.sync.js'
@@ -7,7 +9,7 @@ const handleRouteFromApi = route => route.startsWith('/#') ? route.slice(2) : ro
7 9
 
8 10
 export function workspaceList (state = [], action) {
9 11
   switch (action.type) {
10
-    case `Update/${WORKSPACE_LIST}`:
12
+    case `${UPDATE}/${WORKSPACE_LIST}`:
11 13
       return action.workspaceList.map(ws => ({
12 14
         id: ws.workspace_id,
13 15
         label: ws.label,
@@ -23,13 +25,13 @@ export function workspaceList (state = [], action) {
23 25
         isOpenInSidebar: false
24 26
       }))
25 27
 
26
-    case `Set/${WORKSPACE_LIST}/isOpenInSidebar`:
28
+    case `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`:
27 29
       return state.map(ws => ws.id === action.workspaceId
28 30
         ? {...ws, isOpenInSidebar: action.isOpenInSidebar}
29 31
         : ws
30 32
       )
31 33
 
32
-    case `Set/${USER_ROLE}`: // not used yet
34
+    case `${SET}/${USER_ROLE}`: // not used yet
33 35
       return state.map(ws => {
34 36
         const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
35 37
         return {
@@ -39,7 +41,7 @@ export function workspaceList (state = [], action) {
39 41
         }
40 42
       })
41 43
 
42
-    case `Update/${USER_ROLE}/SubscriptionNotif`: // not used yet
44
+    case `${UPDATE}/${USER_ROLE}/SubscriptionNotif`: // not used yet
43 45
       return state.map(ws => ws.id === action.workspaceId
44 46
         ? {...ws, notif: action.subscriptionNotif}
45 47
         : ws

+ 0 - 41
frontend/src/translate/en.js View File

@@ -1,41 +0,0 @@
1
-const en = {
2
-  translation: { // 'en' in the namespace 'translation'
3
-    Header: {
4
-      Search: 'Search'
5
-    },
6
-    Footer: {
7
-      marketing_msg: 'Create your own collaborative workspaces on trac.im',
8
-      copyright: 'Copyright 2013 - 2017'
9
-    },
10
-    FlashMessage: {
11
-      error: 'Error'
12
-    },
13
-    Login: {
14
-      fail: 'Unknown email or password',
15
-      logout_error: 'Disconnection error'
16
-    },
17
-    FileItemHeader: {
18
-      type: 'Type',
19
-      document_name: "Document's name",
20
-      status: 'Status'
21
-    },
22
-    Folder: {
23
-      create: 'Create in this folder',
24
-      content_type: 'Content type'
25
-    },
26
-    Sidebar: {
27
-      create_new_workspace: 'Create new workspace'
28
-    },
29
-    Account: {
30
-      title: 'My account'
31
-    },
32
-    role: {
33
-      reader: 'Reader',
34
-      contributor: 'Contributor',
35
-      content_manager: 'Content manager',
36
-      manager: 'Manager'
37
-    }
38
-  }
39
-}
40
-
41
-export default en

+ 0 - 41
frontend/src/translate/fr.js View File

@@ -1,41 +0,0 @@
1
-const fr = {
2
-  translation: { // 'fr' in the namespace 'translation'
3
-    Header: {
4
-      Search: 'Rechercher'
5
-    },
6
-    Footer: {
7
-      marketing_msg: 'Créer votre propre espace de travail collaboratif sur trac.im',
8
-      copyright: 'Copyright 2013 - 2017'
9
-    },
10
-    FlashMessage: {
11
-      error: 'Erreur'
12
-    },
13
-    Login: {
14
-      fail: 'Email ou mot de passe inconnu.',
15
-      logout_error: 'Erreur de déconnexion.'
16
-    },
17
-    FileItemHeader: {
18
-      type: 'Type',
19
-      document_name: 'Nom du document',
20
-      status: 'Statut'
21
-    },
22
-    Folder: {
23
-      create: 'Créer dans ce dossier',
24
-      content_type: 'Type de contenu'
25
-    },
26
-    Sidebar: {
27
-      create_new_workspace: 'Créer un workspace'
28
-    },
29
-    Account: {
30
-      title: 'Mon compte'
31
-    },
32
-    role: {
33
-      reader: 'Lecteur',
34
-      contributor: 'Contributeur',
35
-      content_manager: 'Gestionnaire de contenu',
36
-      manager: 'Responsable'
37
-    }
38
-  }
39
-}
40
-
41
-export default fr

+ 17 - 0
frontend_app_html-document/build_html-document.sh View File

@@ -0,0 +1,17 @@
1
+#!/bin/bash
2
+
3
+. ../bash_library.sh # source bash_library.sh
4
+
5
+windoz=""
6
+if [[ $1 = "-w" ]]; then
7
+    windoz="windoz"
8
+fi
9
+
10
+log "npm run build$windoz"
11
+npm run build$windoz
12
+log "cp dist/html-document.app.js ../frontend/dist/app"
13
+cp dist/html-document.app.js ../frontend/dist/app
14
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json"
15
+cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json
16
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json"
17
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json

+ 1 - 1
frontend_app_html-document/dist/index.html View File

@@ -3,7 +3,7 @@
3 3
 <head>
4 4
   <meta charset='utf-8' />
5 5
   <meta name="viewport" content="width=device-width, user-scalable=no" />
6
-  <title>Html-documents App Tracim</title>
6
+  <title>Html-document App Tracim</title>
7 7
   <link rel='shortcut icon' href='favicon.ico'>
8 8
 
9 9
   <link rel="stylesheet" type="text/css" href="./font/font-awesome-4.7.0/css/font-awesome.css">

+ 14 - 0
frontend_app_html-document/i18next.scanner.js View File

@@ -0,0 +1,14 @@
1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+// --------------------
7
+// 2018/07/27 - currently, last version is 2.6.5 but a bug is spaming log with errors. So I'm using 2.6.1
8
+// this issue seems related : https://github.com/i18next/i18next-scanner/issues/88
9
+// --------------------
10
+
11
+vfs.src(['./src/**/*.jsx'])
12
+// .pipe(sort()) // Sort files in stream by path
13
+  .pipe(scanner(option))
14
+  .pipe(vfs.dest('./i18next.scanner'))

+ 5 - 0
frontend_app_html-document/i18next.scanner/en/translation.json View File

@@ -0,0 +1,5 @@
1
+{
2
+  "Last version": "Last version",
3
+  "Validate and create": "Validate and create",
4
+  "Document's title": "Document's title"
5
+}

+ 5 - 0
frontend_app_html-document/i18next.scanner/fr/translation.json View File

@@ -0,0 +1,5 @@
1
+{
2
+  "Last version": "Dernière version",
3
+  "Validate and create": "Valider et créer",
4
+  "Document's title": "Titre du document"
5
+}

+ 4 - 2
frontend_app_html-document/package.json View File

@@ -1,5 +1,5 @@
1 1
 {
2
-  "name": "tracim_app_html-documents",
2
+  "name": "tracim_app_html-document",
3 3
   "version": "1.1.2",
4 4
   "description": "",
5 5
   "main": "index.js",
@@ -7,8 +7,9 @@
7 7
     "servdev": "NODE_ENV=development webpack-dev-server --watch --colors --inline --hot --progress",
8 8
     "servdevwindoz": "set NODE_ENV=development&& webpack-dev-server --watch --colors --inline --hot --progress",
9 9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9871 -- webpack-dev-server --watch --colors --inline --hot --progress",
10
-    "buildwindoz": "set NODE_ENV=production&& webpack -p",
11 10
     "build": "NODE_ENV=production webpack -p",
11
+    "build-translation": "node i18next.scanner.js",
12
+    "buildwindoz": "set NODE_ENV=production&& webpack -p",
12 13
     "test": "echo \"Error: no test specified\" && exit 1"
13 14
   },
14 15
   "author": "",
@@ -41,6 +42,7 @@
41 42
     "whatwg-fetch": "^2.0.3"
42 43
   },
43 44
   "devDependencies": {
45
+    "i18next-scanner": "^2.6.1",
44 46
     "webpack-dashboard": "^1.1.1",
45 47
     "webpack-dev-server": "^2.9.2"
46 48
   },

+ 0 - 13
frontend_app_html-document/rebuild_html-document.sh View File

@@ -1,13 +0,0 @@
1
-#!/bin/bash
2
-
3
-. ../bash_library.sh # source bash_library.sh
4
-
5
-windoz=""
6
-if  [[ $1 = "-w" ]]; then
7
-    windoz="windoz"
8
-fi
9
-
10
-log "npm run build$windoz"
11
-npm run build$windoz
12
-log "cp dist/html-document.app.js ../frontend/dist/app"
13
-cp dist/html-document.app.js ../frontend/dist/app

+ 5 - 5
frontend_app_html-document/src/component/HtmlDocument.jsx View File

@@ -4,27 +4,27 @@ import { MODE } from '../helper.js'
4 4
 
5 5
 const HtmlDocument = props => {
6 6
   return (
7
-    <div className='wsContentHtmlDocument__contentpage__textnote html-documents__contentpage__textnote'>
7
+    <div className='wsContentHtmlDocument__contentpage__textnote html-document__contentpage__textnote'>
8 8
       {(props.mode === MODE.VIEW || props.mode === MODE.REVISION) &&
9 9
         <div>
10
-          <div className='html-documents__contentpage__textnote__version'>
10
+          <div className='html-document__contentpage__textnote__version'>
11 11
             version n°
12 12
             <div dangerouslySetInnerHTML={{__html: props.mode === MODE.VIEW ? props.lastVersion : props.version}} />
13 13
             {props.mode === MODE.REVISION &&
14
-              <div className='html-documents__contentpage__textnote__lastversion'>
14
+              <div className='html-document__contentpage__textnote__lastversion'>
15 15
                 (dernière version : {props.lastVersion})
16 16
               </div>
17 17
             }
18 18
           </div>
19 19
           {/* need try to inject html in stateless component () => <span>{props.text}</span> */}
20
-          <div className='html-documents__contentpage__textnote__text' dangerouslySetInnerHTML={{__html: props.text}} />
20
+          <div className='html-document__contentpage__textnote__text' dangerouslySetInnerHTML={{__html: props.text}} />
21 21
         </div>
22 22
       }
23 23
 
24 24
       {props.mode === MODE.EDIT &&
25 25
         <TextAreaApp
26 26
           id={props.wysiwygNewVersion}
27
-          customClass={'html-documents__editionmode'}
27
+          customClass={'html-document__editionmode'}
28 28
           customColor={props.customColor}
29 29
           onClickCancelBtn={props.onClickCloseEditMode}
30 30
           onClickValidateBtn={props.onClickValidateBtn}

+ 27 - 9
frontend_app_html-document/src/container/HtmlDocument.jsx View File

@@ -1,6 +1,9 @@
1 1
 import React from 'react'
2 2
 import HtmlDocumentComponent from '../component/HtmlDocument.jsx'
3
+import { translate } from 'react-i18next'
4
+import i18n from '../i18n.js'
3 5
 import {
6
+  addAllResourceI18n,
4 7
   handleFetchResult,
5 8
   PopinFixed,
6 9
   PopinFixedHeader,
@@ -20,13 +23,12 @@ import {
20 23
   putHtmlDocContent,
21 24
   putHtmlDocStatus
22 25
 } from '../action.async.js'
23
-import i18n from '../i18n.js'
24 26
 
25 27
 class HtmlDocument extends React.Component {
26 28
   constructor (props) {
27 29
     super(props)
28 30
     this.state = {
29
-      appName: 'html-documents',
31
+      appName: 'html-document',
30 32
       isVisible: true,
31 33
       config: props.data ? props.data.config : debug.config,
32 34
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
@@ -38,22 +40,37 @@ class HtmlDocument extends React.Component {
38 40
       mode: MODE.VIEW
39 41
     }
40 42
 
43
+    // i18n has been init, add resources from frontend
44
+    addAllResourceI18n(i18n, this.state.config.translation)
45
+    i18n.changeLanguage(this.state.loggedUser.lang)
46
+
41 47
     document.addEventListener('appCustomEvent', this.customEventReducer)
42 48
   }
43 49
 
44 50
   customEventReducer = ({ detail: { type, data } }) => { // action: { type: '', data: {} }
45 51
     switch (type) {
46
-      case 'html-documents_showApp':
52
+      case 'html-document_showApp':
47 53
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
48 54
         this.setState({isVisible: true})
49 55
         break
50
-      case 'html-documents_hideApp':
56
+      case 'html-document_hideApp':
51 57
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
52 58
         this.setState({isVisible: false})
53 59
         break
54
-      case 'html-documents_reloadContent':
60
+      case 'html-document_reloadContent':
55 61
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
56 62
         this.setState(prev => ({content: {...prev.content, ...data}, isVisible: true}))
63
+        break
64
+      case 'allApp_changeLang':
65
+        console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
66
+        this.setState(prev => ({
67
+          loggedUser: {
68
+            ...prev.loggedUser,
69
+            lang: data
70
+          }
71
+        }))
72
+        i18n.changeLanguage(data)
73
+        break
57 74
     }
58 75
   }
59 76
 
@@ -284,6 +301,7 @@ class HtmlDocument extends React.Component {
284 301
 
285 302
   render () {
286 303
     const { isVisible, loggedUser, content, timeline, newComment, timelineWysiwyg, config, mode } = this.state
304
+    const { t } = this.props
287 305
 
288 306
     if (!isVisible) return null
289 307
 
@@ -316,12 +334,12 @@ class HtmlDocument extends React.Component {
316 334
 
317 335
               {mode === MODE.REVISION &&
318 336
                 <button
319
-                  className='wsContentGeneric__option__menu__lastversion html-documents__lastversionbtn btn'
337
+                  className='wsContentGeneric__option__menu__lastversion html-document__lastversionbtn btn'
320 338
                   onClick={this.handleClickLastVersion}
321 339
                   style={{backgroundColor: config.hexcolor, color: '#fdfdfd'}}
322 340
                 >
323 341
                   <i className='fa fa-code-fork' />
324
-                  Dernière version
342
+                  {t('Last version')}
325 343
                 </button>
326 344
               }
327 345
             </div>
@@ -358,7 +376,7 @@ class HtmlDocument extends React.Component {
358 376
             lastVersion={timeline.filter(t => t.timelineType === 'revision').length}
359 377
             text={content.raw_content}
360 378
             onChangeText={this.handleChangeText}
361
-            key={'html-documents'}
379
+            key={'html-document'}
362 380
           />
363 381
 
364 382
           <Timeline
@@ -381,4 +399,4 @@ class HtmlDocument extends React.Component {
381 399
   }
382 400
 }
383 401
 
384
-export default HtmlDocument
402
+export default translate()(HtmlDocument)

+ 31 - 5
frontend_app_html-document/src/container/PopupCreateHtmlDocument.jsx View File

@@ -1,13 +1,17 @@
1 1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2 3
 import {
3
-  CardPopupCreateContent, handleFetchResult
4
+  CardPopupCreateContent,
5
+  handleFetchResult,
6
+  addAllResourceI18n
4 7
 } from 'tracim_frontend_lib'
5 8
 import { postHtmlDocContent } from '../action.async.js'
9
+import i18n from '../i18n.js'
6 10
 
7 11
 const debug = { // outdated
8 12
   config: {
9 13
     label: 'Text Document',
10
-    slug: 'html-documents',
14
+    slug: 'html-document',
11 15
     faIcon: 'file-text-o',
12 16
     hexcolor: '#3f52e3',
13 17
     creationLabel: 'Write a document',
@@ -35,13 +39,34 @@ class PopupCreateHtmlDocument extends React.Component {
35 39
   constructor (props) {
36 40
     super(props)
37 41
     this.state = {
38
-      appName: 'html-documents',
42
+      appName: 'html-document', // must remain 'html-document' because it is the name of the react built app (which contains HtmlDocument and PopupCreateHtmlDocument)
39 43
       config: props.data ? props.data.config : debug.config,
40 44
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
41 45
       idWorkspace: props.data ? props.data.idWorkspace : debug.idWorkspace,
42 46
       idFolder: props.data ? props.data.idFolder : debug.idFolder,
43 47
       newContentName: ''
44 48
     }
49
+
50
+    // i18n has been init, add resources from frontend
51
+    addAllResourceI18n(i18n, this.state.config.translation)
52
+    i18n.changeLanguage(this.state.loggedUser.lang)
53
+
54
+    document.addEventListener('appCustomEvent', this.customEventReducer)
55
+  }
56
+
57
+  customEventReducer = ({ detail: { type, data } }) => { // action: { type: '', data: {} }
58
+    switch (type) {
59
+      case 'allApp_changeLang':
60
+        console.log('%c<PopupCreateHtmlDocument> Custom event', 'color: #28a745', type, data)
61
+        this.setState(prev => ({
62
+          loggedUser: {
63
+            ...prev.loggedUser,
64
+            lang: data
65
+          }
66
+        }))
67
+        i18n.changeLanguage(data)
68
+        break
69
+    }
45 70
   }
46 71
 
47 72
   handleChangeNewContentName = e => this.setState({newContentName: e.target.value})
@@ -88,10 +113,11 @@ class PopupCreateHtmlDocument extends React.Component {
88 113
         faIcon={this.state.config.faIcon}
89 114
         contentName={this.state.newContentName}
90 115
         onChangeContentName={this.handleChangeNewContentName}
91
-        btnValidateLabel='Valider et créer'
116
+        btnValidateLabel={this.props.t('Validate and create')}
117
+        inputPlaceholder={this.props.t("Document's title")}
92 118
       />
93 119
     )
94 120
   }
95 121
 }
96 122
 
97
-export default PopupCreateHtmlDocument
123
+export default translate()(PopupCreateHtmlDocument)

+ 5 - 5
frontend_app_html-document/src/css/index.styl View File

@@ -1,6 +1,6 @@
1 1
 @import "../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2 2
 
3
-.html-documents
3
+.html-document
4 4
   width 70%
5 5
   &__header
6 6
     &__editionmode
@@ -68,7 +68,7 @@
68 68
     background-color htmlColor
69 69
 
70 70
 @media (min-width: min-sm) and (max-width: max-lg)
71
-  .html-documents
71
+  .html-document
72 72
     &__contentpage
73 73
       &__content
74 74
         margin 10px
@@ -77,18 +77,18 @@
77 77
 
78 78
 @media (min-width: min-md) and (max-width: max-md)
79 79
 
80
-  .html-documents
80
+  .html-document
81 81
     top 68px
82 82
 
83 83
 @media (min-width: min-sm) and (max-width: max-sm)
84 84
 
85
-  .html-documents
85
+  .html-document
86 86
     top 68px
87 87
     width 100%
88 88
 
89 89
 @media (max-width: max-xs)
90 90
 
91
-  .html-documents
91
+  .html-document
92 92
     top 68px
93 93
     width 100%
94 94
     &__contentpage

+ 17 - 4
frontend_app_html-document/src/helper.js View File

@@ -16,7 +16,7 @@ export const MODE = {
16 16
 export const debug = {
17 17
   config: {
18 18
     label: 'Text Document',
19
-    slug: 'html-documents',
19
+    slug: 'html-document',
20 20
     faIcon: 'file-text-o',
21 21
     hexcolor: '#3f52e3',
22 22
     creationLabel: 'Write a document',
@@ -51,7 +51,19 @@ export const debug = {
51 51
       faIcon: 'warning',
52 52
       hexcolor: '#ababab',
53 53
       globalStatus: 'closed'
54
-    }]
54
+    }],
55
+    translation: {
56
+      en: {
57
+        translation: {
58
+          'Last version': 'Last version debug en'
59
+        }
60
+      },
61
+      fr: {
62
+        translation: {
63
+          'Last version': 'Dernière version debug fr'
64
+        }
65
+      }
66
+    }
55 67
   },
56 68
   loggedUser: { // @FIXME this object is outdated
57 69
     user_id: 5,
@@ -59,6 +71,7 @@ export const debug = {
59 71
     firstname: 'Côme',
60 72
     lastname: 'Stoilenom',
61 73
     email: 'osef@algoo.fr',
74
+    lang: 'en',
62 75
     avatar_url: 'https://avatars3.githubusercontent.com/u/11177014?s=460&v=4',
63 76
     auth: btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
64 77
   },
@@ -69,7 +82,7 @@ export const debug = {
69 82
       user_id: 1 // -1 or 1 for debug
70 83
     },
71 84
     content_id: 22, // 1 or 22 for debug
72
-    content_type: 'html-documents',
85
+    content_type: 'html-document',
73 86
     created: '2018-06-18T14:59:26Z',
74 87
     current_revision_id: 11,
75 88
     is_archived: false,
@@ -86,7 +99,7 @@ export const debug = {
86 99
     show_in_ui: true,
87 100
     slug: 'current-menu',
88 101
     status: 'open',
89
-    sub_content_types: ['thread', 'html-documents', 'file', 'folder'],
102
+    sub_content_types: ['thread', 'html-document', 'file', 'folder'],
90 103
     workspace_id: 1
91 104
   },
92 105
   timeline: timelineDebugData

+ 1 - 11
frontend_app_html-document/src/i18n.js View File

@@ -1,8 +1,5 @@
1 1
 import i18n from 'i18next'
2 2
 import { reactI18nextModule } from 'react-i18next'
3
-import { langFr, langEn } from 'tracim_frontend_lib'
4
-import fr from './translate/fr.js'
5
-import en from './translate/en.js'
6 3
 
7 4
 i18n
8 5
   .use(reactI18nextModule)
@@ -18,14 +15,7 @@ i18n
18 15
     react: {
19 16
       wait: true
20 17
     },
21
-    resources: {
22
-      en: {
23
-        translation: {...langEn.translation, ...en.translation}
24
-      },
25
-      fr: {
26
-        translation: {...langFr.translation, ...fr.translation}
27
-      }
28
-    }
18
+    resources: {} // init with empty resources, they will come from frontend in app constructor
29 19
   })
30 20
 
31 21
 export default i18n

+ 1 - 1
frontend_app_html-document/src/index.js View File

@@ -10,7 +10,7 @@ import PopupCreateHtmlDocument from './container/PopupCreateHtmlDocument.jsx'
10 10
 require('./css/index.styl')
11 11
 
12 12
 const appInterface = {
13
-  name: 'HtmlDocument',
13
+  name: 'html-document',
14 14
   isRendered: false,
15 15
   renderAppFull: data => {
16 16
     return ReactDOM.render(

+ 0 - 9
frontend_app_html-document/src/translate/en.js View File

@@ -1,9 +0,0 @@
1
-const en = {
2
-  translation: { // 'en' in the namespace 'translation'
3
-    PopinFixedOption: {
4
-      new_version: 'New version'
5
-    }
6
-  }
7
-}
8
-
9
-export default en

+ 0 - 9
frontend_app_html-document/src/translate/fr.js View File

@@ -1,9 +0,0 @@
1
-const fr = {
2
-  translation: { // 'fr' in the namespace 'translation'
3
-    PopinFixedOption: {
4
-      new_version: 'nouvelle version'
5
-    }
6
-  }
7
-}
8
-
9
-export default fr

+ 17 - 0
frontend_app_thread/build_thread.sh View File

@@ -0,0 +1,17 @@
1
+#!/bin/bash
2
+
3
+. ../bash_library.sh # source bash_library.sh
4
+
5
+windoz=""
6
+if  [[ $1 = "-w" ]]; then
7
+    windoz="windoz"
8
+fi
9
+
10
+log "npm run build$windoz"
11
+npm run build$windoz
12
+log "cp dist/thread.app.js ../frontend/dist/app"
13
+cp dist/thread.app.js ../frontend/dist/app
14
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json"
15
+cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json
16
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json"
17
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json

+ 14 - 0
frontend_app_thread/i18next.scanner.js View File

@@ -0,0 +1,14 @@
1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+// --------------------
7
+// 2018/07/27 - currently, last version is 2.6.5 but a bug is spaming log with errors. So I'm using 2.6.1
8
+// this issue seems related : https://github.com/i18next/i18next-scanner/issues/88
9
+// --------------------
10
+
11
+vfs.src(['./src/**/*.jsx'])
12
+// .pipe(sort()) // Sort files in stream by path
13
+  .pipe(scanner(option))
14
+  .pipe(vfs.dest('./i18next.scanner'))

+ 4 - 0
frontend_app_thread/i18next.scanner/en/translation.json View File

@@ -0,0 +1,4 @@
1
+{
2
+  "Validate and create": "Validate and create",
3
+  "Topic's subject": "Topic's subject"
4
+}

+ 4 - 0
frontend_app_thread/i18next.scanner/fr/translation.json View File

@@ -0,0 +1,4 @@
1
+{
2
+  "Validate and create": "Valider et créer",
3
+  "Topic's subject": "Sujet de la discussion"
4
+}

+ 2 - 0
frontend_app_thread/package.json View File

@@ -8,6 +8,7 @@
8 8
     "servdevwindoz": "set NODE_ENV=development&& webpack-dev-server --watch --colors --inline --hot --progress",
9 9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9872 -- webpack-dev-server --watch --colors --inline --hot --progress",
10 10
     "build": "NODE_ENV=production webpack -p",
11
+    "build-translation": "node i18next.scanner.js",
11 12
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
12 13
     "test": "echo \"Error: no test specified\" && exit 1"
13 14
   },
@@ -41,6 +42,7 @@
41 42
     "whatwg-fetch": "^2.0.3"
42 43
   },
43 44
   "devDependencies": {
45
+    "i18next-scanner": "^2.6.1",
44 46
     "webpack-dashboard": "^1.1.1",
45 47
     "webpack-dev-server": "^2.9.2"
46 48
   },

+ 0 - 13
frontend_app_thread/rebuild_thread.sh View File

@@ -1,13 +0,0 @@
1
-#!/bin/bash
2
-
3
-. ../bash_library.sh # source bash_library.sh
4
-
5
-windoz=""
6
-if  [[ $1 = "-w" ]]; then
7
-    windoz="windoz"
8
-fi
9
-
10
-log "npm run build$windoz"
11
-npm run build$windoz
12
-log "cp dist/thread.app.js ../frontend/dist/app"
13
-cp dist/thread.app.js ../frontend/dist/app

+ 38 - 5
frontend_app_thread/src/container/PopupCreateThread.jsx View File

@@ -1,13 +1,16 @@
1 1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2 3
 import {
4
+  addAllResourceI18n,
3 5
   CardPopupCreateContent,
4 6
   handleFetchResult
5 7
 } from 'tracim_frontend_lib'
6 8
 import { postThreadContent } from '../action.async.js'
9
+import i18n from '../i18n.js'
7 10
 
8 11
 const debug = { // outdated
9 12
   config: {
10
-    label: 'Thread',
13
+    label: 'PopupCreateThread',
11 14
     slug: 'thread',
12 15
     faIcon: 'file-text-o',
13 16
     hexcolor: '#ad4cf9',
@@ -19,6 +22,14 @@ const debug = { // outdated
19 22
       'Accept': 'application/json',
20 23
       'Content-Type': 'application/json',
21 24
       'Authorization': 'Basic ' + btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
25
+    },
26
+    translation: {
27
+      en: {
28
+        translation: {}
29
+      },
30
+      fr: {
31
+        translation: {}
32
+      }
22 33
     }
23 34
   },
24 35
   loggedUser: {
@@ -33,17 +44,38 @@ const debug = { // outdated
33 44
   idFolder: null
34 45
 }
35 46
 
36
-class PopupCreateHtmlDocument extends React.Component {
47
+class PopupCreateThread extends React.Component {
37 48
   constructor (props) {
38 49
     super(props)
39 50
     this.state = {
40
-      appName: 'thread',
51
+      appName: 'thread', // must remain 'thread' because it is the name of the react built app (which contains Threac and PopupCreateThread)
41 52
       config: props.data ? props.data.config : debug.config,
42 53
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
43 54
       idWorkspace: props.data ? props.data.idWorkspace : debug.idWorkspace,
44 55
       idFolder: props.data ? props.data.idFolder : debug.idFolder,
45 56
       newContentName: ''
46 57
     }
58
+
59
+    // i18n has been init, add resources from frontend
60
+    addAllResourceI18n(i18n, this.state.config.translation)
61
+    i18n.changeLanguage(this.state.loggedUser.lang)
62
+
63
+    document.addEventListener('appCustomEvent', this.customEventReducer)
64
+  }
65
+
66
+  customEventReducer = ({ detail: { type, data } }) => { // action: { type: '', data: {} }
67
+    switch (type) {
68
+      case 'allApp_changeLang':
69
+        console.log('%c<PopupCreateThread> Custom event', 'color: #28a745', type, data)
70
+        this.setState(prev => ({
71
+          loggedUser: {
72
+            ...prev.loggedUser,
73
+            lang: data
74
+          }
75
+        }))
76
+        i18n.changeLanguage(data)
77
+        break
78
+    }
47 79
   }
48 80
 
49 81
   handleChangeNewContentName = e => this.setState({newContentName: e.target.value})
@@ -89,10 +121,11 @@ class PopupCreateHtmlDocument extends React.Component {
89 121
         faIcon={this.state.config.faIcon}
90 122
         contentName={this.state.newContentName}
91 123
         onChangeContentName={this.handleChangeNewContentName}
92
-        btnValidateLabel='Valider et créer'
124
+        btnValidateLabel={this.props.t('Validate and create')}
125
+        inputPlaceholder={this.props.t("Topic's subject")}
93 126
       />
94 127
     )
95 128
   }
96 129
 }
97 130
 
98
-export default PopupCreateHtmlDocument
131
+export default translate()(PopupCreateThread)

+ 16 - 0
frontend_app_thread/src/container/Thread.jsx View File

@@ -2,6 +2,7 @@ import React from 'react'
2 2
 import i18n from '../i18n.js'
3 3
 import { debug } from '../helper.js'
4 4
 import {
5
+  addAllResourceI18n,
5 6
   handleFetchResult,
6 7
   PopinFixed,
7 8
   PopinFixedHeader,
@@ -33,6 +34,10 @@ class Thread extends React.Component {
33 34
       timelineWysiwyg: false
34 35
     }
35 36
 
37
+    // i18n has been init, add resources from frontend
38
+    addAllResourceI18n(i18n, this.state.config.translation)
39
+    i18n.changeLanguage(this.state.loggedUser.lang)
40
+
36 41
     document.addEventListener('appCustomEvent', this.customEventReducer)
37 42
   }
38 43
 
@@ -49,6 +54,17 @@ class Thread extends React.Component {
49 54
       case 'thread_reloadContent':
50 55
         console.log('%c<Thread> Custom event', 'color: #28a745', type, data)
51 56
         this.setState(prev => ({content: {...prev.content, ...data}, isVisible: true}))
57
+        break
58
+      case 'allApp_changeLang':
59
+        console.log('%c<Thread> Custom event', 'color: #28a745', type, data)
60
+        this.setState(prev => ({
61
+          loggedUser: {
62
+            ...prev.loggedUser,
63
+            lang: data
64
+          }
65
+        }))
66
+        i18n.changeLanguage(data)
67
+        break
52 68
     }
53 69
   }
54 70
 

+ 2 - 2
frontend_app_thread/src/helper.js View File

@@ -5,7 +5,7 @@ export const FETCH_CONFIG = {
5 5
   }
6 6
 }
7 7
 
8
-export const debug = { // copied from html-documents => outdated
8
+export const debug = { // copied from html-document => outdated
9 9
   config: {
10 10
     label: 'Thread',
11 11
     slug: 'thread',
@@ -78,7 +78,7 @@ export const debug = { // copied from html-documents => outdated
78 78
     show_in_ui: true,
79 79
     slug: 'current-menu',
80 80
     status: 'open',
81
-    sub_content_types: ['thread', 'html-documents', 'file', 'folder'],
81
+    sub_content_types: ['thread', 'html-document', 'file', 'folder'],
82 82
     workspace_id: 1
83 83
   },
84 84
   timeline: [] // timelineDebugData

+ 1 - 11
frontend_app_thread/src/i18n.js View File

@@ -1,8 +1,5 @@
1 1
 import i18n from 'i18next'
2 2
 import { reactI18nextModule } from 'react-i18next'
3
-import { langFr, langEn } from 'tracim_frontend_lib'
4
-import fr from './translate/fr.js'
5
-import en from './translate/en.js'
6 3
 
7 4
 i18n
8 5
   .use(reactI18nextModule)
@@ -18,14 +15,7 @@ i18n
18 15
     react: {
19 16
       wait: true
20 17
     },
21
-    resources: {
22
-      en: {
23
-        translation: {...langEn.translation, ...en.translation}
24
-      },
25
-      fr: {
26
-        translation: {...langFr.translation, ...fr.translation}
27
-      }
28
-    }
18
+    resources: {} // init with empty resources, they will come from frontend in app constructor
29 19
   })
30 20
 
31 21
 export default i18n

+ 1 - 1
frontend_app_thread/src/index.js View File

@@ -6,7 +6,7 @@ import PopupCreateThread from './container/PopupCreateThread.jsx'
6 6
 require('./css/index.styl')
7 7
 
8 8
 const appInterface = {
9
-  name: 'Thread',
9
+  name: 'thread',
10 10
   isRendered: false,
11 11
   renderAppFull: data => {
12 12
     return ReactDOM.render(

+ 0 - 9
frontend_app_thread/src/translate/en.js View File

@@ -1,9 +0,0 @@
1
-const en = {
2
-  translation: { // 'en' in the namespace 'translation'
3
-    PopinFixedOption: {
4
-      new_version: 'New version'
5
-    }
6
-  }
7
-}
8
-
9
-export default en

+ 0 - 9
frontend_app_thread/src/translate/fr.js View File

@@ -1,9 +0,0 @@
1
-const fr = {
2
-  translation: { // 'fr' in the namespace 'translation'
3
-    PopinFixedOption: {
4
-      new_version: 'nouvelle version'
5
-    }
6
-  }
7
-}
8
-
9
-export default fr

+ 9 - 0
frontend_lib/i18next.scanner.js View File

@@ -0,0 +1,9 @@
1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+vfs.src(['./src/**/*.jsx'])
7
+// .pipe(sort()) // Sort files in stream by path
8
+  .pipe(scanner(option))
9
+  .pipe(vfs.dest('./i18next.scanner'))

+ 1 - 0
frontend_lib/i18next.scanner/en/translation.json View File

@@ -0,0 +1 @@
1
+{}

+ 1 - 0
frontend_lib/i18next.scanner/fr/translation.json View File

@@ -0,0 +1 @@
1
+{}

+ 3 - 1
frontend_lib/package.json View File

@@ -9,8 +9,9 @@
9 9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9870 -- webpack-dev-server --watch --colors --inline --hot --progress",
10 10
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
11 11
     "build": "NODE_ENV=production webpack -p",
12
+    "build-translation": "node i18next.scanner.js",
12 13
     "buildtracimlib": "NODE_ENV=production webpack -p && echo '/* eslint-disable */' | cat - dist/tracim_frontend_lib.js > temp && mv temp dist/tracim_frontend_lib.js && printf '\n/* eslint-enable */\n' >> dist/tracim_frontend_lib.js",
13
-    "buildtracimlibwindoz": "set NODE_ENV=production&& webpack -p && echo '/* eslint-disable */' | cat - dist/tracim_frontend_lib.js > temp && mv temp dist/tracim_frontend_lib.js && printf '\n/* eslint-enable */\n' >> dist/tracim_frontend_lib.js",
14
+    "buildtracimlibwindoz": "set NODE_ENV=production&& webpack -p && echo /* eslint-disable */ | cat - dist/tracim_frontend_lib.js > temp && mv temp dist/tracim_frontend_lib.js && echo /* eslint-enable */>> dist/tracim_frontend_lib.js",
14 15
     "test": "echo \"Error: no test specified\" && exit 1"
15 16
   },
16 17
   "author": "",
@@ -44,6 +45,7 @@
44 45
     "whatwg-fetch": "^2.0.3"
45 46
   },
46 47
   "devDependencies": {
48
+    "i18next-scanner": "^2.6.1",
47 49
     "webpack-dashboard": "^1.1.1",
48 50
     "webpack-dev-server": "^2.9.2"
49 51
   },

+ 3 - 2
frontend_lib/src/component/CardPopup/CardPopupCreateContent.jsx View File

@@ -27,7 +27,7 @@ const PopupCreateContent = props => {
27 27
           <input
28 28
             type='text'
29 29
             className='createcontent__form__input'
30
-            placeHolder='Nommez votre contenu...'
30
+            placeHolder={props.inputPlaceholder}
31 31
             value={props.contentName}
32 32
             onChange={props.onChangeContentName}
33 33
           />
@@ -64,7 +64,8 @@ PopupCreateContent.propTypes = {
64 64
   label: PropTypes.string,
65 65
   customColor: PropTypes.string,
66 66
   faIcon: PropTypes.string,
67
-  btnValidateLabel: PropTypes.string
67
+  btnValidateLabel: PropTypes.string,
68
+  inputPlaceholder: PropTypes.string
68 69
 }
69 70
 
70 71
 PopupCreateContent.defaultProps = {

+ 6 - 7
frontend_lib/src/component/Input/SelectStatus/SelectStatus.jsx View File

@@ -27,21 +27,20 @@ export const SelectStatus = props => {
27 27
       </button>
28 28
 
29 29
       <div className='selectStatus__submenu dropdown-menu' aria-labelledby='dropdownMenu2'>
30
-        <h6 className='dropdown-header'>{props.t('Input.SelectStatus.file_status')}</h6>
31
-
32
-        <div className='dropdown-divider' />
33
-
34 30
         {props.availableStatus.map(s =>
35 31
           <button
36 32
             className='selectStatus__submenu__item current dropdown-item'
37 33
             type='button'
38 34
             onClick={() => props.onChangeStatus(s.slug)}
39 35
             key={`status_${s.slug}`}
40
-            style={{color: s.hexcolor}}
36
+            // style={{color: s.hexcolor}}
41 37
           >
42
-            {s.label /* props.t('Input.SelectStatus.ongoing') */}
38
+            {s.label}
43 39
             <div className='selectStatus__submenu__item__icon'>
44
-              <i className={`fa fa-fw fa-${s.faIcon}`} />
40
+              <i
41
+                className={`fa fa-fw fa-${s.faIcon}`}
42
+                style={{color: s.hexcolor}}
43
+              />
45 44
             </div>
46 45
           </button>
47 46
         )}

+ 8 - 11
frontend_lib/src/component/Timeline/Comment.jsx View File

@@ -25,22 +25,19 @@ const Comment = props => {
25 25
           {props.avatar ? <img src={props.avatar} /> : ''}
26 26
         </div>
27 27
       </div>
28
-      <div
29
-        className={classnames(`${props.customClass}__messagelist__item__authorandhour`, 'timeline__body__messagelist__item__authorandhour')}>
30
-          <div className='mr-5'>
31
-            {props.createdAt}
32
-          </div>
28
+      <div className={classnames(`${props.customClass}__messagelist__item__authorandhour`, 'timeline__body__messagelist__item__authorandhour')}>
29
+        <div className={classnames(`${props.customClass}__messagelist__item__authorandhour__author`, 'timeline__body__messagelist__item__authorandhour__author')}>
33 30
           {props.author}
31
+        </div>
32
+        <div className={classnames(`${props.customClass}__messagelist__item__authorandhour__date`, 'timeline__body__messagelist__item__authorandhour__date')}>
33
+          {props.createdAt}
34
+        </div>
34 35
       </div>
35 36
       <div
36 37
         className={classnames(`${props.customClass}__messagelist__item__content`, 'timeline__body__messagelist__item__content')}
37 38
         style={props.fromMe ? styleSent : styleReceived}
38
-
39
-      >
40
-        <div className='timeline__body__messagelist__item__content__text'>
41
-          {props.text}
42
-        </div>
43
-      </div>
39
+        dangerouslySetInnerHTML={{__html: props.text}}
40
+      />
44 41
     </li>
45 42
   )
46 43
 }

+ 1 - 1
frontend_lib/src/component/Timeline/Timeline.jsx View File

@@ -107,7 +107,7 @@ class Timeline extends React.Component {
107 107
                   }}
108 108
                   key={'timeline__comment__advancedtext'}
109 109
                 >
110
-                  {props.wysiwyg ? 'Texte Simple' : 'Texte Avancé'}
110
+                  {props.wysiwyg ? 'Texte Simple' : 'Texte Riche'}
111 111
                 </button>
112 112
               </div>
113 113
 

+ 3 - 6
frontend_lib/src/component/Timeline/Timeline.styl View File

@@ -36,7 +36,7 @@
36 36
         padding 0 25px 0 35px
37 37
         &__avatar
38 38
           position relative
39
-          top 40px
39
+          top 60px
40 40
           left -20px
41 41
           border-radius 50%
42 42
           width 45px
@@ -46,10 +46,7 @@
46 46
             height 45px
47 47
             border-radius 25px
48 48
         &__authorandhour
49
-          display flex
50
-          align-items center
51
-          justify-content flex-end
52
-          margin-right 35px
49
+          margin-left 35px
53 50
           opacity 0.7
54 51
           font-size 14px
55 52
         &__content
@@ -118,7 +115,7 @@
118 115
       flex-direction row-reverse
119 116
     &__avatar
120 117
       left 20px
121
-    &__createhour
118
+    &__authorandhour
122 119
       margin-left 0
123 120
       margin-right 35px
124 121
 

+ 84 - 84
frontend_lib/src/component/Timeline/debugData.js View File

@@ -1,7 +1,7 @@
1 1
 export const TimelineDebugData = [{
2 2
   'is_deleted': false,
3 3
   'is_archived': false,
4
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
4
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
5 5
   'raw_content': '',
6 6
   'status': 'open',
7 7
   'content_id': 22,
@@ -11,7 +11,7 @@ export const TimelineDebugData = [{
11 11
   'created': '21/06/2018 à 15:12:40',
12 12
   'slug': 'bonjour',
13 13
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
14
-  'content_type': 'html-documents',
14
+  'content_type': 'html-document',
15 15
   'workspace_id': 1,
16 16
   'revision_id': 22,
17 17
   'label': 'bonjour?',
@@ -21,7 +21,7 @@ export const TimelineDebugData = [{
21 21
 }, {
22 22
   'is_deleted': false,
23 23
   'is_archived': false,
24
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
24
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
25 25
   'raw_content': 'bonjour.',
26 26
   'status': 'open',
27 27
   'content_id': 22,
@@ -31,7 +31,7 @@ export const TimelineDebugData = [{
31 31
   'created': '28/06/2018 à 15:13:26',
32 32
   'slug': 'bonjour',
33 33
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
34
-  'content_type': 'html-documents',
34
+  'content_type': 'html-document',
35 35
   'workspace_id': 1,
36 36
   'revision_id': 30,
37 37
   'label': 'bonjour?',
@@ -71,7 +71,7 @@ export const TimelineDebugData = [{
71 71
 }, {
72 72
   'is_deleted': false,
73 73
   'is_archived': false,
74
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
74
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
75 75
   'raw_content': 'bonjour.\nmerci.',
76 76
   'status': 'open',
77 77
   'content_id': 22,
@@ -81,7 +81,7 @@ export const TimelineDebugData = [{
81 81
   'created': '28/06/2018 à 15:43:51',
82 82
   'slug': 'bonjour',
83 83
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
84
-  'content_type': 'html-documents',
84
+  'content_type': 'html-document',
85 85
   'workspace_id': 1,
86 86
   'revision_id': 33,
87 87
   'label': 'bonjour?',
@@ -106,7 +106,7 @@ export const TimelineDebugData = [{
106 106
 }, {
107 107
   'is_deleted': false,
108 108
   'is_archived': false,
109
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
109
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
110 110
   'raw_content': 'bonjour.\nmerci.',
111 111
   'status': 'open',
112 112
   'content_id': 22,
@@ -116,7 +116,7 @@ export const TimelineDebugData = [{
116 116
   'created': '28/06/2018 à 16:01:41',
117 117
   'slug': 'current-menu2',
118 118
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
119
-  'content_type': 'html-documents',
119
+  'content_type': 'html-document',
120 120
   'workspace_id': 1,
121 121
   'revision_id': 35,
122 122
   'label': 'Current Menu2',
@@ -126,7 +126,7 @@ export const TimelineDebugData = [{
126 126
 }, {
127 127
   'is_deleted': false,
128 128
   'is_archived': false,
129
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
129
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
130 130
   'raw_content': 'bonjour.\nmerci.',
131 131
   'status': 'open',
132 132
   'content_id': 22,
@@ -136,7 +136,7 @@ export const TimelineDebugData = [{
136 136
   'created': '28/06/2018 à 16:06:53',
137 137
   'slug': 'current-menu25',
138 138
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
139
-  'content_type': 'html-documents',
139
+  'content_type': 'html-document',
140 140
   'workspace_id': 1,
141 141
   'revision_id': 36,
142 142
   'label': 'Current Menu25',
@@ -146,7 +146,7 @@ export const TimelineDebugData = [{
146 146
 }, {
147 147
   'is_deleted': false,
148 148
   'is_archived': false,
149
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
149
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
150 150
   'raw_content': 'bonjour.\nmerci.',
151 151
   'status': 'open',
152 152
   'content_id': 22,
@@ -156,7 +156,7 @@ export const TimelineDebugData = [{
156 156
   'created': '28/06/2018 à 16:23:30',
157 157
   'slug': '',
158 158
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
159
-  'content_type': 'html-documents',
159
+  'content_type': 'html-document',
160 160
   'workspace_id': 1,
161 161
   'revision_id': 37,
162 162
   'label': '',
@@ -166,7 +166,7 @@ export const TimelineDebugData = [{
166 166
 }, {
167 167
   'is_deleted': false,
168 168
   'is_archived': false,
169
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
169
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
170 170
   'raw_content': 'bonjour.\nmerci.',
171 171
   'status': 'open',
172 172
   'content_id': 22,
@@ -176,7 +176,7 @@ export const TimelineDebugData = [{
176 176
   'created': '28/06/2018 à 16:23:43',
177 177
   'slug': 'woot',
178 178
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
179
-  'content_type': 'html-documents',
179
+  'content_type': 'html-document',
180 180
   'workspace_id': 1,
181 181
   'revision_id': 38,
182 182
   'label': 'woot',
@@ -186,7 +186,7 @@ export const TimelineDebugData = [{
186 186
 }, {
187 187
   'is_deleted': false,
188 188
   'is_archived': false,
189
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
189
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
190 190
   'raw_content': 'bonjour.\nmerci.',
191 191
   'status': 'open',
192 192
   'content_id': 22,
@@ -196,7 +196,7 @@ export const TimelineDebugData = [{
196 196
   'created': '28/06/2018 à 16:35:18',
197 197
   'slug': 'woot2',
198 198
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
199
-  'content_type': 'html-documents',
199
+  'content_type': 'html-document',
200 200
   'workspace_id': 1,
201 201
   'revision_id': 39,
202 202
   'label': 'woot2',
@@ -206,7 +206,7 @@ export const TimelineDebugData = [{
206 206
 }, {
207 207
   'is_deleted': false,
208 208
   'is_archived': false,
209
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
209
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
210 210
   'raw_content': 'bonjour.\nmerci.',
211 211
   'status': 'open',
212 212
   'content_id': 22,
@@ -216,7 +216,7 @@ export const TimelineDebugData = [{
216 216
   'created': '28/06/2018 à 16:36:10',
217 217
   'slug': 'woot3',
218 218
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
219
-  'content_type': 'html-documents',
219
+  'content_type': 'html-document',
220 220
   'workspace_id': 1,
221 221
   'revision_id': 40,
222 222
   'label': 'woot3',
@@ -226,7 +226,7 @@ export const TimelineDebugData = [{
226 226
 }, {
227 227
   'is_deleted': false,
228 228
   'is_archived': false,
229
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
229
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
230 230
   'raw_content': 'bonjour.\nmerci.',
231 231
   'status': 'open',
232 232
   'content_id': 22,
@@ -236,7 +236,7 @@ export const TimelineDebugData = [{
236 236
   'created': '28/06/2018 à 16:37:21',
237 237
   'slug': 'woot4',
238 238
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
239
-  'content_type': 'html-documents',
239
+  'content_type': 'html-document',
240 240
   'workspace_id': 1,
241 241
   'revision_id': 41,
242 242
   'label': 'woot4',
@@ -246,7 +246,7 @@ export const TimelineDebugData = [{
246 246
 }, {
247 247
   'is_deleted': false,
248 248
   'is_archived': false,
249
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
249
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
250 250
   'raw_content': 'bonjour.\nmerci.\nbravo.',
251 251
   'status': 'open',
252 252
   'content_id': 22,
@@ -256,7 +256,7 @@ export const TimelineDebugData = [{
256 256
   'created': '28/06/2018 à 16:37:36',
257 257
   'slug': 'woot4',
258 258
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
259
-  'content_type': 'html-documents',
259
+  'content_type': 'html-document',
260 260
   'workspace_id': 1,
261 261
   'revision_id': 42,
262 262
   'label': 'woot4',
@@ -266,7 +266,7 @@ export const TimelineDebugData = [{
266 266
 }, {
267 267
   'is_deleted': false,
268 268
   'is_archived': false,
269
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
269
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
270 270
   'raw_content': 'bonjour.\nmerci.\nbravo.',
271 271
   'status': 'closed-validated',
272 272
   'content_id': 22,
@@ -276,7 +276,7 @@ export const TimelineDebugData = [{
276 276
   'created': '29/06/2018 à 16:04:02',
277 277
   'slug': 'woot4',
278 278
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
279
-  'content_type': 'html-documents',
279
+  'content_type': 'html-document',
280 280
   'workspace_id': 1,
281 281
   'revision_id': 46,
282 282
   'label': 'woot4',
@@ -286,7 +286,7 @@ export const TimelineDebugData = [{
286 286
 }, {
287 287
   'is_deleted': false,
288 288
   'is_archived': false,
289
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
289
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
290 290
   'raw_content': 'bonjour.\nmerci.\nbravo.',
291 291
   'status': 'closed-deprecated',
292 292
   'content_id': 22,
@@ -296,7 +296,7 @@ export const TimelineDebugData = [{
296 296
   'created': '29/06/2018 à 16:05:32',
297 297
   'slug': 'woot4',
298 298
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
299
-  'content_type': 'html-documents',
299
+  'content_type': 'html-document',
300 300
   'workspace_id': 1,
301 301
   'revision_id': 47,
302 302
   'label': 'woot4',
@@ -306,7 +306,7 @@ export const TimelineDebugData = [{
306 306
 }, {
307 307
   'is_deleted': false,
308 308
   'is_archived': false,
309
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
309
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
310 310
   'raw_content': 'bonjour.\nmerci.\nbravo.',
311 311
   'status': 'open',
312 312
   'content_id': 22,
@@ -316,7 +316,7 @@ export const TimelineDebugData = [{
316 316
   'created': '29/06/2018 à 16:06:35',
317 317
   'slug': 'woot4',
318 318
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
319
-  'content_type': 'html-documents',
319
+  'content_type': 'html-document',
320 320
   'workspace_id': 1,
321 321
   'revision_id': 48,
322 322
   'label': 'woot4',
@@ -326,7 +326,7 @@ export const TimelineDebugData = [{
326 326
 }, {
327 327
   'is_deleted': false,
328 328
   'is_archived': false,
329
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
329
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
330 330
   'raw_content': 'bonjour.\nmerci.\nbravo.',
331 331
   'status': 'closed-deprecated',
332 332
   'content_id': 22,
@@ -336,7 +336,7 @@ export const TimelineDebugData = [{
336 336
   'created': '29/06/2018 à 16:08:36',
337 337
   'slug': 'woot4',
338 338
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
339
-  'content_type': 'html-documents',
339
+  'content_type': 'html-document',
340 340
   'workspace_id': 1,
341 341
   'revision_id': 49,
342 342
   'label': 'woot4',
@@ -346,7 +346,7 @@ export const TimelineDebugData = [{
346 346
 }, {
347 347
   'is_deleted': false,
348 348
   'is_archived': false,
349
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
349
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
350 350
   'raw_content': 'bonjour.\nmerci.\nbravo.',
351 351
   'status': 'open',
352 352
   'content_id': 22,
@@ -356,7 +356,7 @@ export const TimelineDebugData = [{
356 356
   'created': '29/06/2018 à 16:13:15',
357 357
   'slug': 'woot4',
358 358
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
359
-  'content_type': 'html-documents',
359
+  'content_type': 'html-document',
360 360
   'workspace_id': 1,
361 361
   'revision_id': 50,
362 362
   'label': 'woot4',
@@ -366,7 +366,7 @@ export const TimelineDebugData = [{
366 366
 }, {
367 367
   'is_deleted': false,
368 368
   'is_archived': false,
369
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
369
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
370 370
   'raw_content': 'bonjour.\nmerci.\nbravo.',
371 371
   'status': 'closed-validated',
372 372
   'content_id': 22,
@@ -376,7 +376,7 @@ export const TimelineDebugData = [{
376 376
   'created': '29/06/2018 à 16:13:19',
377 377
   'slug': 'woot4',
378 378
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
379
-  'content_type': 'html-documents',
379
+  'content_type': 'html-document',
380 380
   'workspace_id': 1,
381 381
   'revision_id': 51,
382 382
   'label': 'woot4',
@@ -386,7 +386,7 @@ export const TimelineDebugData = [{
386 386
 }, {
387 387
   'is_deleted': false,
388 388
   'is_archived': false,
389
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
389
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
390 390
   'raw_content': 'bonjour.\nmerci.\nbravo.',
391 391
   'status': 'open',
392 392
   'content_id': 22,
@@ -396,7 +396,7 @@ export const TimelineDebugData = [{
396 396
   'created': '29/06/2018 à 16:14:34',
397 397
   'slug': 'woot4',
398 398
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
399
-  'content_type': 'html-documents',
399
+  'content_type': 'html-document',
400 400
   'workspace_id': 1,
401 401
   'revision_id': 52,
402 402
   'label': 'woot4',
@@ -406,7 +406,7 @@ export const TimelineDebugData = [{
406 406
 }, {
407 407
   'is_deleted': false,
408 408
   'is_archived': false,
409
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
409
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
410 410
   'raw_content': 'bonjour.\nmerci.\nbravo.',
411 411
   'status': 'closed-unvalidated',
412 412
   'content_id': 22,
@@ -416,7 +416,7 @@ export const TimelineDebugData = [{
416 416
   'created': '29/06/2018 à 16:14:38',
417 417
   'slug': 'woot4',
418 418
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
419
-  'content_type': 'html-documents',
419
+  'content_type': 'html-document',
420 420
   'workspace_id': 1,
421 421
   'revision_id': 53,
422 422
   'label': 'woot4',
@@ -426,7 +426,7 @@ export const TimelineDebugData = [{
426 426
 }, {
427 427
   'is_deleted': false,
428 428
   'is_archived': false,
429
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
429
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
430 430
   'raw_content': 'bonjour.\nmerci.\nbravo.',
431 431
   'status': 'closed-deprecated',
432 432
   'content_id': 22,
@@ -436,7 +436,7 @@ export const TimelineDebugData = [{
436 436
   'created': '29/06/2018 à 16:15:29',
437 437
   'slug': 'woot4',
438 438
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
439
-  'content_type': 'html-documents',
439
+  'content_type': 'html-document',
440 440
   'workspace_id': 1,
441 441
   'revision_id': 54,
442 442
   'label': 'woot4',
@@ -446,7 +446,7 @@ export const TimelineDebugData = [{
446 446
 }, {
447 447
   'is_deleted': false,
448 448
   'is_archived': false,
449
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
449
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
450 450
   'raw_content': 'bonjour.\nmerci.\nbravo.',
451 451
   'status': 'open',
452 452
   'content_id': 22,
@@ -456,7 +456,7 @@ export const TimelineDebugData = [{
456 456
   'created': '29/06/2018 à 16:15:43',
457 457
   'slug': 'woot4',
458 458
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
459
-  'content_type': 'html-documents',
459
+  'content_type': 'html-document',
460 460
   'workspace_id': 1,
461 461
   'revision_id': 55,
462 462
   'label': 'woot4',
@@ -466,7 +466,7 @@ export const TimelineDebugData = [{
466 466
 }, {
467 467
   'is_deleted': false,
468 468
   'is_archived': false,
469
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
469
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
470 470
   'raw_content': 'bonjour.\nmerci.\nbravo.',
471 471
   'status': 'closed-unvalidated',
472 472
   'content_id': 22,
@@ -476,7 +476,7 @@ export const TimelineDebugData = [{
476 476
   'created': '29/06/2018 à 16:15:53',
477 477
   'slug': 'woot4',
478 478
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
479
-  'content_type': 'html-documents',
479
+  'content_type': 'html-document',
480 480
   'workspace_id': 1,
481 481
   'revision_id': 56,
482 482
   'label': 'woot4',
@@ -486,7 +486,7 @@ export const TimelineDebugData = [{
486 486
 }, {
487 487
   'is_deleted': false,
488 488
   'is_archived': false,
489
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
489
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
490 490
   'raw_content': 'bonjour.\nmerci.\nbravo.',
491 491
   'status': 'closed-deprecated',
492 492
   'content_id': 22,
@@ -496,7 +496,7 @@ export const TimelineDebugData = [{
496 496
   'created': '29/06/2018 à 16:15:55',
497 497
   'slug': 'woot4',
498 498
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
499
-  'content_type': 'html-documents',
499
+  'content_type': 'html-document',
500 500
   'workspace_id': 1,
501 501
   'revision_id': 57,
502 502
   'label': 'woot4',
@@ -506,7 +506,7 @@ export const TimelineDebugData = [{
506 506
 }, {
507 507
   'is_deleted': false,
508 508
   'is_archived': false,
509
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
509
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
510 510
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.</p>',
511 511
   'status': 'closed-deprecated',
512 512
   'content_id': 22,
@@ -516,7 +516,7 @@ export const TimelineDebugData = [{
516 516
   'created': '02/07/2018 à 12:46:16',
517 517
   'slug': 'woot4',
518 518
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
519
-  'content_type': 'html-documents',
519
+  'content_type': 'html-document',
520 520
   'workspace_id': 1,
521 521
   'revision_id': 58,
522 522
   'label': 'woot4',
@@ -631,7 +631,7 @@ export const TimelineDebugData = [{
631 631
 }, {
632 632
   'is_deleted': false,
633 633
   'is_archived': false,
634
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
634
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
635 635
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1</p>',
636 636
   'status': 'closed-deprecated',
637 637
   'content_id': 22,
@@ -641,7 +641,7 @@ export const TimelineDebugData = [{
641 641
   'created': '02/07/2018 à 15:48:04',
642 642
   'slug': 'woot4',
643 643
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
644
-  'content_type': 'html-documents',
644
+  'content_type': 'html-document',
645 645
   'workspace_id': 1,
646 646
   'revision_id': 66,
647 647
   'label': 'woot4',
@@ -651,7 +651,7 @@ export const TimelineDebugData = [{
651 651
 }, {
652 652
   'is_deleted': false,
653 653
   'is_archived': false,
654
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
654
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
655 655
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1</p>',
656 656
   'status': 'open',
657 657
   'content_id': 22,
@@ -661,7 +661,7 @@ export const TimelineDebugData = [{
661 661
   'created': '02/07/2018 à 15:49:32',
662 662
   'slug': 'woot4',
663 663
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
664
-  'content_type': 'html-documents',
664
+  'content_type': 'html-document',
665 665
   'workspace_id': 1,
666 666
   'revision_id': 67,
667 667
   'label': 'woot4',
@@ -671,7 +671,7 @@ export const TimelineDebugData = [{
671 671
 }, {
672 672
   'is_deleted': false,
673 673
   'is_archived': false,
674
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
674
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
675 675
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>',
676 676
   'status': 'open',
677 677
   'content_id': 22,
@@ -681,7 +681,7 @@ export const TimelineDebugData = [{
681 681
   'created': '02/07/2018 à 15:50:36',
682 682
   'slug': 'woot4',
683 683
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
684
-  'content_type': 'html-documents',
684
+  'content_type': 'html-document',
685 685
   'workspace_id': 1,
686 686
   'revision_id': 68,
687 687
   'label': 'woot4',
@@ -691,7 +691,7 @@ export const TimelineDebugData = [{
691 691
 }, {
692 692
   'is_deleted': false,
693 693
   'is_archived': false,
694
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
694
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
695 695
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.123</p>',
696 696
   'status': 'open',
697 697
   'content_id': 22,
@@ -701,7 +701,7 @@ export const TimelineDebugData = [{
701 701
   'created': '02/07/2018 à 15:54:17',
702 702
   'slug': 'woot4',
703 703
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
704
-  'content_type': 'html-documents',
704
+  'content_type': 'html-document',
705 705
   'workspace_id': 1,
706 706
   'revision_id': 69,
707 707
   'label': 'woot4',
@@ -711,7 +711,7 @@ export const TimelineDebugData = [{
711 711
 }, {
712 712
   'is_deleted': false,
713 713
   'is_archived': false,
714
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
714
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
715 715
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1234</p>',
716 716
   'status': 'open',
717 717
   'content_id': 22,
@@ -721,7 +721,7 @@ export const TimelineDebugData = [{
721 721
   'created': '02/07/2018 à 15:54:22',
722 722
   'slug': 'woot4',
723 723
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
724
-  'content_type': 'html-documents',
724
+  'content_type': 'html-document',
725 725
   'workspace_id': 1,
726 726
   'revision_id': 70,
727 727
   'label': 'woot4',
@@ -731,7 +731,7 @@ export const TimelineDebugData = [{
731 731
 }, {
732 732
   'is_deleted': false,
733 733
   'is_archived': false,
734
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
734
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
735 735
   'raw_content': '<p>end</p>',
736 736
   'status': 'open',
737 737
   'content_id': 22,
@@ -741,7 +741,7 @@ export const TimelineDebugData = [{
741 741
   'created': '02/07/2018 à 17:24:41',
742 742
   'slug': 'woot4',
743 743
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
744
-  'content_type': 'html-documents',
744
+  'content_type': 'html-document',
745 745
   'workspace_id': 1,
746 746
   'revision_id': 71,
747 747
   'label': 'woot4',
@@ -751,7 +751,7 @@ export const TimelineDebugData = [{
751 751
 }, {
752 752
   'is_deleted': false,
753 753
   'is_archived': false,
754
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
754
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
755 755
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>',
756 756
   'status': 'open',
757 757
   'content_id': 22,
@@ -761,7 +761,7 @@ export const TimelineDebugData = [{
761 761
   'created': '02/07/2018 à 17:43:28',
762 762
   'slug': 'woot5',
763 763
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
764
-  'content_type': 'html-documents',
764
+  'content_type': 'html-document',
765 765
   'workspace_id': 1,
766 766
   'revision_id': 72,
767 767
   'label': 'woot5',
@@ -786,7 +786,7 @@ export const TimelineDebugData = [{
786 786
 }, {
787 787
   'is_deleted': false,
788 788
   'is_archived': false,
789
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
789
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
790 790
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>&nbsp;</p>',
791 791
   'status': 'open',
792 792
   'content_id': 22,
@@ -796,7 +796,7 @@ export const TimelineDebugData = [{
796 796
   'created': '04/07/2018 à 11:02:27',
797 797
   'slug': 'woot5',
798 798
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
799
-  'content_type': 'html-documents',
799
+  'content_type': 'html-document',
800 800
   'workspace_id': 1,
801 801
   'revision_id': 76,
802 802
   'label': 'woot5',
@@ -806,7 +806,7 @@ export const TimelineDebugData = [{
806 806
 }, {
807 807
   'is_deleted': false,
808 808
   'is_archived': false,
809
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
809
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
810 810
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>',
811 811
   'status': 'open',
812 812
   'content_id': 22,
@@ -816,7 +816,7 @@ export const TimelineDebugData = [{
816 816
   'created': '04/07/2018 à 11:03:53',
817 817
   'slug': 'woot5',
818 818
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
819
-  'content_type': 'html-documents',
819
+  'content_type': 'html-document',
820 820
   'workspace_id': 1,
821 821
   'revision_id': 77,
822 822
   'label': 'woot5',
@@ -826,7 +826,7 @@ export const TimelineDebugData = [{
826 826
 }, {
827 827
   'is_deleted': false,
828 828
   'is_archived': false,
829
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
829
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
830 830
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>',
831 831
   'status': 'open',
832 832
   'content_id': 22,
@@ -836,7 +836,7 @@ export const TimelineDebugData = [{
836 836
   'created': '04/07/2018 à 11:04:19',
837 837
   'slug': 'woot5',
838 838
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
839
-  'content_type': 'html-documents',
839
+  'content_type': 'html-document',
840 840
   'workspace_id': 1,
841 841
   'revision_id': 78,
842 842
   'label': 'woot5',
@@ -861,7 +861,7 @@ export const TimelineDebugData = [{
861 861
 }, {
862 862
   'is_deleted': false,
863 863
   'is_archived': false,
864
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
864
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
865 865
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
866 866
   'status': 'open',
867 867
   'content_id': 22,
@@ -871,7 +871,7 @@ export const TimelineDebugData = [{
871 871
   'created': '04/07/2018 à 11:04:39',
872 872
   'slug': 'woot5',
873 873
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
874
-  'content_type': 'html-documents',
874
+  'content_type': 'html-document',
875 875
   'workspace_id': 1,
876 876
   'revision_id': 80,
877 877
   'label': 'woot5',
@@ -881,7 +881,7 @@ export const TimelineDebugData = [{
881 881
 }, {
882 882
   'is_deleted': false,
883 883
   'is_archived': false,
884
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
884
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
885 885
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
886 886
   'status': 'closed-validated',
887 887
   'content_id': 22,
@@ -891,7 +891,7 @@ export const TimelineDebugData = [{
891 891
   'created': '04/07/2018 à 11:05:13',
892 892
   'slug': 'woot5',
893 893
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
894
-  'content_type': 'html-documents',
894
+  'content_type': 'html-document',
895 895
   'workspace_id': 1,
896 896
   'revision_id': 81,
897 897
   'label': 'woot5',
@@ -901,7 +901,7 @@ export const TimelineDebugData = [{
901 901
 }, {
902 902
   'is_deleted': false,
903 903
   'is_archived': false,
904
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
904
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
905 905
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
906 906
   'status': 'open',
907 907
   'content_id': 22,
@@ -911,7 +911,7 @@ export const TimelineDebugData = [{
911 911
   'created': '04/07/2018 à 11:05:19',
912 912
   'slug': 'woot5',
913 913
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
914
-  'content_type': 'html-documents',
914
+  'content_type': 'html-document',
915 915
   'workspace_id': 1,
916 916
   'revision_id': 82,
917 917
   'label': 'woot5',
@@ -921,7 +921,7 @@ export const TimelineDebugData = [{
921 921
 }, {
922 922
   'is_deleted': false,
923 923
   'is_archived': false,
924
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
924
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
925 925
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
926 926
   'status': 'closed-validated',
927 927
   'content_id': 22,
@@ -931,7 +931,7 @@ export const TimelineDebugData = [{
931 931
   'created': '04/07/2018 à 13:11:50',
932 932
   'slug': 'woot5',
933 933
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
934
-  'content_type': 'html-documents',
934
+  'content_type': 'html-document',
935 935
   'workspace_id': 1,
936 936
   'revision_id': 83,
937 937
   'label': 'woot5',
@@ -941,7 +941,7 @@ export const TimelineDebugData = [{
941 941
 }, {
942 942
   'is_deleted': false,
943 943
   'is_archived': false,
944
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
944
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
945 945
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
946 946
   'status': 'open',
947 947
   'content_id': 22,
@@ -951,7 +951,7 @@ export const TimelineDebugData = [{
951 951
   'created': '04/07/2018 à 14:26:43',
952 952
   'slug': 'woot5',
953 953
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
954
-  'content_type': 'html-documents',
954
+  'content_type': 'html-document',
955 955
   'workspace_id': 1,
956 956
   'revision_id': 84,
957 957
   'label': 'woot5',
@@ -1021,7 +1021,7 @@ export const TimelineDebugData = [{
1021 1021
 }, {
1022 1022
   'is_deleted': false,
1023 1023
   'is_archived': false,
1024
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
1024
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
1025 1025
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1026 1026
   'status': 'closed-validated',
1027 1027
   'content_id': 22,
@@ -1031,7 +1031,7 @@ export const TimelineDebugData = [{
1031 1031
   'created': '06/07/2018 à 16:21:52',
1032 1032
   'slug': 'woot5',
1033 1033
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1034
-  'content_type': 'html-documents',
1034
+  'content_type': 'html-document',
1035 1035
   'workspace_id': 1,
1036 1036
   'revision_id': 109,
1037 1037
   'label': 'woot5',
@@ -1071,7 +1071,7 @@ export const TimelineDebugData = [{
1071 1071
 }, {
1072 1072
   'is_deleted': false,
1073 1073
   'is_archived': false,
1074
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
1074
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
1075 1075
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1076 1076
   'status': 'closed-validated',
1077 1077
   'content_id': 22,
@@ -1081,7 +1081,7 @@ export const TimelineDebugData = [{
1081 1081
   'created': '06/07/2018 à 18:15:04',
1082 1082
   'slug': 'woot55',
1083 1083
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1084
-  'content_type': 'html-documents',
1084
+  'content_type': 'html-document',
1085 1085
   'workspace_id': 1,
1086 1086
   'revision_id': 115,
1087 1087
   'label': 'woot55',
@@ -1091,7 +1091,7 @@ export const TimelineDebugData = [{
1091 1091
 }, {
1092 1092
   'is_deleted': false,
1093 1093
   'is_archived': false,
1094
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
1094
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
1095 1095
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1096 1096
   'status': 'open',
1097 1097
   'content_id': 22,
@@ -1101,7 +1101,7 @@ export const TimelineDebugData = [{
1101 1101
   'created': '06/07/2018 à 18:15:20',
1102 1102
   'slug': 'woot55',
1103 1103
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1104
-  'content_type': 'html-documents',
1104
+  'content_type': 'html-document',
1105 1105
   'workspace_id': 1,
1106 1106
   'revision_id': 116,
1107 1107
   'label': 'woot55',

+ 8 - 0
frontend_lib/src/helper.js View File

@@ -20,3 +20,11 @@ export const libHandleFetchResult = async fetchResult => {
20 20
       return new Promise((resolve, reject) => reject(fetchResult)) // @TODO : handle errors from api result
21 21
   }
22 22
 }
23
+
24
+export const libAddAllResourceI18n = (i18n, translation) => {
25
+  Object.keys(translation).forEach(lang =>
26
+    Object.keys(translation[lang]).forEach(namespace =>
27
+      i18n.addResources(lang, namespace, translation[lang][namespace])
28
+    )
29
+  )
30
+}

+ 5 - 1
frontend_lib/src/index.js View File

@@ -1,4 +1,6 @@
1
-import { libHandleFetchResult } from './helper.js'
1
+import { libAddAllResourceI18n, libHandleFetchResult } from './helper.js'
2
+
3
+// fr and en are deprecated
2 4
 import fr from './translate/fr.js'
3 5
 import en from './translate/en.js'
4 6
 
@@ -24,6 +26,8 @@ import libSelectStatus from './component/Input/SelectStatus/SelectStatus.jsx'
24 26
 export const langFr = fr
25 27
 export const langEn = en
26 28
 
29
+export const addAllResourceI18n = libAddAllResourceI18n
30
+
27 31
 export const handleFetchResult = libHandleFetchResult
28 32
 
29 33
 export const PopinFixed = libPopinFixed

+ 40 - 0
i18next.option.js View File

@@ -0,0 +1,40 @@
1
+module.exports = {
2
+  debug: true,
3
+  removeUnusedKeys: true,
4
+  func: {
5
+    list: ['t', 'props.t', 'this.props.t'],
6
+    extensions: ['.js', '.jsx']
7
+  },
8
+  lngs: ['en', 'fr'],
9
+  defaultLng: 'en',
10
+  keySeparator: false, // false means "keyBasedFallback"
11
+  nsSeparator: false, // false means "keyBasedFallback"
12
+  fallbackLng: false,
13
+
14
+  ns: ['translation'], // namespace
15
+  defaultNS: 'translation',
16
+
17
+  // @param {string} lng The language currently used.
18
+  // @param {string} ns The namespace currently used.
19
+  // @param {string} key The translation key.
20
+  // @return {string} Returns a default value for the translation key.
21
+  // Return key as the default value for English language. Otherwise, returns '__NOT_TRANSLATED__'
22
+  defaultValue: (lng, ns, key) => lng === 'en' ? key : '__NOT_TRANSLATED__',
23
+
24
+  react: {wait: true},
25
+
26
+  resource: {
27
+    // The path where resources get loaded from.
28
+    // /!\ /!\ /!\ Relative to CURRENT working directory. /!\
29
+    loadPath: 'i18next.scanner/{{lng}}/{{ns}}.json',
30
+    // The path to store resources.
31
+    // /!\ /!\ /!\ Relative to the path specified by `vfs.dest('./i18next.scanner')`. /!\
32
+    savePath: '{{lng}}/{{ns}}.json',
33
+    jsonIndent: 2,
34
+    lineEnding: '\n'
35
+  },
36
+  // interpolation: {
37
+  //   escapeValue: false, // not needed for react!!
38
+  // },
39
+  trans: false,
40
+}

+ 16 - 0
install_frontend_dependencies.sh View File

@@ -2,6 +2,8 @@
2 2
 
3 3
 . bash_library.sh # source bash_library.sh
4 4
 
5
+# install Tracim Lib
6
+
5 7
 log "cd frontend_lib"
6 8
 cd frontend_lib
7 9
 log "npm i"
@@ -10,6 +12,8 @@ log "sudo npm link"
10 12
 sudo npm link
11 13
 cd -
12 14
 
15
+# install app Html Document
16
+
13 17
 log "cd frontend_app_html-document"
14 18
 cd frontend_app_html-document
15 19
 log "npm i"
@@ -18,6 +22,18 @@ log "npm link tracim_frontend_lib"
18 22
 npm link tracim_frontend_lib
19 23
 cd -
20 24
 
25
+# install app Thread
26
+
27
+log "cd frontend_app_thread"
28
+cd frontend_app_thread
29
+log "npm i"
30
+npm i
31
+log "npm link tracim_frontend_lib"
32
+npm link tracim_frontend_lib
33
+cd -
34
+
35
+# install Tracim Frontend
36
+
21 37
 log "cd frontend"
22 38
 cd frontend
23 39
 log "npm i"