diff --git a/CHANGELOG.md b/CHANGELOG.md index c4d711a08..07f272707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 12.3.1 + +- optimization for optional lng prop for useTranslation, should now prevent missings when lazy loading translations [1637](https://github.com/i18next/react-i18next/issues/1637) + ### 12.3.0 - optional lng prop for useTranslation (helping on server side [1637](https://github.com/i18next/react-i18next/issues/1637)) diff --git a/package-lock.json b/package-lock.json index a15b73b3e..fe297be23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "eslint-plugin-react": "^7.16.0", "eslint-plugin-testing-library": "^3.10.1", "husky": "^3.0.3", - "i18next": "^22.4.2", + "i18next": "^22.5.0", "jest": "^24.8.0", "jest-cli": "^24.8.4", "lint-staged": "^8.1.3", @@ -8113,9 +8113,9 @@ } }, "node_modules/i18next": { - "version": "22.4.2", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.2.tgz", - "integrity": "sha512-5gRrkgJRxqsNMJWLZbcby60NuZjD5WtA8Yap7DmP42ccrzksaXpT11JC5505IWHqGoywuESSAgtlif2awtQF7A==", + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.0.tgz", + "integrity": "sha512-sqWuJFj+wJAKQP2qBQ+b7STzxZNUmnSxrehBCCj9vDOW9RDYPfqCaK1Hbh2frNYQuPziz6O2CGoJPwtzY3vAYA==", "dev": true, "funding": [ { @@ -21522,9 +21522,9 @@ } }, "i18next": { - "version": "22.4.2", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.2.tgz", - "integrity": "sha512-5gRrkgJRxqsNMJWLZbcby60NuZjD5WtA8Yap7DmP42ccrzksaXpT11JC5505IWHqGoywuESSAgtlif2awtQF7A==", + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.0.tgz", + "integrity": "sha512-sqWuJFj+wJAKQP2qBQ+b7STzxZNUmnSxrehBCCj9vDOW9RDYPfqCaK1Hbh2frNYQuPziz6O2CGoJPwtzY3vAYA==", "dev": true, "requires": { "@babel/runtime": "^7.20.6" diff --git a/package.json b/package.json index 3ee3a9f4c..245386d3c 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "eslint-plugin-react": "^7.16.0", "eslint-plugin-testing-library": "^3.10.1", "husky": "^3.0.3", - "i18next": "^22.4.3", + "i18next": "^22.5.0", "jest": "^24.8.0", "jest-cli": "^24.8.4", "lint-staged": "^8.1.3", diff --git a/react-i18next.js b/react-i18next.js index 66db0e8da..c5de9c5dc 100644 --- a/react-i18next.js +++ b/react-i18next.js @@ -341,8 +341,9 @@ if (typeof args[0] === 'string') alreadyWarned[args[0]] = new Date(); warn.apply(void 0, args); } - function loadNamespaces(i18n, ns, cb) { - i18n.loadNamespaces(ns, function () { + + var loadedClb = function loadedClb(i18n, cb) { + return function () { if (i18n.isInitialized) { cb(); } else { @@ -355,7 +356,18 @@ i18n.on('initialized', initialized); } + }; + }; + + function loadNamespaces(i18n, ns, cb) { + i18n.loadNamespaces(ns, loadedClb(i18n, cb)); + } + function loadLanguages(i18n, lng, ns, cb) { + if (typeof ns === 'string') ns = [ns]; + ns.forEach(function (n) { + if (i18n.options.ns.indexOf(n) < 0) i18n.options.ns.push(n); }); + i18n.loadLanguages(lng, loadedClb(i18n, cb)); } function oldI18nextHasLoadedNamespace(ns, i18n) { @@ -392,6 +404,7 @@ } return i18n.hasLoadedNamespace(ns, { + lng: options.lng, precheck: function precheck(i18nInstance, loadNotPending) { if (options.bindI18n && options.bindI18n.indexOf('languageChanging') > -1 && i18nInstance.services.backendConnector.backend && i18nInstance.isLanguageChangingTo && !loadNotPending(i18nInstance.isLanguageChangingTo, ns)) return false; } @@ -883,6 +896,7 @@ setT = _useState2[1]; var joinedNS = namespaces.join(); + if (props.lng) joinedNS = "".concat(props.lng).concat(joinedNS); var previousJoinedNS = usePrevious(joinedNS); var isMounted = react.useRef(true); react.useEffect(function () { @@ -891,9 +905,15 @@ isMounted.current = true; if (!ready && !useSuspense) { - loadNamespaces(i18n, namespaces, function () { - if (isMounted.current) setT(getT); - }); + if (props.lng) { + loadLanguages(i18n, props.lng, namespaces, function () { + if (isMounted.current) setT(getT); + }); + } else { + loadNamespaces(i18n, namespaces, function () { + if (isMounted.current) setT(getT); + }); + } } if (ready && previousJoinedNS && previousJoinedNS !== joinedNS && isMounted.current) { @@ -931,9 +951,15 @@ if (ready) return ret; if (!ready && !useSuspense) return ret; throw new Promise(function (resolve) { - loadNamespaces(i18n, namespaces, function () { - resolve(); - }); + if (props.lng) { + loadLanguages(i18n, props.lng, namespaces, function () { + return resolve(); + }); + } else { + loadNamespaces(i18n, namespaces, function () { + return resolve(); + }); + } }); } diff --git a/react-i18next.min.js b/react-i18next.min.js index 3059e334c..4cff63db4 100644 --- a/react-i18next.min.js +++ b/react-i18next.min.js @@ -1 +1 @@ -!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],n):n((e=e||self).ReactI18next={},e.React)}(this,(function(e,n){"use strict";function t(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function r(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}function c(e,n){return function(e){if(Array.isArray(e))return e}(e)||function(e,n){var t=e&&("undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"]);if(null==t)return;var r,a,i=[],o=!0,s=!1;try{for(t=t.call(e);!(o=(r=t.next()).done)&&(i.push(r.value),!n||i.length!==n);o=!0);}catch(e){s=!0,a=e}finally{try{o||null==t.return||t.return()}finally{if(s)throw a}}return i}(e,n)||function(e,n){if(!e)return;if("string"==typeof e)return u(e,n);var t=Object.prototype.toString.call(e).slice(8,-1);"Object"===t&&e.constructor&&(t=e.constructor.name);if("Map"===t||"Set"===t)return Array.from(e);if("Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t))return u(e,n)}(e,n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=new Array(n);t<]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function p(e){var n={type:"tag",name:"",voidElement:!1,attrs:{},children:[]},t=e.match(/<\/?([^\s]+?)[/\s>]/);if(t&&(n.name=t[1],(l[t[1]]||"/"===e.charAt(e.length-2))&&(n.voidElement=!0),n.name.startsWith("!--"))){var r=e.indexOf("--\x3e");return{type:"comment",comment:-1!==r?e.slice(4,r):""}}for(var a=new RegExp(f),i=null;null!==(i=a.exec(e));)if(i[0].trim())if(i[1]){var o=i[1].trim(),s=[o,""];o.indexOf("=")>-1&&(s=o.split("=")),n.attrs[s[0]]=s[1],a.lastIndex--}else i[2]&&(n.attrs[i[2]]=i[3].trim().substring(1,i[3].length-1));return n}var d=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,g=/^\s*$/,m=Object.create(null);var h=function(e,n){n||(n={}),n.components||(n.components=m);var t,r=[],a=[],i=-1,o=!1;if(0!==e.indexOf("<")){var s=e.indexOf("<");r.push({type:"text",content:-1===s?e:e.substring(0,s)})}return e.replace(d,(function(s,c){if(o){if(s!=="")return;o=!1}var u,l="/"!==s.charAt(1),f=s.startsWith("\x3c!--"),d=c+s.length,m=e.charAt(d);if(f){var h=p(s);return i<0?(r.push(h),r):((u=a[i]).children.push(h),r)}if(l&&(i++,"tag"===(t=p(s)).type&&n.components[t.name]&&(t.type="component",o=!0),t.voidElement||o||!m||"<"===m||t.children.push({type:"text",content:e.slice(d,e.indexOf("<",d))}),0===i&&r.push(t),(u=a[i-1])&&u.children.push(t),a[i]=t),(!l||t.voidElement)&&(i>-1&&(t.voidElement||t.name===s.slice(2,-1))&&(i--,t=-1===i?r:a[i]),!o&&"<"!==m&&m)){u=-1===i?r:a[i].children;var v=e.indexOf("<",d),y=e.slice(d,-1===v?void 0:v);g.test(y)&&(y=" "),(v>-1&&i+u.length>=0||" "!==y)&&u.push({type:"text",content:y})}})),r};function v(){if(console&&console.warn){for(var e,n=arguments.length,t=new Array(n),r=0;r2&&void 0!==arguments[2]?arguments[2]:{},r=n.languages[0],a=!!n.options&&n.options.fallbackLng,i=n.languages[n.languages.length-1];if("cimode"===r.toLowerCase())return!0;var o=function(e,t){var r=n.services.backendConnector.state["".concat(e,"|").concat(t)];return-1===r||2===r};return!(t.bindI18n&&t.bindI18n.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!o(n.isLanguageChangingTo,e))&&(!!n.hasResourceBundle(r,e)||(!(n.services.backendConnector.backend&&(!n.options.resources||n.options.partialBundledLanguages))||!(!o(r,e)||a&&!o(i,e))))}function w(e,n){var t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!n.languages||!n.languages.length)return b("i18n.languages were undefined or empty",n.languages),!0;var r=void 0!==n.options.ignoreJSONStructure;return r?n.hasLoadedNamespace(e,{precheck:function(n,r){if(t.bindI18n&&t.bindI18n.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!r(n.isLanguageChangingTo,e))return!1}}):x(e,n,t)}function j(e){return e.displayName||e.name||("string"==typeof e&&e.length>0?e:"Unknown")}var S,E=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,N={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},k=function(e){return N[e]},I={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:function(e){return e.replace(E,k)}};function P(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};I=r(r({},I),e)}function R(){return I}function C(e){S=e}function T(){return S}var A=["format"],L=["children","count","parent","i18nKey","context","tOptions","values","defaults","components","ns","i18n","t","shouldUnescape"];function U(e,n){if(!e)return!1;var t=e.props?e.props.children:e.children;return n?t.length>0:!!t}function z(e){return e?e.props?e.props.children:e.children:[]}function B(e){return Array.isArray(e)?e:[e]}function K(e,t,i,o,s,c){if(""===t)return[];var u=o.transKeepBasicHtmlNodesFor||[],l=t&&new RegExp(u.join("|")).test(t);if(!e&&!l)return[t];var f={};!function e(t){B(t).forEach((function(t){"string"!=typeof t&&(U(t)?e(z(t)):"object"!==a(t)||n.isValidElement(t)||Object.assign(f,t))}))}(e);var p=h("<0>".concat(t,"")),d=r(r({},f),s);function g(e,t,r){var a=z(e),i=v(a,t.children,r);return function(e){return"[object Array]"===Object.prototype.toString.call(e)&&e.every((function(e){return n.isValidElement(e)}))}(a)&&0===i.length?a:i}function m(e,t,a,i,o){e.dummy&&(e.children=t),a.push(n.cloneElement(e,r(r({},e.props),{},{key:i}),o?void 0:t))}function v(t,s,f){var p=B(t);return B(s).reduce((function(t,s,h){var y,b,O,x=s.children&&s.children[0]&&s.children[0].content&&i.services.interpolator.interpolate(s.children[0].content,d,i.language);if("tag"===s.type){var w=p[parseInt(s.name,10)];!w&&1===f.length&&f[0][s.name]&&(w=f[0][s.name]),w||(w={});var j=0!==Object.keys(s.attrs).length?(y={props:s.attrs},(O=r({},b=w)).props=Object.assign(y.props,b.props),O):w,S=n.isValidElement(j),E=S&&U(s,!0)&&!s.voidElement,N=l&&"object"===a(j)&&j.dummy&&!S,k="object"===a(e)&&null!==e&&Object.hasOwnProperty.call(e,s.name);if("string"==typeof j){var I=i.services.interpolator.interpolate(j,d,i.language);t.push(I)}else if(U(j)||E){m(j,g(j,s,f),t,h)}else if(N){var P=v(p,s.children,f);t.push(n.cloneElement(j,r(r({},j.props),{},{key:h}),P))}else if(Number.isNaN(parseFloat(s.name))){if(k)m(j,g(j,s,f),t,h,s.voidElement);else if(o.transSupportBasicHtmlNodes&&u.indexOf(s.name)>-1)if(s.voidElement)t.push(n.createElement(s.name,{key:"".concat(s.name,"-").concat(h)}));else{var R=v(p,s.children,f);t.push(n.createElement(s.name,{key:"".concat(s.name,"-").concat(h)},R))}else if(s.voidElement)t.push("<".concat(s.name," />"));else{var C=v(p,s.children,f);t.push("<".concat(s.name,">").concat(C,""))}}else if("object"!==a(j)||S)1===s.children.length&&x?t.push(n.cloneElement(j,r(r({},j.props),{},{key:h}),x)):t.push(n.cloneElement(j,r(r({},j.props),{},{key:h})));else{var T=s.children[0]?x:null;T&&t.push(T)}}else if("text"===s.type){var A=o.transWrapTextNodes,L=c?o.unescape(i.services.interpolator.interpolate(s.content,d,i.language)):i.services.interpolator.interpolate(s.content,d,i.language);A?t.push(n.createElement(A,{key:"".concat(s.name,"-").concat(h)},L)):t.push(L)}return t}),[])}return z(v([{dummy:!0,children:e||[]}],p,B(e||[]))[0])}function V(e){var t=e.children,i=e.count,o=e.parent,c=e.i18nKey,u=e.context,l=e.tOptions,f=void 0===l?{}:l,p=e.values,d=e.defaults,g=e.components,m=e.ns,h=e.i18n,y=e.t,O=e.shouldUnescape,x=s(e,L),w=h||T();if(!w)return b("You will need to pass in an i18next instance by using i18nextReactModule"),t;var j=y||w.t.bind(w)||function(e){return e};u&&(f.context=u);var S=r(r({},R()),w.options&&w.options.react),E=m||j.ns||w.options&&w.options.defaultNS;E="string"==typeof E?[E]:E||["translation"];var N=d||function e(t,r){if(!t)return"";var i="",o=B(t),c=r.transSupportBasicHtmlNodes&&r.transKeepBasicHtmlNodesFor?r.transKeepBasicHtmlNodesFor:[];return o.forEach((function(t,o){if("string"==typeof t)i+="".concat(t);else if(n.isValidElement(t)){var u=Object.keys(t.props).length,l=c.indexOf(t.type)>-1,f=t.props.children;if(!f&&l&&0===u)i+="<".concat(t.type,"/>");else if(f||l&&0===u)if(t.props.i18nIsDynamicList)i+="<".concat(o,">");else if(l&&1===u&&"string"==typeof f)i+="<".concat(t.type,">").concat(f,"");else{var p=e(f,r);i+="<".concat(o,">").concat(p,"")}else i+="<".concat(o,">")}else if(null===t)v("Trans: the passed in value is invalid - seems you passed in a null child.");else if("object"===a(t)){var d=t.format,g=s(t,A),m=Object.keys(g);if(1===m.length){var h=d?"".concat(m[0],", ").concat(d):m[0];i+="{{".concat(h,"}}")}else v("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.",t)}else v("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.",t)})),i}(t,S)||S.transEmptyNodeValue||c,k=S.hashTransKey,I=c||(k?k(N):N),P=p?f.interpolation:{interpolation:r(r({},f.interpolation),{},{prefix:"#$?",suffix:"?$#"})},C=r(r(r(r({},f),{},{count:i},p),P),{},{defaultValue:N,ns:E}),U=K(g||t,I?j(I,C):N,w,S,C,O),z=void 0!==o?o:S.defaultTransParent;return z?n.createElement(z,x,U):U}var D={type:"3rdParty",init:function(e){P(e.options.react),C(e)}},F=n.createContext(),H=function(){function e(){!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),this.usedNamespaces={}}var n,t,r;return n=e,(t=[{key:"addUsedNamespaces",value:function(e){var n=this;e.forEach((function(e){n.usedNamespaces[e]||(n.usedNamespaces[e]=!0)}))}},{key:"getUsedNamespaces",value:function(){return Object.keys(this.usedNamespaces)}}])&&i(n.prototype,t),r&&i(n,r),e}();function W(e){return function(n){return new Promise((function(t){var a=M();e.getInitialProps?e.getInitialProps(n).then((function(e){t(r(r({},e),a))})):t(a)}))}}function M(){var e=T(),n=e.reportNamespaces?e.reportNamespaces.getUsedNamespaces():[],t={},r={};return e.languages.forEach((function(t){r[t]={},n.forEach((function(n){r[t][n]=e.getResourceBundle(t,n)||{}}))})),t.initialI18nStore=r,t.initialLanguage=e.language,t}var $=["children","count","parent","i18nKey","context","tOptions","values","defaults","components","ns","i18n","t","shouldUnescape"];var q=function(e,t){var r=n.useRef();return n.useEffect((function(){r.current=t?r.current:e}),[e,t]),r.current};function Y(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=t.i18n,o=n.useContext(F)||{},s=o.i18n,u=o.defaultNS,l=i||s||T();if(l&&!l.reportNamespaces&&(l.reportNamespaces=new H),!l){b("You will need to pass in an i18next instance by using initReactI18next");var f=function(e,n){return"string"==typeof n?n:n&&"object"===a(n)&&"string"==typeof n.defaultValue?n.defaultValue:Array.isArray(e)?e[e.length-1]:e},p=[f,{},!1];return p.t=f,p.i18n={},p.ready=!1,p}l.options.react&&void 0!==l.options.react.wait&&b("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");var d=r(r(r({},R()),l.options.react),t),g=d.useSuspense,m=d.keyPrefix,h=e||u||l.options&&l.options.defaultNS;h="string"==typeof h?[h]:h||["translation"],l.reportNamespaces.addUsedNamespaces&&l.reportNamespaces.addUsedNamespaces(h);var v=(l.isInitialized||l.initializedStoreOnce)&&h.every((function(e){return w(e,l,d)}));function y(){return l.getFixedT(t.lng||null,"fallback"===d.nsMode?h:h[0],m)}var x=n.useState(y),j=c(x,2),S=j[0],E=j[1],N=h.join(),k=q(N),I=n.useRef(!0);n.useEffect((function(){var e=d.bindI18n,n=d.bindI18nStore;function t(){I.current&&E(y)}return I.current=!0,v||g||O(l,h,(function(){I.current&&E(y)})),v&&k&&k!==N&&I.current&&E(y),e&&l&&l.on(e,t),n&&l&&l.store.on(n,t),function(){I.current=!1,e&&l&&e.split(" ").forEach((function(e){return l.off(e,t)})),n&&l&&n.split(" ").forEach((function(e){return l.store.off(e,t)}))}}),[l,N]);var P=n.useRef(!0);n.useEffect((function(){I.current&&!P.current&&E(y),P.current=!1}),[l,m]);var C=[S,l,v];if(C.t=S,C.i18n=l,C.ready=v,v)return C;if(!v&&!g)return C;throw new Promise((function(e){O(l,h,(function(){e()}))}))}var _=["forwardedRef"];var J=["ns","children"];function Z(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=r.i18n,i=n.useContext(F)||{},o=i.i18n,s=a||o||T();s.options&&s.options.isClone||(e&&!s.initializedStoreOnce&&(s.services.resourceStore.data=e,s.options.ns=Object.values(e).reduce((function(e,n){return Object.keys(n).forEach((function(n){e.indexOf(n)<0&&e.push(n)})),e}),s.options.ns),s.initializedStoreOnce=!0,s.isInitialized=!0),t&&!s.initializedLanguageOnce&&(s.changeLanguage(t),s.initializedLanguageOnce=!0))}var G=["initialI18nStore","initialLanguage"];e.I18nContext=F,e.I18nextProvider=function(e){var t=e.i18n,r=e.defaultNS,a=e.children,i=n.useMemo((function(){return{i18n:t,defaultNS:r}}),[t,r]);return n.createElement(F.Provider,{value:i},a)},e.Trans=function(e){var t=e.children,a=e.count,i=e.parent,o=e.i18nKey,c=e.context,u=e.tOptions,l=void 0===u?{}:u,f=e.values,p=e.defaults,d=e.components,g=e.ns,m=e.i18n,h=e.t,v=e.shouldUnescape,y=s(e,$),b=n.useContext(F)||{},O=b.i18n,x=b.defaultNS,w=m||O||T(),j=h||w&&w.t.bind(w);return V(r({children:t,count:a,parent:i,i18nKey:o,context:c,tOptions:l,values:f,defaults:p,components:d,ns:g||j&&j.ns||x||w&&w.options&&w.options.defaultNS,i18n:w,t:h,shouldUnescape:v},y))},e.TransWithoutContext=V,e.Translation=function(e){var n=e.ns,t=e.children,r=c(Y(n,s(e,J)),3),a=r[0],i=r[1],o=r[2];return t(a,{i18n:i,lng:i.language},o)},e.composeInitialProps=W,e.date=function(){return""},e.getDefaults=R,e.getI18n=T,e.getInitialProps=M,e.initReactI18next=D,e.number=function(){return""},e.plural=function(){return""},e.select=function(){return""},e.selectOrdinal=function(){return""},e.setDefaults=P,e.setI18n=C,e.time=function(){return""},e.useSSR=Z,e.useTranslation=Y,e.withSSR=function(){return function(e){function t(t){var a=t.initialI18nStore,i=t.initialLanguage,o=s(t,G);return Z(a,i),n.createElement(e,r({},o))}return t.getInitialProps=W(e),t.displayName="withI18nextSSR(".concat(j(e),")"),t.WrappedComponent=e,t}},e.withTranslation=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(a){function i(i){var o=i.forwardedRef,u=s(i,_),l=c(Y(e,r(r({},u),{},{keyPrefix:t.keyPrefix})),3),f=l[0],p=l[1],d=l[2],g=r(r({},u),{},{t:f,i18n:p,tReady:d});return t.withRef&&o?g.ref=o:!t.withRef&&o&&(g.forwardedRef=o),n.createElement(a,g)}i.displayName="withI18nextTranslation(".concat(j(a),")"),i.WrappedComponent=a;return t.withRef?n.forwardRef((function(e,t){return n.createElement(i,Object.assign({},e,{forwardedRef:t}))})):i}},Object.defineProperty(e,"__esModule",{value:!0})})); +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],n):n((e=e||self).ReactI18next={},e.React)}(this,(function(e,n){"use strict";function t(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function r(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}function s(e,n){return function(e){if(Array.isArray(e))return e}(e)||function(e,n){var t=e&&("undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"]);if(null==t)return;var r,a,i=[],o=!0,c=!1;try{for(t=t.call(e);!(o=(r=t.next()).done)&&(i.push(r.value),!n||i.length!==n);o=!0);}catch(e){c=!0,a=e}finally{try{o||null==t.return||t.return()}finally{if(c)throw a}}return i}(e,n)||function(e,n){if(!e)return;if("string"==typeof e)return u(e,n);var t=Object.prototype.toString.call(e).slice(8,-1);"Object"===t&&e.constructor&&(t=e.constructor.name);if("Map"===t||"Set"===t)return Array.from(e);if("Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t))return u(e,n)}(e,n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=new Array(n);t<]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function p(e){var n={type:"tag",name:"",voidElement:!1,attrs:{},children:[]},t=e.match(/<\/?([^\s]+?)[/\s>]/);if(t&&(n.name=t[1],(l[t[1]]||"/"===e.charAt(e.length-2))&&(n.voidElement=!0),n.name.startsWith("!--"))){var r=e.indexOf("--\x3e");return{type:"comment",comment:-1!==r?e.slice(4,r):""}}for(var a=new RegExp(f),i=null;null!==(i=a.exec(e));)if(i[0].trim())if(i[1]){var o=i[1].trim(),c=[o,""];o.indexOf("=")>-1&&(c=o.split("=")),n.attrs[c[0]]=c[1],a.lastIndex--}else i[2]&&(n.attrs[i[2]]=i[3].trim().substring(1,i[3].length-1));return n}var d=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,g=/^\s*$/,m=Object.create(null);var h=function(e,n){n||(n={}),n.components||(n.components=m);var t,r=[],a=[],i=-1,o=!1;if(0!==e.indexOf("<")){var c=e.indexOf("<");r.push({type:"text",content:-1===c?e:e.substring(0,c)})}return e.replace(d,(function(c,s){if(o){if(c!=="")return;o=!1}var u,l="/"!==c.charAt(1),f=c.startsWith("\x3c!--"),d=s+c.length,m=e.charAt(d);if(f){var h=p(c);return i<0?(r.push(h),r):((u=a[i]).children.push(h),r)}if(l&&(i++,"tag"===(t=p(c)).type&&n.components[t.name]&&(t.type="component",o=!0),t.voidElement||o||!m||"<"===m||t.children.push({type:"text",content:e.slice(d,e.indexOf("<",d))}),0===i&&r.push(t),(u=a[i-1])&&u.children.push(t),a[i]=t),(!l||t.voidElement)&&(i>-1&&(t.voidElement||t.name===c.slice(2,-1))&&(i--,t=-1===i?r:a[i]),!o&&"<"!==m&&m)){u=-1===i?r:a[i].children;var v=e.indexOf("<",d),y=e.slice(d,-1===v?void 0:v);g.test(y)&&(y=" "),(v>-1&&i+u.length>=0||" "!==y)&&u.push({type:"text",content:y})}})),r};function v(){if(console&&console.warn){for(var e,n=arguments.length,t=new Array(n),r=0;r2&&void 0!==arguments[2]?arguments[2]:{},r=n.languages[0],a=!!n.options&&n.options.fallbackLng,i=n.languages[n.languages.length-1];if("cimode"===r.toLowerCase())return!0;var o=function(e,t){var r=n.services.backendConnector.state["".concat(e,"|").concat(t)];return-1===r||2===r};return!(t.bindI18n&&t.bindI18n.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!o(n.isLanguageChangingTo,e))&&(!!n.hasResourceBundle(r,e)||(!(n.services.backendConnector.backend&&(!n.options.resources||n.options.partialBundledLanguages))||!(!o(r,e)||a&&!o(i,e))))}function E(e,n){var t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!n.languages||!n.languages.length)return b("i18n.languages were undefined or empty",n.languages),!0;var r=void 0!==n.options.ignoreJSONStructure;return r?n.hasLoadedNamespace(e,{lng:t.lng,precheck:function(n,r){if(t.bindI18n&&t.bindI18n.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!r(n.isLanguageChangingTo,e))return!1}}):j(e,n,t)}function S(e){return e.displayName||e.name||("string"==typeof e&&e.length>0?e:"Unknown")}var N,k=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,I={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},P=function(e){return I[e]},R={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:function(e){return e.replace(k,P)}};function C(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};R=r(r({},R),e)}function T(){return R}function A(e){N=e}function L(){return N}var U=["format"],z=["children","count","parent","i18nKey","context","tOptions","values","defaults","components","ns","i18n","t","shouldUnescape"];function B(e,n){if(!e)return!1;var t=e.props?e.props.children:e.children;return n?t.length>0:!!t}function K(e){return e?e.props?e.props.children:e.children:[]}function V(e){return Array.isArray(e)?e:[e]}function D(e,t,i,o,c,s){if(""===t)return[];var u=o.transKeepBasicHtmlNodesFor||[],l=t&&new RegExp(u.join("|")).test(t);if(!e&&!l)return[t];var f={};!function e(t){V(t).forEach((function(t){"string"!=typeof t&&(B(t)?e(K(t)):"object"!==a(t)||n.isValidElement(t)||Object.assign(f,t))}))}(e);var p=h("<0>".concat(t,"")),d=r(r({},f),c);function g(e,t,r){var a=K(e),i=v(a,t.children,r);return function(e){return"[object Array]"===Object.prototype.toString.call(e)&&e.every((function(e){return n.isValidElement(e)}))}(a)&&0===i.length?a:i}function m(e,t,a,i,o){e.dummy&&(e.children=t),a.push(n.cloneElement(e,r(r({},e.props),{},{key:i}),o?void 0:t))}function v(t,c,f){var p=V(t);return V(c).reduce((function(t,c,h){var y,b,O,x=c.children&&c.children[0]&&c.children[0].content&&i.services.interpolator.interpolate(c.children[0].content,d,i.language);if("tag"===c.type){var w=p[parseInt(c.name,10)];!w&&1===f.length&&f[0][c.name]&&(w=f[0][c.name]),w||(w={});var j=0!==Object.keys(c.attrs).length?(y={props:c.attrs},(O=r({},b=w)).props=Object.assign(y.props,b.props),O):w,E=n.isValidElement(j),S=E&&B(c,!0)&&!c.voidElement,N=l&&"object"===a(j)&&j.dummy&&!E,k="object"===a(e)&&null!==e&&Object.hasOwnProperty.call(e,c.name);if("string"==typeof j){var I=i.services.interpolator.interpolate(j,d,i.language);t.push(I)}else if(B(j)||S){m(j,g(j,c,f),t,h)}else if(N){var P=v(p,c.children,f);t.push(n.cloneElement(j,r(r({},j.props),{},{key:h}),P))}else if(Number.isNaN(parseFloat(c.name))){if(k)m(j,g(j,c,f),t,h,c.voidElement);else if(o.transSupportBasicHtmlNodes&&u.indexOf(c.name)>-1)if(c.voidElement)t.push(n.createElement(c.name,{key:"".concat(c.name,"-").concat(h)}));else{var R=v(p,c.children,f);t.push(n.createElement(c.name,{key:"".concat(c.name,"-").concat(h)},R))}else if(c.voidElement)t.push("<".concat(c.name," />"));else{var C=v(p,c.children,f);t.push("<".concat(c.name,">").concat(C,""))}}else if("object"!==a(j)||E)1===c.children.length&&x?t.push(n.cloneElement(j,r(r({},j.props),{},{key:h}),x)):t.push(n.cloneElement(j,r(r({},j.props),{},{key:h})));else{var T=c.children[0]?x:null;T&&t.push(T)}}else if("text"===c.type){var A=o.transWrapTextNodes,L=s?o.unescape(i.services.interpolator.interpolate(c.content,d,i.language)):i.services.interpolator.interpolate(c.content,d,i.language);A?t.push(n.createElement(A,{key:"".concat(c.name,"-").concat(h)},L)):t.push(L)}return t}),[])}return K(v([{dummy:!0,children:e||[]}],p,V(e||[]))[0])}function F(e){var t=e.children,i=e.count,o=e.parent,s=e.i18nKey,u=e.context,l=e.tOptions,f=void 0===l?{}:l,p=e.values,d=e.defaults,g=e.components,m=e.ns,h=e.i18n,y=e.t,O=e.shouldUnescape,x=c(e,z),w=h||L();if(!w)return b("You will need to pass in an i18next instance by using i18nextReactModule"),t;var j=y||w.t.bind(w)||function(e){return e};u&&(f.context=u);var E=r(r({},T()),w.options&&w.options.react),S=m||j.ns||w.options&&w.options.defaultNS;S="string"==typeof S?[S]:S||["translation"];var N=d||function e(t,r){if(!t)return"";var i="",o=V(t),s=r.transSupportBasicHtmlNodes&&r.transKeepBasicHtmlNodesFor?r.transKeepBasicHtmlNodesFor:[];return o.forEach((function(t,o){if("string"==typeof t)i+="".concat(t);else if(n.isValidElement(t)){var u=Object.keys(t.props).length,l=s.indexOf(t.type)>-1,f=t.props.children;if(!f&&l&&0===u)i+="<".concat(t.type,"/>");else if(f||l&&0===u)if(t.props.i18nIsDynamicList)i+="<".concat(o,">");else if(l&&1===u&&"string"==typeof f)i+="<".concat(t.type,">").concat(f,"");else{var p=e(f,r);i+="<".concat(o,">").concat(p,"")}else i+="<".concat(o,">")}else if(null===t)v("Trans: the passed in value is invalid - seems you passed in a null child.");else if("object"===a(t)){var d=t.format,g=c(t,U),m=Object.keys(g);if(1===m.length){var h=d?"".concat(m[0],", ").concat(d):m[0];i+="{{".concat(h,"}}")}else v("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.",t)}else v("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.",t)})),i}(t,E)||E.transEmptyNodeValue||s,k=E.hashTransKey,I=s||(k?k(N):N),P=p?f.interpolation:{interpolation:r(r({},f.interpolation),{},{prefix:"#$?",suffix:"?$#"})},R=r(r(r(r({},f),{},{count:i},p),P),{},{defaultValue:N,ns:S}),C=D(g||t,I?j(I,R):N,w,E,R,O),A=void 0!==o?o:E.defaultTransParent;return A?n.createElement(A,x,C):C}var H={type:"3rdParty",init:function(e){C(e.options.react),A(e)}},W=n.createContext(),M=function(){function e(){!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),this.usedNamespaces={}}var n,t,r;return n=e,(t=[{key:"addUsedNamespaces",value:function(e){var n=this;e.forEach((function(e){n.usedNamespaces[e]||(n.usedNamespaces[e]=!0)}))}},{key:"getUsedNamespaces",value:function(){return Object.keys(this.usedNamespaces)}}])&&i(n.prototype,t),r&&i(n,r),e}();function $(e){return function(n){return new Promise((function(t){var a=q();e.getInitialProps?e.getInitialProps(n).then((function(e){t(r(r({},e),a))})):t(a)}))}}function q(){var e=L(),n=e.reportNamespaces?e.reportNamespaces.getUsedNamespaces():[],t={},r={};return e.languages.forEach((function(t){r[t]={},n.forEach((function(n){r[t][n]=e.getResourceBundle(t,n)||{}}))})),t.initialI18nStore=r,t.initialLanguage=e.language,t}var Y=["children","count","parent","i18nKey","context","tOptions","values","defaults","components","ns","i18n","t","shouldUnescape"];var _=function(e,t){var r=n.useRef();return n.useEffect((function(){r.current=t?r.current:e}),[e,t]),r.current};function J(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=t.i18n,o=n.useContext(W)||{},c=o.i18n,u=o.defaultNS,l=i||c||L();if(l&&!l.reportNamespaces&&(l.reportNamespaces=new M),!l){b("You will need to pass in an i18next instance by using initReactI18next");var f=function(e,n){return"string"==typeof n?n:n&&"object"===a(n)&&"string"==typeof n.defaultValue?n.defaultValue:Array.isArray(e)?e[e.length-1]:e},p=[f,{},!1];return p.t=f,p.i18n={},p.ready=!1,p}l.options.react&&void 0!==l.options.react.wait&&b("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");var d=r(r(r({},T()),l.options.react),t),g=d.useSuspense,m=d.keyPrefix,h=e||u||l.options&&l.options.defaultNS;h="string"==typeof h?[h]:h||["translation"],l.reportNamespaces.addUsedNamespaces&&l.reportNamespaces.addUsedNamespaces(h);var v=(l.isInitialized||l.initializedStoreOnce)&&h.every((function(e){return E(e,l,d)}));function y(){return l.getFixedT(t.lng||null,"fallback"===d.nsMode?h:h[0],m)}var O=n.useState(y),j=s(O,2),S=j[0],N=j[1],k=h.join();t.lng&&(k="".concat(t.lng).concat(k));var I=_(k),P=n.useRef(!0);n.useEffect((function(){var e=d.bindI18n,n=d.bindI18nStore;function r(){P.current&&N(y)}return P.current=!0,v||g||(t.lng?w(l,t.lng,h,(function(){P.current&&N(y)})):x(l,h,(function(){P.current&&N(y)}))),v&&I&&I!==k&&P.current&&N(y),e&&l&&l.on(e,r),n&&l&&l.store.on(n,r),function(){P.current=!1,e&&l&&e.split(" ").forEach((function(e){return l.off(e,r)})),n&&l&&n.split(" ").forEach((function(e){return l.store.off(e,r)}))}}),[l,k]);var R=n.useRef(!0);n.useEffect((function(){P.current&&!R.current&&N(y),R.current=!1}),[l,m]);var C=[S,l,v];if(C.t=S,C.i18n=l,C.ready=v,v)return C;if(!v&&!g)return C;throw new Promise((function(e){t.lng?w(l,t.lng,h,(function(){return e()})):x(l,h,(function(){return e()}))}))}var Z=["forwardedRef"];var G=["ns","children"];function Q(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=r.i18n,i=n.useContext(W)||{},o=i.i18n,c=a||o||L();c.options&&c.options.isClone||(e&&!c.initializedStoreOnce&&(c.services.resourceStore.data=e,c.options.ns=Object.values(e).reduce((function(e,n){return Object.keys(n).forEach((function(n){e.indexOf(n)<0&&e.push(n)})),e}),c.options.ns),c.initializedStoreOnce=!0,c.isInitialized=!0),t&&!c.initializedLanguageOnce&&(c.changeLanguage(t),c.initializedLanguageOnce=!0))}var X=["initialI18nStore","initialLanguage"];e.I18nContext=W,e.I18nextProvider=function(e){var t=e.i18n,r=e.defaultNS,a=e.children,i=n.useMemo((function(){return{i18n:t,defaultNS:r}}),[t,r]);return n.createElement(W.Provider,{value:i},a)},e.Trans=function(e){var t=e.children,a=e.count,i=e.parent,o=e.i18nKey,s=e.context,u=e.tOptions,l=void 0===u?{}:u,f=e.values,p=e.defaults,d=e.components,g=e.ns,m=e.i18n,h=e.t,v=e.shouldUnescape,y=c(e,Y),b=n.useContext(W)||{},O=b.i18n,x=b.defaultNS,w=m||O||L(),j=h||w&&w.t.bind(w);return F(r({children:t,count:a,parent:i,i18nKey:o,context:s,tOptions:l,values:f,defaults:p,components:d,ns:g||j&&j.ns||x||w&&w.options&&w.options.defaultNS,i18n:w,t:h,shouldUnescape:v},y))},e.TransWithoutContext=F,e.Translation=function(e){var n=e.ns,t=e.children,r=s(J(n,c(e,G)),3),a=r[0],i=r[1],o=r[2];return t(a,{i18n:i,lng:i.language},o)},e.composeInitialProps=$,e.date=function(){return""},e.getDefaults=T,e.getI18n=L,e.getInitialProps=q,e.initReactI18next=H,e.number=function(){return""},e.plural=function(){return""},e.select=function(){return""},e.selectOrdinal=function(){return""},e.setDefaults=C,e.setI18n=A,e.time=function(){return""},e.useSSR=Q,e.useTranslation=J,e.withSSR=function(){return function(e){function t(t){var a=t.initialI18nStore,i=t.initialLanguage,o=c(t,X);return Q(a,i),n.createElement(e,r({},o))}return t.getInitialProps=$(e),t.displayName="withI18nextSSR(".concat(S(e),")"),t.WrappedComponent=e,t}},e.withTranslation=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(a){function i(i){var o=i.forwardedRef,u=c(i,Z),l=s(J(e,r(r({},u),{},{keyPrefix:t.keyPrefix})),3),f=l[0],p=l[1],d=l[2],g=r(r({},u),{},{t:f,i18n:p,tReady:d});return t.withRef&&o?g.ref=o:!t.withRef&&o&&(g.forwardedRef=o),n.createElement(a,g)}i.displayName="withI18nextTranslation(".concat(S(a),")"),i.WrappedComponent=a;return t.withRef?n.forwardRef((function(e,t){return n.createElement(i,Object.assign({},e,{forwardedRef:t}))})):i}},Object.defineProperty(e,"__esModule",{value:!0})})); diff --git a/src/useTranslation.js b/src/useTranslation.js index 8f4b6ec7a..63b570b1c 100644 --- a/src/useTranslation.js +++ b/src/useTranslation.js @@ -1,6 +1,6 @@ import { useState, useEffect, useContext, useRef } from 'react'; import { getI18n, getDefaults, ReportNamespaces, I18nContext } from './context.js'; -import { warnOnce, loadNamespaces, hasLoadedNamespace } from './utils.js'; +import { warnOnce, loadNamespaces, loadLanguages, hasLoadedNamespace } from './utils.js'; const usePrevious = (value, ignore) => { const ref = useRef(); @@ -65,7 +65,8 @@ export function useTranslation(ns, props = {}) { } const [t, setT] = useState(getT); - const joinedNS = namespaces.join(); + let joinedNS = namespaces.join(); + if (props.lng) joinedNS = `${props.lng}${joinedNS}`; const previousJoinedNS = usePrevious(joinedNS); const isMounted = useRef(true); @@ -76,9 +77,15 @@ export function useTranslation(ns, props = {}) { // if not ready and not using suspense load the namespaces // in side effect and do not call resetT if unmounted if (!ready && !useSuspense) { - loadNamespaces(i18n, namespaces, () => { - if (isMounted.current) setT(getT); - }); + if (props.lng) { + loadLanguages(i18n, props.lng, namespaces, () => { + if (isMounted.current) setT(getT); + }); + } else { + loadNamespaces(i18n, namespaces, () => { + if (isMounted.current) setT(getT); + }); + } } if (ready && previousJoinedNS && previousJoinedNS !== joinedNS && isMounted.current) { @@ -125,8 +132,10 @@ export function useTranslation(ns, props = {}) { // not yet loaded namespaces -> load them -> and trigger suspense throw new Promise((resolve) => { - loadNamespaces(i18n, namespaces, () => { - resolve(); - }); + if (props.lng) { + loadLanguages(i18n, props.lng, namespaces, () => resolve()); + } else { + loadNamespaces(i18n, namespaces, () => resolve()); + } }); } diff --git a/src/utils.js b/src/utils.js index a3a1eac80..f55dffe74 100644 --- a/src/utils.js +++ b/src/utils.js @@ -21,23 +21,34 @@ export function warnOnce(...args) { // } // } -export function loadNamespaces(i18n, ns, cb) { - i18n.loadNamespaces(ns, () => { - // delay ready if not yet initialized i18n instance - if (i18n.isInitialized) { +const loadedClb = (i18n, cb) => () => { + // delay ready if not yet initialized i18n instance + if (i18n.isInitialized) { + cb(); + } else { + const initialized = () => { + // due to emitter removing issue in i18next we need to delay remove + setTimeout(() => { + i18n.off('initialized', initialized); + }, 0); cb(); - } else { - const initialized = () => { - // due to emitter removing issue in i18next we need to delay remove - setTimeout(() => { - i18n.off('initialized', initialized); - }, 0); - cb(); - }; - - i18n.on('initialized', initialized); - } + }; + i18n.on('initialized', initialized); + } +}; + +export function loadNamespaces(i18n, ns, cb) { + i18n.loadNamespaces(ns, loadedClb(i18n, cb)); +} + +// should work with I18NEXT >= v22.5.0 +export function loadLanguages(i18n, lng, ns, cb) { + // eslint-disable-next-line no-param-reassign + if (typeof ns === 'string') ns = [ns]; + ns.forEach((n) => { + if (i18n.options.ns.indexOf(n) < 0) i18n.options.ns.push(n); }); + i18n.loadLanguages(lng, loadedClb(i18n, cb)); } // WAIT A LITTLE FOR I18NEXT BEING UPDATED IN THE WILD, before removing this old i18next version support @@ -97,6 +108,7 @@ export function hasLoadedNamespace(ns, i18n, options = {}) { // IN I18NEXT > v19.4.5 WE CAN (INTRODUCED JUNE 2020) return i18n.hasLoadedNamespace(ns, { + lng: options.lng, precheck: (i18nInstance, loadNotPending) => { if ( options.bindI18n && diff --git a/test/lngAwareBackendMock.js b/test/lngAwareBackendMock.js new file mode 100644 index 000000000..bfaf4de35 --- /dev/null +++ b/test/lngAwareBackendMock.js @@ -0,0 +1,40 @@ +class Backend { + constructor(services, options = {}) { + this.init(services, options); + this.type = 'backend'; + this.queue = []; + } + + init(services, options) { + this.services = services; + this.options = options; + } + + read(language, namespace, callback) { + this.queue.push({ language, namespace, callback }); + } + + flush(what) { + let q = [...this.queue]; + if (what) { + const filterFor = []; + if (what.language) filterFor.push('language'); + if (what.namespace) filterFor.push('namespace'); + if (filterFor.length > 0) { + q = q.filter((item) => { + const allOk = filterFor.map((ff) => item[ff] === what[ff]).every((r) => r); + if (allOk) return true; + return false; + }); + } + } + q.forEach((item) => { + this.queue.splice(this.queue.indexOf(item), 1); + item.callback(null, { + key1: `${item.language}/${item.namespace} for key1`, + }); + }); + } +} + +export default Backend; diff --git a/test/useTranslation.loadingLng.spec.js b/test/useTranslation.loadingLng.spec.js new file mode 100644 index 000000000..028c23c00 --- /dev/null +++ b/test/useTranslation.loadingLng.spec.js @@ -0,0 +1,85 @@ +import { renderHook } from '@testing-library/react-hooks'; +import i18n from './i18n'; +import BackendMock from './lngAwareBackendMock'; +import { useTranslation } from '../src/useTranslation'; + +jest.unmock('../src/useTranslation'); + +describe('useTranslation loading ns with lng via props', () => { + let newI18n; + let backend; + + beforeEach(() => { + newI18n = i18n.createInstance(); + backend = new BackendMock(); + newI18n.use(backend).init({ + lng: 'en', + fallbackLng: 'en', + }); + }); + + it('should wait for correct translation with suspense', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTranslation('common', { i18n: newI18n, useSuspense: true, lng: 'de' }), + ); + expect(result.all).toHaveLength(0); + backend.flush(); + await waitForNextUpdate(); + const { t } = result.current; + expect(t('key1')).toBe('de/common for key1'); + }); + + it('should wait for correct translation without suspense', async () => { + const { result } = renderHook(() => + useTranslation('common', { i18n: newI18n, useSuspense: false, lng: 'it' }), + ); + const { t } = result.current; + expect(t('key1')).toBe('key1'); + + backend.flush(); + expect(t('key1')).toBe('it/common for key1'); + }); + + it('should return defaultValue if resources not yet loaded', async () => { + const { result } = renderHook(() => + useTranslation('common', { i18n: newI18n, useSuspense: false, lng: 'fr' }), + ); + const { t } = result.current; + expect(t('key1', 'my default value')).toBe('my default value'); + expect(t('key1', { defaultValue: 'my default value' })).toBe('my default value'); + + backend.flush({ language: 'en' }); + expect(t('key1', 'my default value')).toBe('en/common for key1'); + expect(t('key1', { defaultValue: 'my default value' })).toBe('en/common for key1'); + + backend.flush({ language: 'fr' }); + expect(t('key1', 'my default value')).toBe('fr/common for key1'); + expect(t('key1', { defaultValue: 'my default value' })).toBe('fr/common for key1'); + }); + + it('should correctly return and render correct tranlations in multiple useTranslation usages', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useTranslation('newns', { i18n: newI18n, useSuspense: true, lng: 'pt' }), + ); + backend.flush(); + await waitForNextUpdate(); + const { t } = result.current; + expect(t('key1')).toBe('pt/newns for key1'); + + const retDe = renderHook(() => + useTranslation('newns', { i18n: newI18n, useSuspense: true, lng: 'de' }), + ); + backend.flush({ language: 'de' }); + await retDe.waitForNextUpdate(); + const { t: tDE } = retDe.result.current; + expect(tDE('key1')).toBe('de/newns for key1'); + + const retPT = renderHook(() => + useTranslation('newns', { i18n: newI18n, useSuspense: true, lng: 'pt' }), + ); + backend.flush({ language: 'pt' }); + // await retPT.waitForNextUpdate(); // already loaded + const { t: tPT } = retPT.result.current; + expect(tPT('key1')).toBe('pt/newns for key1'); + }); +});