Ver código fonte

Fixes #2184: Headless build relies on locale directory

Removed Jed from the headless build and instead let `__` call sprintf.

It's now up to downstream users for `@converse/headless` to decide
whether they want i18n support for the few translatable strings in that package
and to implement it themselves.
JC Brand 4 anos atrás
pai
commit
2c042cc3fa
64 arquivos alterados com 172 adições e 130 exclusões
  1. 27 19
      package-lock.json
  2. 1 0
      package.json
  3. 1 1
      spec/disco.js
  4. 1 1
      src/components/adhoc-commands.js
  5. 1 1
      src/components/emoji-picker.js
  6. 1 1
      src/components/image_picker.js
  7. 1 1
      src/components/message-actions.js
  8. 1 1
      src/components/message-history.js
  9. 1 1
      src/components/message.js
  10. 1 1
      src/components/toolbar.js
  11. 1 1
      src/converse-bookmark-views.js
  12. 1 1
      src/converse-chatview.js
  13. 1 1
      src/converse-controlbox.js
  14. 1 1
      src/converse-headlines-view.js
  15. 1 1
      src/converse-minimize.js
  16. 1 1
      src/converse-modal.js
  17. 1 1
      src/converse-muc-views.js
  18. 6 3
      src/converse-notification.js
  19. 1 1
      src/converse-omemo.js
  20. 1 1
      src/converse-profile.js
  21. 1 1
      src/converse-register.js
  22. 1 1
      src/converse-roomslist.js
  23. 1 1
      src/converse-rosterview.js
  24. 1 0
      src/converse.js
  25. 2 1
      src/headless/connection.js
  26. 33 23
      src/headless/converse-core.js
  27. 6 6
      src/headless/package-lock.json
  28. 1 1
      src/headless/package.json
  29. 40 22
      src/i18n/index.js
  30. 1 1
      src/modals/add-muc.js
  31. 1 1
      src/modals/moderator-tools.js
  32. 1 1
      src/modals/muc-commands.js
  33. 1 1
      src/modals/muc-details.js
  34. 1 1
      src/modals/muc-list.js
  35. 1 1
      src/templates/add_chatroom_modal.js
  36. 1 1
      src/templates/add_contact_modal.js
  37. 1 1
      src/templates/bookmarks_list.js
  38. 1 1
      src/templates/buttons.js
  39. 1 1
      src/templates/chat_message.js
  40. 1 1
      src/templates/chatbox_head.js
  41. 1 1
      src/templates/chatroom_details_modal.js
  42. 1 1
      src/templates/chatroom_head.js
  43. 1 1
      src/templates/chats_panel.js
  44. 1 1
      src/templates/directives/retraction.js
  45. 1 1
      src/templates/emoji_picker.js
  46. 1 1
      src/templates/file_progress.js
  47. 1 1
      src/templates/image_modal.js
  48. 1 1
      src/templates/list_chatrooms_modal.js
  49. 1 1
      src/templates/login_panel.js
  50. 1 1
      src/templates/message_versions_modal.js
  51. 1 1
      src/templates/moderator_tools_modal.js
  52. 1 1
      src/templates/muc_bookmark_form.js
  53. 1 1
      src/templates/muc_config_form.js
  54. 1 1
      src/templates/muc_invite_modal.js
  55. 1 1
      src/templates/muc_password_form.js
  56. 1 1
      src/templates/muc_sidebar.js
  57. 1 1
      src/templates/occupant.js
  58. 1 1
      src/templates/profile.js
  59. 1 1
      src/templates/profile_modal.js
  60. 1 1
      src/templates/prompt.js
  61. 1 1
      src/templates/rooms_list.js
  62. 1 1
      src/templates/trimmed_chat.js
  63. 1 1
      src/templates/user_details_modal.js
  64. 1 1
      src/templates/user_settings_modal.js

+ 27 - 19
package-lock.json

