mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
fix(web): render a message in OAuth callback without opener (#43053)
Co-authored-by: Taranum01 <taranumwasu@users.noreply.github.com> Co-authored-by: Taranum01 <taranum01@users.noreply.github.com>
This commit is contained in:
31 files changed
+415
-42
No files matched your search
@@ -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
|
||||
|
||||
@@ -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 <div /> 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(<OAuthCallback />)
|
||||
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(<OAuthCallback />)
|
||||
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(<OAuthCallback />)
|
||||
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(<OAuthCallback />)
|
||||
expect(screen.getByText('Authorization failed')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('Please return to the previous page and try again.'),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -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 <div />
|
||||
// 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 <div />
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center bg-background-default-subtle p-6">
|
||||
<div className="w-full max-w-md rounded-2xl border border-components-panel-border bg-components-panel-bg p-8 text-center shadow-sm">
|
||||
<h1 className="text-xl font-semibold text-text-primary">
|
||||
{error
|
||||
? t(($) => $['callback.error'], { ns: 'oauth' })
|
||||
: t(($) => $['callback.success'], { ns: 'oauth' })}
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-text-secondary">
|
||||
{error
|
||||
? errorDescription || t(($) => $['callback.errorHint'], { ns: 'oauth' })
|
||||
: t(($) => $['callback.successHint'], { ns: 'oauth' })}
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default OAuthCallback
|
||||
@@ -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 <div />.
|
||||
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 <div>{state.finished ? 'finished' : 'waiting'}</div>
|
||||
}
|
||||
vi.stubGlobal('window', undefined)
|
||||
try {
|
||||
expect(renderToString(<ServerCallback />)).toContain('waiting')
|
||||
} finally {
|
||||
vi.unstubAllGlobals()
|
||||
}
|
||||
})
|
||||
})
|
||||
+61
-34
@@ -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
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "فشل التفويض",
|
||||
"callback.errorHint": "يرجى العودة إلى الصفحة السابقة والمحاولة مرة أخرى.",
|
||||
"callback.success": "اكتمل التفويض",
|
||||
"callback.successHint": "يمكنك الآن إغلاق علامة التبويب هذه والعودة إلى الصفحة السابقة.",
|
||||
"connect": "الاتصال بـ",
|
||||
"continue": "متابعة",
|
||||
"error.authAppInfoFetchFailed": "فشل جلب معلومات التطبيق للتفويض",
|
||||
|
||||
@@ -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ı",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "مجوزدهی ناموفق بود",
|
||||
"callback.errorHint": "لطفاً به صفحه قبلی برگردید و دوباره تلاش کنید.",
|
||||
"callback.success": "مجوزدهی تکمیل شد",
|
||||
"callback.successHint": "اکنون میتوانید این برگه را ببندید و به صفحه قبلی بازگردید.",
|
||||
"connect": "متصل به",
|
||||
"continue": "ادامه دهید",
|
||||
"error.authAppInfoFetchFailed": "عدم موفقیت در دریافت اطلاعات برنامه برای مجوز",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "प्राधिकरण विफल",
|
||||
"callback.errorHint": "कृपया पिछले पृष्ठ पर लौटें और फिर से प्रयास करें।",
|
||||
"callback.success": "प्राधिकरण पूरा हुआ",
|
||||
"callback.successHint": "अब आप इस टैब को बंद करके पिछले पृष्ठ पर लौट सकते हैं।",
|
||||
"connect": "संयोजित करें",
|
||||
"continue": "जारी रखें",
|
||||
"error.authAppInfoFetchFailed": "प्राधिकरण के लिए ऐप जानकारी प्राप्त करने में असफल हुआ",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "認証に失敗しました",
|
||||
"callback.errorHint": "前のページに戻ってもう一度お試しください。",
|
||||
"callback.success": "認証が完了しました",
|
||||
"callback.successHint": "このタブを閉じて前のページに戻ることができます。",
|
||||
"connect": "接続する",
|
||||
"continue": "続行",
|
||||
"error.authAppInfoFetchFailed": "認証のためのアプリ情報の取得に失敗しました",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "권한 부여 실패",
|
||||
"callback.errorHint": "이전 페이지로 돌아가 다시 시도해 주세요.",
|
||||
"callback.success": "권한 부여 완료",
|
||||
"callback.successHint": "이제 이 탭을 닫고 이전 페이지로 돌아갈 수 있습니다.",
|
||||
"connect": "연결",
|
||||
"continue": "계속",
|
||||
"error.authAppInfoFetchFailed": "인증을 위한 앱 정보를 가져오지 못했습니다.",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "ການອະນຸມັດລົ້ມເຫຼວ",
|
||||
"callback.errorHint": "ກະລຸນາກັບໄປໜ້າກ່ອນ ແລະລອງອີກຄັ້ງ.",
|
||||
"callback.success": "ການອະນຸມັດສຳເລັດແລ້ວ",
|
||||
"callback.successHint": "ຕອນນີ້ທ່ານສາມາດປິດແທັບນີ້ ແລະກັບໄປໜ້າກ່ອນໄດ້.",
|
||||
"connect": "ເຊື່ອມຕໍ່ກັບ",
|
||||
"continue": "ດຳເນີນການຕໍ່",
|
||||
"error.authAppInfoFetchFailed": "ບໍ່ສາມາດດຶງຂໍ້ມູນແອັບຯເພື່ອການອະນຸມັດໄດ້",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "Авторизация не удалась",
|
||||
"callback.errorHint": "Вернитесь на предыдущую страницу и попробуйте ещё раз.",
|
||||
"callback.success": "Авторизация завершена",
|
||||
"callback.successHint": "Теперь вы можете закрыть эту вкладку и вернуться на предыдущую страницу.",
|
||||
"connect": "Подключиться к",
|
||||
"continue": "Продолжить",
|
||||
"error.authAppInfoFetchFailed": "Не удалось получить информацию об приложении для авторизации",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "การอนุญาตล้มเหลว",
|
||||
"callback.errorHint": "โปรดกลับไปยังหน้าก่อนหน้าแล้วลองอีกครั้ง",
|
||||
"callback.success": "การอนุญาตเสร็จสมบูรณ์",
|
||||
"callback.successHint": "ตอนนี้คุณสามารถปิดแท็บนี้และกลับไปยังหน้าก่อนหน้าได้",
|
||||
"connect": "เชื่อมต่อกับ",
|
||||
"continue": "ดำเนินต่อไป",
|
||||
"error.authAppInfoFetchFailed": "ไม่สามารถดึงข้อมูลแอปเพื่อการอนุญาตได้",
|
||||
|
||||
@@ -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ı",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "Авторизація не вдалася",
|
||||
"callback.errorHint": "Поверніться на попередню сторінку та спробуйте ще раз.",
|
||||
"callback.success": "Авторизацію завершено",
|
||||
"callback.successHint": "Тепер ви можете закрити цю вкладку та повернутися на попередню сторінку.",
|
||||
"connect": "Підключитися до",
|
||||
"continue": "Продовжувати",
|
||||
"error.authAppInfoFetchFailed": "Не вдалося отримати інформацію про додаток для авторизації",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "授权失败",
|
||||
"callback.errorHint": "请返回上一页并重试。",
|
||||
"callback.success": "授权完成",
|
||||
"callback.successHint": "现在可以关闭此标签页并返回上一页。",
|
||||
"connect": "连接到",
|
||||
"continue": "继续",
|
||||
"error.authAppInfoFetchFailed": "获取待授权应用的信息失败",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"callback.error": "授權失敗",
|
||||
"callback.errorHint": "請返回上一頁並重試。",
|
||||
"callback.success": "授權完成",
|
||||
"callback.successHint": "現在可以關閉此分頁並返回上一頁。",
|
||||
"connect": "連接到",
|
||||
"continue": "繼續",
|
||||
"error.authAppInfoFetchFailed": "無法獲取應用程式授權信息",
|
||||
|
||||
Vendored
+4
@@ -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'
|
||||
|
||||
Reference in new issue
Block a user