diff --git a/app/components/Benefits.tsx b/app/components/Benefits.tsx index 7e701fc..1d21bfb 100644 --- a/app/components/Benefits.tsx +++ b/app/components/Benefits.tsx @@ -1,5 +1,6 @@ import { mainHeadingSize, mainRounded } from '@/components/theme/padding.utils'; import { Box, Flex, Heading, SimpleGrid, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import { AdaptiveInterface } from './animations/AdaptiveInterface'; import { DeveloperCommunications } from './animations/DeveloperCommunications'; import { WebDevelopment } from './animations/WebDevelopment'; @@ -37,34 +38,25 @@ const BenefitItem = ({ }; export const Benefits = () => { + const t = useTranslations('benefits'); + const items = useTranslations('benefits.items'); + return ( - Benefits + {t('heading')} - + - + - + - + diff --git a/app/components/ComparePricing.tsx b/app/components/ComparePricing.tsx index 2d0b8f8..31bc250 100644 --- a/app/components/ComparePricing.tsx +++ b/app/components/ComparePricing.tsx @@ -1,5 +1,6 @@ import { mainHeadingSize, mainRounded, outerPadding } from '@/components/theme/padding.utils'; import { Box, Flex, Heading, SimpleGrid, Span, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import { BusinessAgreement } from './animations/BusinessAgreement'; import { CustomerAssistance } from './animations/CustomerAssistance'; import { MoneyValueImage } from './animations/MoneyValueImage'; @@ -17,172 +18,131 @@ const TextRow = ({ heading, text }: { heading: string; text: string }) => ( ); -const FullTimeEngineer = () => ( - - - - - - Hiring a full-time engineer - - - - - - - - - With our subscription: - - - - - - - - - -); +const FullTimeEngineer = () => { + const t = useTranslations('comparePricing.fullTimeEngineer'); -export const HiringCompany = () => ( - - - - - - Hiring a development company - - - - - - - - - With our subscription: - - - - - - + return ( + + + + + + {t('title')} + + + + + + + + + {t('withSubscription')} + + + + + + + + - -); + ); +}; -const SubscriptionValue = () => ( - - +export const HiringCompany = () => { + const t = useTranslations('comparePricing.hiringCompany'); + + return ( + + + + - Subscription value at a glance + {t('title')} - - - - - - - + + + + + + + {t('withSubscription')} + + + + + + - { + const t = useTranslations('comparePricing.subscriptionValue'); + + return ( + - - - -); + + + {t('title')} + + + + + + + + + + + + + + + ); +}; export const ComparePricing = () => { + const t = useTranslations('comparePricing'); + return ( - How does this compare? + {t('heading')} diff --git a/app/components/Discuss.tsx b/app/components/Discuss.tsx index b3aa889..741d926 100644 --- a/app/components/Discuss.tsx +++ b/app/components/Discuss.tsx @@ -8,10 +8,13 @@ import { } from '@/components/theme/padding.utils'; import { shadowSharp } from '@/components/theme/theme.utils'; import { Box, Flex, Heading, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import { InlineWidget } from 'react-calendly'; import { OnlineVideoChatting } from './animations/OnlineVideoChatting'; export const Discuss = () => { + const t = useTranslations('discuss'); + return ( { > - Let's discuss your project + {t('heading')} - Schedule a quick call to discuss your React maintenance needs and see if we're a good fit. + {t('description')} diff --git a/app/components/Faq.tsx b/app/components/Faq.tsx index e033d0b..6e5a3d9 100644 --- a/app/components/Faq.tsx +++ b/app/components/Faq.tsx @@ -1,52 +1,17 @@ import { mainHeadingSize, mainRounded, mainSubtextSize, outerPadding } from '@/components/theme/padding.utils'; import { CustomAccordion, CustomAccordionItem } from '@/components/ui/custom-accordion'; import { Box, Flex, Heading, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import { Questions } from './animations/Questions'; -const items = [ - { - title: 'What types of projects do you maintain?', - text: `We specialize in maintaining and upgrading existing React applications, ensuring they remain robust and efficient.` - }, - { - title: 'Do you only maintain existing projects?', - text: `No, we can also help build new projects from scratch! We'll break down the development into manageable tickets and tasks, following the same efficient workflow used for maintenance. This structured approach ensures clear progress tracking and organized development, whether starting fresh or maintaining an existing codebase.` - }, - { - title: 'Can I request both bug fixes and new features?', - text: 'Absolutely! Our service covers bug fixes, feature enhancements, and updates to libraries or frameworks.' - }, - { - title: 'How do I get started with maintenance?', - text: "To get started, contact me to discuss your project and specific needs. I'll also need secure access to your codebase to begin supporting your application. Once everything is set up, we'll choose a ticketing system - whether it's your existing system (like Jira or Trello) or one I create for you - so you can start submitting tasks right away." - }, - { - title: 'How fast will my requests be completed?', - text: 'Each task will start within 48 hours of being submitted. From there, most requests are completed in one to two days, depending on their complexity.' - }, - { - title: 'How does onboarding work?', - text: "When you subscribe to a plan, I'll ensure a seamless onboarding process. If you already have a ticketing system, such as Jira, Trello, or another platform, I'm happy to adapt and work within your existing setup. If not, I'll create one for you and guide you through the process. I'll also securely access your codebase to ensure everything is ready for maintenance. The setup process is quick and focused entirely on your needs." - }, - { - title: 'Who are the developers?', - text: "Trimatech is a one-man agency, ran by Maigo, the founder. Trimatech does not employ other developers, or outsource work to any other entity. You'll work directly with me through the entirety of your experience." - }, - { - title: 'Is there a limit to how many requests I can make?', - text: "Once subscribed, you're able to add as many requests to your queue as you'd like, and they will be delivered one by one." - }, - { - title: 'How does the pause feature work?', - text: "We understand you may not have enough work to fill up entire month. Perhaps you only have one or two requests at the moment. That's where pausing your subscription comes in handy. Billing cycles are based on 31 day period. Let's say you sign up and use the service for 21 days, and then decide to pause your subscription. This means that the billing cycle will be paused and you'll have 10 days of service remaining to be used anytime in the future." - }, - { - title: 'What if I only have a single request?', - text: "That's fine. You can pause your subscription when finished and return when you have additional development needs. There's no need to let the remainder of your subscription go to waste." - } -]; +interface FaqItem { + title: string; + text: string; +} export const Faq = () => { + const t = useTranslations('faq'); + return ( { > - Frequently Asked Questions + {t('heading')} - Schedule a quick call to discuss your React maintenance needs and see if we're a good fit. + {t('description')} - {items.map((item, index) => ( + {t.raw('items').map((item: FaqItem, index: number) => ( {item.text} diff --git a/app/components/GridBackground.tsx b/app/components/GridBackground.tsx index 967bce4..28d6dc1 100644 --- a/app/components/GridBackground.tsx +++ b/app/components/GridBackground.tsx @@ -1,6 +1,7 @@ 'use client'; import { Box, Flex, Heading, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; interface GridBackgroundProps { title: string; @@ -9,6 +10,8 @@ interface GridBackgroundProps { } export function GridBackground({ title, description, showAvailability = true }: GridBackgroundProps) { + const t = useTranslations('gridBackground'); + return ( - Call Now + {t('callNow')} )} diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index 8010b2e..d6c783f 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -1,10 +1,13 @@ import { mainHeadingSize, mainRounded, mainSubtextSize, outerPadding } from '@/components/theme/padding.utils'; import { Box, Flex, Heading, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import { GiEuropeanFlag } from 'react-icons/gi'; import { AbstractBg } from './animations/AbstractBg'; import { IntroCallCard } from './IntroCallCard'; export const Hero = () => { + const t = useTranslations('hero'); + return ( { gap={outerPadding} > - Expert React Maintenance & Development - - Keep your React applications running smoothly with dedicated senior level support. No mid-level - teams, no overhead - just direct access to expert maintenance when you need it. - + {t('heading')} + {t('description')} - EU-Based + {t('euBased')} - Mission: Make all React apps fast, modern, and user-friendly + {t('mission')} diff --git a/app/components/IntroCallCard.tsx b/app/components/IntroCallCard.tsx index 97c99ac..9822619 100644 --- a/app/components/IntroCallCard.tsx +++ b/app/components/IntroCallCard.tsx @@ -3,11 +3,14 @@ import { shadowSharp } from '@/components/theme/theme.utils'; import { Button } from '@/components/ui/button'; import { EMAIL_TO, GITHUB_LINK, LINKEDIN_LINK } from '@/utils/constants'; import { Box, Flex, Heading, IconButton, Image, LinkBox, LinkOverlay, Stack, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import NextLink from 'next/link'; import { FaGithub, FaLinkedin, FaPaperPlane } from 'react-icons/fa'; import { LuArrowRight } from 'react-icons/lu'; export const IntroCallCard = () => { + const t = useTranslations('introCallCard'); + return ( @@ -41,18 +44,18 @@ export const IntroCallCard = () => { - Maigo Erit + {t('ownerName')} - Owner & React Expert + {t('ownerTitle')} - - Book a 15 min intro call + + {t('bookIntroCall')} @@ -62,7 +65,7 @@ export const IntroCallCard = () => { - Prefer to email? + {t('preferEmail')} diff --git a/app/components/PauseTry.tsx b/app/components/PauseTry.tsx index d3f9f3f..fba2b35 100644 --- a/app/components/PauseTry.tsx +++ b/app/components/PauseTry.tsx @@ -1,6 +1,7 @@ import { mainRound } from '@/components/theme/padding.utils'; import { REFUND_PRICE, WEEK_PRICE } from '@/utils/constants'; import { Box, Flex, Heading, Span, Stack, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import { LuCircleCheck, LuCirclePause } from 'react-icons/lu'; const PauseTryItem = ({ @@ -29,66 +30,52 @@ const PauseTryItem = ({ const fontSize = 'md'; export const PauseTry = () => { + const pauseT = useTranslations('pauseTry.pause'); + const tryT = useTranslations('pauseTry.try'); + return ( - }> + }> - - When there’s no work left to be done on your projects, just pause your subscription. - Resuming is easy, and your plan picks up right where you left off, ensuring no lost progress - or continuity gaps. - - - This flexibility is essential for managing your budget effectively, especially during - periods of downtime. - - Benefits include: + {pauseT('text1')} + {pauseT('text2')} + {pauseT('benefits')} - No Commitment: Easily pause whenever necessary without - financial penalties. + {pauseT('noCommitment.title')}:{' '} + {pauseT('noCommitment.description')} - Stay on Track: Return to ongoing work without losing - progress. + {pauseT('stayOnTrack.title')}:{' '} + {pauseT('stayOnTrack.description')} - Focused Management: Use your time and resources - efficiently while still having access to quality React maintenance when you need it. + {pauseT('focusedManagement.title')}:{' '} + {pauseT('focusedManagement.description')} - }> + }> - - For €{WEEK_PRICE}, experience the value of senior-level React maintenance for the first - week. - - In one week, you could either: + {tryT('text1', { weekPrice: WEEK_PRICE })} + {tryT('text2')} - Fix ~8 bugs, from minor UI issues to moderate - functionality fixes. + {tryT('fix.title')} {tryT('fix.description')} - Implement ~2 features, like a custom dashboard component - or an interactive form. + {tryT('implement.title')} {tryT('implement.description')} - Update all libraries to the latest versions and enhance - app performance for smoother functionality. + {tryT('update.title')} {tryT('update.description')} - Set up automated testing, laying the groundwork for - continuous integration and better long-term reliability. + {tryT('setUp.title')} {tryT('setUp.description')} - - If you're not satisfied, you'll be refunded the remaining €{REFUND_PRICE} of your - subscription - no strings attached! - + {tryT('refundText', { refundPrice: REFUND_PRICE })} diff --git a/app/components/PriceForOthers.tsx b/app/components/PriceForOthers.tsx index b155e20..8835219 100644 --- a/app/components/PriceForOthers.tsx +++ b/app/components/PriceForOthers.tsx @@ -1,5 +1,6 @@ import { mainRound } from '@/components/theme/padding.utils'; import { Box, Heading, IconButton, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import NextLink from 'next/link'; import { MdOutlineKeyboardDoubleArrowDown } from 'react-icons/md'; @@ -14,23 +15,25 @@ export const PriceForOthers = ({ subtext: string; href: string; }) => { + const t = useTranslations('priceForOthers'); + return ( {title} - up to{' '} + {t('upTo')}{' '} €{price} {' '} - / month + {t('perMonth')} {subtext} - + diff --git a/app/components/PriceItem.tsx b/app/components/PriceItem.tsx index e892a47..769fee3 100644 --- a/app/components/PriceItem.tsx +++ b/app/components/PriceItem.tsx @@ -2,10 +2,13 @@ import { mainHeadingSize, mainRound } from '@/components/theme/padding.utils'; import { shadowSharp } from '@/components/theme/theme.utils'; import { MAIN_PRICE, PRODUCT_LINK } from '@/utils/constants'; import { Box, Flex, Heading, List, Text, VStack } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import NextLink from 'next/link'; import { PriceItemButton } from './PriceItemButton'; export function PriceItem() { + const t = useTranslations('priceItem'); + return ( - Monthly Club + {t('title')} @@ -32,25 +35,18 @@ export function PriceItem() { €{MAIN_PRICE} - / month + {t('perMonth')} - React development & maintenance by a senior developer + {t('subtitle')} - {[ - 'One task handled at a time for complete focus', - 'New tasks begin within ~48 hours of completion', - 'Unlimited projects managed under one unified backlog', - 'Direct communication with the developer working on your tasks', - 'Flexible subscription to fit your needs - pause or cancel anytime', - 'Full transparency, with tasks tracked in your preferred system (Jira, Trello, etc)' - ].map((feature, index) => ( + {t.raw('features').map((feature: string, index: number) => ( { }; export function PriceItemButton() { + const t = useTranslations('priceItem'); + return ( diff --git a/app/components/Pricing.tsx b/app/components/Pricing.tsx index 21f8d6b..5eb15d7 100644 --- a/app/components/Pricing.tsx +++ b/app/components/Pricing.tsx @@ -1,16 +1,20 @@ import { mainHeadingSize, mainRounded, mainSubtextSize, outerPadding } from '@/components/theme/padding.utils'; import { Box, Flex, Heading, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import { BusinessValue } from './animations/BusinessValue'; import { PriceForOthers } from './PriceForOthers'; import { PriceItem } from './PriceItem'; export const Pricing = () => { + const t = useTranslations('pricing'); + const options = useTranslations('pricing.options'); + return ( - Simple & transparent pricing + {t('heading')} - One comprehensive plan for all your React maintenance needs. + {t('description')} @@ -25,15 +29,15 @@ export const Pricing = () => { flexDirection={['column', 'column', 'column', 'column', 'row']} > diff --git a/app/components/TechScroll.tsx b/app/components/TechScroll.tsx index 1e24654..08dcb4f 100644 --- a/app/components/TechScroll.tsx +++ b/app/components/TechScroll.tsx @@ -1,12 +1,15 @@ import { mainHeadingSize } from '@/components/theme/padding.utils'; import { Box, Heading } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import TechScrollingLogos from './TechScrollingLogos'; export const TechScroll = () => { + const t = useTranslations('techScroll'); + return ( - Technology Stack + {t('heading')} diff --git a/app/components/Testimonials.tsx b/app/components/Testimonials.tsx index 5086795..c57fca8 100644 --- a/app/components/Testimonials.tsx +++ b/app/components/Testimonials.tsx @@ -1,5 +1,6 @@ import { mainRound } from '@/components/theme/padding.utils'; import { Avatar, Box, Flex, Heading, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; import { Carousel } from './Carousel'; const TestimonialItem = ({ @@ -38,49 +39,12 @@ const TestimonialItem = ({ }; export const Testimonials = () => { - const testimonials = [ - { - title: 'Ella Martinez, Activizor', - avatar: '/testimonials/activizor.jpg', - content: - 'Staying on top of updates for our tools was always a headache for our small team. Since we started working with Trimatech, things have changed! Our workflow flows much better, and the performance improvements made a big difference for all of us.' - }, - { - title: 'James Wilson, MedCore Systems', - avatar: '/testimonials/medcore.jpg', - content: - "Our booking system was an ongoing issue, and hiring a dev wasn't in the budget. Partnering with Trimatech has really stabilized everything - fewer complaints from users and much less stress on our end. It's been a fantastic solution for us!" - }, - { - title: 'Sophie Taylor, UrbanTrack', - avatar: '/testimonials/urbantrack.jpg', - content: - 'Our logistics app is super complex, but since engaging with Trimatech, issues are getting fixed much quicker. They even offer suggestions that help us improve our system. It feels great to be proactive instead of always reacting to problems!' - }, - { - title: 'Daniel Brown, RentEase Solutions', - avatar: '/testimonials/rentease.png', - content: - 'We struggled with bugs and updates taking forever. Working with Trimatech has completely turned that around. Everything gets resolved promptly; finally, we can focus on growing our business rather than dealing with glitches!' - }, - { - title: 'Clara Müller, GlobalLink Commerce', - avatar: '/testimonials/globallink.jpg', - content: - "For over a year, Trimatech has been managing our eCommerce platform, and I can't express how comforting that has been. Their expertise means that any bumps are handled swiftly, and our operations run so much smoother now!" - }, - { - title: 'Ryan Harris, Control NQP', - avatar: '/testimonials/control.jpg', - content: - 'After we launched our React desktop app, we realized we needed help keeping it in shape. Maigo has made all the difference - updates, fixes, performance tweaks - all taken care of! I can actually focus on improving the user experience instead of worrying!' - } - ]; + const testimonials = useTranslations('testimonials'); return ( - {testimonials.map((testimonial, index) => ( + {testimonials.raw('items').map((testimonial: any, index: number) => ( {testimonial.content} diff --git a/app/layout.tsx b/app/layout.tsx index 46e001e..8795d4a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,19 +1,18 @@ -import { Metadata } from 'next'; -import Head from 'next/head'; - -import { getURL } from '@/utils/helpers'; -import { PropsWithChildren, Suspense } from 'react'; - +import { Footer } from '@/components/Header/Footer'; +import { HeaderNavbar } from '@/components/Header/HeaderNavbar'; import { ThemeProvider } from '@/components/theme/ThemeProvider'; - import { Toaster } from '@/components/ui/Toaster'; +import { getURL } from '@/utils/helpers'; import { Box } from '@chakra-ui/react'; - -import { Footer } from '@/components/Header/Footer'; -import { HeaderNavbar } from '@/components/Header/HeaderNavbar'; import { Analytics } from '@vercel/analytics/react'; import { SpeedInsights } from '@vercel/speed-insights/next'; +import { Metadata } from 'next'; +import { NextIntlClientProvider } from 'next-intl'; +import { getLocale, getMessages } from 'next-intl/server'; import { M_PLUS_Rounded_1c, Roboto } from 'next/font/google'; +import Head from 'next/head'; +import type { ReactNode } from 'react'; +import { Suspense } from 'react'; const roboto = Roboto({ weight: ['400', '500', '700'], @@ -40,24 +39,28 @@ const meta = { url: getURL() }; +type MetadataTranslation = { + title: string; + description: string; + keywords: string[]; + ogAlt: string; + twitterAlt: string; +}; + export async function generateMetadata(): Promise { + const messages = await getMessages(); + const metadata = (messages as unknown as { metadata: MetadataTranslation }).metadata; + const locale = await getLocale(); + return { - title: meta.title, - description: meta.description, + title: metadata.title, + description: metadata.description, referrer: 'origin-when-cross-origin', - keywords: [ - 'React', - 'Maintenance', - 'Development', - 'Senior Developer', - 'Bug Fixes', - 'Feature Updates', - 'React Consulting', - 'Web Development', - 'Application Maintenance', - 'Code Review' + keywords: metadata.keywords, + authors: [ + { name: 'Trimatech', url: 'https://trimatech.dev/' }, + { name: 'Maigo Erit', url: 'https://trimatech.dev/' } ], - authors: [{ name: 'Trimatech', url: 'https://trimatech.dev/' }], creator: 'Trimatech', publisher: 'Trimatech', robots: meta.robots, @@ -71,60 +74,66 @@ export async function generateMetadata(): Promise { }, openGraph: { url: meta.url, - title: meta.title, - description: meta.description, + title: metadata.title, + description: metadata.description, images: [ { url: meta.cardImage, width: 1200, height: 630, - alt: 'Trimatech - Expert React Maintenance & Development' + alt: metadata.ogAlt } ], type: 'website', - siteName: meta.title, - locale: 'en_US' + siteName: metadata.title, + locale: locale === 'et' ? 'et_EE' : 'en_US' }, twitter: { card: 'summary_large_image', site: '@Trimatech', creator: '@Trimatech', - title: meta.title, - description: meta.description, + title: metadata.title, + description: metadata.description, images: [ { url: meta.cardImage, - alt: 'Trimatech - Expert React Maintenance & Development' + alt: metadata.twitterAlt } ] } }; } -export default async function RootLayout({ children }: PropsWithChildren) { +interface Props { + children: ReactNode; +} + +export default async function RootLayout({ children }: Props) { + const locale = await getLocale(); + const messages = await getMessages(); + return ( <> - + - + - - - - - {children} - -