Add multi-language support (EN/UK) across entire site

Custom i18n system with typed translation dictionaries (~570 keys),
LanguageProvider context, and useTranslation hook. All 31 components
and pages wired with t() calls. Chatbot backend passes language hint
to Claude for Ukrainian responses. Language preference persists via
localStorage. SEO meta tags and html lang attribute update dynamically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-09 13:32:04 +00:00
parent bc80f7667c
commit 6e932d76e4
43 changed files with 3390 additions and 886 deletions

View file

@ -40,3 +40,10 @@ React 19 + TypeScript single-page application built with Vite.
**Font:** Self-hosted Inter (weights 300900) loaded via `@font-face` in `src/index.css`, served from `public/font/Inter/`.
**Base styles:** CSS reset in `src/index.css`. Dark background (`--dark-grey-100`), light text (`--light-grey-100`).
## Active Technologies
- TypeScript 5.x, React 19 + None added (custom i18n solution — no library needed for 2 languages + ~200 strings) (001-multi-language)
- localStorage for language preference (001-multi-language)
## Recent Changes
- 001-multi-language: Added TypeScript 5.x, React 19 + None added (custom i18n solution — no library needed for 2 languages + ~200 strings)

View file

@ -133,6 +133,13 @@ async def chat(req: ChatRequest):
lead_context += " IMPORTANT: Whenever the visitor mentions budget, timeline, requirements, job title, phone, city, or any useful detail — IMMEDIATELY call update_lead with the 'note' field to enrich their CRM profile.]"
messages = [{"role": "user", "content": lead_context}, {"role": "assistant", "content": "Understood. I'll use update_lead to enrich their profile whenever they share new details."}] + messages
# Prepend language hint for non-English visitors
if req.language == "uk":
messages = [
{"role": "user", "content": "[System: The visitor's interface language is Ukrainian. Default to Ukrainian in your responses unless they write in English.]"},
{"role": "assistant", "content": "Зрозуміло. Я буду відповідати українською."},
] + messages
# Get AI response
try:
reply, lead_data = await get_ai_response(messages, session_meta)

View file

@ -11,6 +11,7 @@ class ChatRequest(BaseModel):
session_id: str = Field(..., min_length=1, max_length=64)
message: str = Field(..., min_length=1, max_length=500)
page_context: str = Field(default="/", max_length=200)
language: str = Field(default="en", max_length=5)
lead: ChatLead | None = None

View file

