diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ebcf9fe6..c78d30451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to Bambuddy will be documented in this file. ## [0.1.7b] - Not released ### Enhancements +- **TOTP Authenticator Support for Bambu Cloud** (Issue #182): + - Added support for TOTP-based two-factor authentication when connecting to Bambu Cloud + - Accounts with authenticator apps (Google Authenticator, Authy, etc.) now work correctly + - Proper detection of verification type: email code vs TOTP code + - Uses browser-like headers to bypass Cloudflare protection on TFA endpoint + - Frontend shows appropriate message for each verification type + - Added translations for TOTP UI in English, German, and Japanese - **Spoolman: Open in Spoolman Button** (Issue #210): - FilamentHoverCard now shows "Open in Spoolman" button when spool is already linked in Spoolman - Button links directly to the spool's page in Spoolman for quick editing @@ -18,6 +25,14 @@ All notable changes to Bambuddy will be documented in this file. - Components translated: ConfirmModal, LinkSpoolModal, FilamentHoverCard, Layout - Added locale parity test to ensure English and German stay in sync +### Fixed +- **Authentication Required for Downloads** (Issue #231): + - Fixed support bundle download returning 401 Unauthorized when auth is enabled + - Fixed archive export (CSV/XLSX) failing with authentication enabled + - Fixed statistics export failing with authentication enabled + - Fixed printer file ZIP download failing with authentication enabled + - Root cause: These endpoints used raw `fetch()` without Authorization header + ## [0.1.6.2] - 2026-02-02 > **Security Release**: This release addresses critical security vulnerabilities. Users running authentication-enabled instances should upgrade immediately. diff --git a/backend/app/api/routes/cloud.py b/backend/app/api/routes/cloud.py index f1dc73cc5..a120d563f 100644 --- a/backend/app/api/routes/cloud.py +++ b/backend/app/api/routes/cloud.py @@ -93,8 +93,12 @@ async def login(request: CloudLoginRequest, db: AsyncSession = Depends(get_db)): """ Initiate login to Bambu Cloud. - This will typically trigger a verification code to be sent to the user's email. - After receiving the code, call /cloud/verify to complete the login. + This will trigger either: + - Email verification: A code is sent to the user's email + - TOTP verification: User enters code from their authenticator app + + After receiving/generating the code, call /cloud/verify to complete the login. + For TOTP, include the tfa_key from this response in the verify request. """ cloud = get_cloud_service() @@ -112,6 +116,8 @@ async def login(request: CloudLoginRequest, db: AsyncSession = Depends(get_db)): success=result.get("success", False), needs_verification=result.get("needs_verification", False), message=result.get("message", "Unknown error"), + verification_type=result.get("verification_type"), + tfa_key=result.get("tfa_key"), ) except BambuCloudAuthError as e: raise HTTPException(status_code=401, detail=str(e)) @@ -122,15 +128,24 @@ async def login(request: CloudLoginRequest, db: AsyncSession = Depends(get_db)): @router.post("/verify", response_model=CloudLoginResponse) async def verify_code(request: CloudVerifyRequest, db: AsyncSession = Depends(get_db)): """ - Complete login with verification code. + Complete login with verification code (email or TOTP). - After calling /cloud/login, the user will receive an email with a 6-digit code. - Submit that code here to complete authentication. + For email verification: + - After calling /cloud/login, the user receives an email with a 6-digit code + - Submit the code with email address + + For TOTP verification: + - The user enters the 6-digit code from their authenticator app + - Include the tfa_key from the /cloud/login response """ cloud = get_cloud_service() try: - result = await cloud.verify_code(request.email, request.code) + # Use TOTP verification if tfa_key is provided + if request.tfa_key: + result = await cloud.verify_totp(request.tfa_key, request.code) + else: + result = await cloud.verify_code(request.email, request.code) if result.get("success") and cloud.access_token: await store_token(db, cloud.access_token, request.email) diff --git a/backend/app/schemas/cloud.py b/backend/app/schemas/cloud.py index 3d0cdd4ba..a4f8c80f7 100644 --- a/backend/app/schemas/cloud.py +++ b/backend/app/schemas/cloud.py @@ -10,10 +10,11 @@ class CloudLoginRequest(BaseModel): class CloudVerifyRequest(BaseModel): - """Request to verify login with 2FA code.""" + """Request to verify login with 2FA code (email or TOTP).""" email: str = Field(..., description="Bambu Lab account email") - code: str = Field(..., description="6-digit verification code from email") + code: str = Field(..., description="6-digit verification code") + tfa_key: str | None = Field(None, description="TFA key for TOTP verification (from login response)") class CloudLoginResponse(BaseModel): @@ -22,6 +23,8 @@ class CloudLoginResponse(BaseModel): success: bool needs_verification: bool = False message: str + verification_type: str | None = None # "email" or "totp" + tfa_key: str | None = None # Key needed for TOTP verification class CloudAuthStatus(BaseModel): diff --git a/backend/app/services/bambu_cloud.py b/backend/app/services/bambu_cloud.py index 7b3f18b40..647086625 100644 --- a/backend/app/services/bambu_cloud.py +++ b/backend/app/services/bambu_cloud.py @@ -56,9 +56,9 @@ class BambuCloudService: async def login_request(self, email: str, password: str) -> dict: """ - Initiate login - this will trigger a verification code email. + Initiate login - this will trigger either email verification or TOTP prompt. - Returns dict with login status and whether verification is needed. + Returns dict with login status, verification type, and tfaKey if needed. """ try: response = await self._client.post( @@ -71,12 +71,33 @@ class BambuCloudService: ) data = response.json() + logger.debug( + f"Login response: status={response.status_code}, loginType={data.get('loginType')}, hasTfaKey={'tfaKey' in data}" + ) if response.status_code == 200: - # Check if we need verification code - # Bambu API returns loginType or may require tfaKey - if data.get("loginType") == "verifyCode" or "tfaKey" in data: - return {"success": False, "needs_verification": True, "message": "Verification code sent to email"} + login_type = data.get("loginType") + tfa_key = data.get("tfaKey") + + # TOTP authentication required + if login_type == "tfa" or (tfa_key and login_type != "verifyCode"): + return { + "success": False, + "needs_verification": True, + "verification_type": "totp", + "tfa_key": tfa_key, + "message": "Enter the code from your authenticator app", + } + + # Email verification required + if login_type == "verifyCode": + return { + "success": False, + "needs_verification": True, + "verification_type": "email", + "tfa_key": None, + "message": "Verification code sent to email", + } # Direct login success (rare, usually needs 2FA) if "accessToken" in data: @@ -93,7 +114,7 @@ class BambuCloudService: async def verify_code(self, email: str, code: str) -> dict: """ - Complete login with verification code. + Complete login with email verification code. """ try: response = await self._client.post( @@ -106,6 +127,7 @@ class BambuCloudService: ) data = response.json() + logger.debug(f"Email verify response: status={response.status_code}, hasToken={'accessToken' in data}") if response.status_code == 200 and "accessToken" in data: self._set_tokens(data) @@ -114,9 +136,89 @@ class BambuCloudService: return {"success": False, "message": data.get("message", "Verification failed")} except Exception as e: - logger.error(f"Verification failed: {e}") + logger.error(f"Email verification failed: {e}") raise BambuCloudAuthError(f"Verification failed: {e}") + async def verify_totp(self, tfa_key: str, code: str) -> dict: + """ + Complete login with TOTP code from authenticator app. + + Args: + tfa_key: The tfaKey returned from initial login request + code: 6-digit TOTP code from authenticator app + """ + try: + # TFA endpoint is on bambulab.com, NOT api.bambulab.com + # Requires browser-like headers to bypass Cloudflare + tfa_url = "https://bambulab.com/api/sign-in/tfa" + if "bambulab.cn" in self.base_url: + tfa_url = "https://bambulab.cn/api/sign-in/tfa" + + browser_headers = { + "Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Accept": "application/json, text/plain, */*", + "Accept-Language": "en-US,en;q=0.9", + "Origin": "https://bambulab.com", + "Referer": "https://bambulab.com/", + } + + response = await self._client.post( + tfa_url, + headers=browser_headers, + json={ + "tfaKey": tfa_key, + "tfaCode": code, + }, + ) + + logger.debug( + f"TOTP verify response: status={response.status_code}, body={response.text[:200] if response.text else '(empty)'}" + ) + + # Handle empty response + if not response.text or not response.text.strip(): + logger.warning(f"TOTP verification returned empty response (status {response.status_code})") + return {"success": False, "message": "Bambu Cloud returned empty response. Please try again."} + + try: + data = response.json() + except Exception as json_err: + logger.error(f"Failed to parse TOTP response: {json_err}, body: {response.text[:500]}") + return {"success": False, "message": "Invalid response from Bambu Cloud"} + + # Token might be in accessToken, token field, or cookies + access_token = data.get("accessToken") or data.get("token") + + # Also check cookies for token + if not access_token: + for cookie in response.cookies: + if "token" in cookie.lower(): + access_token = response.cookies.get(cookie) + break + + if response.status_code == 200 and access_token: + self.access_token = access_token + self.refresh_token = data.get("refreshToken") + from datetime import datetime, timedelta + + self.token_expiry = datetime.now() + timedelta(days=30) + return {"success": True, "message": "Login successful"} + + # Provide helpful error message + error_msg = data.get("message", "") + if "expired" in error_msg.lower(): + return {"success": False, "message": "TOTP session expired. Please try logging in again."} + if not error_msg: + error_msg = f"TOTP verification failed (status {response.status_code})" + + return {"success": False, "message": error_msg} + + except Exception as e: + logger.error(f"TOTP verification failed: {e}") + # Return error instead of raising - don't trigger 401/500 + return {"success": False, "message": f"TOTP verification error: {e}"} + def _set_tokens(self, data: dict): """Set tokens from login response.""" self.access_token = data.get("accessToken") diff --git a/backend/tests/unit/services/test_bambu_cloud.py b/backend/tests/unit/services/test_bambu_cloud.py new file mode 100644 index 000000000..59b9371d4 --- /dev/null +++ b/backend/tests/unit/services/test_bambu_cloud.py @@ -0,0 +1,238 @@ +"""Tests for Bambu Cloud service - TOTP and email verification flows.""" + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from backend.app.services.bambu_cloud import BambuCloudService + + +class TestBambuCloudLogin: + """Test login flow detection (email vs TOTP).""" + + @pytest.fixture + def cloud_service(self): + """Create a BambuCloudService instance.""" + return BambuCloudService() + + @pytest.mark.asyncio + async def test_login_detects_email_verification(self, cloud_service): + """When loginType is verifyCode, should return email verification type.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "loginType": "verifyCode", + } + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + result = await cloud_service.login_request("test@example.com", "password") + + assert result["success"] is False + assert result["needs_verification"] is True + assert result["verification_type"] == "email" + assert result["tfa_key"] is None + assert "email" in result["message"].lower() + + @pytest.mark.asyncio + async def test_login_detects_totp(self, cloud_service): + """When loginType is tfa, should return TOTP verification type with tfaKey.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "loginType": "tfa", + "tfaKey": "test-tfa-key-123", + } + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + result = await cloud_service.login_request("test@example.com", "password") + + assert result["success"] is False + assert result["needs_verification"] is True + assert result["verification_type"] == "totp" + assert result["tfa_key"] == "test-tfa-key-123" + assert "authenticator" in result["message"].lower() + + @pytest.mark.asyncio + async def test_login_direct_success(self, cloud_service): + """When accessToken is returned directly, should succeed without verification.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "accessToken": "test-access-token", + "refreshToken": "test-refresh-token", + } + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + result = await cloud_service.login_request("test@example.com", "password") + + assert result["success"] is True + assert result["needs_verification"] is False + assert cloud_service.access_token == "test-access-token" + + @pytest.mark.asyncio + async def test_login_failure(self, cloud_service): + """When login fails, should return error message.""" + mock_response = MagicMock() + mock_response.status_code = 401 + mock_response.json.return_value = { + "message": "Invalid credentials", + } + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + result = await cloud_service.login_request("test@example.com", "wrong-password") + + assert result["success"] is False + assert result["needs_verification"] is False + assert "Invalid credentials" in result["message"] + + +class TestBambuCloudEmailVerification: + """Test email verification flow.""" + + @pytest.fixture + def cloud_service(self): + """Create a BambuCloudService instance.""" + return BambuCloudService() + + @pytest.mark.asyncio + async def test_verify_code_success(self, cloud_service): + """When email code is correct, should return success with token.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "accessToken": "test-access-token", + "refreshToken": "test-refresh-token", + } + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + result = await cloud_service.verify_code("test@example.com", "123456") + + assert result["success"] is True + assert cloud_service.access_token == "test-access-token" + + @pytest.mark.asyncio + async def test_verify_code_failure(self, cloud_service): + """When email code is incorrect, should return failure.""" + mock_response = MagicMock() + mock_response.status_code = 400 + mock_response.json.return_value = { + "message": "Invalid verification code", + } + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + result = await cloud_service.verify_code("test@example.com", "000000") + + assert result["success"] is False + assert "Invalid" in result["message"] or "Verification failed" in result["message"] + + +class TestBambuCloudTOTPVerification: + """Test TOTP verification flow.""" + + @pytest.fixture + def cloud_service(self): + """Create a BambuCloudService instance.""" + return BambuCloudService() + + @pytest.mark.asyncio + async def test_verify_totp_success(self, cloud_service): + """When TOTP code is correct, should return success with token.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = '{"token": "test-access-token"}' + mock_response.json.return_value = { + "token": "test-access-token", + } + mock_response.cookies = {} + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + result = await cloud_service.verify_totp("test-tfa-key", "123456") + + assert result["success"] is True + assert cloud_service.access_token == "test-access-token" + + @pytest.mark.asyncio + async def test_verify_totp_uses_correct_endpoint(self, cloud_service): + """TOTP verification should use bambulab.com, not api.bambulab.com.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = '{"token": "test-token"}' + mock_response.json.return_value = {"token": "test-token"} + mock_response.cookies = {} + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + await cloud_service.verify_totp("test-tfa-key", "123456") + + # Check the URL used + call_args = mock_post.call_args + url = call_args[0][0] + assert "bambulab.com/api/sign-in/tfa" in url + assert "api.bambulab.com" not in url + + @pytest.mark.asyncio + async def test_verify_totp_empty_response(self, cloud_service): + """When TOTP returns empty response, should handle gracefully.""" + mock_response = MagicMock() + mock_response.status_code = 400 + mock_response.text = "" + + with patch.object(cloud_service._client, "post", new_callable=AsyncMock) as mock_post: + mock_post.return_value = mock_response + + result = await cloud_service.verify_totp("test-tfa-key", "123456") + + assert result["success"] is False + assert "empty response" in result["message"].lower() + + @pytest.mark.asyncio + async def test_verify_totp_cloudflare_blocked(self, cloud_service): + """When Cloudflare blocks request, should handle gracefully.""" + mock_response = MagicMock() + mock_response.status_code = 403 + mock_response.text = "
{t('profiles.login.checkEmail', { email })}
+ ++ {verificationType === 'totp' + ? t('profiles.login.enterTotpHint') + : t('profiles.login.checkEmail', { email })} +
r[i]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const a of i)if(a.type==="childList")for(const o of a.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const a={};return i.integrity&&(a.integrity=i.integrity),i.referrerPolicy&&(a.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?a.credentials="include":i.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(i){if(i.ep)return;i.ep=!0;const a=n(i);fetch(i.href,a)}})();var Aw=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function hl(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var zE={exports:{}},Eb={};var FU;function jae(){if(FU)return Eb;FU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,a){var o=null;if(a!==void 0&&(o=""+a),i.key!==void 0&&(o=""+i.key),"key"in i){a={};for(var l in i)l!=="key"&&(a[l]=i[l])}else a=i;return i=a.ref,{$$typeof:t,type:r,key:o,ref:i!==void 0?i:null,props:a}}return Eb.Fragment=e,Eb.jsx=n,Eb.jsxs=n,Eb}var IU;function Pae(){return IU||(IU=1,zE.exports=jae()),zE.exports}var s=Pae(),BE={exports:{}},yn={};var UU;function Dae(){if(UU)return yn;UU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),a=Symbol.for("react.consumer"),o=Symbol.for("react.context"),l=Symbol.for("react.forward_ref"),c=Symbol.for("react.suspense"),u=Symbol.for("react.memo"),d=Symbol.for("react.lazy"),f=Symbol.for("react.activity"),p=Symbol.iterator;function m($){return $===null||typeof $!="object"?null:($=p&&$[p]||$["@@iterator"],typeof $=="function"?$:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b=Object.assign,y={};function v($,X,ne){this.props=$,this.context=X,this.refs=y,this.updater=ne||x}v.prototype.isReactComponent={},v.prototype.setState=function($,X){if(typeof $!="object"&&typeof $!="function"&&$!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,$,X,"setState")},v.prototype.forceUpdate=function($){this.updater.enqueueForceUpdate(this,$,"forceUpdate")};function S(){}S.prototype=v.prototype;function M($,X,ne){this.props=$,this.context=X,this.refs=y,this.updater=ne||x}var T=M.prototype=new S;T.constructor=M,b(T,v.prototype),T.isPureReactComponent=!0;var k=Array.isArray;function C(){}var E={H:null,A:null,T:null,S:null},R=Object.prototype.hasOwnProperty;function A($,X,ne){var se=ne.ref;return{$$typeof:t,type:$,key:X,ref:se!==void 0?se:null,props:ne}}function D($,X){return A($.type,X,$.props)}function I($){return typeof $=="object"&&$!==null&&$.$$typeof===t}function F($){var X={"=":"=0",":":"=2"};return"$"+$.replace(/[=:]/g,function(ne){return X[ne]})}var z=/\/+/g;function j($,X){return typeof $=="object"&&$!==null&&$.key!=null?F(""+$.key):X.toString(36)}function H($){switch($.status){case"fulfilled":return $.value;case"rejected":throw $.reason;default:switch(typeof $.status=="string"?$.then(C,C):($.status="pending",$.then(function(X){$.status==="pending"&&($.status="fulfilled",$.value=X)},function(X){$.status==="pending"&&($.status="rejected",$.reason=X)})),$.status){case"fulfilled":return $.value;case"rejected":throw $.reason}}throw $}function K($,X,ne,se,be){var xe=typeof $;(xe==="undefined"||xe==="boolean")&&($=null);var de=!1;if($===null)de=!0;else switch(xe){case"bigint":case"string":case"number":de=!0;break;case"object":switch($.$$typeof){case t:case e:de=!0;break;case d:return de=$._init,K(de($._payload),X,ne,se,be)}}if(de)return be=be($),de=se===""?"."+j($,0):se,k(be)?(ne="",de!=null&&(ne=de.replace(z,"$&/")+"/"),K(be,X,ne,"",function(Me){return Me})):be!=null&&(I(be)&&(be=D(be,ne+(be.key==null||$&&$.key===be.key?"":(""+be.key).replace(z,"$&/")+"/")+de)),X.push(be)),1;de=0;var Pe=se===""?".":se+":";if(k($))for(var ie=0;ie<$.length;ie++)se=$[ie],xe=Pe+j(se,ie),de+=K(se,X,ne,xe,be);else if(ie=m($),typeof ie=="function")for($=ie.call($),ie=0;!(se=$.next()).done;)se=se.value,xe=Pe+j(se,ie++),de+=K(se,X,ne,xe,be);else if(xe==="object"){if(typeof $.then=="function")return K(H($),X,ne,se,be);throw X=String($),Error("Objects are not valid as a React child (found: "+(X==="[object Object]"?"object with keys {"+Object.keys($).join(", ")+"}":X)+"). If you meant to render a collection of children, use an array instead.")}return de}function V($,X,ne){if($==null)return $;var se=[],be=0;return K($,se,"","",function(xe){return X.call(ne,xe,be++)}),se}function Q($){if($._status===-1){var X=$._result;X=X(),X.then(function(ne){($._status===0||$._status===-1)&&($._status=1,$._result=ne)},function(ne){($._status===0||$._status===-1)&&($._status=2,$._result=ne)}),$._status===-1&&($._status=0,$._result=X)}if($._status===1)return $._result.default;throw $._result}var Z=typeof reportError=="function"?reportError:function($){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var X=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof $=="object"&&$!==null&&typeof $.message=="string"?String($.message):String($),error:$});if(!window.dispatchEvent(X))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",$);return}console.error($)},ae={map:V,forEach:function($,X,ne){V($,function(){X.apply(this,arguments)},ne)},count:function($){var X=0;return V($,function(){X++}),X},toArray:function($){return V($,function(X){return X})||[]},only:function($){if(!I($))throw Error("React.Children.only expected to receive a single React element child.");return $}};return yn.Activity=f,yn.Children=ae,yn.Component=v,yn.Fragment=n,yn.Profiler=i,yn.PureComponent=M,yn.StrictMode=r,yn.Suspense=c,yn.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=E,yn.__COMPILER_RUNTIME={__proto__:null,c:function($){return E.H.useMemoCache($)}},yn.cache=function($){return function(){return $.apply(null,arguments)}},yn.cacheSignal=function(){return null},yn.cloneElement=function($,X,ne){if($==null)throw Error("The argument must be a React element, but you passed "+$+".");var se=b({},$.props),be=$.key;if(X!=null)for(xe in X.key!==void 0&&(be=""+X.key),X)!R.call(X,xe)||xe==="key"||xe==="__self"||xe==="__source"||xe==="ref"&&X.ref===void 0||(se[xe]=X[xe]);var xe=arguments.length-2;if(xe===1)se.children=ne;else if(1Mn?(qn=Wt,Wt=null):qn=Wt.sibling;var ar=ct(Je,Wt,rt[Mn],bt);if(ar===null){Wt===null&&(Wt=qn);break}h&&Wt&&ar.alternate===null&&g(Je,Wt),Ge=L(ar,Ge,Mn),ir===null?Kt=ar:ir.sibling=ar,ir=ar,Wt=qn}if(Mn===rt.length)return w(Je,Wt),Xn&&Pc(Je,Mn),Kt;if(Wt===null){for(;Mn a?.[0]===f[0]&&!r.context?(this.logger.warn(`It seems you are nesting recursively key: ${f[0]} in key: ${n[0]}`),null):this.translate(...f,n),r)),r.interpolation&&this.interpolator.reset()}const o=r.postProcess||this.options.postProcess,l=fn(o)?[o]:o;return e!=null&&l?.length&&r.applyPostProcessor!==!1&&(e=wq.handle(l,e,n,this.options&&this.options.postProcessPassResolved?{i18nResolved:{...i,usedParams:this.getUsedParamsDetails(r)},...r}:r,this)),e}resolve(e,n={}){let r,i,a,o,l;return fn(e)&&(e=[e]),e.forEach(c=>{if(this.isValidLookup(r))return;const u=this.extractFromKey(c,n),d=u.key;i=d;let f=u.namespaces;this.options.fallbackNS&&(f=f.concat(this.options.fallbackNS));const p=n.count!==void 0&&!fn(n.count),m=p&&!n.ordinal&&n.count===0,x=n.context!==void 0&&(fn(n.context)||typeof n.context=="number")&&n.context!=="",b=n.lngs?n.lngs:this.languageUtils.toResolveHierarchy(n.lng||this.language,n.fallbackLng);f.forEach(y=>{this.isValidLookup(r)||(l=y,!ZU[`${b[0]}-${y}`]&&this.utils?.hasLoadedNamespace&&!this.utils?.hasLoadedNamespace(l)&&(ZU[`${b[0]}-${y}`]=!0,this.logger.warn(`key "${i}" for languages "${b.join(", ")}" won't get resolved as namespace "${l}" was not yet loaded`,"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!")),b.forEach(v=>{if(this.isValidLookup(r))return;o=v;const S=[d];if(this.i18nFormat?.addLookupKeys)this.i18nFormat.addLookupKeys(S,d,v,y,n);else{let T;p&&(T=this.pluralResolver.getSuffix(v,n.count,n));const k=`${this.options.pluralSeparator}zero`,C=`${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;if(p&&(n.ordinal&&T.indexOf(C)===0&&S.push(d+T.replace(C,this.options.pluralSeparator)),S.push(d+T),m&&S.push(d+k)),x){const E=`${d}${this.options.contextSeparator||"_"}${n.context}`;S.push(E),p&&(n.ordinal&&T.indexOf(C)===0&&S.push(E+T.replace(C,this.options.pluralSeparator)),S.push(E+T),m&&S.push(E+k))}}let N;for(;N=S.pop();)this.isValidLookup(r)||(a=N,r=this.getResource(v,y,N,n))}))})}),{res:r,usedKey:i,exactUsedKey:a,usedLng:o,usedNS:l}}isValidLookup(e){return e!==void 0&&!(!this.options.returnNull&&e===null)&&!(!this.options.returnEmptyString&&e==="")}getResource(e,n,r,i={}){return this.i18nFormat?.getResource?this.i18nFormat.getResource(e,n,r,i):this.resourceStore.getResource(e,n,r,i)}getUsedParamsDetails(e={}){const n=["defaultValue","ordinal","context","replace","lng","lngs","fallbackLng","ns","keySeparator","nsSeparator","returnObjects","returnDetails","joinArrays","postProcess","interpolation"],r=e.replace&&!fn(e.replace);let i=r?e.replace:e;if(r&&typeof e.count<"u"&&(i.count=e.count),this.options.interpolation.defaultVariables&&(i={...this.options.interpolation.defaultVariables,...i}),!r){i={...i};for(const a of n)delete i[a]}return i}static hasDefaultValue(e){const n="defaultValue";for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)&&n===r.substring(0,n.length)&&e[r]!==void 0)return!0;return!1}}class JU{constructor(e){this.options=e,this.supportedLngs=this.options.supportedLngs||!1,this.logger=sc.create("languageUtils")}getScriptPartFromCode(e){if(e=B0(e),!e||e.indexOf("-")<0)return null;const n=e.split("-");return n.length===2||(n.pop(),n[n.length-1].toLowerCase()==="x")?null:this.formatLanguageCode(n.join("-"))}getLanguagePartFromCode(e){if(e=B0(e),!e||e.indexOf("-")<0)return e;const n=e.split("-");return this.formatLanguageCode(n[0])}formatLanguageCode(e){if(fn(e)&&e.indexOf("-")>-1){let n;try{n=Intl.getCanonicalLocales(e)[0]}catch{}return n&&this.options.lowerCaseLng&&(n=n.toLowerCase()),n||(this.options.lowerCaseLng?e.toLowerCase():e)}return this.options.cleanCode||this.options.lowerCaseLng?e.toLowerCase():e}isSupportedCode(e){return(this.options.load==="languageOnly"||this.options.nonExplicitSupportedLngs)&&(e=this.getLanguagePartFromCode(e)),!this.supportedLngs||!this.supportedLngs.length||this.supportedLngs.indexOf(e)>-1}getBestMatchFromCodes(e){if(!e)return null;let n;return e.forEach(r=>{if(n)return;const i=this.formatLanguageCode(r);(!this.options.supportedLngs||this.isSupportedCode(i))&&(n=i)}),!n&&this.options.supportedLngs&&e.forEach(r=>{if(n)return;const i=this.getScriptPartFromCode(r);if(this.isSupportedCode(i))return n=i;const a=this.getLanguagePartFromCode(r);if(this.isSupportedCode(a))return n=a;n=this.options.supportedLngs.find(o=>{if(o===a)return o;if(!(o.indexOf("-")<0&&a.indexOf("-")<0)&&(o.indexOf("-")>0&&a.indexOf("-")<0&&o.substring(0,o.indexOf("-"))===a||o.indexOf(a)===0&&a.length>1))return o})}),n||(n=this.getFallbackCodes(this.options.fallbackLng)[0]),n}getFallbackCodes(e,n){if(!e)return[];if(typeof e=="function"&&(e=e(n)),fn(e)&&(e=[e]),Array.isArray(e))return e;if(!n)return e.default||[];let r=e[n];return r||(r=e[this.getScriptPartFromCode(n)]),r||(r=e[this.formatLanguageCode(n)]),r||(r=e[this.getLanguagePartFromCode(n)]),r||(r=e.default),r||[]}toResolveHierarchy(e,n){const r=this.getFallbackCodes((n===!1?[]:n)||this.options.fallbackLng||[],e),i=[],a=o=>{o&&(this.isSupportedCode(o)?i.push(o):this.logger.warn(`rejecting language code not found in supportedLngs: ${o}`))};return fn(e)&&(e.indexOf("-")>-1||e.indexOf("_")>-1)?(this.options.load!=="languageOnly"&&a(this.formatLanguageCode(e)),this.options.load!=="languageOnly"&&this.options.load!=="currentOnly"&&a(this.getScriptPartFromCode(e)),this.options.load!=="currentOnly"&&a(this.getLanguagePartFromCode(e))):fn(e)&&a(this.formatLanguageCode(e)),r.forEach(o=>{i.indexOf(o)<0&&a(this.formatLanguageCode(o))}),i}}const ez={zero:0,one:1,two:2,few:3,many:4,other:5},tz={select:t=>t===1?"one":"other",resolvedOptions:()=>({pluralCategories:["one","other"]})};class Zae{constructor(e,n={}){this.languageUtils=e,this.options=n,this.logger=sc.create("pluralResolver"),this.pluralRulesCache={}}addRule(e,n){this.rules[e]=n}clearCache(){this.pluralRulesCache={}}getRule(e,n={}){const r=B0(e==="dev"?"en":e),i=n.ordinal?"ordinal":"cardinal",a=JSON.stringify({cleanedCode:r,type:i});if(a in this.pluralRulesCache)return this.pluralRulesCache[a];let o;try{o=new Intl.PluralRules(r,{type:i})}catch{if(!Intl)return this.logger.error("No Intl support, please use an Intl polyfill!"),tz;if(!e.match(/-|_/))return tz;const c=this.languageUtils.getLanguagePartFromCode(e);o=this.getRule(c,n)}return this.pluralRulesCache[a]=o,o}needsPlural(e,n={}){let r=this.getRule(e,n);return r||(r=this.getRule("dev",n)),r?.resolvedOptions().pluralCategories.length>1}getPluralFormsOfKey(e,n,r={}){return this.getSuffixes(e,r).map(i=>`${n}${i}`)}getSuffixes(e,n={}){let r=this.getRule(e,n);return r||(r=this.getRule("dev",n)),r?r.resolvedOptions().pluralCategories.sort((i,a)=>ez[i]-ez[a]).map(i=>`${this.options.prepend}${n.ordinal?`ordinal${this.options.prepend}`:""}${i}`):[]}getSuffix(e,n,r={}){const i=this.getRule(e,r);return i?`${this.options.prepend}${r.ordinal?`ordinal${this.options.prepend}`:""}${i.select(n)}`:(this.logger.warn(`no plural rule found for: ${e}`),this.getSuffix("dev",n,r))}}const nz=(t,e,n,r=".",i=!0)=>{let a=$ae(t,e,n);return!a&&i&&fn(n)&&(a=Yj(t,n,r),a===void 0&&(a=Yj(e,n,r))),a},WE=t=>t.replace(/\$/g,"$$$$");class Jae{constructor(e={}){this.logger=sc.create("interpolator"),this.options=e,this.format=e?.interpolation?.format||(n=>n),this.init(e)}init(e={}){e.interpolation||(e.interpolation={escapeValue:!0});const{escape:n,escapeValue:r,useRawValueToEscape:i,prefix:a,prefixEscaped:o,suffix:l,suffixEscaped:c,formatSeparator:u,unescapeSuffix:d,unescapePrefix:f,nestingPrefix:p,nestingPrefixEscaped:m,nestingSuffix:x,nestingSuffixEscaped:b,nestingOptionsSeparator:y,maxReplaces:v,alwaysFormat:S}=e.interpolation;this.escape=n!==void 0?n:Gae,this.escapeValue=r!==void 0?r:!0,this.useRawValueToEscape=i!==void 0?i:!1,this.prefix=a?mp(a):o||"{{",this.suffix=l?mp(l):c||"}}",this.formatSeparator=u||",",this.unescapePrefix=d?"":f||"-",this.unescapeSuffix=this.unescapePrefix?"":d||"",this.nestingPrefix=p?mp(p):m||mp("$t("),this.nestingSuffix=x?mp(x):b||mp(")"),this.nestingOptionsSeparator=y||",",this.maxReplaces=v||1e3,this.alwaysFormat=S!==void 0?S:!1,this.resetRegExp()}reset(){this.options&&this.init(this.options)}resetRegExp(){const e=(n,r)=>n?.source===r?(n.lastIndex=0,n):new RegExp(r,"g");this.regexp=e(this.regexp,`${this.prefix}(.+?)${this.suffix}`),this.regexpUnescape=e(this.regexpUnescape,`${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`),this.nestingRegexp=e(this.nestingRegexp,`${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`)}interpolate(e,n,r,i){let a,o,l;const c=this.options&&this.options.interpolation&&this.options.interpolation.defaultVariables||{},u=m=>{if(m.indexOf(this.formatSeparator)<0){const v=nz(n,c,m,this.options.keySeparator,this.options.ignoreJSONStructure);return this.alwaysFormat?this.format(v,void 0,r,{...i,...n,interpolationkey:m}):v}const x=m.split(this.formatSeparator),b=x.shift().trim(),y=x.join(this.formatSeparator).trim();return this.format(nz(n,c,b,this.options.keySeparator,this.options.ignoreJSONStructure),y,r,{...i,...n,interpolationkey:b})};this.resetRegExp();const d=i?.missingInterpolationHandler||this.options.missingInterpolationHandler,f=i?.interpolation?.skipOnVariables!==void 0?i.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables;return[{regex:this.regexpUnescape,safeValue:m=>WE(m)},{regex:this.regexp,safeValue:m=>this.escapeValue?WE(this.escape(m)):WE(m)}].forEach(m=>{for(l=0;a=m.regex.exec(e);){const x=a[1].trim();if(o=u(x),o===void 0)if(typeof d=="function"){const y=d(e,a,i);o=fn(y)?y:""}else if(i&&Object.prototype.hasOwnProperty.call(i,x))o="";else if(f){o=a[0];continue}else this.logger.warn(`missed to pass in variable ${x} for interpolating ${e}`),o="";else!fn(o)&&!this.useRawValueToEscape&&(o=WU(o));const b=m.safeValue(o);if(e=e.replace(a[0],b),f?(m.regex.lastIndex+=o.length,m.regex.lastIndex-=a[0].length):m.regex.lastIndex=0,l++,l>=this.maxReplaces)break}}),e}nest(e,n,r={}){let i,a,o;const l=(c,u)=>{const d=this.nestingOptionsSeparator;if(c.indexOf(d)<0)return c;const f=c.split(new RegExp(`${d}[ ]*{`));let p=`{${f[1]}`;c=f[0],p=this.interpolate(p,o);const m=p.match(/'/g),x=p.match(/"/g);((m?.length??0)%2===0&&!x||x.length%2!==0)&&(p=p.replace(/'/g,'"'));try{o=JSON.parse(p),u&&(o={...u,...o})}catch(b){return this.logger.warn(`failed parsing options string in nesting for key ${c}`,b),`${c}${d}${p}`}return o.defaultValue&&o.defaultValue.indexOf(this.prefix)>-1&&delete o.defaultValue,c};for(;i=this.nestingRegexp.exec(e);){let c=[];o={...r},o=o.replace&&!fn(o.replace)?o.replace:o,o.applyPostProcessor=!1,delete o.defaultValue;const u=/{.*}/.test(i[1])?i[1].lastIndexOf("}")+1:i[1].indexOf(this.formatSeparator);if(u!==-1&&(c=i[1].slice(u).split(this.formatSeparator).map(d=>d.trim()).filter(Boolean),i[1]=i[1].slice(0,u)),a=n(l.call(this,i[1].trim(),o),o),a&&i[0]===e&&!fn(a))return a;fn(a)||(a=WU(a)),a||(this.logger.warn(`missed to resolve ${i[1]} for nesting ${e}`),a=""),c.length&&(a=c.reduce((d,f)=>this.format(d,f,r.lng,{...r,interpolationkey:i[1].trim()}),a.trim())),e=e.replace(i[0],a),this.regexp.lastIndex=0}return e}}const ese=t=>{let e=t.toLowerCase().trim();const n={};if(t.indexOf("(")>-1){const r=t.split("(");e=r[0].toLowerCase().trim();const i=r[1].substring(0,r[1].length-1);e==="currency"&&i.indexOf(":")<0?n.currency||(n.currency=i.trim()):e==="relativetime"&&i.indexOf(":")<0?n.range||(n.range=i.trim()):i.split(";").forEach(o=>{if(o){const[l,...c]=o.split(":"),u=c.join(":").trim().replace(/^'+|'+$/g,""),d=l.trim();n[d]||(n[d]=u),u==="false"&&(n[d]=!1),u==="true"&&(n[d]=!0),isNaN(u)||(n[d]=parseInt(u,10))}})}return{formatName:e,formatOptions:n}},rz=t=>{const e={};return(n,r,i)=>{let a=i;i&&i.interpolationkey&&i.formatParams&&i.formatParams[i.interpolationkey]&&i[i.interpolationkey]&&(a={...a,[i.interpolationkey]:void 0});const o=r+JSON.stringify(a);let l=e[o];return l||(l=t(B0(r),i),e[o]=l),l(n)}},tse=t=>(e,n,r)=>t(B0(n),r)(e);class nse{constructor(e={}){this.logger=sc.create("formatter"),this.options=e,this.init(e)}init(e,n={interpolation:{}}){this.formatSeparator=n.interpolation.formatSeparator||",";const r=n.cacheInBuiltFormats?rz:tse;this.formats={number:r((i,a)=>{const o=new Intl.NumberFormat(i,{...a});return l=>o.format(l)}),currency:r((i,a)=>{const o=new Intl.NumberFormat(i,{...a,style:"currency"});return l=>o.format(l)}),datetime:r((i,a)=>{const o=new Intl.DateTimeFormat(i,{...a});return l=>o.format(l)}),relativetime:r((i,a)=>{const o=new Intl.RelativeTimeFormat(i,{...a});return l=>o.format(l,a.range||"day")}),list:r((i,a)=>{const o=new Intl.ListFormat(i,{...a});return l=>o.format(l)})}}add(e,n){this.formats[e.toLowerCase().trim()]=n}addCached(e,n){this.formats[e.toLowerCase().trim()]=rz(n)}format(e,n,r,i={}){const a=n.split(this.formatSeparator);if(a.length>1&&a[0].indexOf("(")>1&&a[0].indexOf(")")<0&&a.find(l=>l.indexOf(")")>-1)){const l=a.findIndex(c=>c.indexOf(")")>-1);a[0]=[a[0],...a.splice(1,l)].join(this.formatSeparator)}return a.reduce((l,c)=>{const{formatName:u,formatOptions:d}=ese(c);if(this.formats[u]){let f=l;try{const p=i?.formatParams?.[i.interpolationkey]||{},m=p.locale||p.lng||i.locale||i.lng||r;f=this.formats[u](l,m,{...d,...i,...p})}catch(p){this.logger.warn(p)}return f}else this.logger.warn(`there was no format function for ${u}`);return l},e)}}const rse=(t,e)=>{t.pending[e]!==void 0&&(delete t.pending[e],t.pendingCount--)};class ise extends tN{constructor(e,n,r,i={}){super(),this.backend=e,this.store=n,this.services=r,this.languageUtils=r.languageUtils,this.options=i,this.logger=sc.create("backendConnector"),this.waitingReads=[],this.maxParallelReads=i.maxParallelReads||10,this.readingCalls=0,this.maxRetries=i.maxRetries>=0?i.maxRetries:5,this.retryTimeout=i.retryTimeout>=1?i.retryTimeout:350,this.state={},this.queue=[],this.backend?.init?.(r,i.backend,i)}queueLoad(e,n,r,i){const a={},o={},l={},c={};return e.forEach(u=>{let d=!0;n.forEach(f=>{const p=`${u}|${f}`;!r.reload&&this.store.hasResourceBundle(u,f)?this.state[p]=2:this.state[p]<0||(this.state[p]===1?o[p]===void 0&&(o[p]=!0):(this.state[p]=1,d=!1,o[p]===void 0&&(o[p]=!0),a[p]===void 0&&(a[p]=!0),c[f]===void 0&&(c[f]=!0)))}),d||(l[u]=!0)}),(Object.keys(a).length||Object.keys(o).length)&&this.queue.push({pending:o,pendingCount:Object.keys(o).length,loaded:{},errors:[],callback:i}),{toLoad:Object.keys(a),pending:Object.keys(o),toLoadLanguages:Object.keys(l),toLoadNamespaces:Object.keys(c)}}loaded(e,n,r){const i=e.split("|"),a=i[0],o=i[1];n&&this.emit("failedLoading",a,o,n),!n&&r&&this.store.addResourceBundle(a,o,r,void 0,void 0,{skipCopy:!0}),this.state[e]=n?-1:2,n&&r&&(this.state[e]=0);const l={};this.queue.forEach(c=>{Hae(c.loaded,[a],o),rse(c,e),n&&c.errors.push(n),c.pendingCount===0&&!c.done&&(Object.keys(c.loaded).forEach(u=>{l[u]||(l[u]={});const d=c.loaded[u];d.length&&d.forEach(f=>{l[u][f]===void 0&&(l[u][f]=!0)})}),c.done=!0,c.errors.length?c.callback(c.errors):c.callback())}),this.emit("loaded",l),this.queue=this.queue.filter(c=>!c.done)}read(e,n,r,i=0,a=this.retryTimeout,o){if(!e.length)return o(null,{});if(this.readingCalls>=this.maxParallelReads){this.waitingReads.push({lng:e,ns:n,fcName:r,tried:i,wait:a,callback:o});return}this.readingCalls++;const l=(u,d)=>{if(this.readingCalls--,this.waitingReads.length>0){const f=this.waitingReads.shift();this.read(f.lng,f.ns,f.fcName,f.tried,f.wait,f.callback)}if(u&&d&&i Mn?(qn=Wt,Wt=null):qn=Wt.sibling;var sr=ct(Je,Wt,rt[Mn],bt);if(sr===null){Wt===null&&(Wt=qn);break}h&&Wt&&sr.alternate===null&&g(Je,Wt),Ge=L(sr,Ge,Mn),ar===null?Kt=sr:ar.sibling=sr,ar=sr,Wt=qn}if(Mn===rt.length)return w(Je,Wt),Yn&&Pc(Je,Mn),Kt;if(Wt===null){for(;Mn a?.[0]===f[0]&&!r.context?(this.logger.warn(`It seems you are nesting recursively key: ${f[0]} in key: ${n[0]}`),null):this.translate(...f,n),r)),r.interpolation&&this.interpolator.reset()}const o=r.postProcess||this.options.postProcess,l=fn(o)?[o]:o;return e!=null&&l?.length&&r.applyPostProcessor!==!1&&(e=wq.handle(l,e,n,this.options&&this.options.postProcessPassResolved?{i18nResolved:{...i,usedParams:this.getUsedParamsDetails(r)},...r}:r,this)),e}resolve(e,n={}){let r,i,a,o,l;return fn(e)&&(e=[e]),e.forEach(c=>{if(this.isValidLookup(r))return;const u=this.extractFromKey(c,n),d=u.key;i=d;let f=u.namespaces;this.options.fallbackNS&&(f=f.concat(this.options.fallbackNS));const p=n.count!==void 0&&!fn(n.count),m=p&&!n.ordinal&&n.count===0,x=n.context!==void 0&&(fn(n.context)||typeof n.context=="number")&&n.context!=="",b=n.lngs?n.lngs:this.languageUtils.toResolveHierarchy(n.lng||this.language,n.fallbackLng);f.forEach(y=>{this.isValidLookup(r)||(l=y,!ZU[`${b[0]}-${y}`]&&this.utils?.hasLoadedNamespace&&!this.utils?.hasLoadedNamespace(l)&&(ZU[`${b[0]}-${y}`]=!0,this.logger.warn(`key "${i}" for languages "${b.join(", ")}" won't get resolved as namespace "${l}" was not yet loaded`,"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!")),b.forEach(v=>{if(this.isValidLookup(r))return;o=v;const S=[d];if(this.i18nFormat?.addLookupKeys)this.i18nFormat.addLookupKeys(S,d,v,y,n);else{let T;p&&(T=this.pluralResolver.getSuffix(v,n.count,n));const k=`${this.options.pluralSeparator}zero`,C=`${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;if(p&&(n.ordinal&&T.indexOf(C)===0&&S.push(d+T.replace(C,this.options.pluralSeparator)),S.push(d+T),m&&S.push(d+k)),x){const E=`${d}${this.options.contextSeparator||"_"}${n.context}`;S.push(E),p&&(n.ordinal&&T.indexOf(C)===0&&S.push(E+T.replace(C,this.options.pluralSeparator)),S.push(E+T),m&&S.push(E+k))}}let N;for(;N=S.pop();)this.isValidLookup(r)||(a=N,r=this.getResource(v,y,N,n))}))})}),{res:r,usedKey:i,exactUsedKey:a,usedLng:o,usedNS:l}}isValidLookup(e){return e!==void 0&&!(!this.options.returnNull&&e===null)&&!(!this.options.returnEmptyString&&e==="")}getResource(e,n,r,i={}){return this.i18nFormat?.getResource?this.i18nFormat.getResource(e,n,r,i):this.resourceStore.getResource(e,n,r,i)}getUsedParamsDetails(e={}){const n=["defaultValue","ordinal","context","replace","lng","lngs","fallbackLng","ns","keySeparator","nsSeparator","returnObjects","returnDetails","joinArrays","postProcess","interpolation"],r=e.replace&&!fn(e.replace);let i=r?e.replace:e;if(r&&typeof e.count<"u"&&(i.count=e.count),this.options.interpolation.defaultVariables&&(i={...this.options.interpolation.defaultVariables,...i}),!r){i={...i};for(const a of n)delete i[a]}return i}static hasDefaultValue(e){const n="defaultValue";for(const r in e)if(Object.prototype.hasOwnProperty.call(e,r)&&n===r.substring(0,n.length)&&e[r]!==void 0)return!0;return!1}}class JU{constructor(e){this.options=e,this.supportedLngs=this.options.supportedLngs||!1,this.logger=sc.create("languageUtils")}getScriptPartFromCode(e){if(e=B0(e),!e||e.indexOf("-")<0)return null;const n=e.split("-");return n.length===2||(n.pop(),n[n.length-1].toLowerCase()==="x")?null:this.formatLanguageCode(n.join("-"))}getLanguagePartFromCode(e){if(e=B0(e),!e||e.indexOf("-")<0)return e;const n=e.split("-");return this.formatLanguageCode(n[0])}formatLanguageCode(e){if(fn(e)&&e.indexOf("-")>-1){let n;try{n=Intl.getCanonicalLocales(e)[0]}catch{}return n&&this.options.lowerCaseLng&&(n=n.toLowerCase()),n||(this.options.lowerCaseLng?e.toLowerCase():e)}return this.options.cleanCode||this.options.lowerCaseLng?e.toLowerCase():e}isSupportedCode(e){return(this.options.load==="languageOnly"||this.options.nonExplicitSupportedLngs)&&(e=this.getLanguagePartFromCode(e)),!this.supportedLngs||!this.supportedLngs.length||this.supportedLngs.indexOf(e)>-1}getBestMatchFromCodes(e){if(!e)return null;let n;return e.forEach(r=>{if(n)return;const i=this.formatLanguageCode(r);(!this.options.supportedLngs||this.isSupportedCode(i))&&(n=i)}),!n&&this.options.supportedLngs&&e.forEach(r=>{if(n)return;const i=this.getScriptPartFromCode(r);if(this.isSupportedCode(i))return n=i;const a=this.getLanguagePartFromCode(r);if(this.isSupportedCode(a))return n=a;n=this.options.supportedLngs.find(o=>{if(o===a)return o;if(!(o.indexOf("-")<0&&a.indexOf("-")<0)&&(o.indexOf("-")>0&&a.indexOf("-")<0&&o.substring(0,o.indexOf("-"))===a||o.indexOf(a)===0&&a.length>1))return o})}),n||(n=this.getFallbackCodes(this.options.fallbackLng)[0]),n}getFallbackCodes(e,n){if(!e)return[];if(typeof e=="function"&&(e=e(n)),fn(e)&&(e=[e]),Array.isArray(e))return e;if(!n)return e.default||[];let r=e[n];return r||(r=e[this.getScriptPartFromCode(n)]),r||(r=e[this.formatLanguageCode(n)]),r||(r=e[this.getLanguagePartFromCode(n)]),r||(r=e.default),r||[]}toResolveHierarchy(e,n){const r=this.getFallbackCodes((n===!1?[]:n)||this.options.fallbackLng||[],e),i=[],a=o=>{o&&(this.isSupportedCode(o)?i.push(o):this.logger.warn(`rejecting language code not found in supportedLngs: ${o}`))};return fn(e)&&(e.indexOf("-")>-1||e.indexOf("_")>-1)?(this.options.load!=="languageOnly"&&a(this.formatLanguageCode(e)),this.options.load!=="languageOnly"&&this.options.load!=="currentOnly"&&a(this.getScriptPartFromCode(e)),this.options.load!=="currentOnly"&&a(this.getLanguagePartFromCode(e))):fn(e)&&a(this.formatLanguageCode(e)),r.forEach(o=>{i.indexOf(o)<0&&a(this.formatLanguageCode(o))}),i}}const ez={zero:0,one:1,two:2,few:3,many:4,other:5},tz={select:t=>t===1?"one":"other",resolvedOptions:()=>({pluralCategories:["one","other"]})};class Zae{constructor(e,n={}){this.languageUtils=e,this.options=n,this.logger=sc.create("pluralResolver"),this.pluralRulesCache={}}addRule(e,n){this.rules[e]=n}clearCache(){this.pluralRulesCache={}}getRule(e,n={}){const r=B0(e==="dev"?"en":e),i=n.ordinal?"ordinal":"cardinal",a=JSON.stringify({cleanedCode:r,type:i});if(a in this.pluralRulesCache)return this.pluralRulesCache[a];let o;try{o=new Intl.PluralRules(r,{type:i})}catch{if(!Intl)return this.logger.error("No Intl support, please use an Intl polyfill!"),tz;if(!e.match(/-|_/))return tz;const c=this.languageUtils.getLanguagePartFromCode(e);o=this.getRule(c,n)}return this.pluralRulesCache[a]=o,o}needsPlural(e,n={}){let r=this.getRule(e,n);return r||(r=this.getRule("dev",n)),r?.resolvedOptions().pluralCategories.length>1}getPluralFormsOfKey(e,n,r={}){return this.getSuffixes(e,r).map(i=>`${n}${i}`)}getSuffixes(e,n={}){let r=this.getRule(e,n);return r||(r=this.getRule("dev",n)),r?r.resolvedOptions().pluralCategories.sort((i,a)=>ez[i]-ez[a]).map(i=>`${this.options.prepend}${n.ordinal?`ordinal${this.options.prepend}`:""}${i}`):[]}getSuffix(e,n,r={}){const i=this.getRule(e,r);return i?`${this.options.prepend}${r.ordinal?`ordinal${this.options.prepend}`:""}${i.select(n)}`:(this.logger.warn(`no plural rule found for: ${e}`),this.getSuffix("dev",n,r))}}const nz=(t,e,n,r=".",i=!0)=>{let a=$ae(t,e,n);return!a&&i&&fn(n)&&(a=Yj(t,n,r),a===void 0&&(a=Yj(e,n,r))),a},WE=t=>t.replace(/\$/g,"$$$$");class Jae{constructor(e={}){this.logger=sc.create("interpolator"),this.options=e,this.format=e?.interpolation?.format||(n=>n),this.init(e)}init(e={}){e.interpolation||(e.interpolation={escapeValue:!0});const{escape:n,escapeValue:r,useRawValueToEscape:i,prefix:a,prefixEscaped:o,suffix:l,suffixEscaped:c,formatSeparator:u,unescapeSuffix:d,unescapePrefix:f,nestingPrefix:p,nestingPrefixEscaped:m,nestingSuffix:x,nestingSuffixEscaped:b,nestingOptionsSeparator:y,maxReplaces:v,alwaysFormat:S}=e.interpolation;this.escape=n!==void 0?n:Gae,this.escapeValue=r!==void 0?r:!0,this.useRawValueToEscape=i!==void 0?i:!1,this.prefix=a?mp(a):o||"{{",this.suffix=l?mp(l):c||"}}",this.formatSeparator=u||",",this.unescapePrefix=d?"":f||"-",this.unescapeSuffix=this.unescapePrefix?"":d||"",this.nestingPrefix=p?mp(p):m||mp("$t("),this.nestingSuffix=x?mp(x):b||mp(")"),this.nestingOptionsSeparator=y||",",this.maxReplaces=v||1e3,this.alwaysFormat=S!==void 0?S:!1,this.resetRegExp()}reset(){this.options&&this.init(this.options)}resetRegExp(){const e=(n,r)=>n?.source===r?(n.lastIndex=0,n):new RegExp(r,"g");this.regexp=e(this.regexp,`${this.prefix}(.+?)${this.suffix}`),this.regexpUnescape=e(this.regexpUnescape,`${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`),this.nestingRegexp=e(this.nestingRegexp,`${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`)}interpolate(e,n,r,i){let a,o,l;const c=this.options&&this.options.interpolation&&this.options.interpolation.defaultVariables||{},u=m=>{if(m.indexOf(this.formatSeparator)<0){const v=nz(n,c,m,this.options.keySeparator,this.options.ignoreJSONStructure);return this.alwaysFormat?this.format(v,void 0,r,{...i,...n,interpolationkey:m}):v}const x=m.split(this.formatSeparator),b=x.shift().trim(),y=x.join(this.formatSeparator).trim();return this.format(nz(n,c,b,this.options.keySeparator,this.options.ignoreJSONStructure),y,r,{...i,...n,interpolationkey:b})};this.resetRegExp();const d=i?.missingInterpolationHandler||this.options.missingInterpolationHandler,f=i?.interpolation?.skipOnVariables!==void 0?i.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables;return[{regex:this.regexpUnescape,safeValue:m=>WE(m)},{regex:this.regexp,safeValue:m=>this.escapeValue?WE(this.escape(m)):WE(m)}].forEach(m=>{for(l=0;a=m.regex.exec(e);){const x=a[1].trim();if(o=u(x),o===void 0)if(typeof d=="function"){const y=d(e,a,i);o=fn(y)?y:""}else if(i&&Object.prototype.hasOwnProperty.call(i,x))o="";else if(f){o=a[0];continue}else this.logger.warn(`missed to pass in variable ${x} for interpolating ${e}`),o="";else!fn(o)&&!this.useRawValueToEscape&&(o=WU(o));const b=m.safeValue(o);if(e=e.replace(a[0],b),f?(m.regex.lastIndex+=o.length,m.regex.lastIndex-=a[0].length):m.regex.lastIndex=0,l++,l>=this.maxReplaces)break}}),e}nest(e,n,r={}){let i,a,o;const l=(c,u)=>{const d=this.nestingOptionsSeparator;if(c.indexOf(d)<0)return c;const f=c.split(new RegExp(`${d}[ ]*{`));let p=`{${f[1]}`;c=f[0],p=this.interpolate(p,o);const m=p.match(/'/g),x=p.match(/"/g);((m?.length??0)%2===0&&!x||x.length%2!==0)&&(p=p.replace(/'/g,'"'));try{o=JSON.parse(p),u&&(o={...u,...o})}catch(b){return this.logger.warn(`failed parsing options string in nesting for key ${c}`,b),`${c}${d}${p}`}return o.defaultValue&&o.defaultValue.indexOf(this.prefix)>-1&&delete o.defaultValue,c};for(;i=this.nestingRegexp.exec(e);){let c=[];o={...r},o=o.replace&&!fn(o.replace)?o.replace:o,o.applyPostProcessor=!1,delete o.defaultValue;const u=/{.*}/.test(i[1])?i[1].lastIndexOf("}")+1:i[1].indexOf(this.formatSeparator);if(u!==-1&&(c=i[1].slice(u).split(this.formatSeparator).map(d=>d.trim()).filter(Boolean),i[1]=i[1].slice(0,u)),a=n(l.call(this,i[1].trim(),o),o),a&&i[0]===e&&!fn(a))return a;fn(a)||(a=WU(a)),a||(this.logger.warn(`missed to resolve ${i[1]} for nesting ${e}`),a=""),c.length&&(a=c.reduce((d,f)=>this.format(d,f,r.lng,{...r,interpolationkey:i[1].trim()}),a.trim())),e=e.replace(i[0],a),this.regexp.lastIndex=0}return e}}const ese=t=>{let e=t.toLowerCase().trim();const n={};if(t.indexOf("(")>-1){const r=t.split("(");e=r[0].toLowerCase().trim();const i=r[1].substring(0,r[1].length-1);e==="currency"&&i.indexOf(":")<0?n.currency||(n.currency=i.trim()):e==="relativetime"&&i.indexOf(":")<0?n.range||(n.range=i.trim()):i.split(";").forEach(o=>{if(o){const[l,...c]=o.split(":"),u=c.join(":").trim().replace(/^'+|'+$/g,""),d=l.trim();n[d]||(n[d]=u),u==="false"&&(n[d]=!1),u==="true"&&(n[d]=!0),isNaN(u)||(n[d]=parseInt(u,10))}})}return{formatName:e,formatOptions:n}},rz=t=>{const e={};return(n,r,i)=>{let a=i;i&&i.interpolationkey&&i.formatParams&&i.formatParams[i.interpolationkey]&&i[i.interpolationkey]&&(a={...a,[i.interpolationkey]:void 0});const o=r+JSON.stringify(a);let l=e[o];return l||(l=t(B0(r),i),e[o]=l),l(n)}},tse=t=>(e,n,r)=>t(B0(n),r)(e);class nse{constructor(e={}){this.logger=sc.create("formatter"),this.options=e,this.init(e)}init(e,n={interpolation:{}}){this.formatSeparator=n.interpolation.formatSeparator||",";const r=n.cacheInBuiltFormats?rz:tse;this.formats={number:r((i,a)=>{const o=new Intl.NumberFormat(i,{...a});return l=>o.format(l)}),currency:r((i,a)=>{const o=new Intl.NumberFormat(i,{...a,style:"currency"});return l=>o.format(l)}),datetime:r((i,a)=>{const o=new Intl.DateTimeFormat(i,{...a});return l=>o.format(l)}),relativetime:r((i,a)=>{const o=new Intl.RelativeTimeFormat(i,{...a});return l=>o.format(l,a.range||"day")}),list:r((i,a)=>{const o=new Intl.ListFormat(i,{...a});return l=>o.format(l)})}}add(e,n){this.formats[e.toLowerCase().trim()]=n}addCached(e,n){this.formats[e.toLowerCase().trim()]=rz(n)}format(e,n,r,i={}){const a=n.split(this.formatSeparator);if(a.length>1&&a[0].indexOf("(")>1&&a[0].indexOf(")")<0&&a.find(l=>l.indexOf(")")>-1)){const l=a.findIndex(c=>c.indexOf(")")>-1);a[0]=[a[0],...a.splice(1,l)].join(this.formatSeparator)}return a.reduce((l,c)=>{const{formatName:u,formatOptions:d}=ese(c);if(this.formats[u]){let f=l;try{const p=i?.formatParams?.[i.interpolationkey]||{},m=p.locale||p.lng||i.locale||i.lng||r;f=this.formats[u](l,m,{...d,...i,...p})}catch(p){this.logger.warn(p)}return f}else this.logger.warn(`there was no format function for ${u}`);return l},e)}}const rse=(t,e)=>{t.pending[e]!==void 0&&(delete t.pending[e],t.pendingCount--)};class ise extends tN{constructor(e,n,r,i={}){super(),this.backend=e,this.store=n,this.services=r,this.languageUtils=r.languageUtils,this.options=i,this.logger=sc.create("backendConnector"),this.waitingReads=[],this.maxParallelReads=i.maxParallelReads||10,this.readingCalls=0,this.maxRetries=i.maxRetries>=0?i.maxRetries:5,this.retryTimeout=i.retryTimeout>=1?i.retryTimeout:350,this.state={},this.queue=[],this.backend?.init?.(r,i.backend,i)}queueLoad(e,n,r,i){const a={},o={},l={},c={};return e.forEach(u=>{let d=!0;n.forEach(f=>{const p=`${u}|${f}`;!r.reload&&this.store.hasResourceBundle(u,f)?this.state[p]=2:this.state[p]<0||(this.state[p]===1?o[p]===void 0&&(o[p]=!0):(this.state[p]=1,d=!1,o[p]===void 0&&(o[p]=!0),a[p]===void 0&&(a[p]=!0),c[f]===void 0&&(c[f]=!0)))}),d||(l[u]=!0)}),(Object.keys(a).length||Object.keys(o).length)&&this.queue.push({pending:o,pendingCount:Object.keys(o).length,loaded:{},errors:[],callback:i}),{toLoad:Object.keys(a),pending:Object.keys(o),toLoadLanguages:Object.keys(l),toLoadNamespaces:Object.keys(c)}}loaded(e,n,r){const i=e.split("|"),a=i[0],o=i[1];n&&this.emit("failedLoading",a,o,n),!n&&r&&this.store.addResourceBundle(a,o,r,void 0,void 0,{skipCopy:!0}),this.state[e]=n?-1:2,n&&r&&(this.state[e]=0);const l={};this.queue.forEach(c=>{Hae(c.loaded,[a],o),rse(c,e),n&&c.errors.push(n),c.pendingCount===0&&!c.done&&(Object.keys(c.loaded).forEach(u=>{l[u]||(l[u]={});const d=c.loaded[u];d.length&&d.forEach(f=>{l[u][f]===void 0&&(l[u][f]=!0)})}),c.done=!0,c.errors.length?c.callback(c.errors):c.callback())}),this.emit("loaded",l),this.queue=this.queue.filter(c=>!c.done)}read(e,n,r,i=0,a=this.retryTimeout,o){if(!e.length)return o(null,{});if(this.readingCalls>=this.maxParallelReads){this.waitingReads.push({lng:e,ns:n,fcName:r,tried:i,wait:a,callback:o});return}this.readingCalls++;const l=(u,d)=>{if(this.readingCalls--,this.waitingReads.length>0){const f=this.waitingReads.shift();this.read(f.lng,f.ns,f.fcName,f.tried,f.wait,f.callback)}if(u&&d&&i