Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enabling i18n feature for smaller screens #3556

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions components/icons/Icons.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import IconGuide from './Guide';
import IconHome from './Home';
import IconHub from './Hub';
import InfoIcon from './InfoIcon';
import IconLanguage from './Language';
import IconLightBulb from './LightBulb';
import IconLinkedIn from './LinkedIn';
import IconLoupe from './Loupe';
Expand Down Expand Up @@ -236,6 +237,10 @@ These are the icons used in the AsyncAPI website.
<InfoIcon />
</IconItem>

<IconItem name="Language">
<IconLanguage />
</IconItem>

<IconItem name="Light Bulb">
<IconLightBulb />
</IconItem>
Expand Down
24 changes: 24 additions & 0 deletions components/icons/Language.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

/* eslint-disable max-len */
/**
* @description Icons for asyncapi website
*/
export default function IconLanguage({ className = '' }) {
return (
<svg
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
strokeWidth={1.5}
stroke='currentColor'
className={`size-5 ${className}`}
>
<path
strokeLinecap='round'
strokeLinejoin='round'
d='m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802'
/>
</svg>
);
}
35 changes: 21 additions & 14 deletions components/languageSelector/LanguageSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { twMerge } from 'tailwind-merge';

import type { SelectProps } from '../form/Select';
import IconLanguage from '../icons/Language';

/**
* @description LanguageSelect component for selecting a language.
Expand All @@ -12,19 +13,25 @@ import type { SelectProps } from '../form/Select';
*/
export default function LanguageSelect({ className = '', onChange = () => {}, options = [], selected }: SelectProps) {
return (
<select
data-testid='Select-form'
onChange={(ev) => onChange(ev.target.value)}
className={twMerge(
`form-select h-full py-0 px-3 pr-7 inline-flex justify-center rounded-md border border-gray-300 shadow-sm py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:border-gray-500 focus:outline-none focus:ring-0 focus:ring-black ${className}`
)}
value={selected}
>
{options.map((option, index) => (
<option key={index} value={option.value} data-testid='Option-form'>
{option.text}
</option>
))}
</select>
<div className='relative inline-block'>
<div className='relative flex items-center gap-2'>
{/* Display Icon Next to the Select Box */}
<IconLanguage className='pointer-events-none absolute left-3 text-gray-600' />
<select
data-testid='Select-form'
onChange={(ev) => onChange(ev.target.value)}
className={twMerge(
`form-select h-full px-10 pr-7 inline-flex justify-center rounded-md border border-gray-300 shadow-sm py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:border-gray-500 focus:outline-none focus:ring-0 focus:ring-black ${className}`
)}
value={selected}
>
{options.map((option, index) => (
<option key={index} value={option.value} data-testid='Option-form'>
{option.text}
</option>
))}
</select>
</div>
</div>
);
}
36 changes: 34 additions & 2 deletions components/navigation/MobileNavMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Link from 'next/link';
import React, { useState } from 'react';