@@ -4904,20 +4904,20 @@
 			}
 		},
 		"@octokit/endpoint": {
-			"version": "6.0.5",
-			"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.5.tgz",
-			"integrity": "sha512-70K5u6zd45ItOny6aHQAsea8HHQjlQq85yqOMe+Aj8dkhN2qSJ9T+Q3YjUjEYfPRBcuUWNgMn62DQnP/4LAIiQ==",
+			"version": "6.0.6",
+			"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.6.tgz",
+			"integrity": "sha512-7Cc8olaCoL/mtquB7j/HTbPM+sY6Ebr4k2X2y4JoXpVKQ7r5xB4iGQE0IoO58wIPsUk4AzoT65AMEpymSbWTgQ==",
 			"dev": true,
 			"requires": {
 				"@octokit/types": "^5.0.0",
-				"is-plain-object": "^4.0.0",
+				"is-plain-object": "^5.0.0",
 				"universal-user-agent": "^6.0.0"
 			},
 			"dependencies": {
 				"is-plain-object": {
-					"version": "4.1.1",
-					"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-4.1.1.tgz",
-					"integrity": "sha512-5Aw8LLVsDlZsETVMhoMXzqsXwQqr/0vlnBYzIXJbYo2F4yYlhLHs+Ez7Bod7IIQKWkJbJfxrWD7pA1Dw1TKrwA==",
+					"version": "5.0.0",
+					"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+					"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
 					"dev": true
 				},
 				"universal-user-agent": {
@@ -4982,16 +4982,16 @@
 			}
 		},
 		"@octokit/request": {
-			"version": "5.4.7",
-			"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.7.tgz",
-			"integrity": "sha512-FN22xUDP0i0uF38YMbOfx6TotpcENP5W8yJM1e/LieGXn6IoRxDMnBf7tx5RKSW4xuUZ/1P04NFZy5iY3Rax1A==",
+			"version": "5.4.8",
+			"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.8.tgz",
+			"integrity": "sha512-mWbxjsARJzAq5xp+ZrQfotc+MHFz3/Am2qATJwflv4PZ1TjhgIJnr60PCVdZT9Z/tl+uPXooaVgeviy1KkDlLQ==",
 			"dev": true,
 			"requires": {
 				"@octokit/endpoint": "^6.0.1",
 				"@octokit/request-error": "^2.0.0",
 				"@octokit/types": "^5.0.0",
 				"deprecation": "^2.0.0",
-				"is-plain-object": "^4.0.0",
+				"is-plain-object": "^5.0.0",
 				"node-fetch": "^2.3.0",
 				"once": "^1.4.0",
 				"universal-user-agent": "^6.0.0"
@@ -5009,9 +5009,9 @@
 					}
 				},
 				"is-plain-object": {
-					"version": "4.1.1",
-					"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-4.1.1.tgz",
-					"integrity": "sha512-5Aw8LLVsDlZsETVMhoMXzqsXwQqr/0vlnBYzIXJbYo2F4yYlhLHs+Ez7Bod7IIQKWkJbJfxrWD7pA1Dw1TKrwA==",
+					"version": "5.0.0",
+					"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+					"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
 					"dev": true
 				},
 				"universal-user-agent": {
@@ -5730,6 +5730,14 @@
 			"dev": true,
 			"requires": {
 				"sprintf-js": "~1.0.2"
+			},
+			"dependencies": {
+				"sprintf-js": {
+					"version": "1.0.3",
+					"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+					"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+					"dev": true
+				}
 			}
 		},
 		"arr-diff": {
@@ -13012,7 +13020,8 @@
 		"jed": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz",
-			"integrity": "sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ="
+			"integrity": "sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=",
+			"dev": true
 		},
 		"jest-docblock": {
 			"version": "25.3.0",
@@ -22333,10 +22342,9 @@
 			}
 		},
 		"sprintf-js": {
-			"version": "1.0.3",
-			"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
-			"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
-			"dev": true
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+			"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
 		},
 		"sshpk": {
 			"version": "1.16.1",

+ 1 - 0
package.json

@@ -85,6 +85,7 @@
     "imports-loader": "^0.8.0",
     "install": "^0.13.0",
     "jasmine": "^3.5.0",
+    "jed": "1.1.1",
     "jsdoc": "^3.6.4",
     "karma": "^5.1.0",
     "karma-chrome-launcher": "^3.1.0",

+ 1 - 1
spec/disco.js

@@ -1,4 +1,4 @@
-/*global mock */
+/*global mock, converse */
 
 describe("Service Discovery", function () {
 

+ 1 - 1
src/components/adhoc-commands.js

@@ -2,7 +2,7 @@ import "./autocomplete.js"
 import log from "@converse/headless/log";
 import sizzle from "sizzle";
 import { CustomElement } from './element.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { api, converse } from "@converse/headless/converse-core";
 import { html } from "lit-html";
 import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';

+ 1 - 1
src/components/emoji-picker.js

@@ -2,7 +2,7 @@ import "./emoji-picker-content.js";
 import DOMNavigator from "../dom-navigator";
 import { BaseDropdown } from "./dropdown.js";
 import { CustomElement } from './element.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { debounce, find } from "lodash-es";
 import { html } from "lit-element";

+ 1 - 1
src/components/image_picker.js

@@ -1,5 +1,5 @@
 import { CustomElement } from './element.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { html } from 'lit-element';
 import { renderAvatar } from "../templates/directives/avatar.js";
 import { api } from "@converse/headless/converse-core";

+ 1 - 1
src/components/message-actions.js

@@ -1,5 +1,5 @@
 import { CustomElement } from './element.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { api } from "@converse/headless/converse-core";
 import { html } from 'lit-element';
 import { until } from 'lit-html/directives/until.js';

+ 1 - 1
src/components/message-history.js

@@ -2,7 +2,7 @@ import "./message";
 import dayjs from 'dayjs';
 import tpl_new_day from "../templates//new_day.js";
 import { CustomElement } from './element.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { api } from "@converse/headless/converse-core";
 import { html } from 'lit-element';
 import { repeat } from 'lit-html/directives/repeat.js';

+ 1 - 1
src/components/message.js

@@ -8,7 +8,7 @@ import filesize from 'filesize';
 import tpl_chat_message from '../templates/chat_message.js';
 import tpl_spinner from '../templates/spinner.js';
 import { CustomElement } from './element.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { _converse, api, converse } from  '@converse/headless/converse-core';
 import { html } from 'lit-element';
 import { renderAvatar } from './../templates/directives/avatar';

+ 1 - 1
src/components/toolbar.js

@@ -1,6 +1,6 @@
 import "./emoji-picker.js";
 import { CustomElement } from './element.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { html } from 'lit-element';
 import { until } from 'lit-html/directives/until.js';

+ 1 - 1
src/converse-bookmark-views.js

@@ -10,7 +10,7 @@ import tpl_bookmarks_list from "templates/bookmarks_list.js"
 import tpl_muc_bookmark_form from "templates/muc_bookmark_form.js";
 import { Model } from '@converse/skeletor/src/model.js';
 import { View } from '@converse/skeletor/src/view.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { invokeMap } from 'lodash-es';
 
 const { Strophe } = converse.env;

+ 1 - 1
src/converse-chatview.js

@@ -17,7 +17,7 @@ import tpl_toolbar from "templates/toolbar.js";
 import tpl_user_details_modal from "templates/user_details_modal.js";
 import { BootstrapModal } from "./converse-modal.js";
 import { View } from '@converse/skeletor/src/view.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { debounce, isString } from "lodash-es";
 import { html, render } from "lit-html";

+ 1 - 1
src/converse-controlbox.js

@@ -12,7 +12,7 @@ import tpl_controlbox_toggle from "templates/controlbox_toggle.html";
 import tpl_login_panel from "templates/login_panel.js";
 import { Model } from '@converse/skeletor/src/model.js';
 import { View } from "@converse/skeletor/src/view";
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 
 const { Strophe, dayjs } = converse.env;

+ 1 - 1
src/converse-headlines-view.js

@@ -8,7 +8,7 @@ import tpl_chatbox from "templates/chatbox.js";
 import tpl_headline_panel from "templates/headline_panel.js";
 import { ChatBoxView } from "./converse-chatview";
 import { View } from '@converse/skeletor/src/view.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { render } from "lit-html";
 

+ 1 - 1
src/converse-minimize.js

@@ -8,7 +8,7 @@ import 'converse-chatview';
 import tpl_chats_panel from 'templates/chats_panel.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { View } from '@converse/skeletor/src/view';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from '@converse/headless/converse-core';
 import { debounce } from 'lodash-es';
 import { render } from 'lit-html';

+ 1 - 1
src/converse-modal.js

@@ -7,7 +7,7 @@ import { View } from '@converse/skeletor/src/view.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { isString } from "lodash-es";
 import { render } from 'lit-html';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import bootstrap from "bootstrap.native";
 import { converse } from "@converse/headless/converse-core";
 import log from "@converse/headless/log";

+ 1 - 1
src/converse-muc-views.js

@@ -26,7 +26,7 @@ import tpl_spinner from "templates/spinner.html";
 import { ChatBoxView } from "./converse-chatview";
 import { Model } from '@converse/skeletor/src/model.js';
 import { View } from '@converse/skeletor/src/view.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { debounce, isString, isUndefined } from "lodash-es";
 import { render } from "lit-html";

+ 6 - 3
src/converse-notification.js

@@ -5,7 +5,7 @@
  */
 import log from "@converse/headless/log";
 import st from "@converse/headless/utils/stanza";
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 
 const { Strophe, sizzle } = converse.env;
@@ -36,9 +36,12 @@ converse.plugins.add('converse-notification', {
             notify_nicknames_without_references: false
         });
 
+        /**
+         * Is this a group message for which we should notify the user?
+         * @private
+         * @method _converse#shouldNotifyOfGroupMessage
+         */
         _converse.shouldNotifyOfGroupMessage = function (message, data) {
-            /* Is this a group message worthy of notification?
-             */
             const jid = message.getAttribute('from');
             const room_jid = Strophe.getBareJidFromJid(jid);
             const notify_all = api.settings.get('notify_all_room_messages');

+ 1 - 1
src/converse-omemo.js

@@ -9,7 +9,7 @@ import "converse-profile";
 import log from "@converse/headless/log";
 import { Collection } from "@converse/skeletor/src/collection";
 import { Model } from '@converse/skeletor/src/model.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { concat, debounce, difference, invokeMap, range, omit } from "lodash-es";
 import { html } from 'lit-html';

+ 1 - 1
src/converse-profile.js

@@ -14,7 +14,7 @@ import tpl_chat_status_modal from "templates/chat_status_modal";
 import tpl_profile from "templates/profile.js";
 import tpl_profile_modal from "templates/profile_modal";
 import { BootstrapModal } from "./converse-modal.js";
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 
 const u = converse.env.utils;

+ 1 - 1
src/converse-register.js

@@ -7,7 +7,7 @@
  * @license Mozilla Public License (MPLv2)
  */
 import "converse-controlbox";
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { View } from "@converse/skeletor/src/view";
 import { pick } from "lodash-es";
 import { _converse, api, converse } from "@converse/headless/converse-core";

+ 1 - 1
src/converse-roomslist.js

@@ -12,7 +12,7 @@ import { _converse, api, converse } from "@converse/headless/converse-core";
 import tpl_rooms_list from "templates/rooms_list.js";
 import { Model } from '@converse/skeletor/src/model.js';
 import { View } from '@converse/skeletor/src/view.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 
 
 const { Strophe } = converse.env;

+ 1 - 1
src/converse-rosterview.js

@@ -18,7 +18,7 @@ import { BootstrapModal } from "./converse-modal.js";
 import { Model } from '@converse/skeletor/src/model.js';
 import { OrderedListView } from "@converse/skeletor/src/overview";
 import { View } from '@converse/skeletor/src/view.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from './i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { compact, debounce, has, isString, uniq, without } from "lodash-es";
 

+ 1 - 0
src/converse.js

@@ -9,6 +9,7 @@
  * Any of the following components may be removed if they're not needed.
  */
 import "@converse/headless/headless";
+import "i18n";
 import "converse-registry";
 import "converse-autocomplete";
 import "converse-bookmark-views";  // Views for XEP-0048 Bookmarks

+ 2 - 1
src/headless/connection.js

@@ -2,7 +2,6 @@ import log from "./log";
 import sizzle from 'sizzle';
 import u from '@converse/headless/utils/core';
 import { Strophe } from 'strophe.js/src/core';
-import { __ } from './i18n';
 import { _converse, api, clearSession, tearDown } from "./converse-core";
 import { debounce, isElement, noop } from 'lodash';
 
@@ -105,6 +104,7 @@ export class Connection extends Strophe.Connection {
     }
 
     async reconnect () {
+        const { __ } = _converse;
         log.debug('RECONNECTING: the connection has dropped, attempting to reconnect.');
         this.setConnectionStatus(
             Strophe.Status.RECONNECTING,
@@ -245,6 +245,7 @@ export class Connection extends Strophe.Connection {
      * @param { String } message
      */
     onConnectStatusChanged (status, message) {
+        const { __ } = _converse;
         log.debug(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`);
         if (status === Strophe.Status.ATTACHFAIL) {
             this.setConnectionStatus(status);

+ 33 - 23
src/headless/converse-core.js

@@ -5,8 +5,6 @@
  */
 import './polyfill';
 import 'strophe.js/src/websocket';
-import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe';
-import { Connection, MockConnection } from '@converse/headless/connection.js';
 import Storage from '@converse/skeletor/src/storage.js';
 import _ from './lodash.noconflict';
 import advancedFormat from 'dayjs/plugin/advancedFormat';
@@ -17,13 +15,15 @@ import sizzle from 'sizzle';
 import stanza_utils from "@converse/headless/utils/stanza";
 import u from '@converse/headless/utils/core';
 import { Collection } from "@converse/skeletor/src/collection";
+import { Connection, MockConnection } from '@converse/headless/connection.js';
+import { CustomElement } from '../components/element';
 import { Events } from '@converse/skeletor/src/events.js';
 import { Model } from '@converse/skeletor/src/model.js';
 import { Router } from '@converse/skeletor/src/router.js';
-import { CustomElement } from '../components/element';
-import { __, i18n } from './i18n';
+import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe';
 import { assignIn, debounce, invoke, isFunction, isObject, isString, pick } from 'lodash-es';
 import { html } from 'lit-element';
+import { sprintf } from 'sprintf-js';
 
 
 dayjs.extend(advancedFormat);
@@ -152,6 +152,32 @@ CONNECTION_STATUS[Strophe.Status.ERROR] = 'ERROR';
 CONNECTION_STATUS[Strophe.Status.RECONNECTING] = 'RECONNECTING';
 CONNECTION_STATUS[Strophe.Status.REDIRECT] = 'REDIRECT';
 
+
+/**
+ * @namespace i18n
+ */
+export const i18n = {
+    initialize () {},
+
+    /**
+     * Overridable string wrapper method which can be used to provide i18n
+     * support.
+     *
+     * The default implementation in @converse/headless simply calls sprintf
+     * with the passed in arguments.
+     *
+     * If you install the full version of Converse, then this method gets
+     * overwritten in src/i18n/index.js to return a translated string.
+     * @method __
+     * @private
+     * @memberOf i18n
+     * @param { String } str
+     */
+    __ (...args) {
+        return sprintf(...args);
+    }
+};
+
 /**
  * A private, closured object containing the private api (via {@link _converse.api})
  * as well as private methods and internal data-structures.
@@ -223,13 +249,12 @@ export const _converse = {
 
     /**
      * Translate the given string based on the current locale.
-     * Handles all MUC presence stanzas.
      * @method __
      * @private
      * @memberOf _converse
-     * @param { String } str - The string to translate
+     * @param { String } str
      */
-     '__': __,
+    '__': (...args) => i18n.__(...args),
 
     /**
      * A no-op method which is used to signal to gettext that the passed in string
@@ -1431,21 +1456,6 @@ _converse.ConnectionFeedback = Model.extend({
 });
 
 
-async function initLocale () {
-    if (_converse.isTestEnv()) {
-        _converse.locale = 'en';
-    } else {
-        try {
-            _converse.locale = i18n.getLocale(api.settings.get('i18n'), api.settings.get("locales"));
-            await i18n.fetchTranslations(_converse);
-        } catch (e) {
-            log.fatal(e.message);
-            _converse.locale = 'en';
-        }
-    }
-}
-
-
 function setUnloadEvent () {
     if ('onpagehide' in window) {
         // Pagehide gets thrown in more cases than unload. Specifically it
@@ -1536,7 +1546,6 @@ Object.assign(converse, {
             /^converse\?loglevel=(debug|info|warn|error|fatal)$/, 'loglevel',
             l => log.setLogLevel(l)
         );
-        await initLocale();
         _converse.connfeedback = new _converse.ConnectionFeedback();
 
         /* When reloading the page:
@@ -1550,6 +1559,7 @@ Object.assign(converse, {
 
         await initSessionStorage();
         initClientConfig();
+        i18n.initialize();
         initPlugins();
         registerGlobalEventHandlers();
 

+ 6 - 6
src/headless/package-lock.json

@@ -31,12 +31,6 @@
 			"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
 			"dev": true
 		},
-		"jed": {
-			"version": "1.1.1",
-			"resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz",
-			"integrity": "sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=",
-			"dev": true
-		},
 		"lie": {
 			"version": "3.1.1",
 			"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
@@ -82,6 +76,12 @@
 				"lodash": "^4.17.11"
 			}
 		},
+		"sprintf-js": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+			"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
+			"dev": true
+		},
 		"strophe.js": {
 			"version": "github:strophe/strophejs#ceb5640786dc60de4a13b18c9a263f2ef112874c",
 			"from": "github:strophe/strophejs#ceb5640786dc60de4a13b18c9a263f2ef112874c",

+ 1 - 1
src/headless/package.json

@@ -38,10 +38,10 @@
   "devDependencies": {
     "@converse/skeletor": "conversejs/skeletor#b260c554f4ce961c29deea4740083e58a489aa9b",
     "filesize": "^6.1.0",
-    "jed": "1.1.1",
     "localforage": "^1.7.3",
     "lodash-es": "^4.17.15",
     "pluggable.js": "2.0.1",
+    "sprintf-js": "^1.1.2",
     "strophe.js": "strophe/strophejs#ceb5640786dc60de4a13b18c9a263f2ef112874c"
   }
 }

+ 40 - 22
src/headless/i18n.js → src/i18n/index.js

@@ -4,8 +4,10 @@
  * @license Mozilla Public License (MPLv2)
  * @description This is the internationalization module
  */
-import Jed from "jed";
-import dayjs from "dayjs";
+import Jed from 'jed';
+import dayjs from 'dayjs';
+import log from "@converse/headless/log";
+import { _converse, api, i18n } from '@converse/headless/converse-core';
 
 
 function detectLocale (library_check) {
@@ -63,12 +65,30 @@ function isLocaleAvailable (locale, available) {
     }
 }
 
+
+/* Fetch the translations for the given local at the given URL.
+ * @private
+ * @method i18n#fetchTranslations
+ * @param { _converse }
+ */
+async function fetchTranslations (_converse) {
+    const { api, locale } = _converse;
+    if (!isConverseLocale(locale, api.settings.get("locales")) || locale === 'en') {
+        return;
+    }
+    const { default: data } = await import(/*webpackChunkName: "locales/[request]" */ `../i18n/${locale}/LC_MESSAGES/converse.po`);
+    await import(/*webpackChunkName: "locales/dayjs/[request]" */ `dayjs/locale/${locale.toLowerCase().replace('_', '-')}`);
+    dayjs.locale(getLocale(locale, l => dayjs.locale(l)));
+    jed_instance = new Jed(data);
+}
+
+
 let jed_instance;
 
 /**
  * @namespace i18n
  */
-export const i18n = {
+Object.assign(i18n, {
 
     getLocale (preferred_locale, available_locales) {
         return getLocale(preferred_locale, preferred => isConverseLocale(preferred, available_locales));
@@ -86,25 +106,23 @@ export const i18n = {
         }
     },
 
-    /**
-     * Fetch the translations for the given local at the given URL.
-     * @private
-     * @method i18n#fetchTranslations
-     * @param { _converse }
-     */
-    async fetchTranslations (_converse) {
-        const locale = _converse.locale;
-        if (!isConverseLocale(locale, _converse.api.settings.get("locales")) || locale === 'en') {
-            return;
+    async initialize () {
+        if (_converse.isTestEnv()) {
+            _converse.locale = 'en';
+        } else {
+            try {
+                _converse.locale = i18n.getLocale(api.settings.get('i18n'), api.settings.get("locales"));
+                await fetchTranslations(_converse);
+            } catch (e) {
+                log.fatal(e.message);
+                _converse.locale = 'en';
+            }
         }
-        const { default: data } = await import(/*webpackChunkName: "locales/[request]" */ `../i18n/${locale}/LC_MESSAGES/converse.po`);
-        await import(/*webpackChunkName: "locales/dayjs/[request]" */ `dayjs/locale/${locale.toLowerCase().replace('_', '-')}`);
-        dayjs.locale(getLocale(_converse.locale, l => dayjs.locale(l)));
-        jed_instance = new Jed(data);
-    }
-};
+    },
 
+    __ (...args) {
+        return i18n.translate(...args);
+    }
+});
 
-export const __ = function () {
-    return i18n.translate.apply(i18n, arguments);
-}
+export const __ = i18n.__;

+ 1 - 1
src/modals/add-muc.js

@@ -1,7 +1,7 @@
 import tpl_add_chatroom_modal from "templates/add_chatroom_modal.js";
 import { BootstrapModal } from "../converse-modal.js";
 import { Strophe } from 'strophe.js/src/strophe';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 
 const u = converse.env.utils;

+ 1 - 1
src/modals/moderator-tools.js

@@ -3,7 +3,7 @@ import sizzle from "sizzle";
 import tpl_moderator_tools_modal from "../templates/moderator_tools_modal.js";
 import { AFFILIATIONS, ROLES } from "@converse/headless/converse-muc.js";
 import { BootstrapModal } from "../converse-modal.js";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { api, converse } from "@converse/headless/converse-core";
 
 const { Strophe } = converse.env;

+ 1 - 1
src/modals/muc-commands.js

@@ -1,5 +1,5 @@
 import { BootstrapModal } from "../converse-modal.js";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { api, converse } from "@converse/headless/converse-core";
 import log from "@converse/headless/log";
 import sizzle from "sizzle";

+ 1 - 1
src/modals/muc-details.js

@@ -1,5 +1,5 @@
 import { BootstrapModal } from "../converse-modal.js";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import tpl_chatroom_details_modal from "../templates/chatroom_details_modal.js";
 
 

+ 1 - 1
src/modals/muc-list.js

@@ -6,7 +6,7 @@ import tpl_room_description from "templates/room_description.html";
 import tpl_spinner from "templates/spinner.html";
 import { BootstrapModal } from "../converse-modal.js";
 import { Strophe, $iq } from 'strophe.js/src/strophe';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { _converse, api, converse } from "@converse/headless/converse-core";
 import { head } from "lodash-es";
 

+ 1 - 1
src/templates/add_chatroom_modal.js

@@ -1,6 +1,6 @@
 import { html } from "lit-html";
 import { unsafeHTML } from "lit-html/directives/unsafe-html.js";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { modal_header_close_button } from "./buttons"
 import xss from "xss/dist/xss";
 

+ 1 - 1
src/templates/add_contact_modal.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { modal_header_close_button } from "./buttons"
 
 

+ 1 - 1
src/templates/bookmarks_list.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 
 
 const bookmark_item = (o) => {

+ 1 - 1
src/templates/buttons.js

@@ -1,4 +1,4 @@
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { html } from "lit-html";
 
 

+ 1 - 1
src/templates/chat_message.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { renderAvatar } from './../templates/directives/avatar';
 
 

+ 1 - 1
src/templates/chatbox_head.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { until } from 'lit-html/directives/until.js';
 import avatar from "./avatar.js";
 

+ 1 - 1
src/templates/chatroom_details_modal.js

@@ -1,4 +1,4 @@
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { html } from "lit-html";
 import { modal_close_button, modal_header_close_button } from "./buttons"
 import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';

+ 1 - 1
src/templates/chatroom_head.js

@@ -1,5 +1,5 @@
 import '../components/dropdown.js';
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { html } from "lit-html";
 import { until } from 'lit-html/directives/until.js';
 import { converse } from "@converse/headless/converse-core";

+ 1 - 1
src/templates/chats_panel.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 
 export default (o) =>
     html`<div id="minimized-chats" class="${o.chats.length ? '' : 'hidden'}">

+ 1 - 1
src/templates/directives/retraction.js

@@ -1,4 +1,4 @@
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { api } from "@converse/headless/converse-core";
 import { directive, html } from "lit-html";
 

+ 1 - 1
src/templates/emoji_picker.js

@@ -1,4 +1,4 @@
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { _converse, converse, api } from "@converse/headless/converse-core";
 import { html } from "lit-html";
 

+ 1 - 1
src/templates/file_progress.js

@@ -1,4 +1,4 @@
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { html } from "lit-html";
 import { renderAvatar } from './../templates/directives/avatar';
 

+ 1 - 1
src/templates/image_modal.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { modal_close_button, modal_header_close_button } from "./buttons"
 
 

+ 1 - 1
src/templates/list_chatrooms_modal.js

@@ -1,4 +1,4 @@
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { html } from "lit-html";
 import { repeat } from 'lit-html/directives/repeat.js';
 import { modal_close_button, modal_header_close_button } from "./buttons"

+ 1 - 1
src/templates/login_panel.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import tpl_spinner from './spinner.js';
 
 

+ 1 - 1
src/templates/message_versions_modal.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import dayjs from 'dayjs';
 import { modal_close_button, modal_header_close_button } from "./buttons"
 

+ 1 - 1
src/templates/moderator_tools_modal.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import spinner from "./spinner.js";
 import { modal_header_close_button } from "./buttons"
 

+ 1 - 1
src/templates/muc_bookmark_form.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 
 
 export default (o) => {

+ 1 - 1
src/templates/muc_config_form.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
 
 export default (o) => {

+ 1 - 1
src/templates/muc_invite_modal.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { modal_header_close_button } from "./buttons"
 
 

+ 1 - 1
src/templates/muc_password_form.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 
 
 export default (o) => {

+ 1 - 1
src/templates/muc_sidebar.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import tpl_occupant from "./occupant.js";
 
 

+ 1 - 1
src/templates/occupant.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 
 
 const occupant_title = (o) => {

+ 1 - 1
src/templates/profile.js

@@ -1,4 +1,4 @@
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { api } from "@converse/headless/converse-core";
 import { html } from "lit-html";
 

+ 1 - 1
src/templates/profile_modal.js

@@ -1,6 +1,6 @@
 import "../components/image_picker.js";
 import spinner from "./spinner.js";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { _converse, converse } from  "@converse/headless/converse-core";
 import { html } from "lit-html";
 import { modal_header_close_button } from "./buttons";

+ 1 - 1
src/templates/prompt.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 
 
 const tpl_field = (f) => html`

+ 1 - 1
src/templates/rooms_list.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 
 
 const bookmark = (o) => {

+ 1 - 1
src/templates/trimmed_chat.js

@@ -1,5 +1,5 @@
 import { html } from "lit-html";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 
 
 export default (o) => {

+ 1 - 1
src/templates/user_details_modal.js

@@ -1,4 +1,4 @@
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { html } from "lit-html";
 import avatar from "./avatar.js";
 import { modal_close_button, modal_header_close_button } from "./buttons"

+ 1 - 1
src/templates/user_settings_modal.js

@@ -1,6 +1,6 @@
 import '../components/adhoc-commands.js';
 import xss from "xss/dist/xss";
-import { __ } from '@converse/headless/i18n';
+import { __ } from '../i18n';
 import { api } from "@converse/headless/converse-core";
 import { html } from "lit-html";
 import { modal_header_close_button } from "./buttons"