Add missing translations (#2089)

* Add missing translations

* Add eslint-plugin-i18next plugin

* Add missing translation

* Update translations

* Update ESLint rules and improve UI text

* Update WellKnownURLs locales

* Add server-side translations in SetupLinkIndexPage
This commit is contained in:
Kiran K 2023-12-27 17:51:53 +05:30 committed by GitHub
parent 1525035092
commit fde4e59fa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 239 additions and 113 deletions

View File

@ -9,18 +9,20 @@ module.exports = {
},
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
plugins: ['@typescript-eslint', 'i18next'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'next/core-web-vitals',
'plugin:i18next/recommended',
],
overrides: [
{
files: ['*.ts', '*.tsx'],
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'import/no-anonymous-default-export': 'off',
},
},
{

View File

@ -1,8 +1,12 @@
import { useTranslation } from 'next-i18next';
export const PoweredBy = () => {
const { t } = useTranslation('common');
return (
<p className='text-center text-xs text-gray-500 py-5'>
<a href='https://boxyhq.com/' target='_blank' rel='noopener noreferrer'>
Powered by BoxyHQ
{t('boxyhq_powered_by')}
</a>
</p>
);

View File

@ -165,7 +165,7 @@ export const Sidebar = ({ isOpen, setIsOpen }: SidebarProps) => {
<div className='flex flex-shrink-0 items-center px-4'>
<Link href='/' className='flex items-center'>
<Image src={Logo} alt='BoxyHQ' width={36} height={36} className='h-8 w-auto' />
<span className='ml-4 text-xl font-bold text-gray-900'>BoxyHQ Admin Portal</span>
<span className='ml-4 text-xl font-bold text-gray-900'>{t('boxyhq_admin_portal')}</span>
</Link>
</div>
<div className='mt-5 h-0 flex-1 overflow-y-auto'>
@ -182,7 +182,7 @@ export const Sidebar = ({ isOpen, setIsOpen }: SidebarProps) => {
<div className='flex flex-shrink-0 items-center px-4'>
<Link href='/' className='flex items-center'>
<Image src={Logo} alt='BoxyHQ' width={36} height={36} className='h-8 w-auto' />
<span className='ml-4 text-lg font-bold text-gray-900'>BoxyHQ Admin Portal</span>
<span className='ml-4 text-lg font-bold text-gray-900'>{t('boxyhq_admin_portal')}</span>
</Link>
</div>
<div className='mt-5 flex flex-1 flex-col'>

View File

@ -13,49 +13,49 @@ const WellKnownURLs = () => {
const links = [
{
title: 'SP Metadata',
title: t('sp_metadata'),
description: t('sp_metadata_description'),
href: '/.well-known/sp-metadata',
buttonText: viewText,
type: 'idp-config',
},
{
title: 'SAML Configuration',
title: t('saml_configuration'),
description: t('sp_config_description'),
href: '/.well-known/saml-configuration',
buttonText: viewText,
type: 'idp-config',
},
{
title: 'SAML Public Certificate',
title: t('saml_public_cert'),
description: t('saml_public_cert_description'),
href: '/.well-known/saml.cer',
buttonText: downloadText,
type: 'idp-config',
},
{
title: 'OpenID Configuration',
title: t('oidc_configuration'),
description: t('oidc_config_description'),
href: '/.well-known/oidc-configuration',
buttonText: viewText,
type: 'idp-config',
},
{
title: 'OpenID Connect Discovery',
title: t('oidc_discovery'),
description: t('oidc_discovery_description'),
href: '/.well-known/openid-configuration',
buttonText: viewText,
type: 'auth',
},
{
title: 'IdP Metadata',
title: t('idp_metadata'),
description: t('idp_metadata_description'),
href: '/.well-known/idp-metadata',
buttonText: viewText,
type: 'saml-fed',
},
{
title: 'IdP Configuration',
title: t('idp_configuration'),
description: t('idp_config_description'),
href: '/.well-known/idp-configuration',
buttonText: viewText,
@ -74,23 +74,23 @@ const WellKnownURLs = () => {
<Tab
isActive={view === 'idp-config'}
setIsActive={() => setView('idp-config')}
title='Identity Provider Configuration'
description='Links for SAML/OIDC IdP setup'
label='Identity Provider Configuration links'
title={t('idp_configuration_title')}
description={t('idp_configuration_description')}
label={t('idp_configuration_label')}
/>
<Tab
isActive={view === 'auth'}
setIsActive={() => setView('auth')}
title='Auth integration'
description='Links for OAuth 2.0/OpenID Connect auth'
label='Auth integration links'
title={t('auth_integration_title')}
description={t('auth_integration_description')}
label={t('auth_integration_label')}
/>
<Tab
isActive={view === 'saml-fed'}
setIsActive={() => setView('saml-fed')}
title='SAML Federation'
description='Links for SAML Federation app setup'
label='SAML Federation links'
title={t('saml_fed_configuration_title')}
description={t('saml_fed_configuration_description')}
label={t('saml_fed_configuration_label')}
/>
</div>
<div className='space-y-3 mt-8'>

View File

@ -1,6 +1,7 @@
import Link from 'next/link';
import type { Directory } from '@boxyhq/saml-jackson';
import classNames from 'classnames';
import { useTranslation } from 'next-i18next';
const DirectoryTab = ({
directory,
@ -11,32 +12,34 @@ const DirectoryTab = ({
activeTab: string;
setupLinkToken?: string;
}) => {
const { t } = useTranslation('common');
const menus = setupLinkToken
? [
{
name: 'Directory',
name: t('directory'),
href: `/setup/${setupLinkToken}/directory-sync/${directory.id}`,
active: activeTab === 'directory',
},
]
: [
{
name: 'Directory',
name: t('directory'),
href: `/admin/directory-sync/${directory.id}`,
active: activeTab === 'directory',
},
{
name: 'Users',
name: t('users'),
href: `/admin/directory-sync/${directory.id}/users`,
active: activeTab === 'users',
},
{
name: 'Groups',
name: t('groups'),
href: `/admin/directory-sync/${directory.id}/groups`,
active: activeTab === 'groups',
},
{
name: 'Webhook Events',
name: t('webhook_events'),
href: `/admin/directory-sync/${directory.id}/events`,
active: activeTab === 'events',
},

View File

@ -20,7 +20,7 @@ export const AccountLayout = ({ children }: { children: React.ReactNode }) => {
return (
<>
<Head>
<title>Admin Portal | BoxyHQ</title>
<title>{t('boxyhq_admin_portal')}</title>
<link rel='icon' href='/favicon.ico' />
</Head>
<Sidebar isOpen={isOpen} setIsOpen={setIsOpen} />

View File

@ -3,9 +3,11 @@ import classNames from 'classnames';
import { useRouter } from 'next/router';
import { successToast, errorToast } from '@components/Toaster';
import { LinkBack } from '@components/LinkBack';
import { useTranslation } from 'next-i18next';
const AddProject = () => {
const router = useRouter();
const { t } = useTranslation('common');
const [loading, setLoading] = useState(false);
const [project, setProject] = useState({
@ -49,7 +51,7 @@ const AddProject = () => {
}
if (data && data.project) {
successToast('Project created successfully.');
successToast(t('retraced_project_created'));
router.replace(`/admin/retraced/projects/${data.project.id}`);
}
};
@ -58,13 +60,15 @@ const AddProject = () => {
<>
<LinkBack href='/admin/retraced/projects' />
<div className='mt-5'>
<h2 className='mb-5 mt-5 font-bold text-gray-700 dark:text-white md:text-xl'>Create Project</h2>
<h2 className='mb-5 mt-5 font-bold text-gray-700 dark:text-white md:text-xl'>
{t('create_project')}
</h2>
<div className='min-w-[28rem] rounded border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800 md:w-3/4 md:max-w-lg'>
<form onSubmit={createProject}>
<div className='flex flex-col space-y-3'>
<div className='form-control w-full'>
<label className='label'>
<span className='label-text'>Project name</span>
<span className='label-text'>{t('project_name')}</span>
</label>
<input
type='text'
@ -76,7 +80,7 @@ const AddProject = () => {
</div>
<div>
<button className={classNames('btn-primary btn', loading ? 'loading' : '')}>
Create Project
{t('create_project')}
</button>
</div>
</div>

View File

@ -1,5 +1,6 @@
import RetracedEventsBrowser from '@retracedhq/logs-viewer';
import useSWR from 'swr';
import { useTranslation } from 'next-i18next';
import type { ApiError, ApiSuccess } from 'types';
import type { Project } from 'types/retraced';
@ -8,6 +9,8 @@ import Loading from '@components/Loading';
import { fetcher } from '@lib/ui/utils';
const LogsViewer = (props: { project: Project; environmentId: string; groupId: string; host: string }) => {
const { t } = useTranslation('common');
const { project, environmentId, groupId, host } = props;
const token = project.tokens.filter((token) => token.environment_id === environmentId)[0];
@ -36,7 +39,7 @@ const LogsViewer = (props: { project: Project; environmentId: string; groupId: s
<RetracedEventsBrowser
host={`${host}/viewer/v1`}
auditLogToken={viewerToken}
header='Audit Logs'
header={t('audit_logs')}
customClass={'text-primary dark:text-white'}
skipViewLogEvent={true}
/>

View File

@ -19,7 +19,7 @@ const ProjectDetails = (props: { project: Project; host?: string }) => {
<>
<div className='form-control mb-5 max-w-xs'>
<label className='label pl-0'>
<span className='label-text'>Environment</span>
<span className='label-text'>{t('environment')}</span>
</label>
<Select
value={selectedIndex}

View File

@ -1,9 +1,10 @@
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { ButtonPrimary } from '@components/ButtonPrimary';
const NextButton = () => {
const router = useRouter();
const { t } = useTranslation('common');
const onClick = () => {
const { idp, step, token } = router.query as { idp: string; step: string; token: string };
@ -20,7 +21,7 @@ const NextButton = () => {
return (
<div>
<ButtonPrimary onClick={onClick}>Next Step</ButtonPrimary>
<ButtonPrimary onClick={onClick}>{t('next_step')}</ButtonPrimary>
</div>
);
};

View File

@ -1,9 +1,10 @@
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { ButtonOutline } from '@components/ButtonOutline';
const PreviousButton = () => {
const router = useRouter();
const { t } = useTranslation('common');
const onClick = () => {
const { idp, step, token } = router.query as { idp: string; step: string; token: string };
@ -20,7 +21,7 @@ const PreviousButton = () => {
return (
<div>
<ButtonOutline onClick={onClick}>Previous Step</ButtonOutline>
<ButtonOutline onClick={onClick}>{t('previous_step')}</ButtonOutline>
</div>
);
};

View File

@ -196,7 +196,7 @@ const CreateSetupLink = ({ service }: { service: SetupLinkService }) => {
<textarea
id={'redirectUrl'}
name='redirectUrl'
placeholder={'Allowed redirect URLs (newline separated)'}
placeholder={t('allowed_redirect_url')}
value={formObj['redirectUrl']}
required
onChange={handleChange}

View File

@ -1,11 +1,12 @@
import { useTranslation } from 'next-i18next';
const InvalidSetupLinkAlert = ({ message }: { message: string }) => {
const { t } = useTranslation('common');
return (
<div className='flex flex-col gap-3 rounded border border-error p-4'>
<h3 className='text-base font-medium'>{message}</h3>
<p className='leading-6'>
Please contact your administrator to get a new setup link. If you are the administrator, visit the
Admin Portal to create a new setup link for the service.
</p>
<p className='leading-6'>{t('invalid_setup_link_alert')}</p>
</div>
);
};

View File

@ -23,13 +23,10 @@ export const SetupLinkInfo = ({ setupLink, visible, onClose }: SetupLinkInfoProp
title={`Setup link info: tenant '${setupLink.tenant}', product '${setupLink.product}'`}>
<div className='mt-2 flex flex-col gap-3'>
<div>
<InputWithCopyButton
text={setupLink.url}
label='Share this link with your customer to setup their service'
/>
<InputWithCopyButton text={setupLink.url} label={t('share_setup_link')} />
</div>
<p className='text-sm'>
This link is valid till{' '}
{t('setup_link_valid_till')}{' '}
<span className={new Date(setupLink.validTill) < new Date() ? 'text-red-400' : ''}>
{new Date(setupLink.validTill).toString()}
</span>

View File

@ -113,11 +113,11 @@ function BlocklyComponent(props) {
/>
</div>
<div className='mb-6 w-full px-3 md:mb-0 md:w-1/3'>
<ButtonPrimary onClick={uploadModel}>Publish Model</ButtonPrimary>
<ButtonPrimary onClick={uploadModel}>{t('publish_model')}</ButtonPrimary>
</div>
<div className='mb-6 w-full px-3 md:mb-0 md:w-1/3'>
<ButtonBase color='secondary' onClick={toggleRetrieveConfirm}>
Retrieve Model
{t('retrieve_model')}
</ButtonBase>
</div>
</div>

View File

@ -162,10 +162,7 @@ const UpdateApp = ({ hasValidLicense }: { hasValidLicense: boolean }) => {
/>
</div>
<div className='pt-4'>
<p className='text-base leading-6 text-gray-500'>
You can customize the look and feel Identity Provider selection page by setting following
options:
</p>
<p className='text-base leading-6 text-gray-500'>{t('customize_branding')}:</p>
</div>
<div className='form-control w-full md:w-1/2'>
<label className='label'>

View File

@ -171,6 +171,8 @@
"assertion_encryption": "Assertion Encryption",
"sp_saml_config_title": "Service Provider (SP) SAML Configuration",
"sp_saml_config_description": "Your Identity Provider (IdP) will ask for the following information while configuring the SAML application. Share this information with your IT administrator.",
"refer_to_provider_instructions": "Refer to our <guideLink>guides</guideLink> for provider specific instructions.",
"sp_download_our_public_cert": "If you want to encrypt the assertion, you can <downloadLink>download our public certificate.</downloadLink> Otherwise select the Unencrypted option.",
"sp_oidc_config_title": "Service Provider (SP) OIDC Configuration",
"sp_oidc_config_description": "Your Identity Provider (IdP) will ask for the following information while configuring the OIDC application. Share this information with your IT administrator.",
"password": "Password",
@ -214,5 +216,52 @@
"directory_domain": "Directory Domain",
"dsync_google_auth_url": "The URL that you will need to authorize the application to access your Google Directory.",
"show_secret": "Show secret",
"hide_secret": "Hide secret"
"hide_secret": "Hide secret",
"directory": "Directory",
"users": "Users",
"groups": "Groups",
"webhook_events": "Webhook Events",
"sp_metadata": "SP Metadata",
"saml_configuration": "SAML Configuration",
"saml_public_cert": "SAML Public Certificate",
"oidc_configuration": "OpenID Configuration",
"oidc_discovery": "OpenID Connect Discovery",
"idp_metadata": "IdP Metadata",
"idp_configuration": "IdP Configuration",
"idp_configuration_title": "Identity Provider Configuration",
"idp_configuration_description": "Links for SAML/OIDC IdP setup",
"idp_configuration_label": "Identity Provider Configuration links",
"auth_integration_title": "Auth integration",
"auth_integration_description": "Links for OAuth 2.0/OpenID Connect auth",
"auth_integration_label": "Auth integration links",
"saml_fed_configuration_title": "SAML Federation",
"saml_fed_configuration_description": "Links for SAML Federation app setup",
"saml_fed_configuration_label": "SAML Federation links",
"retraced_project_created": "Project created successfully",
"project_name": "Project name",
"create_project": "Create Project",
"share_setup_link": "Share this link with your customer to setup their service",
"setup_link_valid_till": "This link is valid till",
"invalid_setup_link_alert": "Please contact your administrator to get a new setup link. If you are the administrator, visit the Admin Portal to create a new setup link for the service.",
"boxyhq_admin_portal": "BoxyHQ Admin Portal",
"environment:": "Environment",
"group_or_tenant": "Group (Tenant)",
"id": "Id",
"created_at": "Created At",
"previous_step": "Previous Step",
"next_step": "Next Step",
"boxyhq_powered_by": "Powered by BoxyHQ",
"publish_model": "Publish Model",
"retrieve_model": "Retrieve Model",
"guides": "guides",
"learn_to_enable_auth_methods": "Please visit <docLink>BoxyHQ documentation</docLink> to learn how to enable the Magic Link or Email/Password authentication methods.",
"advanced_sp_saml_configuration": "Advanced Service Provider (SP) SAML Configuration",
"select_identity_provider": "Select Identity Provider",
"configure_identity_provider": "Configure {{provider}}",
"change_identity_provider": "Change Identity Provider",
"invalid_request_try_again": "Invalid request. Please try again.",
"choose_an_identity_provider_to_continue": "Choose an Identity Provider to continue. If you don't see your Identity Provider, please contact your administrator.",
"choose_an_app_to_continue": "Choose an app to continue. If you don't see your app, please contact your administrator.",
"no_saml_response_try_again": "No SAMLResponse found. Please try again.",
"customize_branding": "You can customize the look and feel Identity Provider selection page by setting following options"
}

23
package-lock.json generated
View File

@ -56,6 +56,7 @@
"eslint": "8.56.0",
"eslint-config-next": "14.0.4",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-i18next": "6.0.3",
"postcss": "8.4.32",
"prettier": "3.1.1",
"prettier-plugin-tailwindcss": "0.5.9",
@ -7748,6 +7749,19 @@
"ms": "^2.1.1"
}
},
"node_modules/eslint-plugin-i18next": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-i18next/-/eslint-plugin-i18next-6.0.3.tgz",
"integrity": "sha512-RtQXYfg6PZCjejIQ/YG+dUj/x15jPhufJ9hUDGH0kCpJ6CkVMAWOQ9exU1CrbPmzeykxLjrXkjAaOZF/V7+DOA==",
"dev": true,
"dependencies": {
"lodash": "^4.17.21",
"requireindex": "~1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/eslint-plugin-import": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz",
@ -16871,6 +16885,15 @@
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"node_modules/requireindex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz",
"integrity": "sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==",
"dev": true,
"engines": {
"node": ">=0.10.5"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",

View File

@ -100,6 +100,7 @@
"eslint": "8.56.0",
"eslint-config-next": "14.0.4",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-i18next": "6.0.3",
"postcss": "8.4.32",
"prettier": "3.1.1",
"prettier-plugin-tailwindcss": "0.5.9",

View File

@ -4,7 +4,7 @@ import { useRouter } from 'next/router';
import Link from 'next/link';
import Image from 'next/image';
import { useSession, getCsrfToken, signIn, SessionProvider } from 'next-auth/react';
import { useTranslation } from 'next-i18next';
import { useTranslation, Trans } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { errorToast, successToast } from '@components/Toaster';
import { ButtonOutline } from '@components/ButtonOutline';
@ -114,7 +114,9 @@ const Login = ({
<div className='flex justify-center'>
<Image src='/logo.png' alt='BoxyHQ logo' width={50} height={50} />
</div>
<h2 className='text-center text-3xl font-extrabold text-gray-900'>BoxyHQ Admin Portal</h2>
<h2 className='text-center text-3xl font-extrabold text-gray-900'>
{t('boxyhq_admin_portal')}
</h2>
<p className='text-center text-sm text-gray-600'>{t('boxyhq_tagline')}</p>
</div>
@ -170,17 +172,21 @@ const Login = ({
{/* No login methods enabled */}
{!isEmailPasswordEnabled && !isMagicLinkEnabled && (
<div className='mt-10 text-center font-medium text-gray-600'>
<p>
Please visit&nbsp;
<a
href='https://boxyhq.com/docs/admin-portal/overview'
target='_blank'
rel='noopener noreferrer'
className='underline underline-offset-2'>
BoxyHQ documentation
</a>
&nbsp;to learn how to enable the Magic Link or Email/Password authentication methods.
</p>
<Trans
i18nKey='learn_to_enable_auth_methods'
t={t}
components={{
docLink: (
<a
href='https://boxyhq.com/docs/admin-portal/overview'
target='_blank'
rel='noopener noreferrer'
className='underline underline-offset-2'>
{t('documentation')}
</a>
),
}}
/>
</div>
)}

View File

@ -9,6 +9,7 @@ import ErrorMessage from '@components/Error';
import { LinkBack } from '@components/LinkBack';
import { Select } from 'react-daisyui';
import { retracedOptions } from '@lib/env';
import { useTranslation } from 'next-i18next';
const LogsViewer = dynamic(() => import('@components/retraced/LogsViewer'), {
ssr: false,
@ -20,6 +21,7 @@ export interface Props {
const Events: NextPage<Props> = ({ host }: Props) => {
const router = useRouter();
const { t } = useTranslation('common');
const [environment, setEnvironment] = useState('');
const [group, setGroup] = useState('');
@ -62,7 +64,7 @@ const Events: NextPage<Props> = ({ host }: Props) => {
<div className='flex space-x-2'>
<div className='form-control max-w-xs'>
<label className='label pl-0'>
<span className='label-text'>Environment</span>
<span className='label-text'>{t('environment')}</span>
</label>
{project ? (
<Select
@ -81,7 +83,7 @@ const Events: NextPage<Props> = ({ host }: Props) => {
</div>
<div className='form-control max-w-xs'>
<label className='label pl-0'>
<span className='label-text'>Group (Tenant)</span>
<span className='label-text'>{t('group_or_tenant')}</span>
</label>
{groups ? (
<Select

View File

@ -32,7 +32,7 @@ const ProjectList: NextPage = () => {
return (
<div>
<div className='mb-5 flex items-center justify-between'>
<h2 className='font-bold text-gray-700 dark:text-white md:text-xl'>Projects</h2>
<h2 className='font-bold text-gray-700 dark:text-white md:text-xl'>{t('projects')}</h2>
<LinkPrimary Icon={PlusIcon} href={'/admin/retraced/projects/new'}>
{t('new_project')}
</LinkPrimary>
@ -46,16 +46,16 @@ const ProjectList: NextPage = () => {
<thead className='bg-gray-50 text-xs uppercase text-gray-700 dark:bg-gray-700 dark:text-gray-400'>
<tr>
<th scope='col' className='px-6 py-3'>
Name
{t('name')}
</th>
<th scope='col' className='px-6 py-3'>
Id
{t('id')}
</th>
<th scope='col' className='px-6 py-3'>
Created At
{t('created_at')}
</th>
<th scope='col' className='px-6 py-3'>
Actions
{t('actions')}
</th>
</tr>
</thead>

View File

@ -58,7 +58,7 @@ const SAMLTraceInspector: NextPage = () => {
<h3 className='text-base font-semibold leading-6 text-gray-900'>{t('trace_details')}</h3>
<p className='mt-1 flex max-w-2xl gap-6 text-sm text-gray-500'>
<span className='whitespace-nowrap'>
<span className='font-medium text-gray-500'>TraceID:</span>
<span className='font-medium text-gray-500'>{t('trace_id')}</span>
<span className='ml-2 font-bold text-gray-700'> {traceId}</span>
</span>
<span className='whitespace-nowrap'>

View File

@ -53,7 +53,7 @@ export default function ChooseIdPConnection({
{authFlow in selectors ? (
selectors[authFlow]
) : (
<p className='text-center text-sm text-slate-600'>Invalid request. Please try again.</p>
<p className='text-center text-sm text-slate-600'>{t('invalid_request_try_again')}</p>
)}
</div>
<div className='my-4'>
@ -98,10 +98,7 @@ const IdpSelector = ({ connections }: { connections: Connection[] }) => {
);
})}
</ul>
<p className='text-center text-sm text-slate-600'>
Choose an Identity Provider to continue. If you don&apos;t see your Identity Provider, please contact
your administrator.
</p>
<p className='text-center text-sm text-slate-600'>{t('choose_an_identity_provider_to_continue')}</p>
</>
);
};
@ -143,7 +140,7 @@ const AppSelector = ({
};
if (!SAMLResponse) {
return <p className='text-center text-sm text-slate-600'>No SAMLResponse found. Please try again.</p>;
return <p className='text-center text-sm text-slate-600'>{t('no_saml_response_try_again')}</p>;
}
return (
@ -171,9 +168,7 @@ const AppSelector = ({
})}
</ul>
</form>
<p className='text-center text-sm text-slate-600'>
Choose an app to continue. If you don&apos;t see your app, please contact your administrator.
</p>
<p className='text-center text-sm text-slate-600'>{t('choose_an_app_to_continue')}</p>
</>
);
};

View File

@ -3,6 +3,7 @@ import type { NextPage } from 'next';
import { useRouter } from 'next/router';
import Loading from '@components/Loading';
import useSetupLink from '@lib/ui/hooks/useSetupLink';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
const SetupLinkIndexPage: NextPage = () => {
const router = useRouter();
@ -29,4 +30,14 @@ const SetupLinkIndexPage: NextPage = () => {
return null;
};
export const getServerSideProps = async (context) => {
const { locale } = context;
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
},
};
};
export default SetupLinkIndexPage;

View File

@ -10,6 +10,7 @@ import type { GetServerSidePropsContext, InferGetServerSidePropsType } from 'nex
import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
import { ArrowsRightLeftIcon } from '@heroicons/react/24/outline';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';
import jackson from '@lib/jackson';
import { jacksonOptions } from '@lib/env';
@ -24,10 +25,12 @@ import SelectIdentityProviders from '@components/setup-link-instructions/SelectI
type NewConnectionProps = InferGetServerSidePropsType<typeof getServerSideProps>;
const AdvancedSPConfigLink = () => {
const { t } = useTranslation('common');
return (
<div className='py-2'>
<Link href='/.well-known/saml-configuration' target='_blank' className='underline-offset-4'>
<span className='text-xs'>Advanced Service Provider (SP) SAML Configuration</span>
<span className='text-xs'>{t('advanced_sp_saml_configuration')}</span>
</Link>
</div>
);
@ -72,6 +75,8 @@ const NewConnection = ({
publicCertUrl,
oidcCallbackUrl,
}: NewConnectionProps) => {
const { t } = useTranslation('common');
const linkSelectIdp = { pathname: '/setup/[token]/sso-connection/new', query: { token: setupLinkToken } };
const scope = {
@ -108,9 +113,9 @@ const NewConnection = ({
}
progress = (100 / selectedIdP.stepCount) * parseInt(step);
heading = `Configure ${selectedIdP?.name}`;
heading = t('configure_identity_provider', { provider: selectedIdP.name });
} else {
heading = 'Select Identity Provider';
heading = t('select_identity_provider');
}
return (
@ -121,7 +126,7 @@ const NewConnection = ({
{source && (
<Link className='btn btn-xs h-0' href={linkSelectIdp}>
<ArrowsRightLeftIcon className='w-5 h-5' />
Change Identity Provider
{t('change_identity_provider')}
</Link>
)}
</div>

View File

@ -1,6 +1,6 @@
import type { NextPage, InferGetStaticPropsType } from 'next';
import React from 'react';
import { useTranslation } from 'next-i18next';
import { useTranslation, Trans } from 'next-i18next';
import jackson from '@lib/jackson';
import { InputWithCopyButton } from '@components/ClipboardButton';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
@ -18,15 +18,21 @@ const SPConfig: NextPage<InferGetStaticPropsType<typeof getServerSideProps>> = (
<h2 className='font-bold text-gray-700 md:text-xl'>{t('sp_oidc_config_title')}</h2>
<p className='text-sm leading-6 text-gray-800'>{t('sp_oidc_config_description')}</p>
<p className='text-sm leading-6 text-gray-600'>
Refer to our&nbsp;
<a
href='https://boxyhq.com/docs/jackson/sso-providers'
target='_blank'
rel='noreferrer'
className='underline underline-offset-4'>
guides
</a>
&nbsp;for provider specific instructions.
<Trans
i18nKey='refer_to_provider_instructions'
t={t}
components={{
guideLink: (
<a
href='https://boxyhq.com/docs/jackson/sso-providers'
target='_blank'
rel='noreferrer'
className='underline underline-offset-4'>
{t('guides')}
</a>
),
}}
/>
</p>
</div>
<div className='mt-6 flex flex-col gap-6'>

View File

@ -1,7 +1,7 @@
import type { NextPage, InferGetStaticPropsType } from 'next';
import Link from 'next/link';
import React from 'react';
import { useTranslation } from 'next-i18next';
import { useTranslation, Trans } from 'next-i18next';
import jackson from '@lib/jackson';
import { InputWithCopyButton } from '@components/ClipboardButton';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
@ -19,15 +19,21 @@ const SPConfig: NextPage<InferGetStaticPropsType<typeof getServerSideProps>> = (
<h2 className='font-bold text-gray-700 md:text-xl'>{t('sp_saml_config_title')}</h2>
<p className='text-sm leading-6 text-gray-800'>{t('sp_saml_config_description')}</p>
<p className='text-sm leading-6 text-gray-600'>
Refer to our&nbsp;
<a
href='https://boxyhq.com/docs/jackson/sso-providers'
target='_blank'
rel='noreferrer'
className='underline underline-offset-4'>
guides
</a>
&nbsp;for provider specific instructions.
<Trans
i18nKey='refer_to_provider_instructions'
t={t}
components={{
guideLink: (
<a
href='https://boxyhq.com/docs/jackson/sso-providers'
target='_blank'
rel='noreferrer'
className='underline underline-offset-4'>
{t('guides')}
</a>
),
}}
/>
</p>
</div>
<div className='mt-6 flex flex-col gap-6'>
@ -67,11 +73,20 @@ const SPConfig: NextPage<InferGetStaticPropsType<typeof getServerSideProps>> = (
{t('assertion_encryption')}
</label>
<p className='text-sm'>
If you want to encrypt the assertion, you can&nbsp;
<Link href='/.well-known/saml.cer' className='underline underline-offset-4' target='_blank'>
download our public certificate.
</Link>
&nbsp;Otherwise select the Unencrypted option.
<Trans
i18nKey='sp_download_our_public_cert'
t={t}
components={{
downloadLink: (
<Link
href='/.well-known/saml.cer'
className='underline underline-offset-4'
target='_blank'>
{t('download')}
</Link>
),
}}
/>
</p>
</div>
</div>