From 285519d3cf218facbefc10591936c98078c09b6c Mon Sep 17 00:00:00 2001 From: AbnerSilvaBarbosa Date: Wed, 29 Nov 2023 00:13:40 -0300 Subject: [PATCH 1/2] feat: implemented i18n basic example, it's just for see how this work and how is the archteture --- .vscode/settings.json | 1 + messages/en.json | 7 ++ messages/pt-br.json | 7 ++ next.config.js | 3 +- package.json | 1 + pnpm-lock.yaml | 93 +++++++++++++++++++++ src/__tests__/index.test.tsx | 2 +- src/app/{ => [locale]}/global.css | 0 src/app/{ => [locale]}/icon.png | Bin src/app/{ => [locale]}/layout.tsx | 23 ++++- src/app/{ => [locale]}/page.tsx | 8 +- src/app/{ => [locale]}/styles.ts | 0 src/components/clear-button/index.tsx | 5 +- src/components/save-combo-button/index.tsx | 7 +- src/components/share-button/index.tsx | 5 +- src/i18n.ts | 5 ++ src/middleware.ts | 14 ++++ src/types/Ttranslate.ts | 3 + 18 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 messages/en.json create mode 100644 messages/pt-br.json rename src/app/{ => [locale]}/global.css (100%) rename src/app/{ => [locale]}/icon.png (100%) rename src/app/{ => [locale]}/layout.tsx (76%) rename src/app/{ => [locale]}/page.tsx (87%) rename src/app/{ => [locale]}/styles.ts (100%) create mode 100644 src/i18n.ts create mode 100644 src/middleware.ts create mode 100644 src/types/Ttranslate.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index c2c84161..87ad5208 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,7 @@ "Pomodoro", "Svgr", "tailwindcss", + "Ttranslate", "UMAMI", "zustand" ] diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 00000000..312f0d87 --- /dev/null +++ b/messages/en.json @@ -0,0 +1,7 @@ +{ + "Index":{ + "save":"save", + "share":"share", + "clear":"clear" + } +} \ No newline at end of file diff --git a/messages/pt-br.json b/messages/pt-br.json new file mode 100644 index 00000000..7ef3cd71 --- /dev/null +++ b/messages/pt-br.json @@ -0,0 +1,7 @@ +{ + "Index":{ + "save":"salvar", + "share":"compartilhar", + "clear":"limpar" + } +} \ No newline at end of file diff --git a/next.config.js b/next.config.js index 1290c518..76e187a1 100644 --- a/next.config.js +++ b/next.config.js @@ -1,3 +1,4 @@ +const withNextIntl = require('next-intl/plugin')() const withSvgr = require('@newhighsco/next-plugin-svgr') const withPWA = require('@ducanh2912/next-pwa').default({ dest: 'public' @@ -15,4 +16,4 @@ const configWithSvgr = withSvgr({ } }) -module.exports = withPWA(configWithSvgr) +module.exports = withNextIntl(withPWA(configWithSvgr)) diff --git a/package.json b/package.json index 7c8af73e..f4abf90e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@headlessui/react": "1.7.17", "@newhighsco/next-plugin-svgr": "^3.0.104", "next": "14.0.3", + "next-intl": "^3.1.4", "react": "18.2.0", "react-dom": "18.2.0", "react-icons": "4.12.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a86b3b7..86593b20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: next: specifier: 14.0.3 version: 14.0.3(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) + next-intl: + specifier: ^3.1.4 + version: 3.1.4(next@14.0.3)(react@18.2.0) react: specifier: 18.2.0 version: 18.2.0 @@ -1901,6 +1904,59 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@formatjs/ecma402-abstract@1.11.4: + resolution: {integrity: sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==} + dependencies: + '@formatjs/intl-localematcher': 0.2.25 + tslib: 2.6.1 + dev: false + + /@formatjs/ecma402-abstract@1.18.0: + resolution: {integrity: sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==} + dependencies: + '@formatjs/intl-localematcher': 0.5.2 + tslib: 2.6.1 + dev: false + + /@formatjs/fast-memoize@1.2.1: + resolution: {integrity: sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==} + dependencies: + tslib: 2.6.1 + dev: false + + /@formatjs/icu-messageformat-parser@2.1.0: + resolution: {integrity: sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==} + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + '@formatjs/icu-skeleton-parser': 1.3.6 + tslib: 2.6.1 + dev: false + + /@formatjs/icu-skeleton-parser@1.3.6: + resolution: {integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==} + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + tslib: 2.6.1 + dev: false + + /@formatjs/intl-localematcher@0.2.25: + resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==} + dependencies: + tslib: 2.6.1 + dev: false + + /@formatjs/intl-localematcher@0.2.32: + resolution: {integrity: sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==} + dependencies: + tslib: 2.6.1 + dev: false + + /@formatjs/intl-localematcher@0.5.2: + resolution: {integrity: sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==} + dependencies: + tslib: 2.6.1 + dev: false + /@headlessui/react@1.7.17(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==} engines: {node: '>=10'} @@ -5501,6 +5557,15 @@ packages: has: 1.0.3 side-channel: 1.0.4 + /intl-messageformat@9.13.0: + resolution: {integrity: sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==} + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + '@formatjs/fast-memoize': 1.2.1 + '@formatjs/icu-messageformat-parser': 2.1.0 + tslib: 2.6.1 + dev: false + /is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -6772,9 +6837,27 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + /neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + /next-intl@3.1.4(next@14.0.3)(react@18.2.0): + resolution: {integrity: sha512-/mewUryIPdoZT7j+8ipN2rfWuuNGdl+xiwxWxYkYko0kGY/a2IlcgqaMBKwue5V5M17Vk9bwiydv/2wySJXG8w==} + peerDependencies: + next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@formatjs/intl-localematcher': 0.2.32 + negotiator: 0.6.3 + next: 14.0.3(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + use-intl: 3.1.4(react@18.2.0) + dev: false + /next@14.0.3(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-AbYdRNfImBr3XGtvnwOxq8ekVCwbFTv/UJoLwmaX89nk9i051AEY4/HAWzU0YpaTDw8IofUpmuIlvzWF13jxIw==} engines: {node: '>=18.17.0'} @@ -8607,6 +8690,16 @@ packages: requires-port: 1.0.0 dev: true + /use-intl@3.1.4(react@18.2.0): + resolution: {integrity: sha512-iBIHPetLeEdAuIhAXRI9ukfjjvadP2EWoAEl6IvbWojrwYgcP52A6Al1nyzIwZD7iWBU1T3Ri3Hg4HsV7cWlVA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@formatjs/ecma402-abstract': 1.18.0 + intl-messageformat: 9.13.0 + react: 18.2.0 + dev: false + /use-sync-external-store@1.2.0(react@18.2.0): resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index 00746322..dc2cc172 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -1,6 +1,6 @@ import '@testing-library/jest-dom' import { render, screen } from '@testing-library/react' -import Home from '~/app/page' +import Home from '~/app/[locale]/page' describe('Home', () => { it('Renders the page heading', () => { diff --git a/src/app/global.css b/src/app/[locale]/global.css similarity index 100% rename from src/app/global.css rename to src/app/[locale]/global.css diff --git a/src/app/icon.png b/src/app/[locale]/icon.png similarity index 100% rename from src/app/icon.png rename to src/app/[locale]/icon.png diff --git a/src/app/layout.tsx b/src/app/[locale]/layout.tsx similarity index 76% rename from src/app/layout.tsx rename to src/app/[locale]/layout.tsx index bdf40c5c..883d2ecb 100644 --- a/src/app/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -3,6 +3,12 @@ import { Metadata } from 'next' import Script from 'next/script' import { Nunito } from 'next/font/google' +import { NextIntlClientProvider, useMessages } from 'next-intl' +import { notFound } from 'next/navigation' + +// Can be imported from a shared config +const locales = ['en', 'pt-br'] + import './global.css' const APP_NAME = 'Noisekun' @@ -72,7 +78,16 @@ const nunito = Nunito({ variable: '--font-nunito' }) -export default function RootLayout({ children }: { children: ReactNode }) { +export default function RootLayout({ + children, + params: { locale } +}: { + children: ReactNode + params: any +}) { + if (!locales.includes(locale as any)) notFound() + const messages = useMessages() + return ( @@ -82,7 +97,11 @@ export default function RootLayout({ children }: { children: ReactNode }) { data-website-id={process.env.UMAMI_WEBSITE_ID} /> - {children} + + + {children} + + ) } diff --git a/src/app/page.tsx b/src/app/[locale]/page.tsx similarity index 87% rename from src/app/page.tsx rename to src/app/[locale]/page.tsx index 42abb9d9..b713cf62 100644 --- a/src/app/page.tsx +++ b/src/app/[locale]/page.tsx @@ -12,6 +12,7 @@ import { Footer } from '~/components/footer' import { useThemeStore } from '~/stores/theme-store' import { SaveComboButton } from '~/components/save-combo-button' import { InteractionModal } from '~/components/interaction-modal' +import { useTranslations } from 'next-intl' import { sounds } from '~/sounds' @@ -24,6 +25,7 @@ export default function Home() { ) const [querySounds] = useQueryState('sounds') + const t = useTranslations('Index') useEffect(() => { if (!querySounds.length) setUserHasInteracted(true) @@ -35,9 +37,9 @@ export default function Home() {
- - - + + +
{sounds.map(sound => ( diff --git a/src/app/styles.ts b/src/app/[locale]/styles.ts similarity index 100% rename from src/app/styles.ts rename to src/app/[locale]/styles.ts diff --git a/src/components/clear-button/index.tsx b/src/components/clear-button/index.tsx index 982c6560..89cda1bf 100644 --- a/src/components/clear-button/index.tsx +++ b/src/components/clear-button/index.tsx @@ -1,8 +1,9 @@ import { useSoundsStateStore } from '~/stores/sounds-state-store' import { useThemeStore } from '~/stores/theme-store' import { actionButton } from '~/shared/styles/action-button' +import { TTranslate } from '~/types/Ttranslate' -export function ClearButton() { +export function ClearButton({ textTranslate }: TTranslate) { const bulkSoundUpdate = useSoundsStateStore(state => state.bulkUpdate) const soundStates = useSoundsStateStore(state => state.sounds) @@ -29,7 +30,7 @@ export function ClearButton() { title="Clear all active sounds" data-umami-event="Clear Button" > - clear + {textTranslate} ) } diff --git a/src/components/save-combo-button/index.tsx b/src/components/save-combo-button/index.tsx index 5ec45002..84119f23 100644 --- a/src/components/save-combo-button/index.tsx +++ b/src/components/save-combo-button/index.tsx @@ -9,8 +9,9 @@ import { randomString } from '~/utils/random-string' import { actionButton } from '~/shared/styles/action-button' import { input } from './styles' +import { TTranslate } from '~/types/Ttranslate' -export function SaveComboButton() { +export function SaveComboButton({ textTranslate }: TTranslate) { const sounds = useSoundsStateStore(state => state.sounds) const theme = useThemeStore(state => state.theme) const saveCombo = useComboStore(state => state.saveCombo) @@ -73,7 +74,9 @@ export function SaveComboButton() { ) : ( - save + + {textTranslate} + )}
diff --git a/src/components/share-button/index.tsx b/src/components/share-button/index.tsx index 97a84f2d..d16e1519 100644 --- a/src/components/share-button/index.tsx +++ b/src/components/share-button/index.tsx @@ -7,8 +7,9 @@ import { useThemeStore } from '~/stores/theme-store' import { actionButton } from '~/shared/styles/action-button' import { ConfirmationModal } from './confirmation-modal' +import { TTranslate } from '~/types/Ttranslate' -export function ShareButton() { +export function ShareButton({ textTranslate }: TTranslate) { const searchParams = useSearchParams() const soundStates = useSoundsStateStore(state => state.sounds) const theme = useThemeStore(state => state.theme) @@ -43,7 +44,7 @@ export function ShareButton() { title="Share current combo" data-umami-event="Clear Button" > - share + {textTranslate} diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 00000000..6cbbb1a9 --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,5 @@ +import { getRequestConfig } from 'next-intl/server' + +export default getRequestConfig(async ({ locale }) => ({ + messages: (await import(`../messages/${locale}.json`)).default +})) diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 00000000..c427b5dd --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,14 @@ +import createMiddleware from 'next-intl/middleware' + +export default createMiddleware({ + // A list of all locales that are supported + locales: ['en', 'pt-br'], + + // Used when no locale matches + defaultLocale: 'en' +}) + +export const config = { + // Match only internationalized pathnames + matcher: ['/', '/(pt-br|en)/:path*'] +} diff --git a/src/types/Ttranslate.ts b/src/types/Ttranslate.ts new file mode 100644 index 00000000..ca01af04 --- /dev/null +++ b/src/types/Ttranslate.ts @@ -0,0 +1,3 @@ +export type TTranslate = { + textTranslate: string +} From d085c6abc6ad159eed3efbf5848e855fb7ddc315 Mon Sep 17 00:00:00 2001 From: AbnerSilvaBarbosa Date: Thu, 30 Nov 2023 11:52:23 -0300 Subject: [PATCH 2/2] feat: implemented translation in title= for accesible --- messages/en.json | 13 ++++++++++++- messages/pt-br.json | 14 +++++++++++++- src/app/[locale]/page.tsx | 15 ++++++++++++--- src/components/clear-button/index.tsx | 4 ++-- src/components/header/combo-list/index.tsx | 5 +++-- .../header/global-volume-controller/index.tsx | 14 ++++++++++---- src/components/header/index.tsx | 16 ++++++++++++---- src/components/header/pomodoro/index.tsx | 6 +++++- src/components/header/theme-menu/index.tsx | 5 +++-- src/components/save-combo-button/index.tsx | 4 ++-- src/components/share-button/index.tsx | 4 ++-- src/types/Ttranslate.ts | 15 +++++++++++++-- 12 files changed, 89 insertions(+), 26 deletions(-) diff --git a/messages/en.json b/messages/en.json index 312f0d87..05c9aa7a 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2,6 +2,17 @@ "Index":{ "save":"save", "share":"share", - "clear":"clear" + "clear":"clear", + "Save_current_combo":"Save current combo", + "Share_current_combo":"Share current combo", + "Clear_all_active_sounds":"Clear all active sounds" + }, + "Header":{ + "Reset_Pomodoro_timer":"Reset Pomodoro timer", + "Toggle_Pomodoro_timer":"Toggle Pomodoro timer", + "Global_volume_in":"Global volume in", + "Enable/disable_sound":"Enable/disable sound", + "Toggle_theme_menu":"Toggle theme menu", + "Toggle_combo_list":"Toggle combo list" } } \ No newline at end of file diff --git a/messages/pt-br.json b/messages/pt-br.json index 7ef3cd71..2da7e032 100644 --- a/messages/pt-br.json +++ b/messages/pt-br.json @@ -2,6 +2,18 @@ "Index":{ "save":"salvar", "share":"compartilhar", - "clear":"limpar" + "clear":"limpar", + "Save_current_combo":"Salvar combinação atual", + "Share_current_combo":"Compartilhar combinação atual", + "Clear_all_active_sounds":"Limpar todos os sons ativos", + "Toggle_combo_list":"Alternar lista de combinações" + }, + "Header":{ + "Reset_Pomodoro_timer":"Redefinir temporizador Pomodoro", + "Toggle_Pomodoro_timer":"Alternar temporizador Pomodoro", + "Global_volume_in":"Volume global em", + "Enable/disable_sound":"Ativar/desativar som", + "Toggle_theme_menu":"Alternar menu de tema", + "Toggle_combo_list":"Alternar lista de combinações" } } \ No newline at end of file diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx index b713cf62..b3240fed 100644 --- a/src/app/[locale]/page.tsx +++ b/src/app/[locale]/page.tsx @@ -37,9 +37,18 @@ export default function Home() {
- - - + + +
{sounds.map(sound => ( diff --git a/src/components/clear-button/index.tsx b/src/components/clear-button/index.tsx index 89cda1bf..ee33a878 100644 --- a/src/components/clear-button/index.tsx +++ b/src/components/clear-button/index.tsx @@ -3,7 +3,7 @@ import { useThemeStore } from '~/stores/theme-store' import { actionButton } from '~/shared/styles/action-button' import { TTranslate } from '~/types/Ttranslate' -export function ClearButton({ textTranslate }: TTranslate) { +export function ClearButton({ titleTranslate, textTranslate }: TTranslate) { const bulkSoundUpdate = useSoundsStateStore(state => state.bulkUpdate) const soundStates = useSoundsStateStore(state => state.sounds) @@ -27,7 +27,7 @@ export function ClearButton({ textTranslate }: TTranslate) { disabled={isDisabled()} onClick={clear} className={actionButton({ theme })} - title="Clear all active sounds" + title={titleTranslate} data-umami-event="Clear Button" > {textTranslate} diff --git a/src/components/header/combo-list/index.tsx b/src/components/header/combo-list/index.tsx index 9a106fdb..731a3c7e 100644 --- a/src/components/header/combo-list/index.tsx +++ b/src/components/header/combo-list/index.tsx @@ -14,8 +14,9 @@ import { toggleEditContainer, triggerButton } from './styles' +import { TTitleTranslate } from '~/types/Ttranslate' -export function ComboList() { +export function ComboList({ titleTranslate }: TTitleTranslate) { const theme = useThemeStore(set => set.theme) const setTheme = useThemeStore(set => set.setTheme) const sounds = useSoundsStateStore(state => state.sounds) @@ -61,7 +62,7 @@ export function ComboList() {
- Global volume in {Number(globalVolume * 100).toFixed(1)}% + {titleGlobalVolumeTranslate} {Number(globalVolume * 100).toFixed(1)}%