@ -0,0 +1,35 @@
# Specification Quality Checklist: Multi-Language Support
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-09
**Feature**: [spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- All items pass validation
- Blog content: UI-only translation confirmed (articles stay English)

View file

@ -0,0 +1,57 @@
# Data Model: Multi-Language Support
## Entities
### Language
A supported language for the site UI.
| Field | Type | Description |
|-------|------|-------------|
| code | `'en' \| 'uk'` | ISO 639-1 language code |
| displayLabel | `string` | Label shown in toggle ("Eng", "Ukr") |
| locale | `string` | Full locale for date formatting ("en-GB", "uk-UA") |
**Supported languages** (hardcoded, no dynamic loading):
- English: `{ code: 'en', displayLabel: 'Eng', locale: 'en-GB' }`
- Ukrainian: `{ code: 'uk', displayLabel: 'Ukr', locale: 'uk-UA' }`
### Translations
A typed dictionary mapping translation keys to localized strings.
| Field | Type | Description |
|-------|------|-------------|
| [key: TranslationKey] | `string` | Translated text for the given key |
**TranslationKey** is a union type of all valid dot-separated keys (e.g., `'header.nav.home' | 'hero.title' | ...`).
Both `en.ts` and `uk.ts` must satisfy the `Translations` type — TypeScript enforces that no keys are missing.
### Language Preference (Client-Side)
| Storage | Key | Type | Default |
|---------|-----|------|---------|
| localStorage | `aimpress_lang` | `'en' \| 'uk'` | `'en'` |
Read on app initialization. Updated on language toggle. No server-side persistence.
## State Transitions
```
First Visit → lang = 'en' (default)
User clicks 'Ukr' → lang = 'uk', localStorage set
User clicks 'Eng' → lang = 'en', localStorage set
Return Visit → lang = localStorage value (or 'en' if cleared)
```
## Relationships
- `LanguageContext` holds current `Language` and provides `t()` function
- `t(key)` resolves against current language's `Translations` dict, falls back to English
- `ChatRequest` body includes `language` code for backend AI responses
- Date formatting uses `Language.locale` via `Intl.DateTimeFormat`
- `<html lang>` attribute set from `Language.code`

View file

@ -0,0 +1,151 @@
# Implementation Plan: Multi-Language Support
**Branch**: `001-multi-language` | **Date**: 2026-03-09 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/001-multi-language/spec.md`
## Summary
Add English/Ukrainian multi-language support to the AImpress React SPA. A custom i18n solution using React Context + typed JSON dictionaries will power the existing (non-functional) language toggle in the Header. All ~80+ hardcoded UI strings will be extracted into translation files, with localStorage persistence and chatbot backend language hints.
## Technical Context
**Language/Version**: TypeScript 5.x, React 19
**Primary Dependencies**: None added (custom i18n solution — no library needed for 2 languages + ~200 strings)
**Storage**: localStorage for language preference
**Testing**: Manual verification per component (no test framework in project)
**Target Platform**: Web browser (SPA)
**Project Type**: Web application (React SPA with Vite)
**Performance Goals**: Language switch under 1 second, no layout shift
**Constraints**: No URL-based routing for languages, blog content stays English, analytics events stay English
**Scale/Scope**: ~200 translatable strings across ~25 components, 2 languages (en, uk)
## Constitution Check
*Constitution is unconfigured (template only) — no gates to check.*
## Project Structure
### Documentation (this feature)
```text
specs/001-multi-language/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
└── tasks.md # Phase 2 output (created by /speckit.tasks)
```
### Source Code (new files)
```text
src/
i18n/
types.ts # Translations interface (all keys typed)
en.ts # English translation object
uk.ts # Ukrainian translation object
LanguageContext.tsx # LanguageProvider + useTranslation hook
index.ts # Barrel export
```
### Source Code (modified files, by phase)
**Phase 1 — Foundation + Header Toggle:**
- `src/main.tsx` — wrap `<App>` with `<LanguageProvider>`
- `src/components/Header.tsx` — wire toggle to context, replace nav item names with `t()` calls
**Phase 2 — Homepage Components (top to bottom):**
- `src/components/Hero.tsx`
- `src/components/Benefits.tsx`
- `src/components/Banner1.tsx`
- `src/components/RealResults.tsx`
- `src/components/Timeline.tsx`
- `src/components/Banner2.tsx`
- `src/components/ComparisonTable.tsx`
- `src/components/BlogSection.tsx` (date locale fix)
- `src/components/ResourcesSection.tsx`
- `src/components/ContactSection.tsx` + `src/components/ContactForm.tsx`
- `src/components/Footer.tsx`
- `src/components/CookieConsent.tsx`
**Phase 3 — Subpages + Chat + Forms:**
- `src/pages/AboutPage.tsx` (~50 strings)
- `src/pages/ServicesPage.tsx`
- `src/pages/PricingPage.tsx`
- `src/components/ChatBubble.tsx`, `ChatWindow.tsx`, `ChatLeadForm.tsx`, `ChatInput.tsx`
- `src/components/QuoteForm.tsx`
- `src/pages/BlogPage.tsx`, `BlogPostPage.tsx` (UI chrome only)
**Phase 4 — SEO + Chatbot Backend:**
- SEO component: `<html lang={lang}>` + translated titles/descriptions
- `src/hooks/useChat.ts` — pass `language` in request body
- `chatbot-api/models.py` — add `language` field to `ChatRequest`
- `chatbot-api/main.py` — prepend language hint when `language == "uk"`
**Structure Decision**: Custom i18n module at `src/i18n/` with React Context. No external library. Provider wraps the entire app at `main.tsx`.
## Technical Approach
### Why Custom Instead of react-i18next
- Only 2 languages, ~200 strings, no pluralization complexity
- Zero added bundle size beyond translation JSON
- Full TypeScript type safety on keys (typos cause compile errors)
- `react-i18next` would add ~3 dependencies for features we don't need
- Simple `t('key')` API with identical developer experience
### LanguageContext Design
```typescript
// LanguageContext.tsx — ~60 lines
interface LanguageContextValue {
lang: 'en' | 'uk';
setLang: (lang: 'en' | 'uk') => void;
t: (key: TranslationKey) => string;
}
```
- State initialized from `localStorage.getItem('aimpress_lang') || 'en'`
- `setLang` updates state + `localStorage` + `document.documentElement.lang`
- `t(key)` looks up current language dict, falls back to English dict
- `useTranslation()` hook returns `{ t, lang, setLang }`
### Header Toggle Wiring
The existing Header has a visual toggle (lines 112-142) with local state. Changes:
1. Remove local `currentLang` state — derive from context
2. Map: `lang === 'en'``"Eng"`, `lang === 'uk'``"Ukr"`
3. `handleLangSelect` calls `setLang('en')` or `setLang('uk')`
4. `navItems` array moves inside component body to use `t()` calls
### Array-Based Data Pattern
Components like Timeline, Benefits, ComparisonTable define data as module-scope arrays. These must move inside the component body (or become functions accepting `t`) since hooks can only be called inside components.
### Chatbot Backend Changes
1. `ChatRequest` model gets `language: str = "en"` field
2. When `language == "uk"`, prepend synthetic message pair to guide Claude:
- User: `[System: Visitor UI is Ukrainian. Default to Ukrainian.]`
- Assistant: `Зрозуміло. Я буду відповідати українською.`
3. Frontend `useChat` hook passes `lang` from context in request body
### SEO Approach
- Use `react-helmet-async` (already installed) to set `<html lang={lang}>`
- Each page passes `t('seo.pageName.title')` and `t('seo.pageName.description')` to `<SEO>` component
- OpenGraph/Twitter tags inherit from same translated props
## Known Risks
| Risk | Impact | Mitigation |
|------|--------|------------|
| String extraction completeness | Missing strings show English (safe but inconsistent) | Grep for quoted strings in JSX after extraction |
| JSX in translations (e.g. `<br>`) | ~5 cases where plain string lookup doesn't work | Use CSS for line breaks or `tJsx()` variant returning ReactNode |
| Ukrainian text ~15-20% longer | Layout overflow on fixed-width elements | Review all buttons/nav for flexible sizing |
| Privacy Policy / Terms of Use | Long legal text, may need legal review | Stay English-only initially, translate later |
| Blog date locale hardcoded as 'en-US' | Dates won't match language | Map `lang` to locale: `en``en-GB`, `uk``uk-UA` |
## Complexity Tracking
No constitution violations to justify.

View file

@ -0,0 +1,36 @@
# Quickstart: Multi-Language Support
## Adding a New Translation
1. Open `src/i18n/types.ts` and add the key to the `Translations` interface
2. Add the English value in `src/i18n/en.ts`
3. Add the Ukrainian value in `src/i18n/uk.ts`
4. TypeScript will error if any file is missing the new key
## Using Translations in a Component
```typescript
import { useTranslation } from '../i18n';
function MyComponent() {
const { t, lang } = useTranslation();
return <h1>{t('my.section.title')}</h1>;
}
```
## Formatting Dates by Locale
```typescript
const { lang } = useTranslation();
const locale = lang === 'uk' ? 'uk-UA' : 'en-GB';
const formatted = new Date(dateStr).toLocaleDateString(locale, {
year: 'numeric', month: 'long', day: 'numeric'
});
```
## Adding a New Language (Future)
1. Add the new code to the `Lang` type in `types.ts`
2. Create a new translation file (e.g., `de.ts`) satisfying `Translations`
3. Register it in `LanguageContext.tsx` translations map
4. Add the language option to the Header toggle

View file

@ -0,0 +1,72 @@
# Research: Multi-Language Support
## Decision 1: i18n Library Choice
**Decision**: Custom React Context + typed JSON dictionaries (no external library)
**Rationale**:
- Only 2 languages (en, uk) with ~200 strings — well below the complexity threshold where libraries add value
- Full TypeScript type safety on translation keys (compile-time error for typos)
- Zero bundle size overhead
- `react-i18next` would add 3 dependencies (i18next, react-i18next, i18next-browser-languagedetector) for features not needed: namespace lazy-loading, plural rules engine, ICU message format
- Developer experience is identical: `const { t } = useTranslation()``t('key')`
**Alternatives considered**:
- `react-i18next`: Industry standard but overkill for 2 languages + no pluralization needs
- `react-intl` (FormatJS): Even heavier, designed for complex ICU message formatting
- `typesafe-i18n`: Lighter but still an unnecessary dependency for this scale
## Decision 2: Translation File Structure
**Decision**: Single TypeScript file per language with flat dot-separated keys
**Rationale**:
- TypeScript files (not JSON) enable `satisfies Translations` type checking
- Flat keys (`'header.nav.home'`) are greppable and avoid nested access boilerplate
- Single file per language keeps translations easy to review and compare side-by-side
- ~200 keys fit comfortably in a single file without organization issues
**Alternatives considered**:
- JSON files: Lose TypeScript type checking
- Namespace-based splitting (per component): Unnecessary fragmentation for ~200 keys
- Nested objects: Require helper functions to access deeply, harder to grep
## Decision 3: Language Persistence
**Decision**: localStorage with key `aimpress_lang`
**Rationale**:
- Simplest approach for client-side only language state
- Persists across sessions without cookies or server-side state
- No GDPR/privacy concerns (functional preference, not tracking)
**Alternatives considered**:
- Cookie-based: Adds cookie consent complexity for a functional preference
- URL-based (`/en/`, `/uk/`): Ruled out in spec — would require router changes and redirect logic
## Decision 4: Chatbot Language Passing
**Decision**: Add `language` field to API request body + synthetic message pair for Ukrainian
**Rationale**:
- The chatbot backend (FastAPI) already has a system prompt that says "Respond in the same language the visitor uses"
- Explicit language parameter is more reliable than relying on message language detection
- Synthetic message pair (user hint + assistant acknowledgment) guides Claude without modifying the system prompt
**Alternatives considered**:
- Modify system prompt dynamically: Would require changing the knowledge.py architecture
- Query parameter: Less clean than request body field
- Separate endpoint per language: Over-engineered
## Decision 5: Handling JSX in Translations
**Decision**: Avoid JSX in translations; use CSS for line breaks, split strings where needed
**Rationale**:
- Only ~5 cases involve `<br>` in text (Benefits card titles)
- CSS `white-space: pre-line` or `\n` in strings handles most cases
- Keeps translation files as pure string dictionaries
**Alternatives considered**:
- `tJsx(key)` returning ReactNode: Adds complexity to the i18n system for 5 edge cases
- dangerouslySetInnerHTML: Security risk, avoid

View file

@ -0,0 +1,126 @@
# Feature Specification: Multi-Language Support
**Feature Branch**: `001-multi-language`
**Created**: 2026-03-09
**Status**: Draft
**Input**: User description: "Implement multi-language support for the site"
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Browse Site in Ukrainian (Priority: P1)
A Ukrainian-speaking visitor arrives at the AImpress website. They see a language toggle in the header (which already exists visually but is non-functional) and switch from English to Ukrainian. All static page content — headings, descriptions, navigation labels, button text, form labels, and footer — immediately updates to Ukrainian. The visitor can browse the full site in their preferred language without any page reload.
**Why this priority**: The language toggle already exists in the UI ("Eng"/"Ukr"), so users already expect this functionality. Delivering Ukrainian translations for static content is the foundational slice that enables the entire multi-language experience.
**Independent Test**: Can be fully tested by clicking the language toggle in the header and verifying all visible text on every page section changes to Ukrainian. Delivers immediate value to Ukrainian-speaking visitors.
**Acceptance Scenarios**:
1. **Given** a visitor on the English site, **When** they click the language toggle to "Ukr", **Then** all static text on the current page displays in Ukrainian without a page reload.
2. **Given** a visitor browsing in Ukrainian, **When** they navigate to a different page section, **Then** the new section also displays in Ukrainian.
3. **Given** a visitor browsing in Ukrainian, **When** they switch back to "Eng", **Then** all text reverts to English.
---
### User Story 2 - Submit Forms in Preferred Language (Priority: P2)
A visitor browsing in Ukrainian wants to submit the contact form or interact with the chatbot lead form. All form labels, placeholders, validation messages, and success/error messages appear in Ukrainian. The submitted form data is stored as-is (in whichever language the user typed), but system-generated labels and messages display in the selected language.
**Why this priority**: Forms are the primary conversion point. If a Ukrainian visitor sees a translated page but English-only forms, the experience feels incomplete and may reduce conversions.
**Independent Test**: Can be tested by switching to Ukrainian, opening the contact form, verifying all labels/placeholders are in Ukrainian, submitting with invalid data to check validation messages, and submitting successfully to verify the success message is in Ukrainian.
**Acceptance Scenarios**:
1. **Given** a visitor browsing in Ukrainian, **When** they open the contact form, **Then** all form labels, placeholders, and the submit button display in Ukrainian.
2. **Given** a visitor submitting a form in Ukrainian, **When** validation fails, **Then** error messages appear in Ukrainian.
3. **Given** a visitor submitting a form in Ukrainian, **When** submission succeeds, **Then** the success confirmation message appears in Ukrainian.
4. **Given** a visitor using the chatbot lead form in Ukrainian, **When** they see the lead capture form, **Then** all labels, placeholders, and consent text display in Ukrainian.
---
### User Story 3 - Language Preference Persistence (Priority: P3)
A visitor selects Ukrainian as their language. When they return to the site later or refresh the page, the site remembers their preference and displays in Ukrainian automatically. New visitors see the site in a default language based on reasonable defaults (English as fallback).
**Why this priority**: Without persistence, users must re-select their language on every visit, creating friction. This is important for repeat visitors but not as critical as the initial translation itself.
**Independent Test**: Can be tested by selecting Ukrainian, closing the browser tab, reopening the site, and verifying the language is still set to Ukrainian.
**Acceptance Scenarios**:
1. **Given** a visitor who previously selected Ukrainian, **When** they return to the site, **Then** the site loads in Ukrainian automatically.
2. **Given** a first-time visitor, **When** they load the site, **Then** the site displays in English by default.
3. **Given** a visitor who clears their browser data, **When** they return to the site, **Then** the site displays in English (default).
---
### User Story 4 - Chatbot Conversations in Preferred Language (Priority: P4)
A visitor browsing in Ukrainian opens the AI chatbot. The chatbot greeting, system messages, and AI responses are in Ukrainian. The chatbot understands and responds in the language the visitor is using.
**Why this priority**: The chatbot is a key engagement tool but involves backend AI integration. The static UI elements (greeting, status text, input placeholder) should match the selected language, and the AI should respond in the same language the user writes in.
**Independent Test**: Can be tested by switching to Ukrainian, opening the chatbot, verifying the greeting and UI elements are in Ukrainian, and sending a message in Ukrainian to verify the AI responds in Ukrainian.
**Acceptance Scenarios**:
1. **Given** a visitor browsing in Ukrainian, **When** they open the chatbot, **Then** the greeting message, status text, and input placeholder display in Ukrainian.
2. **Given** a visitor chatting in Ukrainian, **When** they send a message in Ukrainian, **Then** the AI responds in Ukrainian.
3. **Given** a visitor who switches language mid-conversation, **When** they switch from Ukrainian to English, **Then** new chatbot UI elements update to English (existing messages remain as-sent).
---
### Edge Cases
- What happens when a translation is missing for a specific string? The system falls back to English for that string.
- What happens when a visitor's browser language is Ukrainian but they haven't explicitly chosen a language? The site defaults to English (explicit selection required via toggle).
- How does the system handle right-to-left languages? Out of scope for this feature — only English and Ukrainian are supported.
- What happens to the cookie consent banner when language changes? It updates to the selected language like all other static content.
- What happens to blog post content when language is switched? Blog article titles and body content remain in English. Only the surrounding blog UI (section title, "View All Posts" link, date formatting) translates to the selected language.
- What happens to date formatting when language changes? Dates display in the locale matching the selected language (e.g., "March 9, 2026" vs "9 березня 2026").
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: System MUST provide a functional language toggle in the header that switches between English and Ukrainian.
- **FR-002**: System MUST translate all static UI text (navigation, headings, descriptions, button labels, footer text) to the selected language.
- **FR-003**: System MUST translate all form labels, placeholders, validation messages, and success/error messages to the selected language.
- **FR-004**: System MUST translate chatbot UI elements (greeting, status, placeholder, system messages) to the selected language.
- **FR-005**: System MUST persist the visitor's language preference across browser sessions.
- **FR-006**: System MUST default to English for first-time visitors.
- **FR-007**: System MUST fall back to English for any missing translation string.
- **FR-008**: System MUST update all visible text instantly when the language is toggled, without requiring a page reload.
- **FR-009**: System MUST format dates according to the selected language's locale conventions.
- **FR-010**: System MUST pass the selected language context to the chatbot so AI responses match the visitor's language.
- **FR-011**: System MUST translate the cookie consent banner text when language changes.
- **FR-012**: System MUST translate page titles, meta descriptions, and OpenGraph tags to the selected language for proper search engine indexing.
### Key Entities
- **Language**: A supported language with a code (e.g., "en", "uk"), display name, and flag/label for the toggle.
- **Translation**: A key-value mapping of translation keys to translated strings, organized by language.
- **Language Preference**: The visitor's stored language selection, persisted locally in the browser.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: 100% of static UI text across all page sections displays correctly in both English and Ukrainian.
- **SC-002**: Visitors can switch languages in under 1 second with no visible page reload or layout shift.
- **SC-003**: Language preference persists across at least 5 consecutive return visits.
- **SC-004**: 100% of form labels, placeholders, and messages display in the selected language.
- **SC-005**: The chatbot greets users and displays UI elements in the selected language on first open.
- **SC-006**: Missing translations gracefully fall back to English with no visible errors or broken UI.
## Assumptions
- Only two languages are in scope: English (default) and Ukrainian. Additional languages may be added in the future but are not part of this feature.
- The existing language toggle UI in the Header component ("Eng"/"Ukr") will be reused and made functional.
- Blog post body content stays in English. Only the surrounding blog UI (section titles, navigation, dates) will be translated.
- The chatbot AI (Claude Sonnet) already supports Ukrainian — no backend model changes are needed to respond in Ukrainian. The system prompt may need a language hint.
- URL structure will not change for different languages (no `/en/` or `/uk/` path prefixes). Language is client-side state only.
- Analytics event names remain in English regardless of UI language.
- SEO meta tags and page titles should also be translated for proper indexing in Ukrainian search engines.

View file

@ -0,0 +1,211 @@
# Tasks: Multi-Language Support (EN/UK)
**Input**: Design documents from `/specs/001-multi-language/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md
**Tests**: Not requested — no test tasks included.
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Create the i18n module and translation files
- [x] T001 Create `Translations` interface with all translation keys in `src/i18n/types.ts`
- [x] T002 Create English translations object (source of truth) in `src/i18n/en.ts` — extract all ~200 hardcoded strings from every component into dot-separated keys (e.g., `header.nav.home`, `hero.title`, `contact.form.fullName`). Must `satisfies Translations`.
- [x] T003 Create Ukrainian translations object in `src/i18n/uk.ts` — translate all keys from `en.ts` into Ukrainian. Must `satisfies Translations`.
- [x] T004 Create `LanguageProvider` context and `useTranslation` hook in `src/i18n/LanguageContext.tsx` — state initialized from `localStorage.getItem('aimpress_lang') || 'en'`, `setLang` updates state + localStorage + `document.documentElement.lang`, `t(key)` resolves against current language dict with English fallback.
- [x] T005 Create barrel export in `src/i18n/index.ts`
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Wire LanguageProvider into the app and connect the Header toggle
**CRITICAL**: No user story work can begin until this phase is complete
- [x] T006 Wrap `<App>` with `<LanguageProvider>` inside the existing `<HelmetProvider>` in `src/main.tsx`
- [x] T007 Wire the existing Header language toggle to the i18n context in `src/components/Header.tsx` — remove local `currentLang` state, import `useTranslation`, map `setLang('en')`/`setLang('uk')` to toggle clicks, derive display label ("Eng"/"Ukr") from context `lang` value
**Checkpoint**: Language toggle is functional — clicking Eng/Ukr updates `lang` in context and localStorage. No visible text changes yet.
---
## Phase 3: User Story 1 — Browse Site in Ukrainian (Priority: P1) MVP
**Goal**: All static homepage text displays in the selected language when the toggle is clicked.
**Independent Test**: Click the language toggle in the header → verify all visible text on every homepage section changes to Ukrainian without page reload. Click back to English → verify all text reverts.
### Implementation for User Story 1
- [x] T008 [US1] Replace hardcoded nav item names with `t()` calls in `src/components/Header.tsx` — move `navItems` array inside component body to use hook, translate login form strings
- [x] T009 [P] [US1] Replace hardcoded title and CTA button text with `t()` calls in `src/components/Hero.tsx`
- [x] T010 [P] [US1] Replace flip card titles/subtitles/back text, "Built for" section, and static card titles/descriptions with `t()` calls in `src/components/Benefits.tsx` — move data arrays inside component body
- [x] T011 [P] [US1] Replace questions array and CTA button text with `t()` calls in `src/components/Banner1.tsx`
- [x] T012 [P] [US1] Replace section title and result descriptions with `t()` calls in `src/components/RealResults.tsx`
- [x] T013 [P] [US1] Replace section title, step titles, durations, descriptions, and details with `t()` calls in `src/components/Timeline.tsx` — move timeline steps array inside component body
- [x] T014 [P] [US1] Replace questions and CTA button text with `t()` calls in `src/components/Banner2.tsx`
- [x] T015 [P] [US1] Replace section title, metric labels/values, alternative names, and footer text with `t()` calls in `src/components/ComparisonTable.tsx` — move comparison data inside component body
- [x] T016 [P] [US1] Replace "Recent Updates" title, "View All Posts" link text with `t()` calls, and update date locale from hardcoded `'en-US'` to `lang === 'uk' ? 'uk-UA' : 'en-GB'` in `src/components/BlogSection.tsx`
- [x] T017 [P] [US1] Replace section title and UI text with `t()` calls in `src/components/ResourcesSection.tsx`
- [x] T018 [P] [US1] Replace heading and subtitle with `t()` calls in `src/components/ContactSection.tsx`
- [x] T019 [P] [US1] Replace copyright text and link labels with `t()` calls in `src/components/Footer.tsx`
- [x] T020 [P] [US1] Replace banner text and button labels with `t()` calls in `src/components/CookieConsent.tsx`
**Checkpoint**: All homepage static text switches between English and Ukrainian via the Header toggle. MVP deliverable.
---
## Phase 4: User Story 2 — Submit Forms in Preferred Language (Priority: P2)
**Goal**: Contact form, chatbot lead form — all labels, placeholders, validation messages, and success/error messages display in the selected language.
**Independent Test**: Switch to Ukrainian → open contact form → verify all labels/placeholders are in Ukrainian → submit with invalid data → verify validation messages in Ukrainian → submit successfully → verify success message in Ukrainian. Repeat for chatbot lead form.
### Implementation for User Story 2
- [x] T021 [P] [US2] Replace form title, labels ("Full Name", "Job Title / Role", "Work Email", etc.), placeholders, submit button text ("Submit a request" / "Sending..."), success message, and validation error messages with `t()` calls in `src/components/ContactForm.tsx`
- [x] T022 [P] [US2] Replace chatbot lead form labels ("Your name *", "Email *", "Company (optional)"), placeholders, consent text ("I agree to the processing..."), and button text with `t()` calls in `src/components/ChatLeadForm.tsx`
- [x] T023 [P] [US2] Replace quote form labels and placeholders with `t()` calls in `src/components/QuoteForm.tsx`
**Checkpoint**: All forms display labels, placeholders, validation, and success messages in the selected language.
---
## Phase 5: User Story 3 — Language Preference Persistence (Priority: P3)
**Goal**: Selected language persists across browser sessions via localStorage.
**Independent Test**: Select Ukrainian → close tab → reopen site → verify it loads in Ukrainian. Clear browser data → reopen → verify English default.
### Implementation for User Story 3
- [x] T024 [US3] Verify and ensure `LanguageContext.tsx` reads from `localStorage` on initialization in `src/i18n/LanguageContext.tsx` — confirm `useState` initializer reads `localStorage.getItem('aimpress_lang')`, confirm `setLang` writes to localStorage, confirm `document.documentElement.lang` is set on mount
- [x] T025 [US3] Verify the Header toggle correctly reflects the persisted language on page load in `src/components/Header.tsx` — ensure the toggle display ("Eng"/"Ukr") matches the stored preference, not a hardcoded default
**Checkpoint**: Language preference persists across page refreshes and browser sessions.
---
## Phase 6: User Story 4 — Chatbot Conversations in Preferred Language (Priority: P4)
**Goal**: Chatbot UI displays in the selected language and AI responds in the matching language.
**Independent Test**: Switch to Ukrainian → open chatbot → verify greeting, status text ("Online"), and input placeholder are in Ukrainian → send a message → verify AI responds in Ukrainian.
### Implementation for User Story 4
- [x] T026 [P] [US4] Replace greeting text with `t()` call in `src/components/ChatBubble.tsx`
- [x] T027 [P] [US4] Replace "Online" status text, welcome message with `t()` calls in `src/components/ChatWindow.tsx`
- [x] T028 [P] [US4] Replace "Type a message..." placeholder with `t()` call in `src/components/ChatInput.tsx`
- [x] T029 [US4] Pass `lang` from `useTranslation()` context to the `useChat` hook in `src/components/ChatWidget.tsx` — pass as parameter to `useChat`
- [x] T030 [US4] Add `language` field to the request body sent to `/api/chat` in `src/hooks/useChat.ts` — accept `lang` parameter, include `language: lang` in fetch body
- [x] T031 [US4] Add `language: str = "en"` field to `ChatRequest` model in `chatbot-api/models.py`
- [x] T032 [US4] Prepend language hint to messages when `req.language == "uk"` in `chatbot-api/main.py` — add synthetic user/assistant message pair before the conversation to guide Claude to respond in Ukrainian
**Checkpoint**: Chatbot UI is fully translated and AI responds in the selected language.
---
## Phase 7: Polish & Cross-Cutting Concerns
**Purpose**: SEO, subpage translations, and final verification
- [x] T033 [P] [US1] Replace all hardcoded text (hero, story, differentiators, values, founder bio, industries, CTA) with `t()` calls in `src/pages/AboutPage.tsx` — move data arrays (differentiators, values, industries) inside component body
- [x] T034 [P] [US1] Replace service titles, descriptions, includes list, and CTA with `t()` calls in `src/pages/ServicesPage.tsx`
- [x] T035 [P] [US1] Replace pricing labels, tier names, feature lists, and CTA with `t()` calls in `src/pages/PricingPage.tsx`
- [x] T036 [P] [US1] Replace page title and UI chrome with `t()` calls in `src/pages/BlogPage.tsx` — update date locale
- [x] T037 [P] [US1] Replace "Back to blog" link text with `t()` call and update date formatting locale in `src/pages/BlogPostPage.tsx`
- [x] T038 Add `<html lang={lang}>` via Helmet and pass translated SEO title/description to each page's `<SEO>` component — update SEO component to accept lang, add `seo.*` translation keys for all pages
- [x] T039 Visual review of all pages in Ukrainian — check for text overflow, broken layouts, or missing translations. Fix any CSS issues caused by longer Ukrainian text (buttons, nav items, cards).
- [x] T040 Run quickstart.md validation — verify the developer workflow (adding a new key, using `t()` in a component, date formatting) works as documented
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies — can start immediately
- **Foundational (Phase 2)**: Depends on Phase 1 completion — BLOCKS all user stories
- **User Stories (Phases 3-6)**: All depend on Phase 2 completion
- US1 (Phase 3): No dependencies on other stories
- US2 (Phase 4): No dependencies on other stories (forms are separate components)
- US3 (Phase 5): Largely verification — persistence is built into Phase 2's LanguageContext
- US4 (Phase 6): No dependencies on other stories (chatbot is separate component tree)
- **Polish (Phase 7)**: Depends on Phase 2 completion (can run in parallel with user stories, but best after US1)
### User Story Dependencies
- **US1 (P1)**: After Phase 2 — no cross-story dependencies
- **US2 (P2)**: After Phase 2 — no cross-story dependencies
- **US3 (P3)**: After Phase 2 — verification only (persistence built into LanguageContext)
- **US4 (P4)**: After Phase 2 — no cross-story dependencies, but includes backend changes
### Parallel Opportunities
- T009T020 (US1 homepage components): ALL parallelizable — different files, no dependencies
- T021T023 (US2 forms): ALL parallelizable — different files
- T026T028 (US4 chat UI): ALL parallelizable — different files
- T033T037 (Polish subpages): ALL parallelizable — different files
- US1, US2, US4 can all proceed in parallel after Phase 2
---
## Parallel Example: User Story 1
```bash
# After Phase 2 is complete, launch all homepage component translations in parallel:
Task: "T009 — Replace text in Hero.tsx"
Task: "T010 — Replace text in Benefits.tsx"
Task: "T011 — Replace text in Banner1.tsx"
Task: "T012 — Replace text in RealResults.tsx"
Task: "T013 — Replace text in Timeline.tsx"
Task: "T014 — Replace text in Banner2.tsx"
Task: "T015 — Replace text in ComparisonTable.tsx"
Task: "T016 — Replace text in BlogSection.tsx"
Task: "T017 — Replace text in ResourcesSection.tsx"
Task: "T018 — Replace text in ContactSection.tsx"
Task: "T019 — Replace text in Footer.tsx"
Task: "T020 — Replace text in CookieConsent.tsx"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Create i18n module + translation files (T001T005)
2. Complete Phase 2: Wire provider + Header toggle (T006T007)
3. Complete Phase 3: Translate all homepage components (T008T020)
4. **STOP and VALIDATE**: Toggle language, verify all homepage text switches
5. Deploy/demo if ready
### Incremental Delivery
1. Phase 1 + 2 → Foundation ready
2. Add US1 (homepage) → Test → Deploy (MVP!)
3. Add US2 (forms) → Test → Deploy
4. Add US3 (persistence verification) → Test → Deploy
5. Add US4 (chatbot) → Test → Deploy
6. Polish (subpages + SEO) → Final deploy
---
## Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- Translation files (T002, T003) are the most labor-intensive tasks — ~200 keys each
- Ukrainian translations should be reviewed by a native speaker before final deploy
- Privacy Policy and Terms of Use pages remain English-only (legal review needed for translation)
- Analytics event names stay in English regardless of UI language

View file

@ -3,16 +3,18 @@ import { motion } from 'framer-motion';
import Modal from './Modal';
import ContactForm from './ContactForm';
import './Banner1.css';
const questions = [
'How many employees in your company?',
'How many hours per day on repetitive tasks?',
'Which processes do you want to automate?',
];
import { useTranslation } from '../i18n';
const Banner1: React.FC = () => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState(false);
const questions = [
t('banner1.q1'),
t('banner1.q2'),
t('banner1.q3'),
];
return (
<section className="banner1-section">
<div className="banner1-bg">
@ -52,7 +54,7 @@ const Banner1: React.FC = () => {
viewport={{ once: true }}
transition={{ duration: 0.4, delay: 0.6 }}
>
Get Your Free Consultation
{t('banner1.cta')}
</motion.button>
</div>
</div>

View file

@ -3,8 +3,10 @@ import { motion } from 'framer-motion';
import Modal from './Modal';
import ContactForm from './ContactForm';
import './Banner2.css';
import { useTranslation } from '../i18n';
const Banner2: React.FC = () => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState(false);
const [isHovered, setIsHovered] = useState(false);
@ -39,7 +41,7 @@ const Banner2: React.FC = () => {
{/* Rotating text */}
<img
src="/bg/banner%202%20text.svg"
alt="Get Your Free Consultation"
alt={t('banner2.cta')}
className="banner2-rotating-text"
/>

View file

@ -2,6 +2,7 @@ import React from 'react';
import { motion } from 'framer-motion';
import FlipCard from './FlipCard';
import './Benefits.css';
import { useTranslation } from '../i18n';
const TRACKERS = Array.from({ length: 25 }, (_, i) => i + 1);
@ -19,37 +20,48 @@ const TiltCard: React.FC<{ children: React.ReactNode; className?: string }> = ({
};
const Benefits: React.FC = () => {
const { t } = useTranslation();
const flipCards = [
{
frontImage: "/bg/cost scale.png",
frontTitle: t('benefits.card1.front'),
frontSubtitle: t('benefits.card1.subtitle'),
backTitle: t('benefits.card1.front'),
backSubtitle: t('benefits.card1.subtitle'),
backText: t('benefits.card1.back'),
},
{
frontImage: "/bg/Accuracy.png",
frontTitle: t('benefits.card2.front'),
frontSubtitle: t('benefits.card2.subtitle'),
backTitle: t('benefits.card2.front'),
backSubtitle: t('benefits.card2.subtitle'),
backText: t('benefits.card2.back'),
},
{
frontImage: "/bg/Availability.png",
frontTitle: t('benefits.card3.front'),
frontSubtitle: t('benefits.card3.subtitle'),
backTitle: t('benefits.card3.front'),
backSubtitle: t('benefits.card3.subtitle'),
backText: t('benefits.card3.back'),
},
];
const staticCards = [
{ icon: "/icons/chatbot icon.svg", title: t('benefits.static1.title'), desc: t('benefits.static1.desc') },
{ icon: "/icons/Business Process icon.svg", title: t('benefits.static2.title'), desc: t('benefits.static2.desc'), descClass: "small-text" },
{ icon: "/icons/Content Farms icon.svg", title: t('benefits.static3.title'), desc: t('benefits.static3.desc') },
{ icon: "/icons/Marketing Automation icon.svg", title: t('benefits.static4.title'), desc: t('benefits.static4.desc') },
];
return (
<section className="benefits-section" id="benefits">
<div className="container">
{/* Flip Cards Grid */}
<div className="benefits-grid">
{[
{
frontImage: "/bg/cost scale.png",
frontTitle: "Cost & Scale",
frontSubtitle: "Elastic Growth, Zero Headcount",
backTitle: "Cost & Scale",
backSubtitle: "Elastic Growth, Zero Headcount",
backText: "Scale 10x without hiring. AI assistants handle peak loads automatically, keeping your operations elastic and efficient.",
},
{
frontImage: "/bg/Accuracy.png",
frontTitle: "Accuracy",
frontSubtitle: "Machine Learning Precision (99.9%)",
backTitle: "Accuracy",
backSubtitle: "Machine Learning Precision (99.9%)",
backText: "Eliminate human error. Our ML-validated workflows guarantee data integrity across CRM, finance, and logistics systems.",
},
{
frontImage: "/bg/Availability.png",
frontTitle: "Availability",
frontSubtitle: "True 24/7/365 Operations",
backTitle: "Availability",
backSubtitle: "True 24/7/365 Operations",
backText: "Your AI workforce never sleeps, takes breaks, or burns out. Serve global clients nonstop.",
},
].map((card, i) => (
{flipCards.map((card, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 40 }}
@ -74,19 +86,14 @@ const Benefits: React.FC = () => {
<div className="container built-content-container">
<div className="built-header">
<h2 className="built-title">Built for Local Entrepreneurs Like You</h2>
<h2 className="built-title">{t('benefits.builtTitle')}</h2>
<p className="built-desc">
AImpress turns chaos into a system. We build automations that save up to 75% of your time and boost sales.
{t('benefits.builtDesc')}
</p>
</div>
<div className="static-cards-grid">
{[
{ icon: "/icons/chatbot icon.svg", title: <>Chatbots & <br /> AI Assistants</>, desc: "→ instant answers 24/7" },
{ icon: "/icons/Business Process icon.svg", title: <>Business Process <br /> Automation (n8n, Make.com)</>, desc: "→ CRM, email, finance, inventory", descClass: "small-text" },
{ icon: "/icons/Content Farms icon.svg", title: <>Content Farms & <br /> AI Copywriting</>, desc: "→ AI creates posts, articles, product descriptions" },
{ icon: "/icons/Marketing Automation icon.svg", title: <>Marketing Automation <br /> (email, CRM, ads)</>, desc: "→ campaigns, lead nurturing, ads on autopilot" },
].map((card, i) => (
{staticCards.map((card, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 40 }}

View file

@ -1,17 +1,19 @@
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import { useTranslation } from '../i18n';
import type { BlogPostSummary } from '../types/blog';
import './BlogSection.css';
function formatDate(dateStr: string) {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d).toLocaleDateString('en-US', {
month: 'short', day: '2-digit', year: 'numeric',
});
}
const BlogSection: React.FC = () => {
const { t, lang } = useTranslation();
function formatDate(dateStr: string) {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d).toLocaleDateString(lang === 'uk' ? 'uk-UA' : 'en-GB', {
month: 'short', day: '2-digit', year: 'numeric',
});
}
const [posts, setPosts] = useState<BlogPostSummary[]>([]);
useEffect(() => {
@ -26,7 +28,7 @@ const BlogSection: React.FC = () => {
return (
<section className="blog-section" id="blog">
<div className="container">
<h2 className="section-title">Recent Updates</h2>
<h2 className="section-title">{t('blogSection.title')}</h2>
<div className="blog-grid">
{posts.map((post, index) => (
@ -50,7 +52,7 @@ const BlogSection: React.FC = () => {
<span className="blog-date">{formatDate(post.date)}</span>
<h3 className="blog-title">{post.title}</h3>
<p className="blog-excerpt">{post.excerpt}</p>
<span className="blog-link">Read More </span>
<span className="blog-link">{t('blogSection.readMore')}</span>
</div>
</Link>
</motion.article>
@ -58,7 +60,7 @@ const BlogSection: React.FC = () => {
</div>
<div className="blog-view-all">
<Link to="/blog" className="blog-view-all-link">View All Posts </Link>
<Link to="/blog" className="blog-view-all-link">{t('blogSection.viewAll')}</Link>
</div>
</div>
</section>

View file

@ -1,5 +1,6 @@
import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useTranslation } from '../i18n';
import './ChatBubble.css';
interface ChatBubbleProps {
@ -15,6 +16,8 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({
showGreeting,
onDismissGreeting,
}) => {
const { t } = useTranslation();
if (isOpen) return null;
return (
@ -28,7 +31,7 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({
exit={{ opacity: 0, y: 8, scale: 0.9 }}
transition={{ duration: 0.25 }}
>
<p>Hi! How can I help you today?</p>
<p>{t('chat.greeting')}</p>
<button
className="chat-bubble__greeting-close"
onClick={(e) => { e.stopPropagation(); onDismissGreeting(); }}
@ -45,7 +48,7 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({
<motion.button
className="chat-bubble"
onClick={onClick}
aria-label="Open chat"
aria-label={t('chat.openChat')}
whileHover={{ scale: 1.08 }}
whileTap={{ scale: 0.95 }}
>

View file

@ -1,4 +1,5 @@
import React, { useState, type KeyboardEvent } from 'react';
import { useTranslation } from '../i18n';
import './ChatInput.css';
interface ChatInputProps {
@ -7,6 +8,7 @@ interface ChatInputProps {
}
const ChatInput: React.FC<ChatInputProps> = ({ onSend, disabled }) => {
const { t } = useTranslation();
const [value, setValue] = useState('');
const handleSend = () => {
@ -30,7 +32,7 @@ const ChatInput: React.FC<ChatInputProps> = ({ onSend, disabled }) => {
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Type a message..."
placeholder={t('chat.inputPlaceholder')}
maxLength={500}
rows={1}
disabled={disabled}
@ -39,7 +41,7 @@ const ChatInput: React.FC<ChatInputProps> = ({ onSend, disabled }) => {
className="chat-input__send"
onClick={handleSend}
disabled={disabled || !value.trim()}
aria-label="Send message"
aria-label={t('chat.send')}
>
<svg viewBox="0 0 24 24" width="20" height="20">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />

View file

@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { useTranslation } from '../i18n';
import './ChatLeadForm.css';
export interface LeadInfo {
@ -12,6 +13,7 @@ interface ChatLeadFormProps {
}
const ChatLeadForm: React.FC<ChatLeadFormProps> = ({ onSubmit }) => {
const { t } = useTranslation();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [company, setCompany] = useState('');
@ -20,10 +22,10 @@ const ChatLeadForm: React.FC<ChatLeadFormProps> = ({ onSubmit }) => {
const validate = () => {
const errs: Record<string, string> = {};
if (!name.trim()) errs.name = 'Please enter your name';
if (!email.trim()) errs.email = 'Please enter your email';
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errs.email = 'Please enter a valid email';
if (!consent) errs.consent = 'Please accept to continue';
if (!name.trim()) errs.name = t('chat.lead.nameError');
if (!email.trim()) errs.email = t('chat.lead.emailError');
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errs.email = t('chat.lead.emailInvalid');
if (!consent) errs.consent = t('chat.lead.consentError');
setErrors(errs);
return Object.keys(errs).length === 0;
};
@ -39,20 +41,20 @@ const ChatLeadForm: React.FC<ChatLeadFormProps> = ({ onSubmit }) => {
<div className="chat-lead-form__header">
<div className="chat-lead-form__avatar">AI</div>
<div>
<div className="chat-lead-form__title">AImpress</div>
<div className="chat-lead-form__subtitle">AI & Automation Consultancy</div>
<div className="chat-lead-form__title">{t('chat.lead.title')}</div>
<div className="chat-lead-form__subtitle">{t('chat.lead.subtitle')}</div>
</div>
</div>
<p className="chat-lead-form__intro">
Hi! Before we start, please introduce yourself so we can better assist you.
{t('chat.lead.intro')}
</p>
<div className="chat-lead-form__fields">
<div className="chat-lead-form__group">
<input
type="text"
placeholder="Your name *"
placeholder={t('chat.lead.namePlaceholder')}
value={name}
onChange={(e) => setName(e.target.value)}
className={errors.name ? 'chat-lead-form__input--error' : ''}
@ -64,7 +66,7 @@ const ChatLeadForm: React.FC<ChatLeadFormProps> = ({ onSubmit }) => {
<div className="chat-lead-form__group">
<input
type="email"
placeholder="Email *"
placeholder={t('chat.lead.emailPlaceholder')}
value={email}
onChange={(e) => setEmail(e.target.value)}
className={errors.email ? 'chat-lead-form__input--error' : ''}
@ -76,7 +78,7 @@ const ChatLeadForm: React.FC<ChatLeadFormProps> = ({ onSubmit }) => {
<div className="chat-lead-form__group">
<input
type="text"
placeholder="Company (optional)"
placeholder={t('chat.lead.companyPlaceholder')}
value={company}
onChange={(e) => setCompany(e.target.value)}
maxLength={200}
@ -90,15 +92,15 @@ const ChatLeadForm: React.FC<ChatLeadFormProps> = ({ onSubmit }) => {
onChange={(e) => setConsent(e.target.checked)}
/>
<span>
I agree to the processing of my personal data in accordance with the{' '}
<a href="/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>
{t('chat.lead.consent')}{' '}
<a href="/privacy" target="_blank" rel="noopener noreferrer">{t('chat.lead.privacyLink')}</a>
</span>
</label>
{errors.consent && <span className="chat-lead-form__error">{errors.consent}</span>}
</div>
<button type="submit" className="chat-lead-form__submit">
Start Chat
{t('chat.lead.submit')}
</button>
</form>
);

View file

@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import { AnimatePresence } from 'framer-motion';
import mixpanel from 'mixpanel-browser';
import { getConsent } from './CookieConsent';
import { useTranslation } from '../i18n';
import { useChat } from '../hooks/useChat';
import ChatBubble from './ChatBubble';
import ChatWindow from './ChatWindow';
@ -11,9 +12,10 @@ const GREETING_KEY = 'aimpress_chat_greeted';
const GREETING_DELAY = 30000; // 30 seconds
const ChatWidget: React.FC = () => {
const { lang } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const [showGreeting, setShowGreeting] = useState(false);
const { messages, loading, rateLimited, lead, sendMessage, setLead, clearChat, sessionId } = useChat();
const { messages, loading, rateLimited, lead, sendMessage, setLead, clearChat, sessionId } = useChat(lang);
const openedAtRef = React.useRef<number>(0);
// Auto-greeting after 30s (once per session)

View file

@ -1,5 +1,6 @@
import React, { useEffect, useRef } from 'react';
import { motion } from 'framer-motion';
import { useTranslation } from '../i18n';
import ChatMessage from './ChatMessage';
import ChatInput from './ChatInput';
import ChatLeadForm from './ChatLeadForm';
@ -28,6 +29,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
onClear,
onLeadSubmit,
}) => {
const { t } = useTranslation();
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@ -50,7 +52,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
<button
className="chat-window__action-btn"
onClick={onClose}
aria-label="Close chat"
aria-label={t('chat.closeChat')}
>
<svg viewBox="0 0 24 24" width="18" height="18">
<path d="M18 6L6 18M6 6l12 12" />
@ -66,16 +68,16 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
<div className="chat-window__header-info">
<div className="chat-window__avatar">AI</div>
<div>
<div className="chat-window__title">AImpress</div>
<div className="chat-window__status">Online</div>
<div className="chat-window__title">{t('chat.headerTitle')}</div>
<div className="chat-window__status">{t('chat.status')}</div>
</div>
</div>
<div className="chat-window__header-actions">
<button
className="chat-window__action-btn"
onClick={onClear}
aria-label="Clear chat"
title="Clear chat"
aria-label={t('chat.clearChat')}
title={t('chat.clearChat')}
>
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14" />
@ -84,7 +86,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
<button
className="chat-window__action-btn"
onClick={onClose}
aria-label="Close chat"
aria-label={t('chat.closeChat')}
>
<svg viewBox="0 0 24 24" width="18" height="18">
<path d="M18 6L6 18M6 6l12 12" />
@ -96,7 +98,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
<div className="chat-window__messages">
{messages.length === 0 && (
<div className="chat-window__welcome">
<p>Hi! I'm the AImpress AI assistant. Ask me about our AI & automation services, pricing, or book a free consultation.</p>
<p>{t('chat.welcome')}</p>
</div>
)}
{messages.map((msg) => (

View file

@ -1,35 +1,37 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useTranslation } from '../i18n';
import Modal from './Modal';
import ContactForm from './ContactForm';
import './ComparisonTable.css';
const metrics = [
{
label: 'Cost',
aimpress: 'From £1,500',
others: ['£5,000+ /mo agency', '£35,000+ /yr in-house'],
},
{
label: 'Speed',
aimpress: 'Instant',
others: ['Depends on manager', '9-to-5 only'],
},
{
label: 'Availability',
aimpress: '24 / 7 / 365',
others: ['Business hours', 'Sick leaves & holidays'],
},
{
label: 'Scalability',
aimpress: 'Unlimited',
others: ['Limited by staff', 'Hard to scale'],
},
];
const AUTOPLAY_MS = 2000;
const ComparisonTable: React.FC = () => {
const { t } = useTranslation();
const metrics = [
{
label: t('comparison.metric1.label'),
aimpress: t('comparison.metric1.ai'),
others: [t('comparison.metric1.agency'), t('comparison.metric1.inhouse')],
},
{
label: t('comparison.metric2.label'),
aimpress: t('comparison.metric2.ai'),
others: [t('comparison.metric2.agency'), t('comparison.metric2.inhouse')],
},
{
label: t('comparison.metric3.label'),
aimpress: t('comparison.metric3.ai'),
others: [t('comparison.metric3.agency'), t('comparison.metric3.inhouse')],
},
{
label: t('comparison.metric4.label'),
aimpress: t('comparison.metric4.ai'),
others: [t('comparison.metric4.agency'), t('comparison.metric4.inhouse')],
},
];
const [isModalOpen, setIsModalOpen] = useState(false);
const [activeMetric, setActiveMetric] = useState(0);
const [isPaused, setIsPaused] = useState(false);
@ -59,7 +61,7 @@ const ComparisonTable: React.FC = () => {
return (
<section className="comparison-section" id="comparison">
<div className="container">
<h2 className="section-title">Why Businesses Switch to AImpress</h2>
<h2 className="section-title">{t('comparison.title')}</h2>
<div className="ct-layout">
{/* Left: AImpress hero card */}
@ -73,7 +75,7 @@ const ComparisonTable: React.FC = () => {
<div className="ct-hero-glow" />
<div className="ct-hero-head">
<img src="/logo/aimpress-logo-mobile.svg" alt="AImpress" className="ct-hero-logo" />
<span className="ct-hero-label">AI-Powered</span>
<span className="ct-hero-label">{t('comparison.aiLabel')}</span>
</div>
<div className="ct-hero-metrics">
@ -99,7 +101,7 @@ const ComparisonTable: React.FC = () => {
</div>
<button className="ct-hero-cta" onClick={() => setIsModalOpen(true)}>
Get Your Free Consultation
{t('comparison.cta')}
</button>
</motion.div>
@ -111,7 +113,7 @@ const ComparisonTable: React.FC = () => {
viewport={{ once: true, margin: "-60px" }}
transition={{ duration: 0.5, delay: 0.15 }}
>
<span className="ct-alt-heading">The alternatives</span>
<span className="ct-alt-heading">{t('comparison.altHeading')}</span>
<AnimatePresence mode="wait">
<motion.div
@ -123,7 +125,7 @@ const ComparisonTable: React.FC = () => {
transition={{ duration: 0.25 }}
>
<div className="ct-alt-card">
<span className="ct-alt-name">Traditional Agency</span>
<span className="ct-alt-name">{t('comparison.alt1')}</span>
<div className="ct-alt-detail">
<span className="ct-alt-metric-label">{metrics[activeMetric].label}</span>
<span className="ct-alt-metric-value">{metrics[activeMetric].others[0]}</span>
@ -134,7 +136,7 @@ const ComparisonTable: React.FC = () => {
</div>
<div className="ct-alt-card">
<span className="ct-alt-name">In-House Hire</span>
<span className="ct-alt-name">{t('comparison.alt2')}</span>
<div className="ct-alt-detail">
<span className="ct-alt-metric-label">{metrics[activeMetric].label}</span>
<span className="ct-alt-metric-value">{metrics[activeMetric].others[1]}</span>
@ -146,9 +148,7 @@ const ComparisonTable: React.FC = () => {
</motion.div>
</AnimatePresence>
<p className="ct-alt-footer">
Switch from legacy methods save up to <strong>70%</strong> on costs and <strong>30+ hours</strong> per week.
</p>
<p className="ct-alt-footer" dangerouslySetInnerHTML={{ __html: t('comparison.footer') }} />
</motion.div>
</div>
</div>

View file

@ -1,6 +1,7 @@
import React, { useState, type ChangeEvent, type FormEvent } from 'react';
import { motion } from 'framer-motion';
import mixpanel from 'mixpanel-browser';
import { useTranslation } from '../i18n';
import './ContactForm.css';
interface FormData {
@ -17,6 +18,7 @@ interface ContactFormProps {
}
const ContactForm: React.FC<ContactFormProps> = () => {
const { t } = useTranslation();
const [formData, setFormData] = useState<FormData>({
fullName: '',
workEmail: '',
@ -74,24 +76,24 @@ const ContactForm: React.FC<ContactFormProps> = () => {
return (
<div className="contact-form-container">
<h2 className="form-title">Get in touch:</h2>
<h2 className="form-title">{t('contactForm.title')}</h2>
{status === 'success' ? (
<div className="success-message">
<h3>Thank you!</h3>
<p>We have received your request and will contact you shortly.</p>
<button onClick={() => setStatus('idle')} className="reset-btn">Send another</button>
<h3>{t('contactForm.successTitle')}</h3>
<p>{t('contactForm.successText')}</p>
<button onClick={() => setStatus('idle')} className="reset-btn">{t('contactForm.sendAnother')}</button>
</div>
) : (
<form onSubmit={handleSubmit} className="contact-form">
<div className="form-grid">
{[
{ label: 'Full Name', name: 'fullName', type: 'text', placeholder: 'John Doe' },
{ label: 'Job Title / Role', name: 'jobTitle', type: 'text', placeholder: 'Project Manager' },
{ label: 'Work Email', name: 'workEmail', type: 'email', placeholder: 'john@company.com' },
{ label: 'Automation Need', name: 'automationNeed', type: 'text', placeholder: 'Workflow optimization' },
{ label: 'Company Name', name: 'companyName', type: 'text', placeholder: 'Tech Solutions Inc.' },
{ label: 'Phone Number', name: 'phoneNumber', type: 'tel', placeholder: '+44...' },
{ label: t('contactForm.fullName'), name: 'fullName', type: 'text', placeholder: t('contactForm.fullNamePlaceholder') },
{ label: t('contactForm.jobTitle'), name: 'jobTitle', type: 'text', placeholder: t('contactForm.jobTitlePlaceholder') },
{ label: t('contactForm.email'), name: 'workEmail', type: 'email', placeholder: t('contactForm.emailPlaceholder') },
{ label: t('contactForm.need'), name: 'automationNeed', type: 'text', placeholder: t('contactForm.needPlaceholder') },
{ label: t('contactForm.company'), name: 'companyName', type: 'text', placeholder: t('contactForm.companyPlaceholder') },
{ label: t('contactForm.phone'), name: 'phoneNumber', type: 'tel', placeholder: t('contactForm.phonePlaceholder') },
].map((field, i) => (
<motion.div
key={field.name}
@ -130,9 +132,9 @@ const ContactForm: React.FC<ContactFormProps> = () => {
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
{status === 'submitting' ? 'Sending...' : 'Submit a request'}
{status === 'submitting' ? t('contactForm.sending') : t('contactForm.submit')}
</motion.button>
{status === 'error' && <p className="error-text">Something went wrong. Please try again.</p>}
{status === 'error' && <p className="error-text">{t('contactForm.error')}</p>}
</motion.div>
</form>
)}

View file

@ -1,9 +1,11 @@
import React from 'react';
import { motion } from 'framer-motion';
import { useTranslation } from '../i18n';
import ContactForm from './ContactForm';
import './ContactSection.css';
const ContactSection: React.FC = () => {
const { t } = useTranslation();
return (
<section className="contact-section" id="contact">
<div className="container">
@ -15,9 +17,9 @@ const ContactSection: React.FC = () => {
transition={{ duration: 0.5 }}
>
<div className="contact-header">
<h2 className="section-title">Ready to Automate?</h2>
<h2 className="section-title">{t('contactSection.title')}</h2>
<p className="contact-subtitle">
Stop wasting time on routine. Start scaling with AI today.
{t('contactSection.subtitle')}
</p>
</div>

View file

@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from '../i18n';
import { initAnalytics } from '../analytics';
import './CookieConsent.css';
@ -12,6 +13,7 @@ export function getConsent(): ConsentValue | null {
}
const CookieConsent = () => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
useEffect(() => {
@ -40,16 +42,15 @@ const CookieConsent = () => {
<div className="cookie-banner">
<div className="cookie-banner-inner">
<p className="cookie-banner-text">
We use cookies and similar technologies to analyse website traffic and improve your
experience. By clicking "Accept", you consent to the use of analytics cookies.
See our <Link to="/privacy-policy">Privacy Policy</Link> for details.
{t('cookie.text')}{' '}
<Link to="/privacy-policy">{t('cookie.privacyLink')}</Link>
</p>
<div className="cookie-banner-actions">
<button className="cookie-btn cookie-btn-reject" onClick={handleReject}>
Reject
{t('cookie.reject')}
</button>
<button className="cookie-btn cookie-btn-accept" onClick={handleAccept}>
Accept
{t('cookie.accept')}
</button>
</div>
</div>

View file

@ -1,8 +1,10 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from '../i18n';
import './Footer.css';
const Footer: React.FC = () => {
const { t } = useTranslation();
return (
<footer className="footer">
<div className="container footer-content">
@ -29,11 +31,11 @@ const Footer: React.FC = () => {
<div className="footer-legal">
<div className="footer-legal-links">
<Link to="/privacy-policy">Privacy Policy</Link>
<Link to="/privacy-policy">{t('footer.privacy')}</Link>
<span className="footer-legal-sep">|</span>
<Link to="/terms-of-use">Terms of Use</Link>
<Link to="/terms-of-use">{t('footer.terms')}</Link>
</div>
<p>&copy; 2026 AImpress LTD. All rights reserved.</p>
<p>{t('footer.copyright')}</p>
</div>
</div>
</footer>

View file

@ -1,29 +1,36 @@
import React, { useState, useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { motion } from 'framer-motion';
import { useTranslation } from '../i18n';
import Modal from './Modal';
import './Header.css';
const navItems = [
{ name: 'Home', link: '/' },
{ name: 'About Us', link: '/about' },
{ name: 'Services', link: '/services' },
{ name: 'Pricing', link: '/pricing' },
{ name: 'Blog', link: '/blog' },
{ name: 'Contacts', link: '#contact' },
];
const Header: React.FC = () => {
const [activeTab, setActiveTab] = useState('Home');
const { t, lang, setLang } = useTranslation();
const navItems = [
{ name: t('header.nav.home'), link: '/' },
{ name: t('header.nav.about'), link: '/about' },
{ name: t('header.nav.services'), link: '/services' },
{ name: t('header.nav.pricing'), link: '/pricing' },
{ name: t('header.nav.blog'), link: '/blog' },
{ name: t('header.nav.contacts'), link: '#contact' },
];
const [activeTab, setActiveTab] = useState(navItems[0].name);
const [hoveredTab, setHoveredTab] = useState<string | null>(null);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isLangOpen, setIsLangOpen] = useState(false);
const [isLoginOpen, setIsLoginOpen] = useState(false);
const [currentLang, setCurrentLang] = useState('Eng');
const [isScrolled, setIsScrolled] = useState(false);
const navigate = useNavigate();
const location = useLocation();
// Update activeTab when language changes
useEffect(() => {
setActiveTab(navItems[0].name);
}, [lang]);
useEffect(() => {
const onScroll = () => {
setIsScrolled(window.scrollY > 50);
@ -38,8 +45,8 @@ const Header: React.FC = () => {
setIsMobileMenuOpen(false);
}, [location.pathname]);
const handleLangSelect = (lang: string) => {
setCurrentLang(lang);
const handleLangSelect = (selectedLang: 'en' | 'uk') => {
setLang(selectedLang);
setIsLangOpen(false);
};
@ -63,6 +70,8 @@ const Header: React.FC = () => {
setIsMobileMenuOpen(false);
};
const currentLangLabel = lang === 'en' ? t('header.lang.en') : t('header.lang.uk');
return (
<>
<header className={`header ${isScrolled ? 'scrolled' : ''} ${isMobileMenuOpen ? 'mobile-open' : ''}`}>
@ -82,7 +91,7 @@ const Header: React.FC = () => {
const isActive = (hoveredTab || activeTab) === item.name;
return (
<li
key={item.name}
key={item.link}
className={`nav-item ${isActive ? 'active' : ''}`}
onMouseEnter={() => setHoveredTab(item.name)}
onClick={() => setActiveTab(item.name)}
@ -115,7 +124,7 @@ const Header: React.FC = () => {
onClick={() => setIsLangOpen(!isLangOpen)}
>
<img src="/icons/planet.svg" alt="Language" className="lang-icon" />
<span>{currentLang}</span>
<span>{currentLangLabel}</span>
</div>
{isLangOpen && (
@ -126,21 +135,21 @@ const Header: React.FC = () => {
exit={{ opacity: 0, y: 10 }}
>
<div
className={`lang-option ${currentLang === 'Eng' ? 'active' : ''}`}
onClick={() => handleLangSelect('Eng')}
className={`lang-option ${lang === 'en' ? 'active' : ''}`}
onClick={() => handleLangSelect('en')}
>
Eng
{t('header.lang.en')}
</div>
<div
className={`lang-option ${currentLang === 'Ukr' ? 'active' : ''}`}
onClick={() => handleLangSelect('Ukr')}
className={`lang-option ${lang === 'uk' ? 'active' : ''}`}
onClick={() => handleLangSelect('uk')}
>
Ukr
{t('header.lang.uk')}
</div>
</motion.div>
)}
</div>
<button className="login-btn" onClick={() => setIsLoginOpen(true)}>Log in</button>
<button className="login-btn" onClick={() => setIsLoginOpen(true)}>{t('header.login')}</button>
</div>
<button className="mobile-menu-toggle" onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}>
@ -162,7 +171,7 @@ const Header: React.FC = () => {
<ul className="mobile-nav-list">
{navItems.map((item) => (
<li
key={item.name}
key={item.link}
className={`mobile-nav-item ${activeTab === item.name ? 'active' : ''}`}
>
<a href={item.link} onClick={(e) => {
@ -188,19 +197,19 @@ const Header: React.FC = () => {
{/* Login Modal */}
<Modal isOpen={isLoginOpen} onClose={() => setIsLoginOpen(false)}>
<div className="login-form-container">
<h2 className="login-title">Welcome Back</h2>
<h2 className="login-title">{t('header.loginModal.title')}</h2>
<form className="login-form" onSubmit={(e) => e.preventDefault()}>
<div className="form-group">
<label>Email / Login</label>
<input type="text" placeholder="Enter your email" className="glass-input" />
<label>{t('header.loginModal.emailLabel')}</label>
<input type="text" placeholder={t('header.loginModal.emailPlaceholder')} className="glass-input" />
</div>
<div className="form-group">
<label>Password</label>
<input type="password" placeholder="Enter your password" className="glass-input" />
<label>{t('header.loginModal.passwordLabel')}</label>
<input type="password" placeholder={t('header.loginModal.passwordPlaceholder')} className="glass-input" />
</div>
<button type="submit" className="submit-btn full-width">Log In</button>
<button type="submit" className="submit-btn full-width">{t('header.loginModal.submit')}</button>
<p className="login-footer">
Don't have an account? <a href="#">Sign up</a>
{t('header.loginModal.signupPrompt')} <a href="#">{t('header.loginModal.signupLink')}</a>
</p>
</form>
</div>

View file

@ -3,8 +3,10 @@ import { motion, useTransform, useMotionValue } from 'framer-motion';
import './Hero.css';
import ContactForm from './ContactForm';
import Modal from './Modal';
import { useTranslation } from '../i18n';
const Hero: React.FC = () => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState(false);
const x = useMotionValue(0);
const y = useMotionValue(0);
@ -47,9 +49,9 @@ const Hero: React.FC = () => {
<div className="circle-1-inner">
<img src="/icons/arrow.svg" alt="" className="circle-icon" />
<div className="circle-text-column">
<span>Build.</span>
<span>Automate.</span>
<span>Impress.</span>
<span>{t('hero.circle1')}</span>
<span>{t('hero.circle2')}</span>
<span>{t('hero.circle3')}</span>
</div>
</div>
</motion.div>
@ -81,10 +83,10 @@ const Hero: React.FC = () => {
<div className="hero-text-content">
<h1 className="hero-title">
Stop Hiring. Start Scaling. Deploy Your AI Digital Workforce.
{t('hero.title')}
</h1>
<button className="cta-button" onClick={() => setIsModalOpen(true)}>
Get your free consultation
{t('hero.cta')}
</button>
</div>
</div>

View file

@ -1,6 +1,7 @@
import React, { useState, type ChangeEvent, type FormEvent } from 'react';
import { motion } from 'framer-motion';
import mixpanel from 'mixpanel-browser';
import { useTranslation } from '../i18n';
import './QuoteForm.css';
interface QuoteData {
@ -13,23 +14,25 @@ interface QuoteData {
projectDescription: string;
}
const serviceOptions = [
'Workflow Automation Implementation',
'System Integration & Synchronisation',
'CRM Workflow Optimisation',
'Marketing Automation Setup',
'AI Integration & Enhancement',
'Infrastructure Setup & Configuration',
'Support Retainer',
'Training & Workshop',
'Other / Not sure yet',
];
interface QuoteFormProps {
onClose?: () => void;
}
const QuoteForm: React.FC<QuoteFormProps> = () => {
const { t } = useTranslation();
const serviceOptions = [
t('quoteForm.service1'),
t('quoteForm.service2'),
t('quoteForm.service3'),
t('quoteForm.service4'),
t('quoteForm.service5'),
t('quoteForm.service6'),
t('quoteForm.service7'),
t('quoteForm.service8'),
t('quoteForm.service9'),
];
const [formData, setFormData] = useState<QuoteData>({
fullName: '',
workEmail: '',
@ -88,22 +91,22 @@ const QuoteForm: React.FC<QuoteFormProps> = () => {
};
const inputFields = [
{ label: 'Full Name', name: 'fullName', type: 'text', placeholder: 'John Doe' },
{ label: 'Job Title / Role', name: 'jobTitle', type: 'text', placeholder: 'Project Manager' },
{ label: 'Work Email', name: 'workEmail', type: 'email', placeholder: 'john@company.com' },
{ label: 'Phone Number', name: 'phoneNumber', type: 'tel', placeholder: '+44...' },
{ label: 'Company Name', name: 'companyName', type: 'text', placeholder: 'Tech Solutions Ltd' },
{ label: t('quoteForm.fullName'), name: 'fullName', type: 'text', placeholder: t('quoteForm.fullNamePlaceholder') },
{ label: t('quoteForm.jobTitle'), name: 'jobTitle', type: 'text', placeholder: t('quoteForm.jobTitlePlaceholder') },
{ label: t('quoteForm.email'), name: 'workEmail', type: 'email', placeholder: t('quoteForm.emailPlaceholder') },
{ label: t('quoteForm.phone'), name: 'phoneNumber', type: 'tel', placeholder: t('quoteForm.phonePlaceholder') },
{ label: t('quoteForm.company'), name: 'companyName', type: 'text', placeholder: t('quoteForm.companyPlaceholder') },
];
return (
<div className="quote-form-container">
<h2 className="form-title">Get Your Quote</h2>
<h2 className="form-title">{t('quoteForm.title')}</h2>
{status === 'success' ? (
<div className="success-message">
<h3>Thank you!</h3>
<p>We've received your quote request. We'll review your requirements and get back to you within 24 hours with a detailed proposal.</p>
<button onClick={() => setStatus('idle')} className="reset-btn">Submit another request</button>
<h3>{t('quoteForm.successTitle')}</h3>
<p>{t('quoteForm.successText')}</p>
<button onClick={() => setStatus('idle')} className="reset-btn">{t('quoteForm.sendAnother')}</button>
</div>
) : (
<form onSubmit={handleSubmit} className="quote-form">
@ -138,7 +141,7 @@ const QuoteForm: React.FC<QuoteFormProps> = () => {
viewport={{ once: true }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<label>Service*</label>
<label>{t('quoteForm.service')}*</label>
<select
name="service"
required
@ -147,7 +150,7 @@ const QuoteForm: React.FC<QuoteFormProps> = () => {
onFocus={() => setFocusedField('service')}
onBlur={() => setFocusedField(null)}
>
<option value="" disabled>Select a service...</option>
<option value="" disabled>{t('quoteForm.serviceDefault')}</option>
{serviceOptions.map((opt) => (
<option key={opt} value={opt}>{opt}</option>
))}
@ -162,7 +165,7 @@ const QuoteForm: React.FC<QuoteFormProps> = () => {
viewport={{ once: true }}
transition={{ duration: 0.3, delay: 0.5 }}
>
<label>Project Description*</label>
<label>{t('quoteForm.description')}*</label>
<textarea
name="projectDescription"
required
@ -170,7 +173,7 @@ const QuoteForm: React.FC<QuoteFormProps> = () => {
onChange={handleChange}
onFocus={() => setFocusedField('projectDescription')}
onBlur={() => setFocusedField(null)}
placeholder="Describe your automation needs, current challenges, and desired outcomes..."
placeholder={t('quoteForm.descriptionPlaceholder')}
rows={4}
/>
</motion.div>
@ -189,9 +192,9 @@ const QuoteForm: React.FC<QuoteFormProps> = () => {
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
{status === 'submitting' ? 'Sending...' : 'Get Your Quote'}
{status === 'submitting' ? t('quoteForm.sending') : t('quoteForm.submit')}
</motion.button>
{status === 'error' && <p className="error-text">Something went wrong. Please try again.</p>}
{status === 'error' && <p className="error-text">{t('quoteForm.error')}</p>}
</motion.div>
</form>
)}

View file

@ -1,27 +1,30 @@
import React from 'react';
import CaseStudyCard from './CaseStudyCard';
import './RealResults.css';
import { useTranslation } from '../i18n';
const RealResults: React.FC = () => {
const { t } = useTranslation();
return (
<section className="real-results-section">
<div className="container">
<h2 className="section-title">Real Results from Real Local Clients</h2>
<h2 className="section-title">{t('realResults.title')}</h2>
<div className="results-grid">
{/* Card 1: AutoBrat Garage */}
<CaseStudyCard
frontImage="/bg/AutoBrat Garage.png"
frontTitle="AutoBrat Garage"
backTitle="AutoBrat Garage"
backSubtitle="Results:"
frontTitle={t('realResults.card1.title')}
backTitle={t('realResults.card1.title')}
backSubtitle={t('realResults.card1.resultsLabel')}
backContent={
<div className="result-list">
<p className="result-desc">Ukrainian-founded specialist garage struggled with local recognition. AImpress created bilingual content showcasing their German vehicle expertise.</p>
<p className="result-desc">{t('realResults.card1.desc')}</p>
<ul>
<li>157% - increase in bookings</li>
<li>85% - service bay utilisation</li>
<li>recognition in Oxford Mail within 6 months</li>
<li>{t('realResults.card1.stat1')}</li>
<li>{t('realResults.card1.stat2')}</li>
<li>{t('realResults.card1.stat3')}</li>
</ul>
</div>
}
@ -30,15 +33,15 @@ const RealResults: React.FC = () => {
{/* Card 2: Cotswold Honey Company */}
<CaseStudyCard
frontImage="/bg/Cotswolld Honey Company.png"
frontTitle="Cotswolld Honey Company"
backTitle="Cotswolld Honey Company"
backSubtitle="Results:"
frontTitle={t('realResults.card2.title')}
backTitle={t('realResults.card2.title')}
backSubtitle={t('realResults.card1.resultsLabel')}
backContent={
<div className="result-list">
<p className="result-desc">Artisanal honey producer limited to seasonal markets. AImpress developed e-commerce and educational content highlighting sustainable practices.</p>
<p className="result-desc">{t('realResults.card2.desc')}</p>
<ul>
<li>78% - increase in online sales</li>
<li>four new retail partnerships in just 4 months</li>
<li>{t('realResults.card2.stat1')}</li>
<li>{t('realResults.card2.stat2')}</li>
</ul>
</div>
}
@ -47,15 +50,15 @@ const RealResults: React.FC = () => {
{/* Card 3: Wcounting Accounting Partners */}
<CaseStudyCard
frontImage="/bg/Wcounting Accounting Partners.png"
frontTitle="Wcounting Accounting Partners"
backTitle="Wcounting Accounting Partners"
backSubtitle="Results:"
frontTitle={t('realResults.card3.title')}
backTitle={t('realResults.card3.title')}
backSubtitle={t('realResults.card1.resultsLabel')}
backContent={
<div className="result-list">
<p className="result-desc">Traditional firm losing younger clients. AImpress created accessible financial guides and Making Tax Digital content.</p>
<p className="result-desc">{t('realResults.card3.desc')}</p>
<ul>
<li>41% - increase in under-40 enquiries</li>
<li>12 - new e-commerce clients within 5 months</li>
<li>{t('realResults.card3.stat1')}</li>
<li>{t('realResults.card3.stat2')}</li>
</ul>
</div>
}

View file

@ -1,12 +1,14 @@
import React from 'react';
import { motion } from 'framer-motion';
import { useTranslation } from '../i18n';
import './ResourcesSection.css';
const ResourcesSection: React.FC = () => {
const { t } = useTranslation();
return (
<section className="resources-section" id="resources">
<div className="container">
<h2 className="section-title">Free Resources to Scale Your Business</h2>
<h2 className="section-title">{t('resources.title')}</h2>
<motion.div
className="resources-video-wrapper"

View file

@ -1,4 +1,5 @@
import { Helmet } from 'react-helmet-async';
import { useTranslation } from '../i18n';
interface SEOProps {
title?: string;
@ -8,38 +9,38 @@ interface SEOProps {
type?: string;
}
const DEFAULTS = {
title: 'AImpress | AI & Automation Consulting for SMEs | London, UK',
description: 'AImpress helps small and medium businesses in the UK automate operations, cut costs, and grow faster with AI-powered solutions. Based in London.',
url: 'https://ai-impress.com',
image: 'https://ai-impress.com/logo/webclip-256x256.png',
type: 'website',
const SEO: React.FC<SEOProps> = ({
title,
description,
url = 'https://ai-impress.com',
image = 'https://ai-impress.com/logo/webclip-256x256.png',
type = 'website',
}) => {
const { t, lang } = useTranslation();
const resolvedTitle = title || t('seo.home.title');
const resolvedDescription = description || t('seo.home.description');
return (
<Helmet>
<html lang={lang} />
<title>{resolvedTitle}</title>
<meta name="description" content={resolvedDescription} />
<link rel="canonical" href={url} />
<meta property="og:title" content={resolvedTitle} />
<meta property="og:description" content={resolvedDescription} />
<meta property="og:image" content={image} />
<meta property="og:url" content={url} />
<meta property="og:type" content={type} />
<meta property="og:site_name" content={t('seo.siteName')} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={resolvedTitle} />
<meta name="twitter:description" content={resolvedDescription} />
<meta name="twitter:image" content={image} />
</Helmet>
);
};
const SEO: React.FC<SEOProps> = ({
title = DEFAULTS.title,
description = DEFAULTS.description,
url = DEFAULTS.url,
image = DEFAULTS.image,
type = DEFAULTS.type,
}) => (
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:url" content={url} />
<meta property="og:type" content={type} />
<meta property="og:site_name" content="AImpress" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
</Helmet>
);
export default SEO;

View file

@ -1,77 +1,79 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import './Timeline.css';
const timelineData = [
{
step: "01",
title: "Challenge Briefing",
duration: "2h",
desc: "Identify top-impact opportunities and define ROI metrics.",
detail: "We audit your workflows, pinpoint bottlenecks, and map out the highest-ROI automation targets — all in a single focused session.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
),
},
{
step: "02",
title: "Tech Assessment & Strategy",
duration: "23 days",
desc: "Review existing stack, architecture, and compliance.",
detail: "Deep dive into your CRM, accounting, comms, and data flow. We design the integration blueprint and security model before writing a single line.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
),
},
{
step: "03",
title: "Proof of Concept",
duration: "812 weeks",
desc: "Pilot for a critical process — visible ROI.",
detail: "We build and deploy a working automation for your most painful workflow. You'll see measurable time and cost savings within the first sprint.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</svg>
),
},
{
step: "04",
title: "MVP Implementation",
duration: "23 months",
desc: "Full deployment, team training, and integration.",
detail: "Roll out across departments with hands-on training, monitoring dashboards, and a dedicated Slack channel for your team to get instant support.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
</svg>
),
},
{
step: "05",
title: "Scaling & Optimization",
duration: "Ongoing",
desc: "Continuous improvement and expansion.",
detail: "Monthly performance reviews, A/B tested automation tweaks, and expansion into new departments. Your AI workforce grows with you.",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/>
</svg>
),
}
];
import { useTranslation } from '../i18n';
const AUTOPLAY_MS = 5000;
const Timeline: React.FC = () => {
const { t } = useTranslation();
const [active, setActive] = useState(0);
const [userClicked, setUserClicked] = useState(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const timelineData = [
{
step: "01",
title: t('timeline.step1.title'),
duration: t('timeline.step1.duration'),
desc: t('timeline.step1.short'),
detail: t('timeline.step1.detail'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
),
},
{
step: "02",
title: t('timeline.step2.title'),
duration: t('timeline.step2.duration'),
desc: t('timeline.step2.short'),
detail: t('timeline.step2.detail'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
),
},
{
step: "03",
title: t('timeline.step3.title'),
duration: t('timeline.step3.duration'),
desc: t('timeline.step3.short'),
detail: t('timeline.step3.detail'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</svg>
),
},
{
step: "04",
title: t('timeline.step4.title'),
duration: t('timeline.step4.duration'),
desc: t('timeline.step4.short'),
detail: t('timeline.step4.detail'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
</svg>
),
},
{
step: "05",
title: t('timeline.step5.title'),
duration: t('timeline.step5.duration'),
desc: t('timeline.step5.short'),
detail: t('timeline.step5.detail'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/>
</svg>
),
}
];
const advance = useCallback(() => {
setActive((prev) => (prev + 1) % timelineData.length);
}, []);
@ -94,7 +96,7 @@ const Timeline: React.FC = () => {
return (
<section className="timeline-section" id="timeline">
<div className="container">
<h2 className="section-title">Your Path to Autonomous Operations</h2>
<h2 className="section-title">{t('timeline.title')}</h2>
{/* Step selector row */}
<div className="tl-steps">

View file

@ -46,7 +46,7 @@ function loadLead(): ChatLead | null {
}
}
export function useChat() {
export function useChat(lang: string = 'en') {
const [messages, setMessages] = useState<ChatMessage[]>(loadMessages);
const [loading, setLoading] = useState(false);
const [rateLimited, setRateLimited] = useState(false);
@ -107,6 +107,7 @@ export function useChat() {
session_id: sessionId.current,
message: trimmed,
page_context: location.pathname,
language: lang,
};
// Include lead info on first message of session
if (lead && messages.length === 0) {
@ -147,7 +148,7 @@ export function useChat() {
setLoading(false);
}
},
[loading, rateLimited, lead, messages.length, location.pathname],
[loading, rateLimited, lead, messages.length, location.pathname, lang],
);
const setLead = useCallback((leadInfo: ChatLead) => {

View file

@ -0,0 +1,58 @@
import { createContext, useContext, useState, useCallback, useMemo, type ReactNode } from 'react';
import type { Lang, TranslationKey, Translations } from './types';
import { en } from './en';
import { uk } from './uk';
const translations: Record<Lang, Translations> = { en, uk };
interface LanguageContextValue {
lang: Lang;
setLang: (lang: Lang) => void;
t: (key: TranslationKey) => string;
}
const LanguageContext = createContext<LanguageContextValue | null>(null);
function getInitialLang(): Lang {
try {
const stored = localStorage.getItem('aimpress_lang');
if (stored === 'en' || stored === 'uk') return stored;
} catch {}
return 'en';
}
export function LanguageProvider({ children }: { children: ReactNode }) {
const [lang, setLangState] = useState<Lang>(getInitialLang);
const setLang = useCallback((newLang: Lang) => {
setLangState(newLang);
try {
localStorage.setItem('aimpress_lang', newLang);
} catch {}
document.documentElement.lang = newLang;
}, []);
const t = useCallback(
(key: TranslationKey): string => {
return translations[lang][key] ?? translations.en[key] ?? key;
},
[lang]
);
// Set initial html lang attribute
document.documentElement.lang = lang;
const value = useMemo(() => ({ lang, setLang, t }), [lang, setLang, t]);
return (
<LanguageContext.Provider value={value}>
{children}
</LanguageContext.Provider>
);
}
export function useTranslation(): LanguageContextValue {
const ctx = useContext(LanguageContext);
if (!ctx) throw new Error('useTranslation must be used within LanguageProvider');
return ctx;
}

568
src/i18n/en.ts Normal file
View file

@ -0,0 +1,568 @@
import type { Translations } from './types';
export const en = {
// Header
'header.nav.home': 'Home',
'header.nav.about': 'About Us',
'header.nav.services': 'Services',
'header.nav.pricing': 'Pricing',
'header.nav.blog': 'Blog',
'header.nav.contacts': 'Contacts',
'header.lang.en': 'Eng',
'header.lang.uk': 'Ukr',
'header.login': 'Log in',
'header.loginModal.title': 'Welcome Back',
'header.loginModal.emailLabel': 'Email / Login',
'header.loginModal.emailPlaceholder': 'Enter your email',
'header.loginModal.passwordLabel': 'Password',
'header.loginModal.passwordPlaceholder': 'Enter your password',
'header.loginModal.submit': 'Log In',
'header.loginModal.signupPrompt': "Don't have an account?",
'header.loginModal.signupLink': 'Sign up',
// Hero
'hero.circle1': 'Build.',
'hero.circle2': 'Automate.',
'hero.circle3': 'Impress.',
'hero.title': 'Stop Hiring. Start Scaling. Deploy Your AI Digital Workforce.',
'hero.cta': 'Get your free consultation',
// Benefits
'benefits.card1.front': 'Cost & Scale',
'benefits.card1.subtitle': 'Elastic Growth, Zero Headcount',
'benefits.card1.back': 'Scale 10x without hiring. AI assistants handle peak loads automatically, keeping your operations elastic and efficient.',
'benefits.card2.front': 'Accuracy',
'benefits.card2.subtitle': 'Machine Learning Precision (99.9%)',
'benefits.card2.back': 'Eliminate human error. Our ML-validated workflows guarantee data integrity across CRM, finance, and logistics systems.',
'benefits.card3.front': 'Availability',
'benefits.card3.subtitle': 'True 24/7/365 Operations',
'benefits.card3.back': 'Your AI workforce never sleeps, takes breaks, or burns out. Serve global clients nonstop.',
'benefits.builtTitle': 'Built for Local Entrepreneurs Like You',
'benefits.builtDesc': 'AImpress turns chaos into a system. We build automations that save up to 75% of your time and boost sales.',
'benefits.static1.title': 'Chatbots & AI Assistants',
'benefits.static1.desc': '\u2192 instant answers 24/7',
'benefits.static2.title': 'Business Process Automation (n8n, Make.com)',
'benefits.static2.desc': '\u2192 CRM, email, finance, inventory',
'benefits.static3.title': 'Content Farms & AI Copywriting',
'benefits.static3.desc': '\u2192 AI creates posts, articles, product descriptions',
'benefits.static4.title': 'Marketing Automation (email, CRM, ads)',
'benefits.static4.desc': '\u2192 campaigns, lead nurturing, ads on autopilot',
// Banner1
'banner1.q1': 'How many employees in your company?',
'banner1.q2': 'How many hours per day on repetitive tasks?',
'banner1.q3': 'Which processes do you want to automate?',
'banner1.cta': 'Get Your Free Consultation',
// RealResults
'realResults.title': 'Real Results from Real Local Clients',
'realResults.card1.title': 'AutoBrat Garage',
'realResults.card1.resultsLabel': 'Results:',
'realResults.card1.desc': 'Ukrainian-founded specialist garage struggled with local recognition. AImpress created bilingual content showcasing their German vehicle expertise.',
'realResults.card1.stat1': '157% - increase in bookings',
'realResults.card1.stat2': '85% - service bay utilisation',
'realResults.card1.stat3': 'recognition in Oxford Mail within 6 months',
'realResults.card2.title': 'Cotswolld Honey Company',
'realResults.card2.desc': 'Artisanal honey producer limited to seasonal markets. AImpress developed e-commerce and educational content highlighting sustainable practices.',
'realResults.card2.stat1': '78% - increase in online sales',
'realResults.card2.stat2': 'four new retail partnerships in just 4 months',
'realResults.card3.title': 'Wcounting Accounting Partners',
'realResults.card3.desc': 'Traditional firm losing younger clients. AImpress created accessible financial guides and Making Tax Digital content.',
'realResults.card3.stat1': '41% - increase in under-40 enquiries',
'realResults.card3.stat2': '12 - new e-commerce clients within 5 months',
// Timeline
'timeline.title': 'Your Path to Autonomous Operations',
'timeline.step1.title': 'Challenge Briefing',
'timeline.step1.duration': '2h',
'timeline.step1.short': 'Identify top-impact opportunities and define ROI metrics.',
'timeline.step1.detail': 'We audit your workflows, pinpoint bottlenecks, and map out the highest-ROI automation targets \u2014 all in a single focused session.',
'timeline.step2.title': 'Tech Assessment & Strategy',
'timeline.step2.duration': '2\u20133 days',
'timeline.step2.short': 'Review existing stack, architecture, and compliance.',
'timeline.step2.detail': 'Deep dive into your CRM, accounting, comms, and data flow. We design the integration blueprint and security model before writing a single line.',
'timeline.step3.title': 'Proof of Concept',
'timeline.step3.duration': '8\u201312 weeks',
'timeline.step3.short': 'Pilot for a critical process \u2014 visible ROI.',
'timeline.step3.detail': "We build and deploy a working automation for your most painful workflow. You'll see measurable time and cost savings within the first sprint.",
'timeline.step4.title': 'MVP Implementation',
'timeline.step4.duration': '2\u20133 months',
'timeline.step4.short': 'Full deployment, team training, and integration.',
'timeline.step4.detail': 'Roll out across departments with hands-on training, monitoring dashboards, and a dedicated Slack channel for your team to get instant support.',
'timeline.step5.title': 'Scaling & Optimization',
'timeline.step5.duration': 'Ongoing',
'timeline.step5.short': 'Continuous improvement and expansion.',
'timeline.step5.detail': 'Monthly performance reviews, A/B tested automation tweaks, and expansion into new departments. Your AI workforce grows with you.',
// Banner2
'banner2.cta': 'Get Your Free Consultation',
// ComparisonTable
'comparison.title': 'Why Businesses Switch to AImpress',
'comparison.aiLabel': 'AI-Powered',
'comparison.metric1.label': 'Cost',
'comparison.metric1.ai': 'From \u00a31,500',
'comparison.metric1.agency': '\u00a35,000+ /mo agency',
'comparison.metric1.inhouse': '\u00a335,000+ /yr in-house',
'comparison.metric2.label': 'Speed',
'comparison.metric2.ai': 'Instant',
'comparison.metric2.agency': 'Depends on manager',
'comparison.metric2.inhouse': '9-to-5 only',
'comparison.metric3.label': 'Availability',
'comparison.metric3.ai': '24 / 7 / 365',
'comparison.metric3.agency': 'Business hours',
'comparison.metric3.inhouse': 'Sick leaves & holidays',
'comparison.metric4.label': 'Scalability',
'comparison.metric4.ai': 'Unlimited',
'comparison.metric4.agency': 'Limited by staff',
'comparison.metric4.inhouse': 'Hard to scale',
'comparison.altHeading': 'The alternatives',
'comparison.alt1': 'Traditional Agency',
'comparison.alt2': 'In-House Hire',
'comparison.footer': 'Switch from legacy methods \u2014 save up to 70% on costs and 30+ hours per week.',
'comparison.cta': 'Get Your Free Consultation',
// BlogSection
'blogSection.title': 'Recent Updates',
'blogSection.readMore': 'Read More \u2192',
'blogSection.viewAll': 'View All Posts \u2192',
// Resources
'resources.title': 'Free Resources to Scale Your Business',
// ContactSection
'contactSection.title': 'Ready to Automate?',
'contactSection.subtitle': 'Stop wasting time on routine. Start scaling with AI today.',
// ContactForm
'contactForm.title': 'Get in touch:',
'contactForm.fullName': 'Full Name',
'contactForm.fullNamePlaceholder': 'John Doe',
'contactForm.jobTitle': 'Job Title / Role',
'contactForm.jobTitlePlaceholder': 'Project Manager',
'contactForm.email': 'Work Email',
'contactForm.emailPlaceholder': 'john@company.com',
'contactForm.need': 'Automation Need',
'contactForm.needPlaceholder': 'Workflow optimization',
'contactForm.company': 'Company Name',
'contactForm.companyPlaceholder': 'Tech Solutions Inc.',
'contactForm.phone': 'Phone Number',
'contactForm.phonePlaceholder': '+44...',
'contactForm.submit': 'Submit a request',
'contactForm.sending': 'Sending...',
'contactForm.error': 'Something went wrong. Please try again.',
'contactForm.successTitle': 'Thank you!',
'contactForm.successText': 'We have received your request and will contact you shortly.',
'contactForm.sendAnother': 'Send another',
// Footer
'footer.privacy': 'Privacy Policy',
'footer.terms': 'Terms of Use',
'footer.copyright': '\u00a9 2026 AImpress LTD. All rights reserved.',
// CookieConsent
'cookie.text': 'We use cookies and similar technologies to analyse website traffic and improve your experience. By clicking "Accept", you consent to the use of analytics cookies. See our {privacyLink} for details.',
'cookie.privacyLink': 'Privacy Policy',
'cookie.reject': 'Reject',
'cookie.accept': 'Accept',
// ChatBubble
'chat.greeting': 'Hi! How can I help you today?',
'chat.openChat': 'Open chat',
// ChatWindow
'chat.headerTitle': 'AImpress',
'chat.status': 'Online',
'chat.clearChat': 'Clear chat',
'chat.closeChat': 'Close chat',
'chat.welcome': "Hi! I'm the AImpress AI assistant. Ask me about our AI & automation services, pricing, or book a free consultation.",
// ChatLeadForm
'chat.lead.title': 'AImpress',
'chat.lead.subtitle': 'AI & Automation Consultancy',
'chat.lead.intro': 'Hi! Before we start, please introduce yourself so we can better assist you.',
'chat.lead.namePlaceholder': 'Your name *',
'chat.lead.nameError': 'Please enter your name',
'chat.lead.emailPlaceholder': 'Email *',
'chat.lead.emailError': 'Please enter your email',
'chat.lead.emailInvalid': 'Please enter a valid email',
'chat.lead.companyPlaceholder': 'Company (optional)',
'chat.lead.consent': 'I agree to the processing of my personal data in accordance with the {privacyLink}',
'chat.lead.privacyLink': 'Privacy Policy',
'chat.lead.consentError': 'Please accept to continue',
'chat.lead.submit': 'Start Chat',
// ChatInput
'chat.inputPlaceholder': 'Type a message...',
'chat.send': 'Send message',
// QuoteForm
'quoteForm.title': 'Get Your Quote',
'quoteForm.fullName': 'Full Name',
'quoteForm.fullNamePlaceholder': 'John Doe',
'quoteForm.jobTitle': 'Job Title / Role',
'quoteForm.jobTitlePlaceholder': 'Project Manager',
'quoteForm.email': 'Work Email',
'quoteForm.emailPlaceholder': 'john@company.com',
'quoteForm.phone': 'Phone Number',
'quoteForm.phonePlaceholder': '+44...',
'quoteForm.company': 'Company Name',
'quoteForm.companyPlaceholder': 'Tech Solutions Ltd',
'quoteForm.service': 'Service',
'quoteForm.serviceDefault': 'Select a service...',
'quoteForm.service1': 'Workflow Automation Implementation',
'quoteForm.service2': 'System Integration & Synchronisation',
'quoteForm.service3': 'CRM Workflow Optimisation',
'quoteForm.service4': 'Marketing Automation Setup',
'quoteForm.service5': 'AI Integration & Enhancement',
'quoteForm.service6': 'Infrastructure Setup & Configuration',
'quoteForm.service7': 'Support Retainer',
'quoteForm.service8': 'Training & Workshop',
'quoteForm.service9': 'Other / Not sure yet',
'quoteForm.description': 'Project Description',
'quoteForm.descriptionPlaceholder': 'Describe your automation needs, current challenges, and desired outcomes...',
'quoteForm.submit': 'Get Your Quote',
'quoteForm.sending': 'Sending...',
'quoteForm.error': 'Something went wrong. Please try again.',
'quoteForm.successTitle': 'Thank you!',
'quoteForm.successText': "We've received your quote request. We'll review your requirements and get back to you within 24 hours with a detailed proposal.",
'quoteForm.sendAnother': 'Submit another request',
// About Page
'about.hero.title': 'About AImpress',
'about.hero.subtitle': 'Making professional automation accessible to impact-driven organisations.',
'about.story.title': 'Our Story',
'about.story.p1': "Automation is no longer optional \u2014 it's the difference between growing and getting left behind. Yet for most SMEs, the options have been limited: offshore teams that are cheap but unreliable, or UK agencies that charge enterprise prices for basic work.",
'about.story.p2': 'AImpress was founded to fill that gap. We\'re a London-based consultancy that brings enterprise-grade automation to small and medium businesses, charities, and public sector organisations \u2014 at prices that actually make sense.',
'about.story.p3': "We don't believe in black-box solutions or vendor lock-in. Every system we build runs on infrastructure you own, with documentation your team can follow. When we leave, you keep everything \u2014 and you know how it works.",
'about.diff.title': 'What Makes Us Different',
'about.diff1.title': 'UK-Based, London Standards',
'about.diff1.desc': 'Competitive rates of \u00a390\u2013120/hr. UK business hours, GDPR-compliant by default, full accountability under English law.',
'about.diff2.title': 'SME Specialisation',
'about.diff2.desc': 'We focus on CRM, marketing, finance, and e-commerce automation \u2014 the workflows that matter most to growing businesses.',
'about.diff3.title': 'Client-Owned Infrastructure',
'about.diff3.desc': 'Your servers, your data, your accounts. We build on infrastructure you own \u2014 no vendor lock-in, no hostage situations.',
'about.diff4.title': 'Knowledge Transfer, Not Dependency',
'about.diff4.desc': 'Every project includes full documentation and team training. We teach your people to maintain what we build.',
'about.values.title': 'Our Values',
'about.val1.name': 'Transparency',
'about.val1.desc': 'Fixed prices, clear scope, no hidden fees',
'about.val2.name': 'Client Ownership',
'about.val2.desc': 'You own everything we build \u2014 code, data, infrastructure',
'about.val3.name': 'Excellence',
'about.val3.desc': 'Enterprise-grade quality at SME-friendly prices',
'about.val4.name': 'Impact Over Profit',
'about.val4.desc': 'Discounted rates for charities, startups, and public sector',
'about.val5.name': 'Pragmatism',
'about.val5.desc': 'We recommend what works, not what costs more',
'about.founder.title': 'Meet the Founder',
'about.founder.name': 'Vadym Samoilenko',
'about.founder.role': 'CEO & Founder of AImpress Ltd',
'about.founder.bgLabel': 'Background:',
'about.founder.bgText': '2.5+ years at OLIVER Agency (WPP) as Global Automation & AI Specialist, architecting AI-powered workflows that reduced manual effort by 30\u201350% across creative operations for global brands',
'about.founder.certLabel': 'AI & Automation Certifications:',
'about.founder.certText': 'Prompt Engineering Specialization (Vanderbilt University), Generative AI for Marketing (Microsoft Copilot), Prompt Design in Vertex AI (Google), AI in Business (LinkedIn), Make Basics',
'about.founder.analyticsLabel': 'Analytics Certifications:',
'about.founder.analyticsText': 'Microsoft Power BI Data Analyst, Laba Business Analytics & Marketing Analytics',
'about.founder.eduLabel': 'Education:',
'about.founder.eduText': "Master's in Economic Cybernetics \u2014 systems modelling, data analysis, process optimisation",
'about.founder.visionLabel': 'Vision:',
'about.founder.visionText': 'Founded AImpress to bring enterprise-level automation to SMEs at accessible price points',
'about.industries.title': 'Industries We Serve',
'about.ind1.name': 'E-commerce',
'about.ind1.desc': 'Order processing, inventory sync, customer journeys',
'about.ind2.name': 'Professional Services',
'about.ind2.desc': 'CRM, invoicing, client onboarding automation',
'about.ind3.name': 'SaaS',
'about.ind3.desc': 'User onboarding, billing, support ticket workflows',
'about.ind4.name': 'Charities',
'about.ind4.desc': 'Donor management, grant reporting, volunteer coordination',
'about.ind5.name': 'Education',
'about.ind5.desc': 'Enrolment, student comms, content delivery',
'about.ind6.name': 'Healthcare',
'about.ind6.desc': 'Appointment scheduling, patient records, compliance',
'about.ind7.name': 'Public Sector',
'about.ind7.desc': 'Case management, reporting, citizen services',
'about.cta.title': 'Ready to Transform Your Operations?',
'about.cta.subtitle': 'Book a free discovery call and find out how automation can work for your business.',
'about.cta.button': 'Book a Free Discovery Call',
// Services Page
'services.hero.title': 'Our Services',
'services.hero.subtitle': 'End-to-end automation consulting \u2014 from discovery to deployment and beyond.',
'services.s1.title': 'AI Chatbots & Virtual Assistants',
'services.s1.price': '\u00a33,000 \u2013 \u00a310,000',
'services.s1.purpose': 'Deploy intelligent chatbots that handle customer support, qualify leads, and book appointments 24/7 \u2014 no human required.',
'services.s1.f1': 'Custom chatbot design & personality',
'services.s1.f2': 'Integration with your website, WhatsApp, or Messenger',
'services.s1.f3': 'Knowledge base training on your business data',
'services.s1.f4': 'Lead qualification & CRM handoff',
'services.s1.f5': 'Multi-language support',
'services.s1.f6': 'Analytics dashboard & conversation insights',
'services.s2.title': 'Custom Website Development',
'services.s2.price': '\u00a32,500 \u2013 \u00a315,000',
'services.s2.purpose': 'High-performance, conversion-optimised websites built with modern tech \u2014 fast, SEO-ready, and fully yours.',
'services.s2.f1': 'Custom design & responsive development',
'services.s2.f2': 'React / Next.js or WordPress build',
'services.s2.f3': 'SEO optimisation & Core Web Vitals',
'services.s2.f4': 'CMS integration for easy content updates',
'services.s2.f5': 'Contact forms, analytics & tracking setup',
'services.s2.f6': 'Hosting setup on your own infrastructure',
'services.s3.title': 'Workflow Automation Implementation',
'services.s3.price': '\u00a33,500 \u2013 \u00a312,000',
'services.s3.purpose': 'Automate repetitive business processes end-to-end so your team focuses on high-value work.',
'services.s3.f1': 'Process discovery & mapping',
'services.s3.f2': 'Custom workflow design & build (n8n / Make.com)',
'services.s3.f3': 'Multi-step logic with conditional branching',
'services.s3.f4': 'Error handling & retry mechanisms',
'services.s3.f5': 'Testing, deployment & documentation',
'services.s4.title': 'System Integration & Synchronisation',
'services.s4.price': '\u00a32,500 \u2013 \u00a310,000+',
'services.s4.purpose': 'Connect your tools into a single source of truth \u2014 CRM, accounting, e-commerce, comms, and more.',
'services.s4.f1': 'API integration between platforms',
'services.s4.f2': 'Bi-directional data sync',
'services.s4.f3': 'Data mapping & transformation',
'services.s4.f4': 'Webhook & event-driven triggers',
'services.s4.f5': 'Monitoring & alerting setup',
'services.s5.title': 'CRM Workflow Optimisation',
'services.s5.price': '\u00a33,000 \u2013 \u00a36,500',
'services.s5.purpose': 'Streamline your sales pipeline, automate follow-ups, and ensure no lead falls through the cracks.',
'services.s5.f1': 'CRM audit & pipeline restructure',
'services.s5.f2': 'Automated lead scoring & routing',
'services.s5.f3': 'Email sequence automation',
'services.s5.f4': 'Task & reminder workflows',
'services.s5.f5': 'Reporting dashboard setup',
'services.s6.title': 'Marketing Automation Setup',
'services.s6.price': '\u00a33,500 \u2013 \u00a38,000',
'services.s6.purpose': 'Put your campaigns, lead nurturing, and ads on autopilot with targeted, data-driven automation.',
'services.s6.f1': 'Email drip campaign setup',
'services.s6.f2': 'Lead capture & form automation',
'services.s6.f3': 'Audience segmentation logic',
'services.s6.f4': 'Social media scheduling integration',
'services.s6.f5': 'Analytics & conversion tracking',
'services.s7.title': 'AI Integration & Enhancement',
'services.s7.price': '\u00a34,000 \u2013 \u00a312,000',
'services.s7.purpose': 'Add AI capabilities to your existing workflows \u2014 chatbots, content generation, document processing, and more.',
'services.s7.f1': 'AI model selection & integration (OpenAI, Claude, etc.)',
'services.s7.f2': 'Custom chatbot / virtual assistant build',
'services.s7.f3': 'Document & data extraction with AI',
'services.s7.f4': 'AI-powered content generation pipelines',
'services.s7.f5': 'Prompt engineering & fine-tuning',
'services.s8.title': 'Infrastructure Setup & Configuration',
'services.s8.price': '\u00a31,500 \u2013 \u00a34,000',
'services.s8.purpose': 'Set up your automation infrastructure on servers you own \u2014 secure, scalable, and fully yours.',
'services.s8.f1': 'Server provisioning (VPS / cloud)',
'services.s8.f2': 'n8n / automation platform deployment',
'services.s8.f3': 'SSL, firewall & security hardening',
'services.s8.f4': 'Backup & recovery configuration',
'services.s8.f5': 'Monitoring & uptime alerting',
'services.popular': 'Popular',
'services.whatsIncluded': "What's included",
'services.showLess': 'Show less',
'services.assurance.title': 'AImpress Assurance Pack',
'services.assurance.subtitle': 'Included free in every project (value \u00a31,500)',
'services.assurance.i1': 'Dedicated project manager',
'services.assurance.i2': 'Weekly progress reports',
'services.assurance.i3': 'Full technical documentation',
'services.assurance.i4': 'End-user training session',
'services.assurance.i5': 'Admin training session',
'services.assurance.i6': '30-day post-launch support',
'services.assurance.i7': 'Bug fixes within SLA',
'services.assurance.i8': 'Knowledge base & runbooks',
'services.assurance.i9': 'Handover & transition plan',
'services.metrics.title': 'Results in Numbers',
'services.metrics.v1': '30\u201350%',
'services.metrics.l1': 'Reduction in manual work',
'services.metrics.v2': '2\u20138 wks',
'services.metrics.l2': 'Average delivery time',
'services.metrics.v3': '24/7',
'services.metrics.l3': 'Automated uptime',
'services.metrics.v4': '\u00a30',
'services.metrics.l4': 'Vendor lock-in fees',
'services.selector.title': 'Which Service Do You Need?',
'services.selector.step1': 'Your Goal',
'services.selector.step2': 'Budget',
'services.selector.step3': 'Results',
'services.selector.goalQ': "What's your primary goal?",
'services.selector.goal1': 'Automate workflows',
'services.selector.goal2': 'Get more leads',
'services.selector.goal3': 'Build online presence',
'services.selector.goal4': 'Connect my tools',
'services.selector.goal5': 'Add AI capabilities',
'services.selector.budgetQ': "What's your budget range?",
'services.selector.budget1': 'Under \u00a35K',
'services.selector.budget2': '\u00a35K \u2013 \u00a310K',
'services.selector.budget3': '\u00a310K+',
'services.selector.resultsHeading': 'We recommend these services:',
'services.selector.noMatch': 'No exact match \u2014 but we can help! Contact us for a custom solution.',
'services.selector.viewButton': 'View Services',
'services.selector.resetButton': 'Start Over',
'services.selector.backButton': '\u2190 Back',
'services.bundles.title': 'Popular Bundles',
'services.bundle1.name': 'Starter',
'services.bundle1.tagline': 'Launch & Engage',
'services.bundle1.price': 'from \u00a35,500',
'services.bundle2.name': 'Growth',
'services.bundle2.tagline': 'Scale & Convert',
'services.bundle2.price': 'from \u00a39,000',
'services.bundle2.badge': 'Most Popular',
'services.bundle3.name': 'Full Stack',
'services.bundle3.tagline': 'Transform Everything',
'services.bundle3.price': 'Custom pricing',
'services.bundle.cta': 'Get Started',
'services.cta.title': 'Not Sure Where to Start?',
'services.cta.subtitle': "Book a free consultation and we'll map out the best automation strategy for your business.",
'services.cta.button': 'Get Your Free Consultation',
// Pricing Page
'pricing.hero.title': 'Simple, Transparent Pricing',
'pricing.hero.subtitle': 'Fixed-price projects. No surprise costs. No vendor lock-in.',
'pricing.impl.title': 'Implementation Pricing',
'pricing.impl.s1': 'AI Chatbots & Virtual Assistants',
'pricing.impl.s1.price': '\u00a33,000 \u2013 \u00a310,000',
'pricing.impl.s2': 'Custom Website Development',
'pricing.impl.s2.price': '\u00a32,500 \u2013 \u00a315,000',
'pricing.impl.s3': 'Workflow Automation Implementation',
'pricing.impl.s3.price': '\u00a33,500 \u2013 \u00a312,000',
'pricing.impl.s4': 'System Integration & Synchronisation',
'pricing.impl.s4.price': '\u00a32,500 \u2013 \u00a310,000+',
'pricing.impl.s5': 'CRM Workflow Optimisation',
'pricing.impl.s5.price': '\u00a33,000 \u2013 \u00a36,500',
'pricing.impl.s6': 'Marketing Automation Setup',
'pricing.impl.s6.price': '\u00a33,500 \u2013 \u00a38,000',
'pricing.impl.s7': 'AI Integration & Enhancement',
'pricing.impl.s7.price': '\u00a34,000 \u2013 \u00a312,000',
'pricing.impl.s8': 'Infrastructure Setup & Configuration',
'pricing.impl.s8.price': '\u00a31,500 \u2013 \u00a34,000',
'pricing.popular': 'Popular',
'pricing.retainers.title': 'Support Retainers',
'pricing.ret1.name': 'Essential',
'pricing.ret1.price': '\u00a31,000',
'pricing.ret1.period': '/month',
'pricing.ret1.hours': '10 hours',
'pricing.ret1.sla': '48h response',
'pricing.ret1.f1': '10 hours of support',
'pricing.ret1.f2': 'Bug fixes & minor updates',
'pricing.ret1.f3': '48-hour response SLA',
'pricing.ret1.f4': 'Email support',
'pricing.ret1.f5': 'Monthly health check',
'pricing.ret2.name': 'Professional',
'pricing.ret2.price': '\u00a32,000',
'pricing.ret2.hours': '22 hours',
'pricing.ret2.sla': '24h response',
'pricing.ret2.f1': '22 hours of support',
'pricing.ret2.f2': 'Bug fixes, updates & new features',
'pricing.ret2.f3': '24-hour response SLA',
'pricing.ret2.f4': 'Email + Slack support',
'pricing.ret2.f5': 'Bi-weekly strategy call',
'pricing.ret2.f6': 'Priority scheduling',
'pricing.ret2.badge': 'Most Popular',
'pricing.ret3.name': 'Enterprise',
'pricing.ret3.price': '\u00a33,500',
'pricing.ret3.hours': '40 hours',
'pricing.ret3.sla': '4h response',
'pricing.ret3.f1': '40 hours of support',
'pricing.ret3.f2': 'Full-scope development & support',
'pricing.ret3.f3': '4-hour response SLA',
'pricing.ret3.f4': 'Dedicated Slack channel',
'pricing.ret3.f5': 'Weekly strategy call',
'pricing.ret3.f6': 'Priority scheduling',
'pricing.ret3.f7': 'Quarterly roadmap review',
'pricing.training.title': 'Training & Workshops',
'pricing.training.t1': 'End-User Training',
'pricing.training.t1.price': '\u00a3250',
'pricing.training.t1.desc': 'Per session',
'pricing.training.t2': 'Admin Training',
'pricing.training.t2.price': '\u00a3600 \u2013 \u00a31,000',
'pricing.training.t3': 'Certification Programme',
'pricing.training.t3.price': '\u00a31,800',
'pricing.training.t3.desc': 'Full course',
'pricing.training.t4': 'Custom Workshop',
'pricing.training.t4.price': '\u00a3400 \u2013 \u00a31,200',
'pricing.training.t4.desc': 'Half to full day',
'pricing.payment.title': 'Payment Terms',
'pricing.payment.r1': 'Under \u00a35,000',
'pricing.payment.r1.split': '100% upfront',
'pricing.payment.r2': '\u00a35,000 \u2013 \u00a310,000',
'pricing.payment.r2.split': '50% / 50%',
'pricing.payment.r3': 'Over \u00a310,000',
'pricing.payment.r3.split': '33% / 33% / 34%',
'pricing.payment.r4': 'Public Sector',
'pricing.payment.r4.split': 'Net 30 terms',
'pricing.discounts.title': 'Impact Grant Programme',
'pricing.discounts.intro': 'We believe automation should be accessible to organisations making a difference. Eligible groups receive significant discounts.',
'pricing.discounts.g1': 'Charities & Non-Profits',
'pricing.discounts.g1.disc': 'Up to 50%',
'pricing.discounts.g2': 'Startups (< 2 years)',
'pricing.discounts.g3': 'Education',
'pricing.discounts.g4': 'Public Sector',
'pricing.discounts.g4.disc': '25% + free pilot project',
'pricing.discounts.g5': 'Ukrainian Businesses',
'pricing.compare.title': 'How We Compare',
'pricing.compare.aimpress': 'AImpress',
'pricing.compare.agency': 'Agency',
'pricing.compare.inhouse': 'In-House',
'pricing.compare.r1.metric': 'Setup Cost',
'pricing.compare.r1.ai': 'From \u00a31,500',
'pricing.compare.r1.agency': '\u00a35,000+ /mo',
'pricing.compare.r1.inhouse': '\u00a335,000+ /yr',
'pricing.compare.r2.metric': 'Time to Deploy',
'pricing.compare.r2.ai': '2\u20138 weeks',
'pricing.compare.r2.agency': '3\u20136 months',
'pricing.compare.r2.inhouse': '6\u201312 months',
'pricing.compare.r3.metric': 'Availability',
'pricing.compare.r3.ai': '24/7 automated',
'pricing.compare.r3.agency': 'Business hours',
'pricing.compare.r3.inhouse': '9-to-5 only',
'pricing.compare.r4.metric': 'Scalability',
'pricing.compare.r4.ai': 'Unlimited',
'pricing.compare.r4.agency': 'Staff-limited',
'pricing.compare.r4.inhouse': 'Hard to scale',
'pricing.compare.r5.metric': 'You Own It',
'pricing.compare.r5.ai': 'Yes, always',
'pricing.compare.r5.agency': 'Rarely',
'pricing.compare.r6.metric': 'Hidden Costs',
'pricing.compare.r6.ai': 'None',
'pricing.compare.r6.agency': 'Change requests',
'pricing.compare.r6.inhouse': 'Benefits, turnover',
'pricing.faq.title': 'Frequently Asked Questions',
'pricing.faq.q1': 'Are you a UK company?',
'pricing.faq.a1': 'Yes. AImpress Ltd is registered in England & Wales (company number 16417799), VAT-registered, and ICO-registered. We operate under English law with full GDPR compliance.',
'pricing.faq.q2': 'Do you offer managed hosting?',
'pricing.faq.a2': 'We set up infrastructure on your own servers or cloud accounts. You own everything. We can provide ongoing management through our support retainers if needed.',
'pricing.faq.q3': 'What platforms do you work with?',
'pricing.faq.a3': 'We work with n8n, Make.com, Zapier, Power Automate, HubSpot, Salesforce, Pipedrive, Mailchimp, OpenAI, Claude, and many more. If your tool has an API, we can integrate it.',
'pricing.faq.q4': 'Can you work with our existing tools?',
'pricing.faq.a4': "Absolutely. We build on top of your current stack \u2014 we don't rip and replace. Our goal is to connect and automate what you already have.",
'pricing.faq.q5': "What if I don't qualify for a discount?",
'pricing.faq.a5': 'Our standard rates are already competitive at \u00a390\u2013120/hr \u2014 significantly below typical UK agency rates. We offer fixed-price projects so you always know the total cost upfront.',
'pricing.faq.q6': 'Do you work with clients outside the UK?',
'pricing.faq.a6': "Yes, we work with international clients. Our primary focus is UK businesses, but we're happy to support companies anywhere that need professional automation consulting.",
'pricing.cta.title': 'Get Your Quote Today',
'pricing.cta.subtitle': "Tell us about your project and we'll send you a detailed, fixed-price proposal within 24 hours.",
// Blog Page
'blog.title': 'Blog',
'blog.loading': 'Loading posts...',
'blog.noPosts': 'No posts yet.',
'blog.readMore': 'Read More \u2192',
// Blog Post Page
'blogPost.back': '\u2190 Back to Blog',
'blogPost.notFound': 'Post not found',
'blogPost.loading': 'Loading...',
'blogPost.source': 'Source:',
// SEO
'seo.home.title': 'AImpress | AI & Automation Consulting for SMEs | London, UK',
'seo.home.description': 'AImpress helps small and medium businesses in the UK automate operations, cut costs, and grow faster with AI-powered solutions. Based in London.',
'seo.about.title': 'About Us | AImpress \u2014 AI & Automation Consulting, London',
'seo.about.description': 'AImpress Ltd is a London-based automation consultancy for SMEs, charities, and public sector. Founded 2024. Client-owned infrastructure, no vendor lock-in.',
'seo.services.title': 'AI & Automation Services for UK Businesses | AImpress',
'seo.services.description': 'Workflow automation, system integration, CRM optimisation, marketing automation, AI integration, and infrastructure setup. Fixed-price projects from \u00a31,500.',
'seo.pricing.title': 'Pricing | AImpress \u2014 Transparent Automation Costs from \u00a31,500',
'seo.pricing.description': 'Fixed-price automation projects from \u00a31,500. Support retainers from \u00a31,000/month. Up to 50% discount for charities, startups, and public sector.',
'seo.blog.title': 'Blog | AImpress \u2014 AI Automation Insights',
'seo.blog.description': 'Insights, guides, and case studies on AI automation for small and medium businesses. Stay ahead with AImpress.',
'seo.siteName': 'AImpress',
} as const satisfies Translations;

2
src/i18n/index.ts Normal file
View file

@ -0,0 +1,2 @@
export { LanguageProvider, useTranslation } from './LanguageContext';
export type { Lang, TranslationKey, Translations } from './types';

570
src/i18n/types.ts Normal file
View file

@ -0,0 +1,570 @@
export type Lang = 'en' | 'uk';
export type TranslationKey = keyof Translations;
export interface Translations {
// Header
'header.nav.home': string;
'header.nav.about': string;
'header.nav.services': string;
'header.nav.pricing': string;
'header.nav.blog': string;
'header.nav.contacts': string;
'header.lang.en': string;
'header.lang.uk': string;
'header.login': string;
'header.loginModal.title': string;
'header.loginModal.emailLabel': string;
'header.loginModal.emailPlaceholder': string;
'header.loginModal.passwordLabel': string;
'header.loginModal.passwordPlaceholder': string;
'header.loginModal.submit': string;
'header.loginModal.signupPrompt': string;
'header.loginModal.signupLink': string;
// Hero
'hero.circle1': string;
'hero.circle2': string;
'hero.circle3': string;
'hero.title': string;
'hero.cta': string;
// Benefits
'benefits.card1.front': string;
'benefits.card1.subtitle': string;
'benefits.card1.back': string;
'benefits.card2.front': string;
'benefits.card2.subtitle': string;
'benefits.card2.back': string;
'benefits.card3.front': string;
'benefits.card3.subtitle': string;
'benefits.card3.back': string;
'benefits.builtTitle': string;
'benefits.builtDesc': string;
'benefits.static1.title': string;
'benefits.static1.desc': string;
'benefits.static2.title': string;
'benefits.static2.desc': string;
'benefits.static3.title': string;
'benefits.static3.desc': string;
'benefits.static4.title': string;
'benefits.static4.desc': string;
// Banner1
'banner1.q1': string;
'banner1.q2': string;
'banner1.q3': string;
'banner1.cta': string;
// RealResults
'realResults.title': string;
'realResults.card1.title': string;
'realResults.card1.resultsLabel': string;
'realResults.card1.desc': string;
'realResults.card1.stat1': string;
'realResults.card1.stat2': string;
'realResults.card1.stat3': string;
'realResults.card2.title': string;
'realResults.card2.desc': string;
'realResults.card2.stat1': string;
'realResults.card2.stat2': string;
'realResults.card3.title': string;
'realResults.card3.desc': string;
'realResults.card3.stat1': string;
'realResults.card3.stat2': string;
// Timeline
'timeline.title': string;
'timeline.step1.title': string;
'timeline.step1.duration': string;
'timeline.step1.short': string;
'timeline.step1.detail': string;
'timeline.step2.title': string;
'timeline.step2.duration': string;
'timeline.step2.short': string;
'timeline.step2.detail': string;
'timeline.step3.title': string;
'timeline.step3.duration': string;
'timeline.step3.short': string;
'timeline.step3.detail': string;
'timeline.step4.title': string;
'timeline.step4.duration': string;
'timeline.step4.short': string;
'timeline.step4.detail': string;
'timeline.step5.title': string;
'timeline.step5.duration': string;
'timeline.step5.short': string;
'timeline.step5.detail': string;
// Banner2
'banner2.cta': string;
// ComparisonTable
'comparison.title': string;
'comparison.aiLabel': string;
'comparison.metric1.label': string;
'comparison.metric1.ai': string;
'comparison.metric1.agency': string;
'comparison.metric1.inhouse': string;
'comparison.metric2.label': string;
'comparison.metric2.ai': string;
'comparison.metric2.agency': string;
'comparison.metric2.inhouse': string;
'comparison.metric3.label': string;
'comparison.metric3.ai': string;
'comparison.metric3.agency': string;
'comparison.metric3.inhouse': string;
'comparison.metric4.label': string;
'comparison.metric4.ai': string;
'comparison.metric4.agency': string;
'comparison.metric4.inhouse': string;
'comparison.altHeading': string;
'comparison.alt1': string;
'comparison.alt2': string;
'comparison.footer': string;
'comparison.cta': string;
// BlogSection
'blogSection.title': string;
'blogSection.readMore': string;
'blogSection.viewAll': string;
// Resources
'resources.title': string;
// ContactSection
'contactSection.title': string;
'contactSection.subtitle': string;
// ContactForm
'contactForm.title': string;
'contactForm.fullName': string;
'contactForm.fullNamePlaceholder': string;
'contactForm.jobTitle': string;
'contactForm.jobTitlePlaceholder': string;
'contactForm.email': string;
'contactForm.emailPlaceholder': string;
'contactForm.need': string;
'contactForm.needPlaceholder': string;
'contactForm.company': string;
'contactForm.companyPlaceholder': string;
'contactForm.phone': string;
'contactForm.phonePlaceholder': string;
'contactForm.submit': string;
'contactForm.sending': string;
'contactForm.error': string;
'contactForm.successTitle': string;
'contactForm.successText': string;
'contactForm.sendAnother': string;
// Footer
'footer.privacy': string;
'footer.terms': string;
'footer.copyright': string;
// CookieConsent
'cookie.text': string;
'cookie.privacyLink': string;
'cookie.reject': string;
'cookie.accept': string;
// ChatBubble
'chat.greeting': string;
'chat.openChat': string;
// ChatWindow
'chat.headerTitle': string;
'chat.status': string;
'chat.clearChat': string;
'chat.closeChat': string;
'chat.welcome': string;
// ChatLeadForm
'chat.lead.title': string;
'chat.lead.subtitle': string;
'chat.lead.intro': string;
'chat.lead.namePlaceholder': string;
'chat.lead.nameError': string;
'chat.lead.emailPlaceholder': string;
'chat.lead.emailError': string;
'chat.lead.emailInvalid': string;
'chat.lead.companyPlaceholder': string;
'chat.lead.consent': string;
'chat.lead.privacyLink': string;
'chat.lead.consentError': string;
'chat.lead.submit': string;
// ChatInput
'chat.inputPlaceholder': string;
'chat.send': string;
// QuoteForm
'quoteForm.title': string;
'quoteForm.fullName': string;
'quoteForm.fullNamePlaceholder': string;
'quoteForm.jobTitle': string;
'quoteForm.jobTitlePlaceholder': string;
'quoteForm.email': string;
'quoteForm.emailPlaceholder': string;
'quoteForm.phone': string;
'quoteForm.phonePlaceholder': string;
'quoteForm.company': string;
'quoteForm.companyPlaceholder': string;
'quoteForm.service': string;
'quoteForm.serviceDefault': string;
'quoteForm.service1': string;
'quoteForm.service2': string;
'quoteForm.service3': string;
'quoteForm.service4': string;
'quoteForm.service5': string;
'quoteForm.service6': string;
'quoteForm.service7': string;
'quoteForm.service8': string;
'quoteForm.service9': string;
'quoteForm.description': string;
'quoteForm.descriptionPlaceholder': string;
'quoteForm.submit': string;
'quoteForm.sending': string;
'quoteForm.error': string;
'quoteForm.successTitle': string;
'quoteForm.successText': string;
'quoteForm.sendAnother': string;
// About Page
'about.hero.title': string;
'about.hero.subtitle': string;
'about.story.title': string;
'about.story.p1': string;
'about.story.p2': string;
'about.story.p3': string;
'about.diff.title': string;
'about.diff1.title': string;
'about.diff1.desc': string;
'about.diff2.title': string;
'about.diff2.desc': string;
'about.diff3.title': string;
'about.diff3.desc': string;
'about.diff4.title': string;
'about.diff4.desc': string;
'about.values.title': string;
'about.val1.name': string;
'about.val1.desc': string;
'about.val2.name': string;
'about.val2.desc': string;
'about.val3.name': string;
'about.val3.desc': string;
'about.val4.name': string;
'about.val4.desc': string;
'about.val5.name': string;
'about.val5.desc': string;
'about.founder.title': string;
'about.founder.name': string;
'about.founder.role': string;
'about.founder.bgLabel': string;
'about.founder.bgText': string;
'about.founder.certLabel': string;
'about.founder.certText': string;
'about.founder.analyticsLabel': string;
'about.founder.analyticsText': string;
'about.founder.eduLabel': string;
'about.founder.eduText': string;
'about.founder.visionLabel': string;
'about.founder.visionText': string;
'about.industries.title': string;
'about.ind1.name': string;
'about.ind1.desc': string;
'about.ind2.name': string;
'about.ind2.desc': string;
'about.ind3.name': string;
'about.ind3.desc': string;
'about.ind4.name': string;
'about.ind4.desc': string;
'about.ind5.name': string;
'about.ind5.desc': string;
'about.ind6.name': string;
'about.ind6.desc': string;
'about.ind7.name': string;
'about.ind7.desc': string;
'about.cta.title': string;
'about.cta.subtitle': string;
'about.cta.button': string;
// Services Page
'services.hero.title': string;
'services.hero.subtitle': string;
'services.s1.title': string;
'services.s1.price': string;
'services.s1.purpose': string;
'services.s1.f1': string;
'services.s1.f2': string;
'services.s1.f3': string;
'services.s1.f4': string;
'services.s1.f5': string;
'services.s1.f6': string;
'services.s2.title': string;
'services.s2.price': string;
'services.s2.purpose': string;
'services.s2.f1': string;
'services.s2.f2': string;
'services.s2.f3': string;
'services.s2.f4': string;
'services.s2.f5': string;
'services.s2.f6': string;
'services.s3.title': string;
'services.s3.price': string;
'services.s3.purpose': string;
'services.s3.f1': string;
'services.s3.f2': string;
'services.s3.f3': string;
'services.s3.f4': string;
'services.s3.f5': string;
'services.s4.title': string;
'services.s4.price': string;
'services.s4.purpose': string;
'services.s4.f1': string;
'services.s4.f2': string;
'services.s4.f3': string;
'services.s4.f4': string;
'services.s4.f5': string;
'services.s5.title': string;
'services.s5.price': string;
'services.s5.purpose': string;
'services.s5.f1': string;
'services.s5.f2': string;
'services.s5.f3': string;
'services.s5.f4': string;
'services.s5.f5': string;
'services.s6.title': string;
'services.s6.price': string;
'services.s6.purpose': string;
'services.s6.f1': string;
'services.s6.f2': string;
'services.s6.f3': string;
'services.s6.f4': string;
'services.s6.f5': string;
'services.s7.title': string;
'services.s7.price': string;
'services.s7.purpose': string;
'services.s7.f1': string;
'services.s7.f2': string;
'services.s7.f3': string;
'services.s7.f4': string;
'services.s7.f5': string;
'services.s8.title': string;
'services.s8.price': string;
'services.s8.purpose': string;
'services.s8.f1': string;
'services.s8.f2': string;
'services.s8.f3': string;
'services.s8.f4': string;
'services.s8.f5': string;
'services.popular': string;
'services.whatsIncluded': string;
'services.showLess': string;
'services.assurance.title': string;
'services.assurance.subtitle': string;
'services.assurance.i1': string;
'services.assurance.i2': string;
'services.assurance.i3': string;
'services.assurance.i4': string;
'services.assurance.i5': string;
'services.assurance.i6': string;
'services.assurance.i7': string;
'services.assurance.i8': string;
'services.assurance.i9': string;
'services.metrics.title': string;
'services.metrics.v1': string;
'services.metrics.l1': string;
'services.metrics.v2': string;
'services.metrics.l2': string;
'services.metrics.v3': string;
'services.metrics.l3': string;
'services.metrics.v4': string;
'services.metrics.l4': string;
'services.selector.title': string;
'services.selector.step1': string;
'services.selector.step2': string;
'services.selector.step3': string;
'services.selector.goalQ': string;
'services.selector.goal1': string;
'services.selector.goal2': string;
'services.selector.goal3': string;
'services.selector.goal4': string;
'services.selector.goal5': string;
'services.selector.budgetQ': string;
'services.selector.budget1': string;
'services.selector.budget2': string;
'services.selector.budget3': string;
'services.selector.resultsHeading': string;
'services.selector.noMatch': string;
'services.selector.viewButton': string;
'services.selector.resetButton': string;
'services.selector.backButton': string;
'services.bundles.title': string;
'services.bundle1.name': string;
'services.bundle1.tagline': string;
'services.bundle1.price': string;
'services.bundle2.name': string;
'services.bundle2.tagline': string;
'services.bundle2.price': string;
'services.bundle2.badge': string;
'services.bundle3.name': string;
'services.bundle3.tagline': string;
'services.bundle3.price': string;
'services.bundle.cta': string;
'services.cta.title': string;
'services.cta.subtitle': string;
'services.cta.button': string;
// Pricing Page
'pricing.hero.title': string;
'pricing.hero.subtitle': string;
'pricing.impl.title': string;
'pricing.impl.s1': string;
'pricing.impl.s1.price': string;
'pricing.impl.s2': string;
'pricing.impl.s2.price': string;
'pricing.impl.s3': string;
'pricing.impl.s3.price': string;
'pricing.impl.s4': string;
'pricing.impl.s4.price': string;
'pricing.impl.s5': string;
'pricing.impl.s5.price': string;
'pricing.impl.s6': string;
'pricing.impl.s6.price': string;
'pricing.impl.s7': string;
'pricing.impl.s7.price': string;
'pricing.impl.s8': string;
'pricing.impl.s8.price': string;
'pricing.popular': string;
'pricing.retainers.title': string;
'pricing.ret1.name': string;
'pricing.ret1.price': string;
'pricing.ret1.period': string;
'pricing.ret1.hours': string;
'pricing.ret1.sla': string;
'pricing.ret1.f1': string;
'pricing.ret1.f2': string;
'pricing.ret1.f3': string;
'pricing.ret1.f4': string;
'pricing.ret1.f5': string;
'pricing.ret2.name': string;
'pricing.ret2.price': string;
'pricing.ret2.hours': string;
'pricing.ret2.sla': string;
'pricing.ret2.f1': string;
'pricing.ret2.f2': string;
'pricing.ret2.f3': string;
'pricing.ret2.f4': string;
'pricing.ret2.f5': string;
'pricing.ret2.f6': string;
'pricing.ret2.badge': string;
'pricing.ret3.name': string;
'pricing.ret3.price': string;
'pricing.ret3.hours': string;
'pricing.ret3.sla': string;
'pricing.ret3.f1': string;
'pricing.ret3.f2': string;
'pricing.ret3.f3': string;
'pricing.ret3.f4': string;
'pricing.ret3.f5': string;
'pricing.ret3.f6': string;
'pricing.ret3.f7': string;
'pricing.training.title': string;
'pricing.training.t1': string;
'pricing.training.t1.price': string;
'pricing.training.t1.desc': string;
'pricing.training.t2': string;
'pricing.training.t2.price': string;
'pricing.training.t3': string;
'pricing.training.t3.price': string;
'pricing.training.t3.desc': string;
'pricing.training.t4': string;
'pricing.training.t4.price': string;
'pricing.training.t4.desc': string;
'pricing.payment.title': string;
'pricing.payment.r1': string;
'pricing.payment.r1.split': string;
'pricing.payment.r2': string;
'pricing.payment.r2.split': string;
'pricing.payment.r3': string;
'pricing.payment.r3.split': string;
'pricing.payment.r4': string;
'pricing.payment.r4.split': string;
'pricing.discounts.title': string;
'pricing.discounts.intro': string;
'pricing.discounts.g1': string;
'pricing.discounts.g1.disc': string;
'pricing.discounts.g2': string;
'pricing.discounts.g3': string;
'pricing.discounts.g4': string;
'pricing.discounts.g4.disc': string;
'pricing.discounts.g5': string;
'pricing.compare.title': string;
'pricing.compare.aimpress': string;
'pricing.compare.agency': string;
'pricing.compare.inhouse': string;
'pricing.compare.r1.metric': string;
'pricing.compare.r1.ai': string;
'pricing.compare.r1.agency': string;
'pricing.compare.r1.inhouse': string;
'pricing.compare.r2.metric': string;
'pricing.compare.r2.ai': string;
'pricing.compare.r2.agency': string;
'pricing.compare.r2.inhouse': string;
'pricing.compare.r3.metric': string;
'pricing.compare.r3.ai': string;
'pricing.compare.r3.agency': string;
'pricing.compare.r3.inhouse': string;
'pricing.compare.r4.metric': string;
'pricing.compare.r4.ai': string;
'pricing.compare.r4.agency': string;
'pricing.compare.r4.inhouse': string;
'pricing.compare.r5.metric': string;
'pricing.compare.r5.ai': string;
'pricing.compare.r5.agency': string;
'pricing.compare.r6.metric': string;
'pricing.compare.r6.ai': string;
'pricing.compare.r6.agency': string;
'pricing.compare.r6.inhouse': string;
'pricing.faq.title': string;
'pricing.faq.q1': string;
'pricing.faq.a1': string;
'pricing.faq.q2': string;
'pricing.faq.a2': string;
'pricing.faq.q3': string;
'pricing.faq.a3': string;
'pricing.faq.q4': string;
'pricing.faq.a4': string;
'pricing.faq.q5': string;
'pricing.faq.a5': string;
'pricing.faq.q6': string;
'pricing.faq.a6': string;
'pricing.cta.title': string;
'pricing.cta.subtitle': string;
// Blog Page
'blog.title': string;
'blog.loading': string;
'blog.noPosts': string;
'blog.readMore': string;
// Blog Post Page
'blogPost.back': string;
'blogPost.notFound': string;
'blogPost.loading': string;
'blogPost.source': string;
// SEO
'seo.home.title': string;
'seo.home.description': string;
'seo.about.title': string;
'seo.about.description': string;
'seo.services.title': string;
'seo.services.description': string;
'seo.pricing.title': string;
'seo.pricing.description': string;
'seo.blog.title': string;
'seo.blog.description': string;
'seo.siteName': string;
}

568
src/i18n/uk.ts Normal file
View file

@ -0,0 +1,568 @@
import type { Translations } from './types';
export const uk = {
// Header
'header.nav.home': 'Головна',
'header.nav.about': 'Про нас',
'header.nav.services': 'Послуги',
'header.nav.pricing': 'Ціни',
'header.nav.blog': 'Блог',
'header.nav.contacts': 'Контакти',
'header.lang.en': 'Eng',
'header.lang.uk': 'Укр',
'header.login': 'Увійти',
'header.loginModal.title': 'З поверненням',
'header.loginModal.emailLabel': 'Email / Логін',
'header.loginModal.emailPlaceholder': 'Введіть ваш email',
'header.loginModal.passwordLabel': 'Пароль',
'header.loginModal.passwordPlaceholder': 'Введіть ваш пароль',
'header.loginModal.submit': 'Увійти',
'header.loginModal.signupPrompt': 'Немає акаунту?',
'header.loginModal.signupLink': 'Зареєструватися',
// Hero
'hero.circle1': 'Будуй.',
'hero.circle2': 'Автоматизуй.',
'hero.circle3': 'Вражай.',
'hero.title': 'Припиніть наймати. Починайте масштабувати. Розгорніть свою AI-команду.',
'hero.cta': 'Отримати безкоштовну консультацію',
// Benefits
'benefits.card1.front': 'Вартість і масштаб',
'benefits.card1.subtitle': 'Еластичне зростання, нуль штату',
'benefits.card1.back': 'Масштабуйтесь у 10 разів без найму. AI-асистенти автоматично обробляють пікові навантаження, зберігаючи гнучкість та ефективність ваших операцій.',
'benefits.card2.front': 'Точність',
'benefits.card2.subtitle': 'Точність машинного навчання (99,9%)',
'benefits.card2.back': 'Усуньте людський фактор. Наші ML-валідовані процеси гарантують цілісність даних у CRM, фінансах та логістиці.',
'benefits.card3.front': 'Доступність',
'benefits.card3.subtitle': 'Справжня робота 24/7/365',
'benefits.card3.back': 'Ваша AI-команда ніколи не спить, не бере перерв і не вигоряє. Обслуговуйте клієнтів по всьому світу безперервно.',
'benefits.builtTitle': 'Створено для місцевих підприємців, таких як ви',
'benefits.builtDesc': 'AImpress перетворює хаос на систему. Ми створюємо автоматизації, які економлять до 75% вашого часу та збільшують продажі.',
'benefits.static1.title': 'Чат-боти та AI-асистенти',
'benefits.static1.desc': '→ миттєві відповіді 24/7',
'benefits.static2.title': 'Автоматизація бізнес-процесів (n8n, Make.com)',
'benefits.static2.desc': '→ CRM, email, фінанси, складський облік',
'benefits.static3.title': 'Контент-ферми та AI-копірайтинг',
'benefits.static3.desc': '→ AI створює пости, статті, описи товарів',
'benefits.static4.title': 'Маркетингова автоматизація (email, CRM, реклама)',
'benefits.static4.desc': '→ кампанії, прогрів лідів, реклама на автопілоті',
// Banner1
'banner1.q1': 'Скільки працівників у вашій компанії?',
'banner1.q2': 'Скільки годин на день витрачається на рутинні завдання?',
'banner1.q3': 'Які процеси ви хочете автоматизувати?',
'banner1.cta': 'Отримати безкоштовну консультацію',
// RealResults
'realResults.title': 'Реальні результати реальних клієнтів',
'realResults.card1.title': 'AutoBrat Garage',
'realResults.card1.resultsLabel': 'Результати:',
'realResults.card1.desc': 'Спеціалізована майстерня, заснована українцями, мала проблеми з місцевою впізнаваністю. AImpress створив двомовний контент, що демонструє їхню експертизу з німецьких автомобілів.',
'realResults.card1.stat1': '157% — зростання бронювань',
'realResults.card1.stat2': '85% — завантаженість сервісних боксів',
'realResults.card1.stat3': 'згадка в Oxford Mail протягом 6 місяців',
'realResults.card2.title': 'Cotswolld Honey Company',
'realResults.card2.desc': 'Виробник крафтового меду, обмежений сезонними ринками. AImpress розробив e-commerce та освітній контент, що підкреслює сталі практики.',
'realResults.card2.stat1': '78% — зростання онлайн-продажів',
'realResults.card2.stat2': 'чотири нових роздрібних партнерства всього за 4 місяці',
'realResults.card3.title': 'Wcounting Accounting Partners',
'realResults.card3.desc': 'Традиційна фірма, що втрачала молодих клієнтів. AImpress створив зрозумілі фінансові гіди та контент щодо Making Tax Digital.',
'realResults.card3.stat1': '41% — зростання запитів від клієнтів до 40 років',
'realResults.card3.stat2': '12 — нових e-commerce клієнтів за 5 місяців',
// Timeline
'timeline.title': 'Ваш шлях до автономних операцій',
'timeline.step1.title': 'Брифінг завдання',
'timeline.step1.duration': '2 год',
'timeline.step1.short': 'Визначте найвпливовіші можливості та метрики ROI.',
'timeline.step1.detail': 'Ми аудируємо ваші процеси, знаходимо вузькі місця та визначаємо пріоритетні цілі автоматизації з найвищим ROI — все за одну фокусну сесію.',
'timeline.step2.title': 'Технічна оцінка та стратегія',
'timeline.step2.duration': '23 дні',
'timeline.step2.short': 'Огляд існуючого стеку, архітектури та відповідності.',
'timeline.step2.detail': 'Глибокий аналіз вашої CRM, бухгалтерії, комунікацій та потоків даних. Ми проєктуємо план інтеграції та модель безпеки ще до написання першого рядка коду.',
'timeline.step3.title': 'Proof of Concept',
'timeline.step3.duration': '812 тижнів',
'timeline.step3.short': 'Пілот для критичного процесу — видимий ROI.',
'timeline.step3.detail': 'Ми створюємо та впроваджуємо робочу автоматизацію для вашого найболючішого процесу. Ви побачите вимірну економію часу та коштів вже у першому спринті.',
'timeline.step4.title': 'Впровадження MVP',
'timeline.step4.duration': '23 місяці',
'timeline.step4.short': 'Повне розгортання, навчання команди та інтеграція.',
'timeline.step4.detail': 'Впровадження у всіх відділах з практичним навчанням, моніторинговими дашбордами та виділеним Slack-каналом для миттєвої підтримки.',
'timeline.step5.title': 'Масштабування та оптимізація',
'timeline.step5.duration': 'Постійно',
'timeline.step5.short': 'Безперервне вдосконалення та розширення.',
'timeline.step5.detail': 'Щомісячні огляди продуктивності, A/B-тестування автоматизацій та розширення на нові відділи. Ваша AI-команда зростає разом з вами.',
// Banner2
'banner2.cta': 'Отримати безкоштовну консультацію',
// ComparisonTable
'comparison.title': 'Чому бізнеси переходять на AImpress',
'comparison.aiLabel': 'AI-Powered',
'comparison.metric1.label': 'Вартість',
'comparison.metric1.ai': 'Від £1 500',
'comparison.metric1.agency': '£5 000+ /міс агенція',
'comparison.metric1.inhouse': '£35 000+ /рік штатний спеціаліст',
'comparison.metric2.label': 'Швидкість',
'comparison.metric2.ai': 'Миттєво',
'comparison.metric2.agency': 'Залежить від менеджера',
'comparison.metric2.inhouse': 'Тільки з 9 до 18',
'comparison.metric3.label': 'Доступність',
'comparison.metric3.ai': '24 / 7 / 365',
'comparison.metric3.agency': 'Робочі години',
'comparison.metric3.inhouse': 'Лікарняні та відпустки',
'comparison.metric4.label': 'Масштабованість',
'comparison.metric4.ai': 'Необмежена',
'comparison.metric4.agency': 'Обмежена штатом',
'comparison.metric4.inhouse': 'Складно масштабувати',
'comparison.altHeading': 'Альтернативи',
'comparison.alt1': 'Традиційна агенція',
'comparison.alt2': 'Штатний працівник',
'comparison.footer': 'Перейдіть з застарілих методів — заощаджуйте до <strong>70%</strong> на витратах та <strong>30+ годин</strong> на тиждень.',
'comparison.cta': 'Отримати безкоштовну консультацію',
// BlogSection
'blogSection.title': 'Останні оновлення',
'blogSection.readMore': 'Читати далі →',
'blogSection.viewAll': 'Усі публікації →',
// Resources
'resources.title': 'Безкоштовні ресурси для масштабування вашого бізнесу',
// ContactSection
'contactSection.title': 'Готові до автоматизації?',
'contactSection.subtitle': 'Припиніть витрачати час на рутину. Починайте масштабуватись з AI вже сьогодні.',
// ContactForm
'contactForm.title': 'Зв\'яжіться з нами:',
'contactForm.fullName': 'Повне ім\'я',
'contactForm.fullNamePlaceholder': 'Іван Петренко',
'contactForm.jobTitle': 'Посада / Роль',
'contactForm.jobTitlePlaceholder': 'Менеджер проєктів',
'contactForm.email': 'Робочий email',
'contactForm.emailPlaceholder': 'ivan@company.com',
'contactForm.need': 'Потреба в автоматизації',
'contactForm.needPlaceholder': 'Оптимізація процесів',
'contactForm.company': 'Назва компанії',
'contactForm.companyPlaceholder': 'Тех Рішення',
'contactForm.phone': 'Номер телефону',
'contactForm.phonePlaceholder': '+44...',
'contactForm.submit': 'Надіслати запит',
'contactForm.sending': 'Надсилання...',
'contactForm.error': 'Щось пішло не так. Будь ласка, спробуйте ще раз.',
'contactForm.successTitle': 'Дякуємо!',
'contactForm.successText': 'Ми отримали ваш запит і зв\'яжемося з вами найближчим часом.',
'contactForm.sendAnother': 'Надіслати ще',
// Footer
'footer.privacy': 'Політика конфіденційності',
'footer.terms': 'Умови використання',
'footer.copyright': '© 2026 AImpress LTD. Усі права захищені.',
// CookieConsent
'cookie.text': 'Ми використовуємо файли cookie та подібні технології для аналізу трафіку на сайті та покращення вашого досвіду. Натискаючи «Прийняти», ви погоджуєтесь на використання аналітичних cookie.',
'cookie.privacyLink': 'Політика конфіденційності',
'cookie.reject': 'Відхилити',
'cookie.accept': 'Прийняти',
// ChatBubble
'chat.greeting': 'Привіт! Чим я можу вам допомогти?',
'chat.openChat': 'Відкрити чат',
// ChatWindow
'chat.headerTitle': 'AImpress',
'chat.status': 'Онлайн',
'chat.clearChat': 'Очистити чат',
'chat.closeChat': 'Закрити чат',
'chat.welcome': 'Привіт! Я AI-асистент AImpress. Запитайте мене про наші послуги з AI та автоматизації, ціни або запишіться на безкоштовну консультацію.',
// ChatLeadForm
'chat.lead.title': 'AImpress',
'chat.lead.subtitle': 'Консалтинг з AI та автоматизації',
'chat.lead.intro': 'Привіт! Перед початком, будь ласка, представтеся, щоб ми могли краще вам допомогти.',
'chat.lead.namePlaceholder': 'Ваше ім\'я *',
'chat.lead.nameError': 'Будь ласка, введіть ваше ім\'я',
'chat.lead.emailPlaceholder': 'Email *',
'chat.lead.emailError': 'Будь ласка, введіть ваш email',
'chat.lead.emailInvalid': 'Будь ласка, введіть дійсний email',
'chat.lead.companyPlaceholder': 'Компанія (необов\'язково)',
'chat.lead.consent': 'Я погоджуюсь на обробку моїх персональних даних відповідно до',
'chat.lead.privacyLink': 'Політики конфіденційності',
'chat.lead.consentError': 'Будь ласка, надайте згоду для продовження',
'chat.lead.submit': 'Розпочати чат',
// ChatInput
'chat.inputPlaceholder': 'Введіть повідомлення...',
'chat.send': 'Надіслати повідомлення',
// QuoteForm
'quoteForm.title': 'Отримати пропозицію',
'quoteForm.fullName': 'Повне ім\'я',
'quoteForm.fullNamePlaceholder': 'Іван Петренко',
'quoteForm.jobTitle': 'Посада / Роль',
'quoteForm.jobTitlePlaceholder': 'Менеджер проєктів',
'quoteForm.email': 'Робочий email',
'quoteForm.emailPlaceholder': 'ivan@company.com',
'quoteForm.phone': 'Номер телефону',
'quoteForm.phonePlaceholder': '+44...',
'quoteForm.company': 'Назва компанії',
'quoteForm.companyPlaceholder': 'Тех Рішення',
'quoteForm.service': 'Послуга',
'quoteForm.serviceDefault': 'Оберіть послугу...',
'quoteForm.service1': 'Впровадження автоматизації процесів',
'quoteForm.service2': 'Системна інтеграція та синхронізація',
'quoteForm.service3': 'Оптимізація CRM-процесів',
'quoteForm.service4': 'Налаштування маркетингової автоматизації',
'quoteForm.service5': 'Інтеграція та впровадження AI',
'quoteForm.service6': 'Налаштування інфраструктури',
'quoteForm.service7': 'Ретейнер підтримки',
'quoteForm.service8': 'Навчання та воркшопи',
'quoteForm.service9': 'Інше / Ще не визначився',
'quoteForm.description': 'Опис проєкту',
'quoteForm.descriptionPlaceholder': 'Опишіть ваші потреби в автоматизації, поточні виклики та бажані результати...',
'quoteForm.submit': 'Отримати пропозицію',
'quoteForm.sending': 'Надсилання...',
'quoteForm.error': 'Щось пішло не так. Будь ласка, спробуйте ще раз.',
'quoteForm.successTitle': 'Дякуємо!',
'quoteForm.successText': 'Ми отримали ваш запит на пропозицію. Ми розглянемо ваші вимоги та надішлемо детальну пропозицію протягом 24 годин.',
'quoteForm.sendAnother': 'Надіслати ще один запит',
// About Page
'about.hero.title': 'Про AImpress',
'about.hero.subtitle': 'Робимо професійну автоматизацію доступною для організацій, що створюють зміни.',
'about.story.title': 'Наша історія',
'about.story.p1': 'Автоматизація — це більше не опція, а різниця між зростанням та відставанням. Проте для більшості МСП варіанти були обмежені: офшорні команди — дешево, але ненадійно, або британські агенції, що беруть корпоративні ціни за базову роботу.',
'about.story.p2': 'AImpress створено, щоб заповнити цю прогалину. Ми — лондонська консалтингова компанія, що приносить автоматизацію корпоративного рівня малому та середньому бізнесу, благодійним організаціям та державному сектору — за цінами, які дійсно мають сенс.',
'about.story.p3': 'Ми не віримо в «чорні скриньки» чи прив\'язку до постачальника. Кожна система, яку ми створюємо, працює на вашій власній інфраструктурі з документацією, яку ваша команда може використовувати. Коли ми завершуємо — ви зберігаєте все, і ви знаєте, як це працює.',
'about.diff.title': 'Що нас відрізняє',
'about.diff1.title': 'Базування у Великобританії, лондонські стандарти',
'about.diff1.desc': 'Конкурентні ставки £90120/год. Британські робочі години, GDPR-відповідність за замовчуванням, повна відповідальність за законодавством Англії.',
'about.diff2.title': 'Спеціалізація на МСП',
'about.diff2.desc': 'Ми фокусуємось на CRM, маркетингу, фінансах та e-commerce автоматизації — процесах, що найбільше важливі для бізнесу, що зростає.',
'about.diff3.title': 'Інфраструктура, що належить клієнту',
'about.diff3.desc': 'Ваші сервери, ваші дані, ваші акаунти. Ми будуємо на інфраструктурі, якою ви володієте — жодної прив\'язки до постачальника.',
'about.diff4.title': 'Передача знань, а не залежність',
'about.diff4.desc': 'Кожен проєкт включає повну документацію та навчання команди. Ми вчимо ваших людей підтримувати те, що ми створили.',
'about.values.title': 'Наші цінності',
'about.val1.name': 'Прозорість',
'about.val1.desc': 'Фіксовані ціни, чіткий обсяг, жодних прихованих платежів',
'about.val2.name': 'Власність клієнта',
'about.val2.desc': 'Ви володієте всім, що ми створюємо — код, дані, інфраструктура',
'about.val3.name': 'Досконалість',
'about.val3.desc': 'Якість корпоративного рівня за цінами, доступними для МСП',
'about.val4.name': 'Вплив, а не прибуток',
'about.val4.desc': 'Знижені тарифи для благодійних організацій, стартапів та державного сектору',
'about.val5.name': 'Прагматизм',
'about.val5.desc': 'Ми рекомендуємо те, що працює, а не те, що коштує більше',
'about.founder.title': 'Засновник',
'about.founder.name': 'Вадим Самойленко',
'about.founder.role': 'CEO та засновник AImpress Ltd',
'about.founder.bgLabel': 'Досвід:',
'about.founder.bgText': '2,5+ роки в OLIVER Agency (WPP) як глобальний спеціаліст з автоматизації та AI, розробка AI-процесів, що зменшили ручну роботу на 3050% у креативних операціях для глобальних брендів',
'about.founder.certLabel': 'Сертифікації AI та автоматизації:',
'about.founder.certText': 'Prompt Engineering Specialization (Vanderbilt University), Generative AI for Marketing (Microsoft Copilot), Prompt Design in Vertex AI (Google), AI in Business (LinkedIn), Make Basics',
'about.founder.analyticsLabel': 'Сертифікації з аналітики:',
'about.founder.analyticsText': 'Microsoft Power BI Data Analyst, Laba Business Analytics & Marketing Analytics',
'about.founder.eduLabel': 'Освіта:',
'about.founder.eduText': 'Магістр економічної кібернетики — системне моделювання, аналіз даних, оптимізація процесів',
'about.founder.visionLabel': 'Бачення:',
'about.founder.visionText': 'Заснував AImpress, щоб зробити автоматизацію корпоративного рівня доступною для МСП',
'about.industries.title': 'Галузі, які ми обслуговуємо',
'about.ind1.name': 'E-commerce',
'about.ind1.desc': 'Обробка замовлень, синхронізація складу, клієнтські шляхи',
'about.ind2.name': 'Професійні послуги',
'about.ind2.desc': 'CRM, виставлення рахунків, автоматизація онбордингу клієнтів',
'about.ind3.name': 'SaaS',
'about.ind3.desc': 'Онбординг користувачів, білінг, процеси обробки тікетів',
'about.ind4.name': 'Благодійні організації',
'about.ind4.desc': 'Управління донорами, грантова звітність, координація волонтерів',
'about.ind5.name': 'Освіта',
'about.ind5.desc': 'Зарахування, комунікація зі студентами, доставка контенту',
'about.ind6.name': 'Охорона здоров\'я',
'about.ind6.desc': 'Планування прийомів, медичні записи, відповідність нормам',
'about.ind7.name': 'Державний сектор',
'about.ind7.desc': 'Управління справами, звітність, послуги для громадян',
'about.cta.title': 'Готові трансформувати свої операції?',
'about.cta.subtitle': 'Замовте безкоштовний дзвінок та дізнайтеся, як автоматизація може працювати для вашого бізнесу.',
'about.cta.button': 'Замовити безкоштовну консультацію',
// Services Page
'services.hero.title': 'Наші послуги',
'services.hero.subtitle': 'Комплексний консалтинг з автоматизації — від аналізу до впровадження та подальшої підтримки.',
'services.s1.title': 'AI чат-боти та віртуальні асистенти',
'services.s1.price': '£3 000 £10 000',
'services.s1.purpose': 'Розгорніть інтелектуальних чат-ботів, що обслуговують клієнтів, кваліфікують ліди та записують на зустрічі 24/7 — без участі людини.',
'services.s1.f1': 'Розробка чат-бота та персоналізація',
'services.s1.f2': 'Інтеграція з вашим сайтом, WhatsApp або Messenger',
'services.s1.f3': 'Навчання бази знань на даних вашого бізнесу',
'services.s1.f4': 'Кваліфікація лідів та передача в CRM',
'services.s1.f5': 'Мультимовна підтримка',
'services.s1.f6': 'Аналітична панель та інсайти з розмов',
'services.s2.title': 'Розробка веб-сайтів',
'services.s2.price': '£2 500 £15 000',
'services.s2.purpose': 'Високопродуктивні, конверсійно-оптимізовані сайти на сучасних технологіях — швидкі, SEO-готові та повністю ваші.',
'services.s2.f1': 'Індивідуальний дизайн та адаптивна розробка',
'services.s2.f2': 'Розробка на React / Next.js або WordPress',
'services.s2.f3': 'SEO-оптимізація та Core Web Vitals',
'services.s2.f4': 'Інтеграція CMS для зручного оновлення контенту',
'services.s2.f5': 'Контактні форми, аналітика та налаштування трекінгу',
'services.s2.f6': 'Налаштування хостингу на вашій інфраструктурі',
'services.s3.title': 'Впровадження автоматизації процесів',
'services.s3.price': '£3 500 £12 000',
'services.s3.purpose': 'Автоматизуйте повторювані бізнес-процеси від початку до кінця, щоб ваша команда фокусувалась на високоцінній роботі.',
'services.s3.f1': 'Аналіз та картування процесів',
'services.s3.f2': 'Розробка та створення процесів (n8n / Make.com)',
'services.s3.f3': 'Багатокрокова логіка з умовним розгалуженням',
'services.s3.f4': 'Обробка помилок та механізми повторних спроб',
'services.s3.f5': 'Тестування, розгортання та документація',
'services.s4.title': 'Системна інтеграція та синхронізація',
'services.s4.price': '£2 500 £10 000+',
'services.s4.purpose': 'Об\'єднайте свої інструменти в єдине джерело правди — CRM, бухгалтерія, e-commerce, комунікації тощо.',
'services.s4.f1': 'API-інтеграція між платформами',
'services.s4.f2': 'Двонаправлена синхронізація даних',
'services.s4.f3': 'Маппінг та трансформація даних',
'services.s4.f4': 'Webhook та тригери на основі подій',
'services.s4.f5': 'Налаштування моніторингу та сповіщень',
'services.s5.title': 'Оптимізація CRM-процесів',
'services.s5.price': '£3 000 £6 500',
'services.s5.purpose': 'Оптимізуйте воронку продажів, автоматизуйте follow-up та переконайтеся, що жоден лід не загубиться.',
'services.s5.f1': 'Аудит CRM та реструктуризація воронки',
'services.s5.f2': 'Автоматичний скоринг та маршрутизація лідів',
'services.s5.f3': 'Автоматизація email-послідовностей',
'services.s5.f4': 'Процеси завдань та нагадувань',
'services.s5.f5': 'Налаштування звітних панелей',
'services.s6.title': 'Налаштування маркетингової автоматизації',
'services.s6.price': '£3 500 £8 000',
'services.s6.purpose': 'Переведіть кампанії, прогрів лідів та рекламу на автопілот з цілеспрямованою, дата-орієнтованою автоматизацією.',
'services.s6.f1': 'Налаштування email drip-кампаній',
'services.s6.f2': 'Автоматизація збору лідів та форм',
'services.s6.f3': 'Логіка сегментації аудиторії',
'services.s6.f4': 'Інтеграція з плануванням соціальних мереж',
'services.s6.f5': 'Аналітика та відстеження конверсій',
'services.s7.title': 'Інтеграція та впровадження AI',
'services.s7.price': '£4 000 £12 000',
'services.s7.purpose': 'Додайте AI-можливості до існуючих процесів — чат-боти, генерація контенту, обробка документів тощо.',
'services.s7.f1': 'Вибір та інтеграція AI-моделей (OpenAI, Claude тощо)',
'services.s7.f2': 'Розробка чат-бота / віртуального асистента',
'services.s7.f3': 'Витяг даних з документів за допомогою AI',
'services.s7.f4': 'Конвеєри AI-генерації контенту',
'services.s7.f5': 'Prompt engineering та fine-tuning',
'services.s8.title': 'Налаштування інфраструктури',
'services.s8.price': '£1 500 £4 000',
'services.s8.purpose': 'Налаштуйте інфраструктуру автоматизації на серверах, якими ви володієте — безпечно, масштабовано та повністю ваше.',
'services.s8.f1': 'Розгортання серверів (VPS / хмара)',
'services.s8.f2': 'Розгортання n8n / платформи автоматизації',
'services.s8.f3': 'SSL, файервол та посилення безпеки',
'services.s8.f4': 'Налаштування резервного копіювання та відновлення',
'services.s8.f5': 'Моніторинг та сповіщення про доступність',
'services.popular': 'Популярне',
'services.whatsIncluded': 'Що включено',
'services.showLess': 'Показати менше',
'services.assurance.title': 'Пакет гарантій AImpress',
'services.assurance.subtitle': 'Безкоштовно включено в кожен проєкт (вартість £1 500)',
'services.assurance.i1': 'Виділений менеджер проєкту',
'services.assurance.i2': 'Щотижневі звіти про прогрес',
'services.assurance.i3': 'Повна технічна документація',
'services.assurance.i4': 'Навчання кінцевих користувачів',
'services.assurance.i5': 'Навчання адміністраторів',
'services.assurance.i6': '30-денна підтримка після запуску',
'services.assurance.i7': 'Виправлення помилок в рамках SLA',
'services.assurance.i8': 'База знань та runbooks',
'services.assurance.i9': 'План передачі та переходу',
'services.metrics.title': 'Результати в цифрах',
'services.metrics.v1': '3050%',
'services.metrics.l1': 'Зменшення ручної роботи',
'services.metrics.v2': '28 тижнів',
'services.metrics.l2': 'Середній час реалізації',
'services.metrics.v3': '24/7',
'services.metrics.l3': 'Автоматизований аптайм',
'services.metrics.v4': '£0',
'services.metrics.l4': 'Плата за прив\'язку до постачальника',
'services.selector.title': 'Яка послуга вам потрібна?',
'services.selector.step1': 'Ваша мета',
'services.selector.step2': 'Бюджет',
'services.selector.step3': 'Результати',
'services.selector.goalQ': 'Яка ваша основна мета?',
'services.selector.goal1': 'Автоматизувати процеси',
'services.selector.goal2': 'Отримати більше лідів',
'services.selector.goal3': 'Побудувати онлайн-присутність',
'services.selector.goal4': 'З\'єднати мої інструменти',
'services.selector.goal5': 'Додати AI-можливості',
'services.selector.budgetQ': 'Який ваш діапазон бюджету?',
'services.selector.budget1': 'До £5K',
'services.selector.budget2': '£5K £10K',
'services.selector.budget3': '£10K+',
'services.selector.resultsHeading': 'Ми рекомендуємо ці послуги:',
'services.selector.noMatch': 'Точного збігу немає — але ми можемо допомогти! Зв\'яжіться з нами для індивідуального рішення.',
'services.selector.viewButton': 'Переглянути послуги',
'services.selector.resetButton': 'Почати спочатку',
'services.selector.backButton': '← Назад',
'services.bundles.title': 'Популярні пакети',
'services.bundle1.name': 'Starter',
'services.bundle1.tagline': 'Запуск та залучення',
'services.bundle1.price': 'від £5 500',
'services.bundle2.name': 'Growth',
'services.bundle2.tagline': 'Масштабування та конверсія',
'services.bundle2.price': 'від £9 000',
'services.bundle2.badge': 'Найпопулярніший',
'services.bundle3.name': 'Full Stack',
'services.bundle3.tagline': 'Трансформація всього',
'services.bundle3.price': 'Індивідуальна ціна',
'services.bundle.cta': 'Розпочати',
'services.cta.title': 'Не знаєте, з чого почати?',
'services.cta.subtitle': 'Замовте безкоштовну консультацію, і ми розробимо найкращу стратегію автоматизації для вашого бізнесу.',
'services.cta.button': 'Отримати безкоштовну консультацію',
// Pricing Page
'pricing.hero.title': 'Прості, прозорі ціни',
'pricing.hero.subtitle': 'Проєкти з фіксованою ціною. Без несподіваних витрат. Без прив\'язки до постачальника.',
'pricing.impl.title': 'Ціни на впровадження',
'pricing.impl.s1': 'AI чат-боти та віртуальні асистенти',
'pricing.impl.s1.price': '£3 000 £10 000',
'pricing.impl.s2': 'Розробка веб-сайтів',
'pricing.impl.s2.price': '£2 500 £15 000',
'pricing.impl.s3': 'Впровадження автоматизації процесів',
'pricing.impl.s3.price': '£3 500 £12 000',
'pricing.impl.s4': 'Системна інтеграція та синхронізація',
'pricing.impl.s4.price': '£2 500 £10 000+',
'pricing.impl.s5': 'Оптимізація CRM-процесів',
'pricing.impl.s5.price': '£3 000 £6 500',
'pricing.impl.s6': 'Налаштування маркетингової автоматизації',
'pricing.impl.s6.price': '£3 500 £8 000',
'pricing.impl.s7': 'Інтеграція та впровадження AI',
'pricing.impl.s7.price': '£4 000 £12 000',
'pricing.impl.s8': 'Налаштування інфраструктури',
'pricing.impl.s8.price': '£1 500 £4 000',
'pricing.popular': 'Популярне',
'pricing.retainers.title': 'Ретейнери підтримки',
'pricing.ret1.name': 'Essential',
'pricing.ret1.price': '£1 000',
'pricing.ret1.period': '/місяць',
'pricing.ret1.hours': '10 годин',
'pricing.ret1.sla': 'Відповідь протягом 48 год',
'pricing.ret1.f1': '10 годин підтримки',
'pricing.ret1.f2': 'Виправлення помилок та незначні оновлення',
'pricing.ret1.f3': 'SLA відповіді 48 годин',
'pricing.ret1.f4': 'Підтримка через email',
'pricing.ret1.f5': 'Щомісячна перевірка стану',
'pricing.ret2.name': 'Professional',
'pricing.ret2.price': '£2 000',
'pricing.ret2.hours': '22 години',
'pricing.ret2.sla': 'Відповідь протягом 24 год',
'pricing.ret2.f1': '22 години підтримки',
'pricing.ret2.f2': 'Виправлення помилок, оновлення та нові функції',
'pricing.ret2.f3': 'SLA відповіді 24 години',
'pricing.ret2.f4': 'Підтримка через email та Slack',
'pricing.ret2.f5': 'Стратегічний дзвінок раз на два тижні',
'pricing.ret2.f6': 'Пріоритетне планування',
'pricing.ret2.badge': 'Найпопулярніший',
'pricing.ret3.name': 'Enterprise',
'pricing.ret3.price': '£3 500',
'pricing.ret3.hours': '40 годин',
'pricing.ret3.sla': 'Відповідь протягом 4 год',
'pricing.ret3.f1': '40 годин підтримки',
'pricing.ret3.f2': 'Повний спектр розробки та підтримки',
'pricing.ret3.f3': 'SLA відповіді 4 години',
'pricing.ret3.f4': 'Виділений Slack-канал',
'pricing.ret3.f5': 'Щотижневий стратегічний дзвінок',
'pricing.ret3.f6': 'Пріоритетне планування',
'pricing.ret3.f7': 'Квартальний огляд дорожньої карти',
'pricing.training.title': 'Навчання та воркшопи',
'pricing.training.t1': 'Навчання кінцевих користувачів',
'pricing.training.t1.price': '£250',
'pricing.training.t1.desc': 'За сесію',
'pricing.training.t2': 'Навчання адміністраторів',
'pricing.training.t2.price': '£600 £1 000',
'pricing.training.t3': 'Програма сертифікації',
'pricing.training.t3.price': '£1 800',
'pricing.training.t3.desc': 'Повний курс',
'pricing.training.t4': 'Індивідуальний воркшоп',
'pricing.training.t4.price': '£400 £1 200',
'pricing.training.t4.desc': 'Від пів дня до повного дня',
'pricing.payment.title': 'Умови оплати',
'pricing.payment.r1': 'До £5 000',
'pricing.payment.r1.split': '100% передоплата',
'pricing.payment.r2': '£5 000 £10 000',
'pricing.payment.r2.split': '50% / 50%',
'pricing.payment.r3': 'Понад £10 000',
'pricing.payment.r3.split': '33% / 33% / 34%',
'pricing.payment.r4': 'Державний сектор',
'pricing.payment.r4.split': 'Оплата протягом 30 днів',
'pricing.discounts.title': 'Програма грантів впливу',
'pricing.discounts.intro': 'Ми переконані, що автоматизація повинна бути доступною для організацій, що роблять різницю. Відповідні групи отримують значні знижки.',
'pricing.discounts.g1': 'Благодійні та некомерційні організації',
'pricing.discounts.g1.disc': 'До 50%',
'pricing.discounts.g2': 'Стартапи (менше 2 років)',
'pricing.discounts.g3': 'Освіта',
'pricing.discounts.g4': 'Державний сектор',
'pricing.discounts.g4.disc': '25% + безкоштовний пілотний проєкт',
'pricing.discounts.g5': 'Українські бізнеси',
'pricing.compare.title': 'Як ми порівнюємося',
'pricing.compare.aimpress': 'AImpress',
'pricing.compare.agency': 'Агенція',
'pricing.compare.inhouse': 'Штатний спеціаліст',
'pricing.compare.r1.metric': 'Вартість запуску',
'pricing.compare.r1.ai': 'Від £1 500',
'pricing.compare.r1.agency': '£5 000+ /міс',
'pricing.compare.r1.inhouse': '£35 000+ /рік',
'pricing.compare.r2.metric': 'Час розгортання',
'pricing.compare.r2.ai': '28 тижнів',
'pricing.compare.r2.agency': '36 місяців',
'pricing.compare.r2.inhouse': '612 місяців',
'pricing.compare.r3.metric': 'Доступність',
'pricing.compare.r3.ai': '24/7 автоматизовано',
'pricing.compare.r3.agency': 'Робочі години',
'pricing.compare.r3.inhouse': 'Тільки з 9 до 18',
'pricing.compare.r4.metric': 'Масштабованість',
'pricing.compare.r4.ai': 'Необмежена',
'pricing.compare.r4.agency': 'Обмежена штатом',
'pricing.compare.r4.inhouse': 'Складно масштабувати',
'pricing.compare.r5.metric': 'Ви це володієте',
'pricing.compare.r5.ai': 'Так, завжди',
'pricing.compare.r5.agency': 'Рідко',
'pricing.compare.r6.metric': 'Приховані витрати',
'pricing.compare.r6.ai': 'Немає',
'pricing.compare.r6.agency': 'Запити на зміни',
'pricing.compare.r6.inhouse': 'Соцпакет, плинність кадрів',
'pricing.faq.title': 'Часті запитання',
'pricing.faq.q1': 'Ви британська компанія?',
'pricing.faq.a1': 'Так. AImpress Ltd зареєстрована в Англії та Уельсі (реєстраційний номер 16417799), зареєстрована як платник ПДВ та в ICO. Ми працюємо за англійським законодавством з повною відповідністю GDPR.',
'pricing.faq.q2': 'Ви пропонуєте керований хостинг?',
'pricing.faq.a2': 'Ми налаштовуємо інфраструктуру на ваших власних серверах або хмарних акаунтах. Ви володієте всім. За потреби ми можемо забезпечити постійне управління через наші ретейнери підтримки.',
'pricing.faq.q3': 'З якими платформами ви працюєте?',
'pricing.faq.a3': 'Ми працюємо з n8n, Make.com, Zapier, Power Automate, HubSpot, Salesforce, Pipedrive, Mailchimp, OpenAI, Claude та багатьма іншими. Якщо ваш інструмент має API, ми можемо його інтегрувати.',
'pricing.faq.q4': 'Чи можете ви працювати з нашими існуючими інструментами?',
'pricing.faq.a4': 'Безумовно. Ми будуємо поверх вашого поточного стеку — ми не замінюємо все з нуля. Наша мета — з\'єднати та автоматизувати те, що у вас вже є.',
'pricing.faq.q5': 'Що якщо я не підпадаю під знижку?',
'pricing.faq.a5': 'Наші стандартні ставки вже конкурентні — £90120/год, що значно нижче типових ставок британських агенцій. Ми пропонуємо проєкти з фіксованою ціною, тому ви завжди знаєте загальну вартість наперед.',
'pricing.faq.q6': 'Чи працюєте ви з клієнтами за межами Великобританії?',
'pricing.faq.a6': 'Так, ми працюємо з міжнародними клієнтами. Наш основний фокус — британський бізнес, але ми раді допомогти компаніям будь-де, яким потрібен професійний консалтинг з автоматизації.',
'pricing.cta.title': 'Отримайте вашу пропозицію сьогодні',
'pricing.cta.subtitle': 'Розкажіть про ваш проєкт, і ми надішлемо детальну пропозицію з фіксованою ціною протягом 24 годин.',
// Blog Page
'blog.title': 'Блог',
'blog.loading': 'Завантаження публікацій...',
'blog.noPosts': 'Публікацій поки немає.',
'blog.readMore': 'Читати далі →',
// Blog Post Page
'blogPost.back': '← Назад до блогу',
'blogPost.notFound': 'Публікацію не знайдено',
'blogPost.loading': 'Завантаження...',
'blogPost.source': 'Джерело:',
// SEO
'seo.home.title': 'AImpress | Консалтинг з AI та автоматизації для МСП | Лондон, Великобританія',
'seo.home.description': 'AImpress допомагає малому та середньому бізнесу у Великобританії автоматизувати операції, скоротити витрати та швидше зростати за допомогою AI-рішень. Базується в Лондоні.',
'seo.about.title': 'Про нас | AImpress — Консалтинг з AI та автоматизації, Лондон',
'seo.about.description': 'AImpress Ltd — лондонська консалтингова компанія з автоматизації для МСП, благодійних організацій та державного сектору. Заснована 2024. Інфраструктура клієнта, без прив\'язки до постачальника.',
'seo.services.title': 'Послуги AI та автоматизації для бізнесу у Великобританії | AImpress',
'seo.services.description': 'Автоматизація процесів, системна інтеграція, оптимізація CRM, маркетингова автоматизація, інтеграція AI та налаштування інфраструктури. Проєкти з фіксованою ціною від £1 500.',
'seo.pricing.title': 'Ціни | AImpress — Прозорі витрати на автоматизацію від £1 500',
'seo.pricing.description': 'Проєкти автоматизації з фіксованою ціною від £1 500. Ретейнери підтримки від £1 000/місяць. До 50% знижки для благодійних організацій, стартапів та державного сектору.',
'seo.blog.title': 'Блог | AImpress — Інсайти з AI-автоматизації',
'seo.blog.description': 'Інсайти, гіди та кейси з AI-автоматизації для малого та середнього бізнесу. Будьте попереду з AImpress.',
'seo.siteName': 'AImpress',
} as const satisfies Translations;

View file

@ -2,15 +2,18 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { HelmetProvider } from 'react-helmet-async'
import { LanguageProvider } from './i18n'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<HelmetProvider>
<BrowserRouter>
<App />
</BrowserRouter>
<LanguageProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</LanguageProvider>
</HelmetProvider>
</StrictMode>,
)

View file

@ -4,6 +4,7 @@ import { Helmet } from 'react-helmet-async';
import SEO from '../components/SEO';
import Modal from '../components/Modal';
import ContactForm from '../components/ContactForm';
import { useTranslation } from '../i18n';
import './AboutPage.css';
const fadeUp = {
@ -13,128 +14,129 @@ const fadeUp = {
transition: { duration: 0.5 },
};
const differentiators = [
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="10" r="3"/><path d="M12 2a8 8 0 0 0-8 8c0 5.4 7 12 8 12s8-6.6 8-12a8 8 0 0 0-8-8z"/>
</svg>
),
title: 'UK-Based, London Standards',
desc: 'Competitive rates of £90120/hr. UK business hours, GDPR-compliant by default, full accountability under English law.',
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 7V5a4 4 0 0 0-8 0v2"/>
</svg>
),
title: 'SME Specialisation',
desc: 'We focus on CRM, marketing, finance, and e-commerce automation — the workflows that matter most to growing businesses.',
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
),
title: 'Client-Owned Infrastructure',
desc: 'Your servers, your data, your accounts. We build on infrastructure you own — no vendor lock-in, no hostage situations.',
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</svg>
),
title: 'Knowledge Transfer, Not Dependency',
desc: 'Every project includes full documentation and team training. We teach your people to maintain what we build.',
},
];
const values = [
{ name: 'Transparency', desc: 'Fixed prices, clear scope, no hidden fees' },
{ name: 'Client Ownership', desc: 'You own everything we build — code, data, infrastructure' },
{ name: 'Excellence', desc: 'Enterprise-grade quality at SME-friendly prices' },
{ name: 'Impact Over Profit', desc: 'Discounted rates for charities, startups, and public sector' },
{ name: 'Pragmatism', desc: 'We recommend what works, not what costs more' },
];
const industries = [
{
name: 'E-commerce',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
</svg>
),
desc: 'Order processing, inventory sync, customer journeys',
},
{
name: 'Professional Services',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 7V5a4 4 0 0 0-8 0v2"/>
</svg>
),
desc: 'CRM, invoicing, client onboarding automation',
},
{
name: 'SaaS',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
</svg>
),
desc: 'User onboarding, billing, support ticket workflows',
},
{
name: 'Charities',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
</svg>
),
desc: 'Donor management, grant reporting, volunteer coordination',
},
{
name: 'Education',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</svg>
),
desc: 'Enrolment, student comms, content delivery',
},
{
name: 'Healthcare',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
</svg>
),
desc: 'Appointment scheduling, patient records, compliance',
},
{
name: 'Public Sector',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 21h18M3 10h18M5 6l7-3 7 3M4 10v11M20 10v11M8 14v3M12 14v3M16 14v3"/>
</svg>
),
desc: 'Case management, reporting, citizen services',
},
];
const AboutPage = () => {
const { t } = useTranslation();
useEffect(() => { window.scrollTo(0, 0); }, []);
const [isModalOpen, setIsModalOpen] = useState(false);
const differentiators = [
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="10" r="3"/><path d="M12 2a8 8 0 0 0-8 8c0 5.4 7 12 8 12s8-6.6 8-12a8 8 0 0 0-8-8z"/>
</svg>
),
title: t('about.diff1.title'),
desc: t('about.diff1.desc'),
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 7V5a4 4 0 0 0-8 0v2"/>
</svg>
),
title: t('about.diff2.title'),
desc: t('about.diff2.desc'),
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
),
title: t('about.diff3.title'),
desc: t('about.diff3.desc'),
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</svg>
),
title: t('about.diff4.title'),
desc: t('about.diff4.desc'),
},
];
const values = [
{ name: t('about.val1.name'), desc: t('about.val1.desc') },
{ name: t('about.val2.name'), desc: t('about.val2.desc') },
{ name: t('about.val3.name'), desc: t('about.val3.desc') },
{ name: t('about.val4.name'), desc: t('about.val4.desc') },
{ name: t('about.val5.name'), desc: t('about.val5.desc') },
];
const industries = [
{
name: t('about.ind1.name'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
</svg>
),
desc: t('about.ind1.desc'),
},
{
name: t('about.ind2.name'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 7V5a4 4 0 0 0-8 0v2"/>
</svg>
),
desc: t('about.ind2.desc'),
},
{
name: t('about.ind3.name'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
</svg>
),
desc: t('about.ind3.desc'),
},
{
name: t('about.ind4.name'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
</svg>
),
desc: t('about.ind4.desc'),
},
{
name: t('about.ind5.name'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</svg>
),
desc: t('about.ind5.desc'),
},
{
name: t('about.ind6.name'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
</svg>
),
desc: t('about.ind6.desc'),
},
{
name: t('about.ind7.name'),
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 21h18M3 10h18M5 6l7-3 7 3M4 10v11M20 10v11M8 14v3M12 14v3M16 14v3"/>
</svg>
),
desc: t('about.ind7.desc'),
},
];
return (
<div className="about-page">
<SEO
title="About Us | AImpress — AI & Automation Consulting, London"
description="AImpress Ltd is a London-based automation consultancy for SMEs, charities, and public sector. Founded 2024. Client-owned infrastructure, no vendor lock-in."
title={t('seo.about.title')}
description={t('seo.about.description')}
url="https://ai-impress.com/about"
/>
<Helmet>
@ -160,9 +162,9 @@ const AboutPage = () => {
{/* Hero */}
<section className="about-hero">
<div className="container">
<motion.h1 {...fadeUp}>About AImpress</motion.h1>
<motion.h1 {...fadeUp}>{t('about.hero.title')}</motion.h1>
<motion.p className="about-hero-sub" {...fadeUp} transition={{ duration: 0.5, delay: 0.1 }}>
Making professional automation accessible to impact-driven organisations.
{t('about.hero.subtitle')}
</motion.p>
</div>
</section>
@ -171,23 +173,11 @@ const AboutPage = () => {
<section className="about-section">
<div className="container">
<motion.div className="about-story" {...fadeUp}>
<h2 className="section-title">Our Story</h2>
<h2 className="section-title">{t('about.story.title')}</h2>
<div className="about-story-text">
<p>
Automation is no longer optional it's the difference between growing and getting left behind.
Yet for most SMEs, the options have been limited: offshore teams that are cheap but unreliable,
or UK agencies that charge enterprise prices for basic work.
</p>
<p>
AImpress was founded to fill that gap. We're a London-based consultancy that brings
enterprise-grade automation to small and medium businesses, charities, and public sector
organisations at prices that actually make sense.
</p>
<p>
We don't believe in black-box solutions or vendor lock-in. Every system we build runs on
infrastructure you own, with documentation your team can follow. When we leave, you keep
everything and you know how it works.
</p>
<p>{t('about.story.p1')}</p>
<p>{t('about.story.p2')}</p>
<p>{t('about.story.p3')}</p>
</div>
</motion.div>
</div>
@ -196,7 +186,7 @@ const AboutPage = () => {
{/* What Makes Us Different */}
<section className="about-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>What Makes Us Different</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('about.diff.title')}</motion.h2>
<div className="about-diff-grid">
{differentiators.map((d, i) => (
<motion.div
@ -220,7 +210,7 @@ const AboutPage = () => {
{/* Our Values — gradient banner */}
<section className="about-values-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Our Values</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('about.values.title')}</motion.h2>
<div className="about-values-grid">
{values.map((v, i) => (
<motion.div
@ -243,7 +233,7 @@ const AboutPage = () => {
{/* Meet the Founder */}
<section className="about-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Meet the Founder</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('about.founder.title')}</motion.h2>
<motion.div
className="about-founder-card"
{...fadeUp}
@ -253,29 +243,23 @@ const AboutPage = () => {
<img src="/founder-vadym.png" alt="Vadym Samoilenko — CEO & Founder of AImpress" className="about-founder-img" />
</motion.div>
<div className="about-founder-info">
<h3>Vadym Samoilenko</h3>
<span className="about-founder-role">CEO & Founder of AImpress Ltd</span>
<h3>{t('about.founder.name')}</h3>
<span className="about-founder-role">{t('about.founder.role')}</span>
<ul className="about-founder-details">
<li>
<strong>Background:</strong> 2.5+ years at OLIVER Agency (WPP) as Global Automation & AI Specialist,
architecting AI-powered workflows that reduced manual effort by 3050% across creative operations for global brands
<strong>{t('about.founder.bgLabel')}</strong> {t('about.founder.bgText')}
</li>
<li>
<strong>AI & Automation Certifications:</strong> Prompt Engineering Specialization (Vanderbilt University),
Generative AI for Marketing (Microsoft Copilot), Prompt Design in Vertex AI (Google),
AI in Business (LinkedIn), Make Basics
<strong>{t('about.founder.certLabel')}</strong> {t('about.founder.certText')}
</li>
<li>
<strong>Analytics Certifications:</strong> Microsoft Power BI Data Analyst, Laba Business Analytics
& Marketing Analytics
<strong>{t('about.founder.analyticsLabel')}</strong> {t('about.founder.analyticsText')}
</li>
<li>
<strong>Education:</strong> Master's in Economic Cybernetics systems modelling,
data analysis, process optimisation
<strong>{t('about.founder.eduLabel')}</strong> {t('about.founder.eduText')}
</li>
<li>
<strong>Vision:</strong> Founded AImpress to bring enterprise-level automation to SMEs
at accessible price points
<strong>{t('about.founder.visionLabel')}</strong> {t('about.founder.visionText')}
</li>
</ul>
</div>
@ -286,7 +270,7 @@ const AboutPage = () => {
{/* Industries We Serve */}
<section className="about-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Industries We Serve</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('about.industries.title')}</motion.h2>
<div className="about-industries-grid">
{industries.map((ind, i) => (
<motion.div
@ -314,15 +298,15 @@ const AboutPage = () => {
<section className="about-section about-cta-section">
<div className="container">
<motion.div className="about-cta" {...fadeUp}>
<h2>Ready to Transform Your Operations?</h2>
<p>Book a free discovery call and find out how automation can work for your business.</p>
<h2>{t('about.cta.title')}</h2>
<p>{t('about.cta.subtitle')}</p>
<motion.button
className="about-cta-btn"
onClick={() => setIsModalOpen(true)}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
Book a Free Discovery Call
{t('about.cta.button')}
</motion.button>
</motion.div>
</div>

View file

@ -2,21 +2,25 @@ import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import SEO from '../components/SEO';
import { useTranslation } from '../i18n';
import type { BlogPostSummary } from '../types/blog';
import '../components/BlogSection.css';
import './BlogPage.css';
function formatDate(dateStr: string) {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d).toLocaleDateString('en-US', {
month: 'short', day: '2-digit', year: 'numeric',
});
}
const BlogPage: React.FC = () => {
const { t, lang } = useTranslation();
const [posts, setPosts] = useState<BlogPostSummary[]>([]);
const [loading, setLoading] = useState(true);
const dateLocale = lang === 'uk' ? 'uk-UA' : 'en-GB';
function formatDate(dateStr: string) {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d).toLocaleDateString(dateLocale, {
month: 'short', day: '2-digit', year: 'numeric',
});
}
useEffect(() => {
fetch('/blog/posts.json')
.then(r => r.json())
@ -28,17 +32,17 @@ const BlogPage: React.FC = () => {
return (
<main className="blog-page">
<SEO
title="Blog | AImpress — AI Automation Insights"
description="Insights, guides, and case studies on AI automation for small and medium businesses. Stay ahead with AImpress."
title={t('seo.blog.title')}
description={t('seo.blog.description')}
url="https://ai-impress.com/blog"
/>
<div className="container">
<h1 className="section-title">Blog</h1>
<h1 className="section-title">{t('blog.title')}</h1>
{loading ? (
<p className="blog-loading">Loading posts...</p>
<p className="blog-loading">{t('blog.loading')}</p>
) : posts.length === 0 ? (
<p className="blog-loading">No posts yet.</p>
<p className="blog-loading">{t('blog.noPosts')}</p>
) : (
<div className="blog-grid">
{posts.map((post, index) => (
@ -62,7 +66,7 @@ const BlogPage: React.FC = () => {
<span className="blog-date">{formatDate(post.date)}</span>
<h3 className="blog-title">{post.title}</h3>
<p className="blog-excerpt">{post.excerpt}</p>
<span className="blog-link">Read More </span>
<span className="blog-link">{t('blog.readMore')}</span>
</div>
</Link>
</motion.article>

View file

@ -2,22 +2,26 @@ import { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import SEO from '../components/SEO';
import { useTranslation } from '../i18n';
import type { BlogPostFull } from '../types/blog';
import './BlogPostPage.css';
function formatDate(dateStr: string) {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d).toLocaleDateString('en-US', {
month: 'short', day: '2-digit', year: 'numeric',
});
}
const BlogPostPage: React.FC = () => {
const { t, lang } = useTranslation();
const { slug } = useParams<{ slug: string }>();
const [post, setPost] = useState<BlogPostFull | null>(null);
const [loading, setLoading] = useState(true);
const [notFound, setNotFound] = useState(false);
const dateLocale = lang === 'uk' ? 'uk-UA' : 'en-GB';
function formatDate(dateStr: string) {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d).toLocaleDateString(dateLocale, {
month: 'short', day: '2-digit', year: 'numeric',
});
}
useEffect(() => {
if (!slug) return;
fetch(`/blog/posts/${slug}.json`)
@ -34,7 +38,7 @@ const BlogPostPage: React.FC = () => {
return (
<main className="blog-post-page">
<div className="blog-post-container">
<p className="blog-post-loading">Loading...</p>
<p className="blog-post-loading">{t('blogPost.loading')}</p>
</div>
</main>
);
@ -44,8 +48,8 @@ const BlogPostPage: React.FC = () => {
return (
<main className="blog-post-page">
<div className="blog-post-container">
<Link to="/blog" className="blog-post-back"> Back to Blog</Link>
<h1 className="blog-post-title">Post not found</h1>
<Link to="/blog" className="blog-post-back">{t('blogPost.back')}</Link>
<h1 className="blog-post-title">{t('blogPost.notFound')}</h1>
</div>
</main>
);
@ -68,7 +72,7 @@ const BlogPostPage: React.FC = () => {
image={post.coverImage || undefined}
type="article"
/>
<Link to="/blog" className="blog-post-back"> Back to Blog</Link>
<Link to="/blog" className="blog-post-back">{t('blogPost.back')}</Link>
{post.coverImage && (
<img src={post.coverImage} alt={post.title} className="blog-post-cover" />
@ -95,7 +99,7 @@ const BlogPostPage: React.FC = () => {
{post.sourceUrl && (
<div className="blog-post-source">
<span className="blog-post-source-label">Source:</span>
<span className="blog-post-source-label">{t('blogPost.source')}</span>
<a href={post.sourceUrl} target="_blank" rel="noopener noreferrer">
{post.sourceTitle || post.sourceUrl}
</a>

View file

@ -3,6 +3,7 @@ import { motion } from 'framer-motion';
import { Helmet } from 'react-helmet-async';
import SEO from '../components/SEO';
import QuoteForm from '../components/QuoteForm';
import { useTranslation } from '../i18n';
import './PricingPage.css';
const fadeUp = {
@ -12,133 +13,116 @@ const fadeUp = {
transition: { duration: 0.5 },
};
const implementationPricing = [
{ service: 'AI Chatbots & Virtual Assistants', price: '£3,000 £10,000', popular: true },
{ service: 'Custom Website Development', price: '£2,500 £15,000', popular: true },
{ service: 'Workflow Automation Implementation', price: '£3,500 £12,000' },
{ service: 'System Integration & Synchronisation', price: '£2,500 £10,000+' },
{ service: 'CRM Workflow Optimisation', price: '£3,000 £6,500' },
{ service: 'Marketing Automation Setup', price: '£3,500 £8,000' },
{ service: 'AI Integration & Enhancement', price: '£4,000 £12,000' },
{ service: 'Infrastructure Setup & Configuration', price: '£1,500 £4,000' },
];
const retainerTiers = [
{
name: 'Essential',
price: '£1,000',
period: '/month',
hours: '10 hours',
sla: '48h response',
features: [
'10 hours of support',
'Bug fixes & minor updates',
'48-hour response SLA',
'Email support',
'Monthly health check',
],
},
{
name: 'Professional',
price: '£2,000',
period: '/month',
hours: '22 hours',
sla: '24h response',
popular: true,
features: [
'22 hours of support',
'Bug fixes, updates & new features',
'24-hour response SLA',
'Email + Slack support',
'Bi-weekly strategy call',
'Priority scheduling',
],
},
{
name: 'Enterprise',
price: '£3,500',
period: '/month',
hours: '40 hours',
sla: '4h response',
features: [
'40 hours of support',
'Full-scope development & support',
'4-hour response SLA',
'Dedicated Slack channel',
'Weekly strategy call',
'Priority scheduling',
'Quarterly roadmap review',
],
},
];
const training = [
{ type: 'End-User Training', price: '£250', desc: 'Per session' },
{ type: 'Admin Training', price: '£600 £1,000', desc: 'Per session' },
{ type: 'Certification Programme', price: '£1,800', desc: 'Full course' },
{ type: 'Custom Workshop', price: '£400 £1,200', desc: 'Half to full day' },
];
const paymentTerms = [
{ range: 'Under £5,000', split: '100% upfront', visual: [100] },
{ range: '£5,000 £10,000', split: '50% / 50%', visual: [50, 50] },
{ range: 'Over £10,000', split: '33% / 33% / 34%', visual: [33, 33, 34] },
{ range: 'Public Sector', split: 'Net 30 terms', visual: [100] },
];
const discounts = [
{ group: 'Charities & Non-Profits', discount: 'Up to 50%' },
{ group: 'Startups (< 2 years)', discount: 'Up to 50%' },
{ group: 'Education', discount: 'Up to 50%' },
{ group: 'Public Sector', discount: '25% + free pilot project' },
{ group: 'Ukrainian Businesses', discount: 'Up to 50%' },
];
const comparison = [
{ metric: 'Setup Cost', aimpress: 'From £1,500', agency: '£5,000+ /mo', inhouse: '£35,000+ /yr' },
{ metric: 'Time to Deploy', aimpress: '28 weeks', agency: '36 months', inhouse: '612 months' },
{ metric: 'Availability', aimpress: '24/7 automated', agency: 'Business hours', inhouse: '9-to-5 only' },
{ metric: 'Scalability', aimpress: 'Unlimited', agency: 'Staff-limited', inhouse: 'Hard to scale' },
{ metric: 'You Own It', aimpress: 'Yes, always', agency: 'Rarely', inhouse: 'Yes' },
{ metric: 'Hidden Costs', aimpress: 'None', agency: 'Change requests', inhouse: 'Benefits, turnover' },
];
const faq = [
{
q: 'Are you a UK company?',
a: 'Yes. AImpress Ltd is registered in England & Wales (company number 16417799), VAT-registered, and ICO-registered. We operate under English law with full GDPR compliance.',
},
{
q: 'Do you offer managed hosting?',
a: 'We set up infrastructure on your own servers or cloud accounts. You own everything. We can provide ongoing management through our support retainers if needed.',
},
{
q: 'What platforms do you work with?',
a: 'We work with n8n, Make.com, Zapier, Power Automate, HubSpot, Salesforce, Pipedrive, Mailchimp, OpenAI, Claude, and many more. If your tool has an API, we can integrate it.',
},
{
q: 'Can you work with our existing tools?',
a: 'Absolutely. We build on top of your current stack — we don\'t rip and replace. Our goal is to connect and automate what you already have.',
},
{
q: 'What if I don\'t qualify for a discount?',
a: 'Our standard rates are already competitive at £90120/hr — significantly below typical UK agency rates. We offer fixed-price projects so you always know the total cost upfront.',
},
{
q: 'Do you work with clients outside the UK?',
a: 'Yes, we work with international clients. Our primary focus is UK businesses, but we\'re happy to support companies anywhere that need professional automation consulting.',
},
];
const PricingPage = () => {
const { t } = useTranslation();
useEffect(() => { window.scrollTo(0, 0); }, []);
const [openFaq, setOpenFaq] = useState<number | null>(null);
const implementationPricing = [
{ service: t('pricing.impl.s1'), price: t('pricing.impl.s1.price'), popular: true },
{ service: t('pricing.impl.s2'), price: t('pricing.impl.s2.price'), popular: true },
{ service: t('pricing.impl.s3'), price: t('pricing.impl.s3.price') },
{ service: t('pricing.impl.s4'), price: t('pricing.impl.s4.price') },
{ service: t('pricing.impl.s5'), price: t('pricing.impl.s5.price') },
{ service: t('pricing.impl.s6'), price: t('pricing.impl.s6.price') },
{ service: t('pricing.impl.s7'), price: t('pricing.impl.s7.price') },
{ service: t('pricing.impl.s8'), price: t('pricing.impl.s8.price') },
];
const retainerTiers = [
{
name: t('pricing.ret1.name'),
price: t('pricing.ret1.price'),
period: t('pricing.ret1.period'),
hours: t('pricing.ret1.hours'),
sla: t('pricing.ret1.sla'),
features: [
t('pricing.ret1.f1'),
t('pricing.ret1.f2'),
t('pricing.ret1.f3'),
t('pricing.ret1.f4'),
t('pricing.ret1.f5'),
],
},
{
name: t('pricing.ret2.name'),
price: t('pricing.ret2.price'),
period: t('pricing.ret1.period'),
hours: t('pricing.ret2.hours'),
sla: t('pricing.ret2.sla'),
popular: true,
features: [
t('pricing.ret2.f1'),
t('pricing.ret2.f2'),
t('pricing.ret2.f3'),
t('pricing.ret2.f4'),
t('pricing.ret2.f5'),
t('pricing.ret2.f6'),
],
},
{
name: t('pricing.ret3.name'),
price: t('pricing.ret3.price'),
period: t('pricing.ret1.period'),
hours: t('pricing.ret3.hours'),
sla: t('pricing.ret3.sla'),
features: [
t('pricing.ret3.f1'),
t('pricing.ret3.f2'),
t('pricing.ret3.f3'),
t('pricing.ret3.f4'),
t('pricing.ret3.f5'),
t('pricing.ret3.f6'),
t('pricing.ret3.f7'),
],
},
];
const training = [
{ type: t('pricing.training.t1'), price: t('pricing.training.t1.price'), desc: t('pricing.training.t1.desc') },
{ type: t('pricing.training.t2'), price: t('pricing.training.t2.price'), desc: t('pricing.training.t1.desc') },
{ type: t('pricing.training.t3'), price: t('pricing.training.t3.price'), desc: t('pricing.training.t3.desc') },
{ type: t('pricing.training.t4'), price: t('pricing.training.t4.price'), desc: t('pricing.training.t4.desc') },
];
const paymentTerms = [
{ range: t('pricing.payment.r1'), split: t('pricing.payment.r1.split'), visual: [100] },
{ range: t('pricing.payment.r2'), split: t('pricing.payment.r2.split'), visual: [50, 50] },
{ range: t('pricing.payment.r3'), split: t('pricing.payment.r3.split'), visual: [33, 33, 34] },
{ range: t('pricing.payment.r4'), split: t('pricing.payment.r4.split'), visual: [100] },
];
const discounts = [
{ group: t('pricing.discounts.g1'), discount: t('pricing.discounts.g1.disc') },
{ group: t('pricing.discounts.g2'), discount: t('pricing.discounts.g1.disc') },
{ group: t('pricing.discounts.g3'), discount: t('pricing.discounts.g1.disc') },
{ group: t('pricing.discounts.g4'), discount: t('pricing.discounts.g4.disc') },
{ group: t('pricing.discounts.g5'), discount: t('pricing.discounts.g1.disc') },
];
const comparison = [
{ metric: t('pricing.compare.r1.metric'), aimpress: t('pricing.compare.r1.ai'), agency: t('pricing.compare.r1.agency'), inhouse: t('pricing.compare.r1.inhouse') },
{ metric: t('pricing.compare.r2.metric'), aimpress: t('pricing.compare.r2.ai'), agency: t('pricing.compare.r2.agency'), inhouse: t('pricing.compare.r2.inhouse') },
{ metric: t('pricing.compare.r3.metric'), aimpress: t('pricing.compare.r3.ai'), agency: t('pricing.compare.r3.agency'), inhouse: t('pricing.compare.r3.inhouse') },
{ metric: t('pricing.compare.r4.metric'), aimpress: t('pricing.compare.r4.ai'), agency: t('pricing.compare.r4.agency'), inhouse: t('pricing.compare.r4.inhouse') },
{ metric: t('pricing.compare.r5.metric'), aimpress: t('pricing.compare.r5.ai'), agency: t('pricing.compare.r5.agency') },
{ metric: t('pricing.compare.r6.metric'), aimpress: t('pricing.compare.r6.ai'), agency: t('pricing.compare.r6.agency'), inhouse: t('pricing.compare.r6.inhouse') },
];
const faq = [
{ q: t('pricing.faq.q1'), a: t('pricing.faq.a1') },
{ q: t('pricing.faq.q2'), a: t('pricing.faq.a2') },
{ q: t('pricing.faq.q3'), a: t('pricing.faq.a3') },
{ q: t('pricing.faq.q4'), a: t('pricing.faq.a4') },
{ q: t('pricing.faq.q5'), a: t('pricing.faq.a5') },
{ q: t('pricing.faq.q6'), a: t('pricing.faq.a6') },
];
return (
<div className="pricing-page">
<SEO
title="Pricing | AImpress — Transparent Automation Costs from £1,500"
description="Fixed-price automation projects from £1,500. Support retainers from £1,000/month. Up to 50% discount for charities, startups, and public sector."
title={t('seo.pricing.title')}
description={t('seo.pricing.description')}
url="https://ai-impress.com/pricing"
/>
<Helmet>
@ -160,9 +144,9 @@ const PricingPage = () => {
{/* Hero */}
<section className="pricing-hero">
<div className="container">
<motion.h1 {...fadeUp}>Simple, Transparent Pricing</motion.h1>
<motion.h1 {...fadeUp}>{t('pricing.hero.title')}</motion.h1>
<motion.p className="pricing-hero-sub" {...fadeUp} transition={{ duration: 0.5, delay: 0.1 }}>
Fixed-price projects. No surprise costs. No vendor lock-in.
{t('pricing.hero.subtitle')}
</motion.p>
</div>
</section>
@ -170,7 +154,7 @@ const PricingPage = () => {
{/* Implementation Pricing */}
<section className="pricing-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Implementation Pricing</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('pricing.impl.title')}</motion.h2>
<div className="pricing-table-wrap">
<div className="pricing-table">
{implementationPricing.map((item, i) => (
@ -184,7 +168,7 @@ const PricingPage = () => {
>
<span className="pricing-table-service">
{item.service}
{'popular' in item && item.popular && <span className="pricing-popular-tag">Popular</span>}
{'popular' in item && item.popular && <span className="pricing-popular-tag">{t('pricing.popular')}</span>}
</span>
<span className="pricing-table-price">{item.price}</span>
</motion.div>
@ -197,7 +181,7 @@ const PricingPage = () => {
{/* Support Retainers */}
<section className="pricing-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Support Retainers</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('pricing.retainers.title')}</motion.h2>
<div className="retainer-grid">
{retainerTiers.map((tier, i) => (
<motion.div
@ -208,7 +192,7 @@ const PricingPage = () => {
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.5, delay: i * 0.1 }}
>
{tier.popular && <span className="retainer-popular-badge">Most Popular</span>}
{tier.popular && <span className="retainer-popular-badge">{t('pricing.ret2.badge')}</span>}
<h3>{tier.name}</h3>
<div className="retainer-price">
<span className="retainer-amount">{tier.price}</span>
@ -235,20 +219,20 @@ const PricingPage = () => {
{/* Training & Workshops */}
<section className="pricing-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Training & Workshops</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('pricing.training.title')}</motion.h2>
<div className="training-grid">
{training.map((t, i) => (
{training.map((tr, i) => (
<motion.div
key={t.type}
key={tr.type}
className="training-card"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: i * 0.08 }}
>
<h3>{t.type}</h3>
<span className="training-price">{t.price}</span>
<span className="training-desc">{t.desc}</span>
<h3>{tr.type}</h3>
<span className="training-price">{tr.price}</span>
<span className="training-desc">{tr.desc}</span>
</motion.div>
))}
</div>
@ -258,7 +242,7 @@ const PricingPage = () => {
{/* Payment Terms */}
<section className="pricing-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Payment Terms</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('pricing.payment.title')}</motion.h2>
<div className="payment-grid">
{paymentTerms.map((pt, i) => (
<motion.div
@ -288,8 +272,8 @@ const PricingPage = () => {
<section className="discount-section">
<div className="container">
<motion.div className="discount-block" {...fadeUp}>
<h2>Impact Grant Programme</h2>
<p className="discount-intro">We believe automation should be accessible to organisations making a difference. Eligible groups receive significant discounts.</p>
<h2>{t('pricing.discounts.title')}</h2>
<p className="discount-intro">{t('pricing.discounts.intro')}</p>
<div className="discount-grid">
{discounts.map((d, i) => (
<motion.div
@ -312,15 +296,15 @@ const PricingPage = () => {
{/* Comparison */}
<section className="pricing-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>How We Compare</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('pricing.compare.title')}</motion.h2>
<motion.div className="comparison-table-wrap" {...fadeUp}>
<table className="comparison-table">
<thead>
<tr>
<th></th>
<th className="comparison-highlight">AImpress</th>
<th>Agency</th>
<th>In-House</th>
<th className="comparison-highlight">{t('pricing.compare.aimpress')}</th>
<th>{t('pricing.compare.agency')}</th>
<th>{t('pricing.compare.inhouse')}</th>
</tr>
</thead>
<tbody>
@ -329,7 +313,7 @@ const PricingPage = () => {
<td className="comparison-metric">{row.metric}</td>
<td className="comparison-highlight">{row.aimpress}</td>
<td>{row.agency}</td>
<td>{row.inhouse}</td>
<td>{'inhouse' in row ? row.inhouse : ''}</td>
</tr>
))}
</tbody>
@ -341,7 +325,7 @@ const PricingPage = () => {
{/* FAQ */}
<section className="pricing-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Frequently Asked Questions</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('pricing.faq.title')}</motion.h2>
<div className="faq-list">
{faq.map((item, i) => (
<motion.div
@ -377,9 +361,9 @@ const PricingPage = () => {
{/* Quote Form CTA */}
<section className="pricing-section pricing-cta-section" id="get-quote">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Get Your Quote Today</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('pricing.cta.title')}</motion.h2>
<motion.p className="pricing-quote-subtitle" {...fadeUp} transition={{ duration: 0.5, delay: 0.1 }}>
Tell us about your project and we'll send you a detailed, fixed-price proposal within 24 hours.
{t('pricing.cta.subtitle')}
</motion.p>
<motion.div className="pricing-quote-form-wrap" {...fadeUp} transition={{ duration: 0.5, delay: 0.15 }}>
<QuoteForm />

View file

@ -4,6 +4,7 @@ import { Helmet } from 'react-helmet-async';
import SEO from '../components/SEO';
import Modal from '../components/Modal';
import ContactForm from '../components/ContactForm';
import { useTranslation } from '../i18n';
import './ServicesPage.css';
const fadeUp = {
@ -13,236 +14,11 @@ const fadeUp = {
transition: { duration: 0.5 },
};
const services = [
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
),
title: 'AI Chatbots & Virtual Assistants',
price: '£3,000 £10,000',
purpose: 'Deploy intelligent chatbots that handle customer support, qualify leads, and book appointments 24/7 — no human required.',
popular: true,
includes: [
'Custom chatbot design & personality',
'Integration with your website, WhatsApp, or Messenger',
'Knowledge base training on your business data',
'Lead qualification & CRM handoff',
'Multi-language support',
'Analytics dashboard & conversation insights',
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
</svg>
),
title: 'Custom Website Development',
price: '£2,500 £15,000',
purpose: 'High-performance, conversion-optimised websites built with modern tech — fast, SEO-ready, and fully yours.',
popular: true,
includes: [
'Custom design & responsive development',
'React / Next.js or WordPress build',
'SEO optimisation & Core Web Vitals',
'CMS integration for easy content updates',
'Contact forms, analytics & tracking setup',
'Hosting setup on your own infrastructure',
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
),
title: 'Workflow Automation Implementation',
price: '£3,500 £12,000',
purpose: 'Automate repetitive business processes end-to-end so your team focuses on high-value work.',
includes: [
'Process discovery & mapping',
'Custom workflow design & build (n8n / Make.com)',
'Multi-step logic with conditional branching',
'Error handling & retry mechanisms',
'Testing, deployment & documentation',
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
<polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>
</svg>
),
title: 'System Integration & Synchronisation',
price: '£2,500 £10,000+',
purpose: 'Connect your tools into a single source of truth — CRM, accounting, e-commerce, comms, and more.',
includes: [
'API integration between platforms',
'Bi-directional data sync',
'Data mapping & transformation',
'Webhook & event-driven triggers',
'Monitoring & alerting setup',
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
),
title: 'CRM Workflow Optimisation',
price: '£3,000 £6,500',
purpose: 'Streamline your sales pipeline, automate follow-ups, and ensure no lead falls through the cracks.',
includes: [
'CRM audit & pipeline restructure',
'Automated lead scoring & routing',
'Email sequence automation',
'Task & reminder workflows',
'Reporting dashboard setup',
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
),
title: 'Marketing Automation Setup',
price: '£3,500 £8,000',
purpose: 'Put your campaigns, lead nurturing, and ads on autopilot with targeted, data-driven automation.',
includes: [
'Email drip campaign setup',
'Lead capture & form automation',
'Audience segmentation logic',
'Social media scheduling integration',
'Analytics & conversion tracking',
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</svg>
),
title: 'AI Integration & Enhancement',
price: '£4,000 £12,000',
purpose: 'Add AI capabilities to your existing workflows — chatbots, content generation, document processing, and more.',
includes: [
'AI model selection & integration (OpenAI, Claude, etc.)',
'Custom chatbot / virtual assistant build',
'Document & data extraction with AI',
'AI-powered content generation pipelines',
'Prompt engineering & fine-tuning',
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
<line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
</svg>
),
title: 'Infrastructure Setup & Configuration',
price: '£1,500 £4,000',
purpose: 'Set up your automation infrastructure on servers you own — secure, scalable, and fully yours.',
includes: [
'Server provisioning (VPS / cloud)',
'n8n / automation platform deployment',
'SSL, firewall & security hardening',
'Backup & recovery configuration',
'Monitoring & uptime alerting',
],
},
];
const assurancePack = [
'Dedicated project manager',
'Weekly progress reports',
'Full technical documentation',
'End-user training session',
'Admin training session',
'30-day post-launch support',
'Bug fixes within SLA',
'Knowledge base & runbooks',
'Handover & transition plan',
];
const metrics = [
{ value: '3050%', label: 'Reduction in manual work' },
{ value: '28 wks', label: 'Average delivery time' },
{ value: '24/7', label: 'Automated uptime' },
{ value: '£0', label: 'Vendor lock-in fees' },
];
type GoalKey = 'automate' | 'leads' | 'presence' | 'connect' | 'ai';
type BudgetKey = 'under5k' | '5k-10k' | '10k+';
const goalOptions: { key: GoalKey; label: string }[] = [
{ key: 'automate', label: 'Automate workflows' },
{ key: 'leads', label: 'Get more leads' },
{ key: 'presence', label: 'Build online presence' },
{ key: 'connect', label: 'Connect my tools' },
{ key: 'ai', label: 'Add AI capabilities' },
];
const budgetOptions: { key: BudgetKey; label: string }[] = [
{ key: 'under5k', label: 'Under £5K' },
{ key: '5k-10k', label: '£5K £10K' },
{ key: '10k+', label: '£10K+' },
];
const goalToServices: Record<GoalKey, string[]> = {
automate: ['Workflow Automation Implementation', 'System Integration & Synchronisation'],
leads: ['AI Chatbots & Virtual Assistants', 'CRM Workflow Optimisation', 'Marketing Automation Setup'],
presence: ['Custom Website Development'],
connect: ['System Integration & Synchronisation', 'Infrastructure Setup & Configuration'],
ai: ['AI Integration & Enhancement', 'AI Chatbots & Virtual Assistants'],
};
const budgetFilter: Record<BudgetKey, string[]> = {
'under5k': [
'Infrastructure Setup & Configuration', 'CRM Workflow Optimisation',
'Custom Website Development', 'System Integration & Synchronisation',
'AI Chatbots & Virtual Assistants',
],
'5k-10k': [
'Infrastructure Setup & Configuration', 'CRM Workflow Optimisation',
'Custom Website Development', 'System Integration & Synchronisation',
'AI Chatbots & Virtual Assistants', 'Workflow Automation Implementation',
'Marketing Automation Setup', 'AI Integration & Enhancement',
],
'10k+': services.map(s => s.title),
};
const bundles = [
{
name: 'Starter',
tagline: 'Launch & Engage',
services: ['Custom Website Development', 'AI Chatbots & Virtual Assistants'],
price: 'from £5,500',
},
{
name: 'Growth',
tagline: 'Scale & Convert',
services: ['CRM Workflow Optimisation', 'Marketing Automation Setup', 'System Integration & Synchronisation'],
price: 'from £9,000',
popular: true,
},
{
name: 'Full Stack',
tagline: 'Transform Everything',
services: services.map(s => s.title),
price: 'Custom pricing',
},
];
const ServicesPage = () => {
const { t } = useTranslation();
useEffect(() => { window.scrollTo(0, 0); }, []);
const [isModalOpen, setIsModalOpen] = useState(false);
const [expandedCard, setExpandedCard] = useState<number | null>(null);
@ -251,6 +27,232 @@ const ServicesPage = () => {
const [selectedBudget, setSelectedBudget] = useState<BudgetKey | null>(null);
const serviceCardsRef = useRef<HTMLDivElement>(null);
const services = [
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
),
title: t('services.s1.title'),
price: t('services.s1.price'),
purpose: t('services.s1.purpose'),
popular: true,
includes: [
t('services.s1.f1'),
t('services.s1.f2'),
t('services.s1.f3'),
t('services.s1.f4'),
t('services.s1.f5'),
t('services.s1.f6'),
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
</svg>
),
title: t('services.s2.title'),
price: t('services.s2.price'),
purpose: t('services.s2.purpose'),
popular: true,
includes: [
t('services.s2.f1'),
t('services.s2.f2'),
t('services.s2.f3'),
t('services.s2.f4'),
t('services.s2.f5'),
t('services.s2.f6'),
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
),
title: t('services.s3.title'),
price: t('services.s3.price'),
purpose: t('services.s3.purpose'),
includes: [
t('services.s3.f1'),
t('services.s3.f2'),
t('services.s3.f3'),
t('services.s3.f4'),
t('services.s3.f5'),
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
<polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>
</svg>
),
title: t('services.s4.title'),
price: t('services.s4.price'),
purpose: t('services.s4.purpose'),
includes: [
t('services.s4.f1'),
t('services.s4.f2'),
t('services.s4.f3'),
t('services.s4.f4'),
t('services.s4.f5'),
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
),
title: t('services.s5.title'),
price: t('services.s5.price'),
purpose: t('services.s5.purpose'),
includes: [
t('services.s5.f1'),
t('services.s5.f2'),
t('services.s5.f3'),
t('services.s5.f4'),
t('services.s5.f5'),
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
),
title: t('services.s6.title'),
price: t('services.s6.price'),
purpose: t('services.s6.purpose'),
includes: [
t('services.s6.f1'),
t('services.s6.f2'),
t('services.s6.f3'),
t('services.s6.f4'),
t('services.s6.f5'),
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</svg>
),
title: t('services.s7.title'),
price: t('services.s7.price'),
purpose: t('services.s7.purpose'),
includes: [
t('services.s7.f1'),
t('services.s7.f2'),
t('services.s7.f3'),
t('services.s7.f4'),
t('services.s7.f5'),
],
},
{
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
<line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
</svg>
),
title: t('services.s8.title'),
price: t('services.s8.price'),
purpose: t('services.s8.purpose'),
includes: [
t('services.s8.f1'),
t('services.s8.f2'),
t('services.s8.f3'),
t('services.s8.f4'),
t('services.s8.f5'),
],
},
];
const assurancePack = [
t('services.assurance.i1'),
t('services.assurance.i2'),
t('services.assurance.i3'),
t('services.assurance.i4'),
t('services.assurance.i5'),
t('services.assurance.i6'),
t('services.assurance.i7'),
t('services.assurance.i8'),
t('services.assurance.i9'),
];
const metrics = [
{ value: t('services.metrics.v1'), label: t('services.metrics.l1') },
{ value: t('services.metrics.v2'), label: t('services.metrics.l2') },
{ value: t('services.metrics.v3'), label: t('services.metrics.l3') },
{ value: t('services.metrics.v4'), label: t('services.metrics.l4') },
];
const goalOptions: { key: GoalKey; label: string }[] = [
{ key: 'automate', label: t('services.selector.goal1') },
{ key: 'leads', label: t('services.selector.goal2') },
{ key: 'presence', label: t('services.selector.goal3') },
{ key: 'connect', label: t('services.selector.goal4') },
{ key: 'ai', label: t('services.selector.goal5') },
];
const budgetOptions: { key: BudgetKey; label: string }[] = [
{ key: 'under5k', label: t('services.selector.budget1') },
{ key: '5k-10k', label: t('services.selector.budget2') },
{ key: '10k+', label: t('services.selector.budget3') },
];
const goalToServices: Record<GoalKey, string[]> = {
automate: [t('services.s3.title'), t('services.s4.title')],
leads: [t('services.s1.title'), t('services.s5.title'), t('services.s6.title')],
presence: [t('services.s2.title')],
connect: [t('services.s4.title'), t('services.s8.title')],
ai: [t('services.s7.title'), t('services.s1.title')],
};
const budgetFilter: Record<BudgetKey, string[]> = {
'under5k': [
t('services.s8.title'), t('services.s5.title'),
t('services.s2.title'), t('services.s4.title'),
t('services.s1.title'),
],
'5k-10k': [
t('services.s8.title'), t('services.s5.title'),
t('services.s2.title'), t('services.s4.title'),
t('services.s1.title'), t('services.s3.title'),
t('services.s6.title'), t('services.s7.title'),
],
'10k+': services.map(s => s.title),
};
const bundles = [
{
name: t('services.bundle1.name'),
tagline: t('services.bundle1.tagline'),
services: [t('services.s2.title'), t('services.s1.title')],
price: t('services.bundle1.price'),
},
{
name: t('services.bundle2.name'),
tagline: t('services.bundle2.tagline'),
services: [t('services.s5.title'), t('services.s6.title'), t('services.s4.title')],
price: t('services.bundle2.price'),
popular: true,
},
{
name: t('services.bundle3.name'),
tagline: t('services.bundle3.tagline'),
services: services.map(s => s.title),
price: t('services.bundle3.price'),
},
];
const recommendedServices = selectedGoal && selectedBudget
? goalToServices[selectedGoal].filter(s => budgetFilter[selectedBudget].includes(s))
: [];
@ -258,8 +260,8 @@ const ServicesPage = () => {
return (
<div className="services-page">
<SEO
title="AI & Automation Services for UK Businesses | AImpress"
description="Workflow automation, system integration, CRM optimisation, marketing automation, AI integration, and infrastructure setup. Fixed-price projects from £1,500."
title={t('seo.services.title')}
description={t('seo.services.description')}
url="https://ai-impress.com/services"
/>
<Helmet>
@ -292,9 +294,9 @@ const ServicesPage = () => {
{/* Hero */}
<section className="services-hero">
<div className="container">
<motion.h1 {...fadeUp}>Our Services</motion.h1>
<motion.h1 {...fadeUp}>{t('services.hero.title')}</motion.h1>
<motion.p className="services-hero-sub" {...fadeUp} transition={{ duration: 0.5, delay: 0.1 }}>
End-to-end automation consulting from discovery to deployment and beyond.
{t('services.hero.subtitle')}
</motion.p>
</div>
</section>
@ -315,7 +317,7 @@ const ServicesPage = () => {
transition={{ duration: 0.5, delay: i * 0.08 }}
onClick={() => setExpandedCard(isExpanded ? null : i)}
>
{'popular' in s && s.popular && <span className="service-popular-badge">Popular</span>}
{'popular' in s && s.popular && <span className="service-popular-badge">{t('services.popular')}</span>}
<div className="service-card-header">
<div className="service-card-icon">{s.icon}</div>
<div className="service-card-price">{s.price}</div>
@ -335,7 +337,7 @@ const ServicesPage = () => {
</motion.ul>
)}
<span className="service-card-toggle">
{isExpanded ? 'Show less' : 'What\'s included'}
{isExpanded ? t('services.showLess') : t('services.whatsIncluded')}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ transform: isExpanded ? 'rotate(180deg)' : 'rotate(0)' }}>
<polyline points="6 9 12 15 18 9"/>
</svg>
@ -352,8 +354,8 @@ const ServicesPage = () => {
<div className="container">
<motion.div className="assurance-pack" {...fadeUp}>
<div className="assurance-header">
<h2>AImpress Assurance Pack</h2>
<span className="assurance-badge">Included free in every project (value £1,500)</span>
<h2>{t('services.assurance.title')}</h2>
<span className="assurance-badge">{t('services.assurance.subtitle')}</span>
</div>
<div className="assurance-grid">
{assurancePack.map((item, i) => (
@ -379,7 +381,7 @@ const ServicesPage = () => {
{/* Results in Numbers */}
<section className="services-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Results in Numbers</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('services.metrics.title')}</motion.h2>
<div className="metrics-grid">
{metrics.map((m, i) => (
<motion.div
@ -401,10 +403,10 @@ const ServicesPage = () => {
{/* Which Service Do You Need? */}
<section className="services-section selector-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Which Service Do You Need?</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('services.selector.title')}</motion.h2>
<div className="selector-steps">
{['Your Goal', 'Budget', 'Results'].map((label, i) => (
{[t('services.selector.step1'), t('services.selector.step2'), t('services.selector.step3')].map((label, i) => (
<div key={label} className={`selector-step-dot ${selectorStep >= i ? 'selector-step-dot--active' : ''}`}>
<span className="selector-step-num">{i + 1}</span>
<span className="selector-step-label">{label}</span>
@ -415,7 +417,7 @@ const ServicesPage = () => {
<AnimatePresence mode="wait">
{selectorStep === 0 && (
<motion.div key="goal" className="selector-panel" initial={{ opacity: 0, x: 40 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -40 }} transition={{ duration: 0.3 }}>
<p className="selector-question">What's your primary goal?</p>
<p className="selector-question">{t('services.selector.goalQ')}</p>
<div className="selector-options">
{goalOptions.map((opt) => (
<motion.button
@ -434,7 +436,7 @@ const ServicesPage = () => {
{selectorStep === 1 && (
<motion.div key="budget" className="selector-panel" initial={{ opacity: 0, x: 40 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -40 }} transition={{ duration: 0.3 }}>
<p className="selector-question">What's your budget range?</p>
<p className="selector-question">{t('services.selector.budgetQ')}</p>
<div className="selector-options">
{budgetOptions.map((opt) => (
<motion.button
@ -453,7 +455,7 @@ const ServicesPage = () => {
{selectorStep === 2 && (
<motion.div key="results" className="selector-panel" initial={{ opacity: 0, x: 40 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -40 }} transition={{ duration: 0.3 }}>
<p className="selector-question">We recommend these services:</p>
<p className="selector-question">{t('services.selector.resultsHeading')}</p>
<div className="selector-results">
{recommendedServices.length > 0 ? recommendedServices.map((sTitle) => {
const svc = services.find(s => s.title === sTitle);
@ -468,7 +470,7 @@ const ServicesPage = () => {
</div>
);
}) : (
<p className="selector-no-results">No exact match but we can help! Contact us for a custom solution.</p>
<p className="selector-no-results">{t('services.selector.noMatch')}</p>
)}
</div>
<div className="selector-actions">
@ -478,10 +480,10 @@ const ServicesPage = () => {
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
View Services
{t('services.selector.viewButton')}
</motion.button>
<button className="selector-reset" onClick={() => { setSelectorStep(0); setSelectedGoal(null); setSelectedBudget(null); }}>
Start Over
{t('services.selector.resetButton')}
</button>
</div>
</motion.div>
@ -490,7 +492,7 @@ const ServicesPage = () => {
{selectorStep > 0 && selectorStep < 2 && (
<button className="selector-back" onClick={() => setSelectorStep(s => s - 1)}>
Back
{t('services.selector.backButton')}
</button>
)}
</div>
@ -499,7 +501,7 @@ const ServicesPage = () => {
{/* Popular Bundles */}
<section className="services-section">
<div className="container">
<motion.h2 className="section-title" {...fadeUp}>Popular Bundles</motion.h2>
<motion.h2 className="section-title" {...fadeUp}>{t('services.bundles.title')}</motion.h2>
<div className="bundles-grid">
{bundles.map((b, i) => (
<motion.div
@ -510,7 +512,7 @@ const ServicesPage = () => {
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.5, delay: i * 0.1 }}
>
{'popular' in b && b.popular && <span className="bundle-popular-badge">Most Popular</span>}
{'popular' in b && b.popular && <span className="bundle-popular-badge">{t('services.bundle2.badge')}</span>}
<h3 className="bundle-name">{b.name}</h3>
<p className="bundle-tagline">{b.tagline}</p>
<ul className="bundle-services">
@ -525,7 +527,7 @@ const ServicesPage = () => {
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
Get Started
{t('services.bundle.cta')}
</motion.button>
</motion.div>
))}
@ -537,15 +539,15 @@ const ServicesPage = () => {
<section className="services-section services-cta-section">
<div className="container">
<motion.div className="services-cta" {...fadeUp}>
<h2>Not Sure Where to Start?</h2>
<p>Book a free consultation and we'll map out the best automation strategy for your business.</p>
<h2>{t('services.cta.title')}</h2>
<p>{t('services.cta.subtitle')}</p>
<motion.button
className="services-cta-btn"
onClick={() => setIsModalOpen(true)}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
Get Your Free Consultation
{t('services.cta.button')}
</motion.button>
</motion.div>
</div>