diff --git a/README-CONTRIBUIDOR.md b/README-CONTRIBUIDOR.md index 660047f..5f4d524 100644 --- a/README-CONTRIBUIDOR.md +++ b/README-CONTRIBUIDOR.md @@ -37,7 +37,10 @@ Antes de começar, certifique-se de ter instalado: diaum/ ├── src/ │ ├── app/ # Páginas e rotas (Next.js App Router) +│ │ ├── [locale]/ # Páginas da aplicação que são localizadas │ ├── components/ # Componentes React reutilizáveis +│ ├── locales/ # Textos de localização +│ ├── i18n/ # Configurações da localização como routing e definição de linguagem │ ├── data/ # Dados e configurações │ │ ├── siteDetails.ts │ │ └── ... @@ -49,7 +52,7 @@ diaum/ ### Pastas Principais -- **`/src/app`**: Contém as páginas e rotas do site +- **`/src/app/[locale]`**: Contém as páginas e rotas do site - **`/src/components`**: Componentes React modulares e reutilizáveis - **`/src/data`**: Arquivos de configuração e dados do site - **`/public`**: Assets estáticos (imagens, ícones, etc.) diff --git a/next.config.mjs b/next.config.mjs index e817f5f..64a3c26 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,7 @@ +import createNextIntlPlugin from 'next-intl/plugin' + +const withNextIntl = createNextIntlPlugin() + /** @type {import('next').NextConfig} */ const nextConfig = { images: { @@ -30,4 +34,4 @@ const nextConfig = { }, } -export default nextConfig +export default withNextIntl(nextConfig) diff --git a/package-lock.json b/package-lock.json index 798766a..8531615 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "clsx": "^2.1.1", "framer-motion": "^11.11.9", "next": "^14.2.13", + "next-intl": "^4.3.12", "react": "^18", "react-dom": "^18", "react-icons": "^5.3.0" @@ -221,6 +222,66 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", + "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.2", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", + "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", + "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/icu-skeleton-parser": "1.8.16", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", + "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", + "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, "node_modules/@headlessui/react": { "version": "2.2.9", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz", @@ -773,6 +834,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -2210,6 +2277,45 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3792,6 +3898,35 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "10.7.18", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", + "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.4", + "tslib": "^2.8.0" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4634,6 +4769,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "14.2.33", "resolved": "https://registry.npmjs.org/next/-/next-14.2.33.tgz", @@ -4694,6 +4838,33 @@ "tslib": "^2.4.0" } }, + "node_modules/next-intl": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.3.12.tgz", + "integrity": "sha512-yAmrQ3yx0zpNva/knniDvam3jT2d01Lv2aRgRxUIDL9zm9O4AsDjWbDIxX13t5RNf0KVnKkxH+iRcqEAmWecPg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^1.0.0", + "use-intl": "^4.3.12" + }, + "peerDependencies": { + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -6430,6 +6601,9 @@ } }, "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", @@ -6544,6 +6718,10 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6665,6 +6843,20 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/use-intl": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.3.12.tgz", + "integrity": "sha512-RxW2/D17irlDOJOzClKl+kWA7ReGLpo/A8f/LF7w1kIxO6mPKVh422JJ/pDCcvtYFCI4aPtn1AXUfELKbM+7tg==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^2.2.0", + "@schummar/icu-type-parser": "1.21.5", + "intl-messageformat": "^10.5.14" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 311d8c0..47a724f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "clsx": "^2.1.1", "framer-motion": "^11.11.9", "next": "^14.2.13", + "next-intl": "^4.3.12", "react": "^18", "react-dom": "^18", "react-icons": "^5.3.0" diff --git a/src/app/page.tsx b/src/app/[locale]/(home)/page.tsx similarity index 100% rename from src/app/page.tsx rename to src/app/[locale]/(home)/page.tsx diff --git a/src/app/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx similarity index 87% rename from src/app/blog/[slug]/page.tsx rename to src/app/[locale]/blog/[slug]/page.tsx index 786b4d2..463f00f 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -1,7 +1,8 @@ import { notFound } from 'next/navigation' import Image from 'next/image' import { fetchBySlug, fetchPageBlocks, extractBlogPost } from '@/lib/notion' -import { NotionRenderer } from '../../../components/NotionRenderer' +import { NotionRenderer } from '@/components/NotionRenderer' +import { getLocale, getTranslations } from 'next-intl/server' interface BlogPostPageProps { params: { @@ -16,11 +17,13 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) { notFound() } + const translation = await getTranslations('blog') + const locale = await getLocale() const post = extractBlogPost(page) const blocks = await fetchPageBlocks(page.id) return ( -
+
@@ -69,7 +72,7 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
)}
-

