From f79ed842fa61ffda1037033f59c262e38849a385 Mon Sep 17 00:00:00 2001 From: Taranum Wasu <81034301+Taranum01@users.noreply.github.com> Date: Tue, 29 Sep 2026 10:02:45 +0000 Subject: [PATCH] fix(web): render a message in OAuth callback without opener (#43053) Co-authored-by: Taranum01 Co-authored-by: Taranum01 --- oxlint-suppressions.json | 5 - .../oauth-callback/__tests__/page.spec.tsx | 100 ++++++++++++++ web/app/oauth-callback/page.tsx | 27 +++- web/hooks/__tests__/use-oauth.spec.tsx | 126 ++++++++++++++++++ web/hooks/use-oauth.ts | 95 ++++++++----- web/i18n/locales/ar-TN/oauth.json | 4 + web/i18n/locales/az-AZ/oauth.json | 4 + web/i18n/locales/de-DE/oauth.json | 4 + web/i18n/locales/en-US/oauth.json | 4 + web/i18n/locales/es-ES/oauth.json | 4 + web/i18n/locales/fa-IR/oauth.json | 4 + web/i18n/locales/fr-FR/oauth.json | 4 + web/i18n/locales/hi-IN/oauth.json | 4 + web/i18n/locales/id-ID/oauth.json | 4 + web/i18n/locales/it-IT/oauth.json | 4 + web/i18n/locales/ja-JP/oauth.json | 4 + web/i18n/locales/ko-KR/oauth.json | 4 + web/i18n/locales/lo-LA/oauth.json | 4 + web/i18n/locales/nl-NL/oauth.json | 4 + web/i18n/locales/pl-PL/oauth.json | 4 + web/i18n/locales/pt-BR/oauth.json | 4 + web/i18n/locales/ro-RO/oauth.json | 4 + web/i18n/locales/ru-RU/oauth.json | 4 + web/i18n/locales/sl-SI/oauth.json | 4 + web/i18n/locales/th-TH/oauth.json | 4 + web/i18n/locales/tr-TR/oauth.json | 4 + web/i18n/locales/uk-UA/oauth.json | 4 + web/i18n/locales/vi-VN/oauth.json | 4 + web/i18n/locales/zh-Hans/oauth.json | 4 + web/i18n/locales/zh-Hant/oauth.json | 4 + web/i18n/resources.generated.d.ts | 4 + 31 files changed, 415 insertions(+), 42 deletions(-) create mode 100644 web/app/oauth-callback/__tests__/page.spec.tsx create mode 100644 web/hooks/__tests__/use-oauth.spec.tsx diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 0259cb04543..4df4e48e59c 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -4171,11 +4171,6 @@ "count": 2 } }, - "web/hooks/use-oauth.ts": { - "typescript/no-explicit-any": { - "count": 1 - } - }, "web/hooks/use-pay.tsx": { "eslint-react/set-state-in-effect": { "count": 4 diff --git a/web/app/oauth-callback/__tests__/page.spec.tsx b/web/app/oauth-callback/__tests__/page.spec.tsx new file mode 100644 index 00000000000..5a89f68855e --- /dev/null +++ b/web/app/oauth-callback/__tests__/page.spec.tsx @@ -0,0 +1,100 @@ +import { render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createReactI18nextMock } from '@/test/i18n-mock' + +vi.mock('react-i18next', () => + createReactI18nextMock({ + 'oauth.callback.success': 'Authorization complete', + 'oauth.callback.successHint': 'You can now close this tab and return to the previous page.', + 'oauth.callback.error': 'Authorization failed', + 'oauth.callback.errorHint': 'Please return to the previous page and try again.', + 'login.signBtn': 'Sign in', + }), +) + +vi.mock('@/hooks/use-oauth', () => ({ + useOAuthCallback: vi.fn(), +})) + +vi.mock('@/hooks/use-document-title', () => ({ + default: vi.fn(), +})) + +const { useOAuthCallback } = await import('@/hooks/use-oauth') +const OAuthCallback = (await import('../page')).default + +const setOpener = (opener: Window | null) => { + Object.defineProperty(window, 'opener', { configurable: true, writable: true, value: opener }) +} + +describe('OAuthCallback page', () => { + beforeEach(() => { + vi.restoreAllMocks() + }) + + afterEach(() => { + setOpener(null) + }) + + it('renders an empty
when opened as a popup (hasOpener=true) so the popup can close cleanly', () => { + setOpener({ postMessage: vi.fn() } as unknown as Window) + vi.mocked(useOAuthCallback).mockReturnValue({ + hasOpener: true, + finished: false, + error: null, + errorDescription: null, + }) + + const { container } = render() + expect(container.firstChild).not.toBeNull() + expect(container.firstChild!.nodeName).toBe('DIV') + expect(container.firstChild!.childNodes.length).toBe(0) + }) + + it('renders a success message when opened in a new tab with a subscription_id (#39752)', () => { + setOpener(null) + vi.mocked(useOAuthCallback).mockReturnValue({ + hasOpener: false, + finished: true, + error: null, + errorDescription: null, + }) + + render() + expect(screen.getByRole('main')).toBeInTheDocument() + expect(screen.getByText('Authorization complete')).toBeInTheDocument() + expect( + screen.getByText('You can now close this tab and return to the previous page.'), + ).toBeInTheDocument() + }) + + it('renders an error message with the provider description when the provider reported an error (#39752)', () => { + setOpener(null) + vi.mocked(useOAuthCallback).mockReturnValue({ + hasOpener: false, + finished: true, + error: 'access_denied', + errorDescription: 'Please re-authorize the app.', + }) + + render() + expect(screen.getByText('Authorization failed')).toBeInTheDocument() + expect(screen.getByText('Please re-authorize the app.')).toBeInTheDocument() + }) + + it('falls back to a generic error hint when the provider did not supply an error_description', () => { + setOpener(null) + vi.mocked(useOAuthCallback).mockReturnValue({ + hasOpener: false, + finished: true, + error: 'server_error', + errorDescription: null, + }) + + render() + expect(screen.getByText('Authorization failed')).toBeInTheDocument() + expect( + screen.getByText('Please return to the previous page and try again.'), + ).toBeInTheDocument() + }) +}) diff --git a/web/app/oauth-callback/page.tsx b/web/app/oauth-callback/page.tsx index ad1e0204feb..096e892ee67 100644 --- a/web/app/oauth-callback/page.tsx +++ b/web/app/oauth-callback/page.tsx @@ -4,11 +4,32 @@ import useDocumentTitle from '@/hooks/use-document-title' import { useOAuthCallback } from '@/hooks/use-oauth' const OAuthCallback = () => { - const { t } = useTranslation(['login']) + const { t } = useTranslation(['login', 'oauth']) useDocumentTitle(t(($) => $.signBtn, { ns: 'login' })) - useOAuthCallback() + const { hasOpener, finished, error, errorDescription } = useOAuthCallback() - return
+ // When the page is opened as a popup by the console, the hook posts the + // OAuth result to the opener and calls window.close(). The page only + // needs to render a fallback message when the page was opened in a new + // tab (no `window.opener`); see issue #39752. + if (hasOpener || !finished) return
+ + return ( +
+
+

+ {error + ? t(($) => $['callback.error'], { ns: 'oauth' }) + : t(($) => $['callback.success'], { ns: 'oauth' })} +

+

+ {error + ? errorDescription || t(($) => $['callback.errorHint'], { ns: 'oauth' }) + : t(($) => $['callback.successHint'], { ns: 'oauth' })} +

+
+
+ ) } export default OAuthCallback diff --git a/web/hooks/__tests__/use-oauth.spec.tsx b/web/hooks/__tests__/use-oauth.spec.tsx new file mode 100644 index 00000000000..f3d6d0e0eed --- /dev/null +++ b/web/hooks/__tests__/use-oauth.spec.tsx @@ -0,0 +1,126 @@ +import { renderHook } from '@testing-library/react' +import { renderToString } from 'react-dom/server' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { useOAuthCallback } from '../use-oauth' + +const originalOpener = window.opener +const originalClose = window.close + +const setOpener = (opener: Window | null) => { + Object.defineProperty(window, 'opener', { configurable: true, writable: true, value: opener }) +} + +describe('useOAuthCallback', () => { + beforeEach(() => { + vi.restoreAllMocks() + }) + + afterEach(() => { + setOpener(originalOpener) + window.close = originalClose + }) + + it('posts a success message and closes the popup when window.opener is present and a subscription_id is in the query', () => { + const postMessage = vi.fn() + setOpener({ origin: 'https://console.example.test', postMessage } as unknown as Window) + const close = vi.fn() + window.close = close + window.history.replaceState({}, '', '/oauth-callback?subscription_id=sub-123') + + const { result } = renderHook(() => useOAuthCallback()) + + expect(postMessage).toHaveBeenCalledWith( + { type: 'oauth_callback', success: true, subscriptionId: 'sub-123' }, + 'https://console.example.test', + ) + expect(close).toHaveBeenCalledTimes(1) + expect(result.current.hasOpener).toBe(true) + expect(result.current.finished).toBe(true) + }) + + it('posts an error message and closes the popup when window.opener is present and an error is in the query', () => { + const postMessage = vi.fn() + setOpener({ origin: 'https://console.example.test', postMessage } as unknown as Window) + const close = vi.fn() + window.close = close + window.history.replaceState( + {}, + '', + '/oauth-callback?error=access_denied&error_description=User%20denied%20access', + ) + + renderHook(() => useOAuthCallback()) + + expect(postMessage).toHaveBeenCalledWith( + { + type: 'oauth_callback', + success: false, + error: 'access_denied', + errorDescription: 'User denied access', + }, + 'https://console.example.test', + ) + expect(close).toHaveBeenCalledTimes(1) + }) + + it('falls back to targetOrigin "*" when window.opener has no origin (e.g. file:// popup)', () => { + const postMessage = vi.fn() + setOpener({ postMessage } as unknown as Window) + const close = vi.fn() + window.close = close + window.history.replaceState({}, '', '/oauth-callback?subscription_id=sub-1') + + renderHook(() => useOAuthCallback()) + + expect(postMessage).toHaveBeenCalledWith(expect.any(Object), '*') + }) + + it('exposes success state without posting a message when window.opener is missing (new-tab case) (#39752)', () => { + setOpener(null) + const close = vi.fn() + window.close = close + window.history.replaceState({}, '', '/oauth-callback?subscription_id=sub-tab-1') + + const { result } = renderHook(() => useOAuthCallback()) + + // No opener, so we must NOT try to close (would close the only tab). + expect(close).not.toHaveBeenCalled() + // The page needs the state to render a meaningful message instead of an empty
. + expect(result.current.hasOpener).toBe(false) + expect(result.current.finished).toBe(true) + expect(result.current.error).toBeNull() + expect(result.current.errorDescription).toBeNull() + }) + + it('exposes the provider error in state when window.opener is missing (#39752)', () => { + setOpener(null) + const close = vi.fn() + window.close = close + window.history.replaceState( + {}, + '', + '/oauth-callback?error=access_denied&error_description=Please%20re-authorize', + ) + + const { result } = renderHook(() => useOAuthCallback()) + + expect(close).not.toHaveBeenCalled() + expect(result.current.hasOpener).toBe(false) + expect(result.current.finished).toBe(true) + expect(result.current.error).toBe('access_denied') + expect(result.current.errorDescription).toBe('Please re-authorize') + }) + + it('renders without browser globals on the server and waits for hydration', () => { + const ServerCallback = () => { + const state = useOAuthCallback() + return
{state.finished ? 'finished' : 'waiting'}
+ } + vi.stubGlobal('window', undefined) + try { + expect(renderToString()).toContain('waiting') + } finally { + vi.unstubAllGlobals() + } + }) +}) diff --git a/web/hooks/use-oauth.ts b/web/hooks/use-oauth.ts index d0cdc6d12b5..298261d85a7 100644 --- a/web/hooks/use-oauth.ts +++ b/web/hooks/use-oauth.ts @@ -1,51 +1,78 @@ 'use client' -import { useEffect } from 'react' +import { useEffect, useSyncExternalStore } from 'react' import { validateRedirectUrl } from '@/utils/urlValidation' -export const useOAuthCallback = () => { +export type OAuthCallbackState = { + /** True when the callback tab/popup was opened by another window via window.open. */ + hasOpener: boolean + /** True after the callback has posted a message to the opener (or decided not to). */ + finished: boolean + /** Provider-supplied error code, if any. */ + error: string | null + /** Provider-supplied error description, if any. */ + errorDescription: string | null +} + +// OAuth redirects load a new page. The URL and opener do not change during +// this page's lifetime, so a subscription is not needed after hydration. +const subscribeToCallback = () => () => {} +const getCallbackSnapshot = () => JSON.stringify([!!window.opener, window.location.search]) +const getServerCallbackSnapshot = () => null + +export const useOAuthCallback = (): OAuthCallbackState => { + const snapshot = useSyncExternalStore( + subscribeToCallback, + getCallbackSnapshot, + getServerCallbackSnapshot, + ) + const [hasOpener, search] = + snapshot === null ? [false, ''] : (JSON.parse(snapshot) as [boolean, string]) + const urlParams = new URLSearchParams(search) + const error = urlParams.get('error') + const errorDescription = urlParams.get('error_description') + useEffect(() => { const urlParams = new URLSearchParams(window.location.search) const subscriptionId = urlParams.get('subscription_id') const error = urlParams.get('error') const errorDescription = urlParams.get('error_description') + const opener = window.opener - if (window.opener) { - // Use window.opener.origin instead of '*' for security - const targetOrigin = window.opener?.origin || '*' + if (!opener) return - if (subscriptionId) { - window.opener.postMessage( - { - type: 'oauth_callback', - success: true, - subscriptionId, - }, - targetOrigin, - ) - } else if (error) { - window.opener.postMessage( - { - type: 'oauth_callback', - success: false, - error, - errorDescription, - }, - targetOrigin, - ) - } else { - window.opener.postMessage( - { - type: 'oauth_callback', - }, - targetOrigin, - ) - } - window.close() + // Use window.opener.origin instead of '*' for security. + const targetOrigin = opener.origin || '*' + + if (subscriptionId) { + opener.postMessage({ type: 'oauth_callback', success: true, subscriptionId }, targetOrigin) + } else if (error) { + opener.postMessage( + { type: 'oauth_callback', success: false, error, errorDescription }, + targetOrigin, + ) + } else { + opener.postMessage({ type: 'oauth_callback' }, targetOrigin) } + window.close() }, []) + + return { + hasOpener, + finished: snapshot !== null, + error, + errorDescription, + } } -export const openOAuthPopup = (url: string, callback: (data?: any) => void) => { +type OAuthCallbackMessage = { + type: 'oauth_callback' + success?: boolean + subscriptionId?: string + error?: string + errorDescription?: string +} + +export const openOAuthPopup = (url: string, callback: (data?: OAuthCallbackMessage) => void) => { const width = 600 const height = 600 const left = window.screenX + (window.outerWidth - width) / 2 diff --git a/web/i18n/locales/ar-TN/oauth.json b/web/i18n/locales/ar-TN/oauth.json index 7ac178c7e33..839398df86d 100644 --- a/web/i18n/locales/ar-TN/oauth.json +++ b/web/i18n/locales/ar-TN/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "فشل التفويض", + "callback.errorHint": "يرجى العودة إلى الصفحة السابقة والمحاولة مرة أخرى.", + "callback.success": "اكتمل التفويض", + "callback.successHint": "يمكنك الآن إغلاق علامة التبويب هذه والعودة إلى الصفحة السابقة.", "connect": "الاتصال بـ", "continue": "متابعة", "error.authAppInfoFetchFailed": "فشل جلب معلومات التطبيق للتفويض", diff --git a/web/i18n/locales/az-AZ/oauth.json b/web/i18n/locales/az-AZ/oauth.json index 6c7220ee335..8462b6f9869 100644 --- a/web/i18n/locales/az-AZ/oauth.json +++ b/web/i18n/locales/az-AZ/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "İcazə vermək mümkün olmadı", + "callback.errorHint": "Əvvəlki səhifəyə qayıdıb yenidən cəhd edin.", + "callback.success": "İcazə verildi", + "callback.successHint": "İndi bu vərəqi bağlayıb əvvəlki səhifəyə qayıda bilərsiniz.", "connect": "Qoşulma:", "continue": "Davam et", "error.authAppInfoFetchFailed": "İcazə vermək üçün tətbiq məlumatlarını əldə etmək mümkün olmadı", diff --git a/web/i18n/locales/de-DE/oauth.json b/web/i18n/locales/de-DE/oauth.json index 65b6471e8e6..eeaa70e3a5b 100644 --- a/web/i18n/locales/de-DE/oauth.json +++ b/web/i18n/locales/de-DE/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Autorisierung fehlgeschlagen", + "callback.errorHint": "Bitte kehren Sie zur vorherigen Seite zurück und versuchen Sie es erneut.", + "callback.success": "Autorisierung abgeschlossen", + "callback.successHint": "Sie können diesen Tab jetzt schließen und zur vorherigen Seite zurückkehren.", "connect": "Verbinden mit", "continue": "Fortsetzen", "error.authAppInfoFetchFailed": "Fehler beim Abrufen der App-Informationen für die Autorisierung", diff --git a/web/i18n/locales/en-US/oauth.json b/web/i18n/locales/en-US/oauth.json index 6383839b9f9..09701ccd714 100644 --- a/web/i18n/locales/en-US/oauth.json +++ b/web/i18n/locales/en-US/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Authorization failed", + "callback.errorHint": "Please return to the previous page and try again.", + "callback.success": "Authorization complete", + "callback.successHint": "You can now close this tab and return to the previous page.", "connect": "Connect to", "continue": "Continue", "error.authAppInfoFetchFailed": "Failed to fetch app info for authorization", diff --git a/web/i18n/locales/es-ES/oauth.json b/web/i18n/locales/es-ES/oauth.json index e54fc7fa4ac..e0f06dc1ac5 100644 --- a/web/i18n/locales/es-ES/oauth.json +++ b/web/i18n/locales/es-ES/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "La autorización falló", + "callback.errorHint": "Vuelve a la página anterior e inténtalo de nuevo.", + "callback.success": "Autorización completada", + "callback.successHint": "Ya puedes cerrar esta pestaña y volver a la página anterior.", "connect": "Conectar a", "continue": "Continuar", "error.authAppInfoFetchFailed": "No se pudo obtener la información de la aplicación para la autorización", diff --git a/web/i18n/locales/fa-IR/oauth.json b/web/i18n/locales/fa-IR/oauth.json index 1a0116d61cb..1fbcfe291e3 100644 --- a/web/i18n/locales/fa-IR/oauth.json +++ b/web/i18n/locales/fa-IR/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "مجوزدهی ناموفق بود", + "callback.errorHint": "لطفاً به صفحه قبلی برگردید و دوباره تلاش کنید.", + "callback.success": "مجوزدهی تکمیل شد", + "callback.successHint": "اکنون می‌توانید این برگه را ببندید و به صفحه قبلی بازگردید.", "connect": "متصل به", "continue": "ادامه دهید", "error.authAppInfoFetchFailed": "عدم موفقیت در دریافت اطلاعات برنامه برای مجوز", diff --git a/web/i18n/locales/fr-FR/oauth.json b/web/i18n/locales/fr-FR/oauth.json index 7c82c92a831..482df6c7555 100644 --- a/web/i18n/locales/fr-FR/oauth.json +++ b/web/i18n/locales/fr-FR/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Échec de l’autorisation", + "callback.errorHint": "Veuillez revenir à la page précédente et réessayer.", + "callback.success": "Autorisation terminée", + "callback.successHint": "Vous pouvez maintenant fermer cet onglet et revenir à la page précédente.", "connect": "Se connecter à", "continue": "Continuer", "error.authAppInfoFetchFailed": "Échec de la récupération des informations de l'application pour l'autorisation", diff --git a/web/i18n/locales/hi-IN/oauth.json b/web/i18n/locales/hi-IN/oauth.json index b1a58fa0da9..0b99ae34457 100644 --- a/web/i18n/locales/hi-IN/oauth.json +++ b/web/i18n/locales/hi-IN/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "प्राधिकरण विफल", + "callback.errorHint": "कृपया पिछले पृष्ठ पर लौटें और फिर से प्रयास करें।", + "callback.success": "प्राधिकरण पूरा हुआ", + "callback.successHint": "अब आप इस टैब को बंद करके पिछले पृष्ठ पर लौट सकते हैं।", "connect": "संयोजित करें", "continue": "जारी रखें", "error.authAppInfoFetchFailed": "प्राधिकरण के लिए ऐप जानकारी प्राप्त करने में असफल हुआ", diff --git a/web/i18n/locales/id-ID/oauth.json b/web/i18n/locales/id-ID/oauth.json index 688c7873b55..163b9e6a911 100644 --- a/web/i18n/locales/id-ID/oauth.json +++ b/web/i18n/locales/id-ID/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Otorisasi gagal", + "callback.errorHint": "Kembali ke halaman sebelumnya dan coba lagi.", + "callback.success": "Otorisasi selesai", + "callback.successHint": "Anda sekarang dapat menutup tab ini dan kembali ke halaman sebelumnya.", "connect": "Hubungkan", "continue": "Lanjut", "error.authAppInfoFetchFailed": "Gagal mengambil info aplikasi untuk otorisasi", diff --git a/web/i18n/locales/it-IT/oauth.json b/web/i18n/locales/it-IT/oauth.json index 82b2c1d1f01..41da6019d1b 100644 --- a/web/i18n/locales/it-IT/oauth.json +++ b/web/i18n/locales/it-IT/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Autorizzazione non riuscita", + "callback.errorHint": "Torna alla pagina precedente e riprova.", + "callback.success": "Autorizzazione completata", + "callback.successHint": "Ora puoi chiudere questa scheda e tornare alla pagina precedente.", "connect": "Connetti a", "continue": "Continua", "error.authAppInfoFetchFailed": "Impossibile recuperare le informazioni sull'app per l'autorizzazione", diff --git a/web/i18n/locales/ja-JP/oauth.json b/web/i18n/locales/ja-JP/oauth.json index 14e2f0ddf48..970cb90d5f2 100644 --- a/web/i18n/locales/ja-JP/oauth.json +++ b/web/i18n/locales/ja-JP/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "認証に失敗しました", + "callback.errorHint": "前のページに戻ってもう一度お試しください。", + "callback.success": "認証が完了しました", + "callback.successHint": "このタブを閉じて前のページに戻ることができます。", "connect": "接続する", "continue": "続行", "error.authAppInfoFetchFailed": "認証のためのアプリ情報の取得に失敗しました", diff --git a/web/i18n/locales/ko-KR/oauth.json b/web/i18n/locales/ko-KR/oauth.json index af510c6014b..8c74ef9b0aa 100644 --- a/web/i18n/locales/ko-KR/oauth.json +++ b/web/i18n/locales/ko-KR/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "권한 부여 실패", + "callback.errorHint": "이전 페이지로 돌아가 다시 시도해 주세요.", + "callback.success": "권한 부여 완료", + "callback.successHint": "이제 이 탭을 닫고 이전 페이지로 돌아갈 수 있습니다.", "connect": "연결", "continue": "계속", "error.authAppInfoFetchFailed": "인증을 위한 앱 정보를 가져오지 못했습니다.", diff --git a/web/i18n/locales/lo-LA/oauth.json b/web/i18n/locales/lo-LA/oauth.json index d7aff0dac91..c876c46af11 100644 --- a/web/i18n/locales/lo-LA/oauth.json +++ b/web/i18n/locales/lo-LA/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "ການອະນຸມັດລົ້ມເຫຼວ", + "callback.errorHint": "ກະລຸນາກັບໄປໜ້າກ່ອນ ແລະລອງອີກຄັ້ງ.", + "callback.success": "ການອະນຸມັດສຳເລັດແລ້ວ", + "callback.successHint": "ຕອນນີ້ທ່ານສາມາດປິດແທັບນີ້ ແລະກັບໄປໜ້າກ່ອນໄດ້.", "connect": "ເຊື່ອມຕໍ່ກັບ", "continue": "ດຳເນີນການຕໍ່", "error.authAppInfoFetchFailed": "ບໍ່ສາມາດດຶງຂໍ້ມູນແອັບຯເພື່ອການອະນຸມັດໄດ້", diff --git a/web/i18n/locales/nl-NL/oauth.json b/web/i18n/locales/nl-NL/oauth.json index 6383839b9f9..5cfba8aa612 100644 --- a/web/i18n/locales/nl-NL/oauth.json +++ b/web/i18n/locales/nl-NL/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Autorisatie mislukt", + "callback.errorHint": "Ga terug naar de vorige pagina en probeer het opnieuw.", + "callback.success": "Autorisatie voltooid", + "callback.successHint": "Je kunt dit tabblad nu sluiten en teruggaan naar de vorige pagina.", "connect": "Connect to", "continue": "Continue", "error.authAppInfoFetchFailed": "Failed to fetch app info for authorization", diff --git a/web/i18n/locales/pl-PL/oauth.json b/web/i18n/locales/pl-PL/oauth.json index 71d8cef67ba..7dce4b6dd03 100644 --- a/web/i18n/locales/pl-PL/oauth.json +++ b/web/i18n/locales/pl-PL/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Autoryzacja nie powiodła się", + "callback.errorHint": "Wróć do poprzedniej strony i spróbuj ponownie.", + "callback.success": "Autoryzacja zakończona", + "callback.successHint": "Możesz teraz zamknąć tę kartę i wrócić do poprzedniej strony.", "connect": "Połącz z", "continue": "Kontynuuj", "error.authAppInfoFetchFailed": "Nie udało się pobrać informacji o aplikacji w celu autoryzacji", diff --git a/web/i18n/locales/pt-BR/oauth.json b/web/i18n/locales/pt-BR/oauth.json index 35ab3d43970..fed5471bb9b 100644 --- a/web/i18n/locales/pt-BR/oauth.json +++ b/web/i18n/locales/pt-BR/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Falha na autorização", + "callback.errorHint": "Volte à página anterior e tente novamente.", + "callback.success": "Autorização concluída", + "callback.successHint": "Agora você pode fechar esta aba e voltar à página anterior.", "connect": "Conectar a", "continue": "Continue", "error.authAppInfoFetchFailed": "Falha ao buscar informações do aplicativo para autorização", diff --git a/web/i18n/locales/ro-RO/oauth.json b/web/i18n/locales/ro-RO/oauth.json index 5bb5da3141c..5ff71ea653e 100644 --- a/web/i18n/locales/ro-RO/oauth.json +++ b/web/i18n/locales/ro-RO/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Autorizarea a eșuat", + "callback.errorHint": "Reveniți la pagina anterioară și încercați din nou.", + "callback.success": "Autorizare finalizată", + "callback.successHint": "Acum puteți închide această filă și reveni la pagina anterioară.", "connect": "Conectează la", "continue": "Continuați", "error.authAppInfoFetchFailed": "Nu s-au putut obține informațiile aplicației pentru autorizare", diff --git a/web/i18n/locales/ru-RU/oauth.json b/web/i18n/locales/ru-RU/oauth.json index ce742da416e..0ab11a6fdcf 100644 --- a/web/i18n/locales/ru-RU/oauth.json +++ b/web/i18n/locales/ru-RU/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Авторизация не удалась", + "callback.errorHint": "Вернитесь на предыдущую страницу и попробуйте ещё раз.", + "callback.success": "Авторизация завершена", + "callback.successHint": "Теперь вы можете закрыть эту вкладку и вернуться на предыдущую страницу.", "connect": "Подключиться к", "continue": "Продолжить", "error.authAppInfoFetchFailed": "Не удалось получить информацию об приложении для авторизации", diff --git a/web/i18n/locales/sl-SI/oauth.json b/web/i18n/locales/sl-SI/oauth.json index 0d6aec23930..e7964710a67 100644 --- a/web/i18n/locales/sl-SI/oauth.json +++ b/web/i18n/locales/sl-SI/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Avtorizacija ni uspela", + "callback.errorHint": "Vrnite se na prejšnjo stran in poskusite znova.", + "callback.success": "Avtorizacija je zaključena", + "callback.successHint": "Zdaj lahko zaprete ta zavihek in se vrnete na prejšnjo stran.", "connect": "Poveži se z", "continue": "Nadaljuj", "error.authAppInfoFetchFailed": "Pridobivanje informacij o aplikaciji za avtorizacijo ni uspelo", diff --git a/web/i18n/locales/th-TH/oauth.json b/web/i18n/locales/th-TH/oauth.json index 4b732876503..9822b6ec6ac 100644 --- a/web/i18n/locales/th-TH/oauth.json +++ b/web/i18n/locales/th-TH/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "การอนุญาตล้มเหลว", + "callback.errorHint": "โปรดกลับไปยังหน้าก่อนหน้าแล้วลองอีกครั้ง", + "callback.success": "การอนุญาตเสร็จสมบูรณ์", + "callback.successHint": "ตอนนี้คุณสามารถปิดแท็บนี้และกลับไปยังหน้าก่อนหน้าได้", "connect": "เชื่อมต่อกับ", "continue": "ดำเนินต่อไป", "error.authAppInfoFetchFailed": "ไม่สามารถดึงข้อมูลแอปเพื่อการอนุญาตได้", diff --git a/web/i18n/locales/tr-TR/oauth.json b/web/i18n/locales/tr-TR/oauth.json index 0a23e04a960..bb6cffcbe44 100644 --- a/web/i18n/locales/tr-TR/oauth.json +++ b/web/i18n/locales/tr-TR/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Yetkilendirme başarısız", + "callback.errorHint": "Lütfen önceki sayfaya dönüp tekrar deneyin.", + "callback.success": "Yetkilendirme tamamlandı", + "callback.successHint": "Artık bu sekmeyi kapatıp önceki sayfaya dönebilirsiniz.", "connect": "Bağlan", "continue": "Devam et", "error.authAppInfoFetchFailed": "Yetkilendirme için uygulama bilgisi alınamadı", diff --git a/web/i18n/locales/uk-UA/oauth.json b/web/i18n/locales/uk-UA/oauth.json index 28028304522..b3f078939bb 100644 --- a/web/i18n/locales/uk-UA/oauth.json +++ b/web/i18n/locales/uk-UA/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Авторизація не вдалася", + "callback.errorHint": "Поверніться на попередню сторінку та спробуйте ще раз.", + "callback.success": "Авторизацію завершено", + "callback.successHint": "Тепер ви можете закрити цю вкладку та повернутися на попередню сторінку.", "connect": "Підключитися до", "continue": "Продовжувати", "error.authAppInfoFetchFailed": "Не вдалося отримати інформацію про додаток для авторизації", diff --git a/web/i18n/locales/vi-VN/oauth.json b/web/i18n/locales/vi-VN/oauth.json index 88355688f28..535cf3710c4 100644 --- a/web/i18n/locales/vi-VN/oauth.json +++ b/web/i18n/locales/vi-VN/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "Ủy quyền không thành công", + "callback.errorHint": "Vui lòng quay lại trang trước và thử lại.", + "callback.success": "Ủy quyền hoàn tất", + "callback.successHint": "Bây giờ bạn có thể đóng tab này và quay lại trang trước.", "connect": "Kết nối với", "continue": "Tiếp tục", "error.authAppInfoFetchFailed": "Không thể lấy thông tin ứng dụng để xác thực", diff --git a/web/i18n/locales/zh-Hans/oauth.json b/web/i18n/locales/zh-Hans/oauth.json index 05b8a47aea9..0d39f9098db 100644 --- a/web/i18n/locales/zh-Hans/oauth.json +++ b/web/i18n/locales/zh-Hans/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "授权失败", + "callback.errorHint": "请返回上一页并重试。", + "callback.success": "授权完成", + "callback.successHint": "现在可以关闭此标签页并返回上一页。", "connect": "连接到", "continue": "继续", "error.authAppInfoFetchFailed": "获取待授权应用的信息失败", diff --git a/web/i18n/locales/zh-Hant/oauth.json b/web/i18n/locales/zh-Hant/oauth.json index 66675c77f44..61ce543cead 100644 --- a/web/i18n/locales/zh-Hant/oauth.json +++ b/web/i18n/locales/zh-Hant/oauth.json @@ -1,4 +1,8 @@ { + "callback.error": "授權失敗", + "callback.errorHint": "請返回上一頁並重試。", + "callback.success": "授權完成", + "callback.successHint": "現在可以關閉此分頁並返回上一頁。", "connect": "連接到", "continue": "繼續", "error.authAppInfoFetchFailed": "無法獲取應用程式授權信息", diff --git a/web/i18n/resources.generated.d.ts b/web/i18n/resources.generated.d.ts index 86dd9c7b6fc..830ffe28045 100644 --- a/web/i18n/resources.generated.d.ts +++ b/web/i18n/resources.generated.d.ts @@ -3467,6 +3467,10 @@ export default interface Resources { 'toolsPage.toolPlugin': 'Tool Plugin' } oauth: { + 'callback.error': 'Authorization failed' + 'callback.errorHint': 'Please return to the previous page and try again.' + 'callback.success': 'Authorization complete' + 'callback.successHint': 'You can now close this tab and return to the previous page.' connect: 'Connect to' continue: 'Continue' 'error.authAppInfoFetchFailed': 'Failed to fetch app info for authorization'