import { SearchButton } from '../AlgoliaSearch';
import IconLanguage from '../icons/Language';
import NavItemDropdown from '../icons/NavItemDropdown';
import SearchIcon from '../icons/SearchIcon';
import AsyncAPILogo from '../logos/AsyncAPILogo';
Expand All @@ -19,13 +20,21 @@ interface MenuItem {

interface MobileNavMenuProps {
onClickClose?: () => void;
uniqueLangs: { key: string; text: string; value: string }[];
currentLanguage: string;
changeLanguage: (locale: string, langPicker: boolean) => void;
}

/**
* @description MobileNavMenu component for displaying a responsive navigation menu on mobile devices.
* @param {MobileNavMenuProps} props - The props for the MobileNavMenu component.
*/
export default function MobileNavMenu({ onClickClose = () => {} }: MobileNavMenuProps) {
export default function MobileNavMenu({
onClickClose = () => {},
uniqueLangs,
currentLanguage,
changeLanguage
}: MobileNavMenuProps) {
const [open, setOpen] = useState<string | null>(null);

/**
Expand Down Expand Up @@ -104,7 +113,7 @@ export default function MobileNavMenu({ onClickClose = () => {} }: MobileNavMenu
</h4>
{open === 'community' && <MenuBlocks items={communityItems} />}
</div>
<div className='space-y-2 px-5 py-2' onClick={() => showMenu('others')} data-testid='MobileNav-others'>
<div className='space-y-2 px-5 pt-2' onClick={() => showMenu('others')} data-testid='MobileNav-others'>
<div className='grid gap-4'>
<div>
<h4 className='mb-4 flex justify-between font-medium text-gray-800'>
Expand All @@ -127,6 +136,29 @@ export default function MobileNavMenu({ onClickClose = () => {} }: MobileNavMenu
</div>
</div>
</div>
<div className='space-y-2 px-5 py-2' onClick={() => showMenu('language')}>
<div className='grid gap-4'>
<div>
<h4 className='mb-4 flex justify-between font-medium text-gray-800'>
<a className='flex cursor-pointer items-center gap-x-2'>
Language <IconLanguage />
</a>
<NavItemDropdown />
</h4>
{open === 'language' &&
uniqueLangs.map((lang) => (
<button
key={lang.key}
onClick={() => changeLanguage(lang.value.toLowerCase(), true)}
className={`mb-4 ml-2 block w-full rounded-lg py-1 text-start text-sm font-medium leading-6 text-gray-700 transition duration-150 ease-in-out hover:bg-gray-50 ${currentLanguage.toLowerCase() === lang.text.toLowerCase() ? 'text-secondary-500' : ''}`}
data-testid='MobileNav-language-item'
>
{lang.text}
</button>
))}
</div>
</div>
</div>
Comment on lines +139 to +161
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve accessibility of the language selection menu.

The current implementation has two potential accessibility issues:

  1. Using a div with onClick instead of a button for the language menu trigger
  2. Missing ARIA attributes for the expandable menu

Consider this improvement:

-          <div className='space-y-2 px-5 py-2' onClick={() => showMenu('language')}>
+          <div className='space-y-2 px-5 py-2'>
+            <button
+              onClick={() => showMenu('language')}
+              aria-expanded={open === 'language'}
+              aria-controls="language-menu"
+              className="w-full text-left"
+            >
               <div className='grid gap-4'>
                 <div>
                   <h4 className='mb-4 flex justify-between font-medium text-gray-800'>
                     <a className='flex cursor-pointer items-center gap-x-2'>
                       Language <IconLanguage />
                     </a>
                     <NavItemDropdown />
                   </h4>
-                  {open === 'language' &&
+                  <div
+                    id="language-menu"
+                    role="menu"
+                    className={open === 'language' ? 'block' : 'hidden'}
+                  >
                     uniqueLangs.map((lang) => (
                       <button
                         key={lang.key}
                         onClick={() => changeLanguage(lang.value.toLowerCase(), true)}
+                        role="menuitem"
                         className={`mb-4 ml-2 block w-full rounded-lg py-1 text-start text-sm font-medium leading-6 text-gray-700 transition duration-150 ease-in-out hover:bg-gray-50 ${
                           currentLanguage.toLowerCase() === lang.text.toLowerCase() ? 'text-secondary-500' : ''
                         }`}
                         data-testid='MobileNav-language-item'
                       >
                         {lang.text}
                       </button>
-                    ))}
+                    )}
+                  </div>
                 </div>
               </div>
+            </button>
           </div>

Committable suggestion skipped: line range outside the PR's diff.

</div>
</div>
</div>
Expand Down
21 changes: 14 additions & 7 deletions components/navigation/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function NavBar({ className = '', hideLogo = false }: NavBarProps
/**
* Retrieves unique language options based on the current path and i18nPaths configuration.
*
* @returns {string[]} - An array of unique language options in uppercase.
* @returns {string[]} - An array of unique language options with first letter in uppercase.
*/
const getUniqueLangs = (): string[] => {
let pathnameWithoutLocale = pathname;
Expand All @@ -56,10 +56,10 @@ export default function NavBar({ className = '', hideLogo = false }: NavBarProps
// Filter unique languages based on i18nPaths that include the modified pathnameWithoutLocale
const uniqueLangs = Object.keys(i18nPaths)
.filter((lang) => i18nPaths[lang].includes(pathnameWithoutLocale))
.map((lang) => lang.toUpperCase());
.map((lang) => lang.charAt(0).toUpperCase() + lang.slice(1));

// If no unique languages are found, default to ["EN"]
return uniqueLangs.length === 0 ? ['EN'] : uniqueLangs;
// If no unique languages are found, default to ["English"]
return uniqueLangs.length === 0 ? ['English'] : uniqueLangs;
};

const uniqueLangs = getUniqueLangs().map((lang) => ({
Expand Down Expand Up @@ -147,7 +147,7 @@ export default function NavBar({ className = '', hideLogo = false }: NavBarProps

return (
<div className={`bg-white ${className} z-50`}>
<div className='flex w-full items-center justify-between py-6 lg:justify-start lg:space-x-10'>
<div className='flex w-full items-center justify-between py-6 lg:justify-start lg:space-x-2'>
{!hideLogo && (
<div className='lg:w-auto lg:flex-1'>
<div className='flex'>
Expand Down Expand Up @@ -233,7 +233,7 @@ export default function NavBar({ className = '', hideLogo = false }: NavBarProps
changeLanguage(value.toLowerCase(), true);
}}
className=''
selected={i18n.language ? i18n.language.toUpperCase() : 'EN'}
selected={i18n.language ? i18n.language.charAt(0).toUpperCase() + i18n.language.slice(1) : 'English'}
/>

<GithubButton
Expand All @@ -247,7 +247,14 @@ export default function NavBar({ className = '', hideLogo = false }: NavBarProps
</div>

{/* Mobile menu, show/hide based on mobile menu state. */}
{mobileMenuOpen && <MobileNavMenu onClickClose={() => setMobileMenuOpen(false)} />}
{mobileMenuOpen && (
<MobileNavMenu
onClickClose={() => setMobileMenuOpen(false)}
uniqueLangs={uniqueLangs}
currentLanguage={i18n.language ? i18n.language.charAt(0).toUpperCase() + i18n.language.slice(1) : 'English'}
changeLanguage={changeLanguage}
/>
)}
</div>
);
}
6 changes: 3 additions & 3 deletions next-i18next.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module.exports = {
i18n: {
locales: ['en', 'de'],
defaultLocale : 'en',
locales: ['english', 'deutsch'],
defaultLocale : 'english',
namespaces: ['landing-page', 'common', 'tools'],
defaultNamespace: 'landing-page',
react: { useSuspense: false },// this line
},

};
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion utils/getStatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const getStaticPaths = () => ({
* @returns An object containing the internationalization props.
*/
export async function getI18nProps(ctx: any, ns = ['common']) {
const locale = ctx?.params?.lang ? ctx.params.lang : 'en';
const locale = ctx?.params?.lang ? ctx.params.lang : 'english';
const props = {
...(await serverSideTranslations(locale, ns))
};
Expand Down
8 changes: 4 additions & 4 deletions utils/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ interface I18nPaths {
}

export const i18nPaths: I18nPaths = {
en: [
english: [
'', // Homepage Route
'/tools/cli',
'/newsletter'
],
de: [
deutsch: [
'', // Homepage Route
'/tools/cli',
'/newsletter'
]
};

export const languages = ['en', 'de'];
export const defaultLanguage = 'en';
export const languages = ['english', 'deutsch'];
export const defaultLanguage = 'english';

export { useTranslation };
Loading