Escrito por

+

{translation('article.writtenBy')}

{post.author}

- Publicado em{' '} - {new Date(post.publishedAt).toLocaleDateString('pt-BR', { + {translation('article.publishedAt')}{' '} + {new Date(post.publishedAt).toLocaleDateString(locale, { year: 'numeric', month: 'long', day: 'numeric', diff --git a/src/app/blog/page.tsx b/src/app/[locale]/blog/page.tsx similarity index 94% rename from src/app/blog/page.tsx rename to src/app/[locale]/blog/page.tsx index b94d1ad..52b6c36 100644 --- a/src/app/blog/page.tsx +++ b/src/app/[locale]/blog/page.tsx @@ -2,8 +2,11 @@ import Link from 'next/link' import Image from 'next/image' import { fetchPages, extractBlogPost } from '@/lib/notion' import { PageObjectResponse } from '@notionhq/client/build/src/api-endpoints' +import { getTranslations } from 'next-intl/server' export default async function BlogPage() { + const translation = await getTranslations('blog') + try { const response = await fetchPages() const posts = response.results @@ -11,13 +14,11 @@ export default async function BlogPage() { .map((page: PageObjectResponse) => extractBlogPost(page)) return ( -

+

Blog

-

- Artigos e insights sobre tecnologia e desenvolvimento -

+

{translation('headline')}

@@ -88,7 +89,7 @@ export default async function BlogPage() { href={`/blog/${post.slug}`} className="text-blue-600 hover:text-blue-800 font-medium" > - Ler mais → + {translation('labelLinks.readMore')}
diff --git a/src/app/[locale]/termos-de-servico/page.tsx b/src/app/[locale]/termos-de-servico/page.tsx new file mode 100644 index 0000000..63cd36d --- /dev/null +++ b/src/app/[locale]/termos-de-servico/page.tsx @@ -0,0 +1,98 @@ +import type { Metadata } from 'next' +import Container from '@/components/Container' +import { useTranslations } from 'next-intl' +import { getTranslations } from 'next-intl/server' + +export async function generateMetadata(): Promise { + const t = await getTranslations('termsOfService') + return { + title: t('title'), + description: t('description'), + } +} + +export default function TermsOfServicePage() { + const translation = useTranslations('termsOfService') + + return ( + + {/* LOGO E TÍTULO */} +
+ + {translation('title')} + +
+ {/* TERMOS ORIGINAIS */} +
+

{translation('title')}

+ +

+ {translation('content.firstParagraph')} +

+ +

{translation('content.secondParagraph')}

+ +

{translation('content.thirdParagraph')}

+ +

{translation('content.fourthParagraph')}

+ +

{translation('content.fifthParagraph')}

+ +

+ {translation('content.sixthParagraph')} +

+ +

+ {translation('content.seventhParagraph')} +

+ +

{translation('content.sections.1.title')}

+

{translation('content.sections.1.text')}

+ +

{translation('content.sections.2.title')}

+

{translation('content.sections.2.text')}

+ +

{translation('content.sections.3.title')}

+

{translation('content.sections.3.paragraphs.0')}

+

{translation('content.sections.3.paragraphs.1')}

+ +

{translation('content.sections.4.title')}

+

{translation('content.sections.4.paragraphs.0')}

+

{translation('content.sections.4.paragraphs.1')}

+ +

{translation('content.sections.5.title')}

+

{translation('content.sections.5.text')}

+ +

{translation('content.sections.6.title')}

+

{translation('content.sections.6.paragraphs.0')}

+

{translation('content.sections.6.paragraphs.1')}

+

{translation('content.sections.6.paragraphs.2')}

+ +

{translation('content.sections.7.title')}

+

{translation('content.sections.7.text')}

+ +

{translation('content.sections.8.title')}

+

{translation('content.sections.8.text')}

+ +

{translation('content.sections.9.title')}

+

{translation('content.sections.9.text')}

+ +

{translation('content.sections.10.title')}

+

{translation('content.sections.10.text')}

+ +

{translation('content.sections.11.title')}

+

{translation('content.sections.11.text')}

+ +

{translation('content.sections.12.title')}

+

{translation('content.sections.12.text')}

+ +

{translation('content.effectiveDate')}

+
+
+ ) +} diff --git a/src/app/test-notion/page.tsx b/src/app/[locale]/test-notion/page.tsx similarity index 100% rename from src/app/test-notion/page.tsx rename to src/app/[locale]/test-notion/page.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c049c01..8ae147b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,80 +7,92 @@ import Footer from '@/components/Footer' import { siteDetails } from '@/data/siteDetails' import './globals.css' +import { Locale } from '@/locales' +import { NextIntlClientProvider, useMessages } from 'next-intl' +import { getLocale, getTranslations } from 'next-intl/server' const manrope = Manrope({ subsets: ['latin'] }) const sourceSans = Source_Sans_3({ subsets: ['latin'] }) -export const metadata: Metadata = { - title: siteDetails.metadata.title, - description: siteDetails.metadata.description, - keywords: [ - `${siteDetails.siteName}`, - 'app', - 'bloqueio', - 'conteúdo', - 'pornografia', - 'hábitos', - 'foco', - 'controle', - 'vida', - ], - authors: [{ name: siteDetails.siteName }], - creator: siteDetails.siteName, - publisher: siteDetails.siteName, - icons: { - icon: [{ url: '/favicon.ico', sizes: 'any' }], - }, - robots: { - index: true, - follow: true, - googleBot: { +export async function generateMetadata(): Promise { + const t = await getTranslations('globals.metadata') + const locale = await getLocale() + + const keywords = [ + siteDetails.siteName, + ...t('keywords') + .split(',') + .map(k => k.trim()), + ] + + return { + title: t('title'), + description: t('description'), + keywords: keywords, + authors: [{ name: siteDetails.siteName }], + creator: siteDetails.siteName, + publisher: siteDetails.siteName, + icons: { + icon: [{ url: '/favicon.ico', sizes: 'any' }], + }, + robots: { index: true, follow: true, - 'max-video-preview': -1, - 'max-image-preview': 'large', - 'max-snippet': -1, - }, - }, - openGraph: { - title: siteDetails.metadata.title, - description: siteDetails.metadata.description, - url: siteDetails.siteUrl, - type: 'website', - locale: siteDetails.locale, - siteName: siteDetails.siteName, - images: [ - { - url: '/images/diaum-logo.png', - width: 1200, - height: 630, - alt: `${siteDetails.metadata.title}`, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, }, - ], - }, - twitter: { - card: 'summary_large_image', - title: siteDetails.metadata.title, - description: siteDetails.metadata.description, - images: ['/images/diaum-logo.png'], - }, - alternates: { - canonical: siteDetails.siteUrl, - }, + }, + openGraph: { + title: t('title'), + description: t('description'), + url: siteDetails.siteUrl, + type: 'website', + locale: locale, + siteName: siteDetails.siteName, + images: [ + { + url: '/images/diaum-logo.png', + width: 1200, + height: 630, + alt: `${t('title')}`, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: t('title'), + description: t('description'), + images: ['/images/diaum-logo.png'], + }, + alternates: { + canonical: siteDetails.siteUrl, + }, + } } -export default function RootLayout({ - children, -}: Readonly<{ +interface RootLayoutProps { children: React.ReactNode -}>) { + params: { + locale: Locale + } +} + +export default function RootLayout({ children, params: { locale } }: Readonly) { + const messages = useMessages() + return ( - + {siteDetails.googleAnalyticsId && } -
-
{children}
-