fix: resolve localization issues and ensure English translations for UI elements
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '../src/payload.config'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// Helper to remove ids
|
||||
function removeIds(obj: any): any {
|
||||
if (Array.isArray(obj)) return obj.map(removeIds)
|
||||
if (obj !== null && typeof obj === 'object') {
|
||||
const newObj: any = {}
|
||||
for (const key of Object.keys(obj)) {
|
||||
if (key !== 'id') {
|
||||
newObj[key] = removeIds(obj[key])
|
||||
}
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
// We already uploaded media 8 and 9 in previous script. Let's find any media.
|
||||
const allMedia = await payload.find({ collection: 'media', limit: 10 })
|
||||
const defaultImageId = allMedia.docs[0]?.id || 8
|
||||
|
||||
// Load snapshot
|
||||
const snapshotPath = path.join(process.cwd(), 'scripts', 'content-snapshot.json')
|
||||
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'))
|
||||
|
||||
const enTitles: Record<string, string> = {
|
||||
'home': 'Home',
|
||||
'uslugi': 'Services',
|
||||
'jak-sie-umowic': 'How to make an appointment',
|
||||
'o-nas': 'About Us',
|
||||
'faq': 'FAQ',
|
||||
'kontakt': 'Contact',
|
||||
'stowarzyszenie': 'Association',
|
||||
'polityka-prywatnosci': 'Privacy Policy',
|
||||
'polityka-cookies': 'Cookie Policy',
|
||||
'impressum': 'Imprint'
|
||||
}
|
||||
|
||||
for (const pageSnapshot of snapshot.pages) {
|
||||
const slug = pageSnapshot.pl.slug
|
||||
if (slug === 'home') continue // Already fixed
|
||||
|
||||
const dbPage = await payload.find({ collection: 'pages', where: { slug: { equals: slug } }, locale: 'pl' })
|
||||
if (!dbPage.docs[0]) continue
|
||||
const pageId = dbPage.docs[0].id
|
||||
|
||||
const updateLayout = async (loc: 'pl' | 'de' | 'en', baseLoc: 'pl' | 'de') => {
|
||||
let layout = removeIds(pageSnapshot[baseLoc]?.layout || pageSnapshot.pl.layout)
|
||||
|
||||
// Inject images
|
||||
layout = layout.map((block: any) => {
|
||||
if (block.blockType === 'hero' || block.blockType === 'pageHeader' || block.blockType === 'about' || block.blockType === 'contactSection') {
|
||||
block.image = defaultImageId
|
||||
}
|
||||
if (block.blockType === 'servicesList') {
|
||||
if (block.items) {
|
||||
block.items = block.items.map((i: any) => ({ ...i, image: defaultImageId }))
|
||||
}
|
||||
}
|
||||
return block
|
||||
})
|
||||
|
||||
try {
|
||||
await payload.update({
|
||||
collection: 'pages',
|
||||
id: pageId,
|
||||
locale: loc,
|
||||
data: {
|
||||
title: loc === 'en' ? (enTitles[slug] || pageSnapshot[baseLoc].title) : (pageSnapshot[baseLoc]?.title || pageSnapshot.pl.title),
|
||||
layout
|
||||
}
|
||||
})
|
||||
console.log(`Successfully fixed ${slug} layout for ${loc}`)
|
||||
} catch(e) {
|
||||
console.error(`Failed to fix ${slug} for ${loc}`)
|
||||
}
|
||||
}
|
||||
|
||||
await updateLayout('pl', 'pl')
|
||||
if (pageSnapshot.de) {
|
||||
await updateLayout('de', 'de')
|
||||
}
|
||||
await updateLayout('en', 'pl') // Clone EN from PL base
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
run().catch(console.error)
|
||||
@@ -15,7 +15,17 @@ export const ContactInfoBlock: React.FC<Props> = ({
|
||||
hours = 'Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu',
|
||||
}) => {
|
||||
const pathname = usePathname()
|
||||
const isDe = pathname?.split('/').filter(Boolean)[0] === 'de'
|
||||
const localeSegment = pathname?.split('/').filter(Boolean)[0]
|
||||
const isDe = localeSegment === 'de'
|
||||
const isEn = localeSegment === 'en'
|
||||
|
||||
const t = {
|
||||
contactInfo: isEn ? 'Contact Information' : (isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'),
|
||||
address: isEn ? 'Address' : (isDe ? 'Adresse' : 'Adres'),
|
||||
phone: isEn ? 'Phone' : (isDe ? 'Telefon' : 'Telefon'),
|
||||
email: isEn ? 'Email' : (isDe ? 'E-Mail' : 'E-mail'),
|
||||
hours: isEn ? 'Working Hours' : (isDe ? 'Öffnungszeiten' : 'Godziny pracy'),
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-16 md:py-24 bg-background">
|
||||
@@ -34,7 +44,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
|
||||
<div className="absolute top-0 right-0 w-12 h-12 bg-background border-l border-b border-border shadow-sm transform translate-x-6 -translate-y-6 rotate-45 pointer-events-none" />
|
||||
|
||||
<h2 className="font-heading text-2xl md:text-3xl font-bold text-foreground mb-10 text-center md:text-left">
|
||||
{isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'}
|
||||
{t.contactInfo}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10 md:gap-12">
|
||||
@@ -43,7 +53,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
|
||||
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||
<MapPin size={24} strokeWidth={2.5} />
|
||||
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||
{isDe ? 'Adresse' : 'Adres'}
|
||||
{t.address}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base max-w-[65ch]">
|
||||
@@ -57,7 +67,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
|
||||
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||
<Phone size={24} strokeWidth={2.5} />
|
||||
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||
{isDe ? 'Telefon' : 'Telefon'}
|
||||
{t.phone}
|
||||
</h3>
|
||||
</div>
|
||||
<a
|
||||
@@ -74,7 +84,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
|
||||
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||
<Mail size={24} strokeWidth={2.5} />
|
||||
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||
{isDe ? 'E-Mail' : 'E-mail'}
|
||||
{t.email}
|
||||
</h3>
|
||||
</div>
|
||||
<a
|
||||
@@ -91,7 +101,7 @@ export const ContactInfoBlock: React.FC<Props> = ({
|
||||
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||
<Clock size={24} strokeWidth={2.5} />
|
||||
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||
{isDe ? 'Öffnungszeiten' : 'Godziny pracy'}
|
||||
{t.hours}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base max-w-[65ch]">
|
||||
|
||||
@@ -12,7 +12,18 @@ export const ContactSectionClient: React.FC<any> = ({
|
||||
formNode,
|
||||
}) => {
|
||||
const pathname = usePathname()
|
||||
const isDe = pathname?.split('/').filter(Boolean)[0] === 'de'
|
||||
const localeSegment = pathname?.split('/').filter(Boolean)[0]
|
||||
const isDe = localeSegment === 'de'
|
||||
const isEn = localeSegment === 'en'
|
||||
|
||||
const t = {
|
||||
contactInfo: isEn ? 'Contact Information' : (isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'),
|
||||
address: isEn ? 'Address' : (isDe ? 'Adresse' : 'Adres'),
|
||||
phone: isEn ? 'Phone' : (isDe ? 'Telefon' : 'Telefon'),
|
||||
email: isEn ? 'Email' : (isDe ? 'E-Mail' : 'E-mail'),
|
||||
hours: isEn ? 'Working Hours' : (isDe ? 'Öffnungszeiten' : 'Godziny pracy'),
|
||||
writeToUs: isEn ? 'Write to us' : (isDe ? 'Schreiben Sie uns' : 'Napisz do nas'),
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-16 md:py-24 bg-background">
|
||||
@@ -28,7 +39,7 @@ export const ContactSectionClient: React.FC<any> = ({
|
||||
className="flex flex-col"
|
||||
>
|
||||
<h2 className="font-heading text-3xl md:text-4xl font-extrabold text-foreground mb-8 text-balance">
|
||||
{isDe ? 'Kontaktinformationen' : 'Dane kontaktowe'}
|
||||
{t.contactInfo}
|
||||
</h2>
|
||||
|
||||
<div className="bg-card border border-border p-8 relative overflow-hidden flex-grow"
|
||||
@@ -42,7 +53,7 @@ export const ContactSectionClient: React.FC<any> = ({
|
||||
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||
<MapPin size={24} strokeWidth={2.5} />
|
||||
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||
{isDe ? 'Adresse' : 'Adres'}
|
||||
{t.address}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base">
|
||||
@@ -56,7 +67,7 @@ export const ContactSectionClient: React.FC<any> = ({
|
||||
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||
<Phone size={24} strokeWidth={2.5} />
|
||||
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||
{isDe ? 'Telefon' : 'Telefon'}
|
||||
{t.phone}
|
||||
</h3>
|
||||
</div>
|
||||
<a
|
||||
@@ -73,7 +84,7 @@ export const ContactSectionClient: React.FC<any> = ({
|
||||
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||
<Mail size={24} strokeWidth={2.5} />
|
||||
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||
{isDe ? 'E-Mail' : 'E-mail'}
|
||||
{t.email}
|
||||
</h3>
|
||||
</div>
|
||||
<a
|
||||
@@ -90,7 +101,7 @@ export const ContactSectionClient: React.FC<any> = ({
|
||||
<div className="flex items-center gap-3 text-accent border-b border-border/50 pb-3">
|
||||
<Clock size={24} strokeWidth={2.5} />
|
||||
<h3 className="font-heading font-bold text-lg text-foreground tracking-wide">
|
||||
{isDe ? 'Öffnungszeiten' : 'Godziny pracy'}
|
||||
{t.hours}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-foreground/80 leading-relaxed font-medium whitespace-pre-line text-base">
|
||||
@@ -111,7 +122,7 @@ export const ContactSectionClient: React.FC<any> = ({
|
||||
className="flex flex-col"
|
||||
>
|
||||
<h2 className="font-heading text-3xl md:text-4xl font-extrabold text-foreground mb-8 text-balance">
|
||||
{isDe ? 'Schreiben Sie uns' : 'Napisz do nas'}
|
||||
{t.writeToUs}
|
||||
</h2>
|
||||
<div className="bg-card border border-border p-8 relative overflow-hidden flex-grow"
|
||||
style={{ boxShadow: '0 10px 40px -10px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.03)' }}
|
||||
|
||||
@@ -2,15 +2,38 @@ import React from 'react'
|
||||
import type { Page } from '../../payload-types'
|
||||
import { MessageCircle } from 'lucide-react'
|
||||
import { MotionStamp } from '../../components/MotionStamp'
|
||||
import Image from 'next/image'
|
||||
import type { Media } from '../../payload-types'
|
||||
|
||||
type BaseProps = Extract<NonNullable<Page['layout']>[0], { blockType: 'finalCta' }>
|
||||
type Props = BaseProps & { whatsappNumber?: string | null }
|
||||
|
||||
export const FinalCTABlock: React.FC<Props> = ({ eyebrow, title, subtitle, buttonText, whatsappNumber }) => {
|
||||
export const FinalCTABlock: React.FC<Props> = ({ eyebrow, title, subtitle, buttonText, image, whatsappNumber }) => {
|
||||
const whatsappLink = whatsappNumber ? `https://wa.me/${whatsappNumber}` : '#'
|
||||
|
||||
const bgMedia = typeof image === 'object' && image !== null ? (image as Media) : null
|
||||
const imageUrl = bgMedia?.url
|
||||
const imageAlt = bgMedia?.alt || 'Tło Call to Action'
|
||||
|
||||
return (
|
||||
<MotionStamp id="final-cta" className="py-20 md:py-32 text-center" style={{ backgroundColor: 'var(--accent-color)', color: 'var(--bg-color)' }}>
|
||||
<div className="container-premium">
|
||||
<section id="final-cta" className="relative py-20 md:py-32 text-center overflow-hidden">
|
||||
{/* Background Image Layer */}
|
||||
<div className="absolute inset-0 z-0 bg-[var(--accent-color)]">
|
||||
{imageUrl && (
|
||||
<>
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={imageAlt}
|
||||
fill
|
||||
className="object-cover object-center opacity-40 mix-blend-multiply"
|
||||
/>
|
||||
{/* Opcjonalny gradient/overlay dla lepszej czytelności */}
|
||||
<div className="absolute inset-0 bg-black/30" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<MotionStamp className="container-premium relative z-10">
|
||||
<div className="max-w-3xl mx-auto flex flex-col items-center">
|
||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold tracking-tight mb-6 text-balance text-[var(--bg-color)]">
|
||||
{title}
|
||||
@@ -36,7 +59,7 @@ export const FinalCTABlock: React.FC<Props> = ({ eyebrow, title, subtitle, butto
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</MotionStamp>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,5 +7,11 @@ export const FinalCTA: Block = {
|
||||
{ name: 'title', type: 'text', required: true, localized: true },
|
||||
{ name: 'subtitle', type: 'text', localized: true },
|
||||
{ name: 'buttonText', type: 'text', required: true, localized: true },
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -35,9 +35,11 @@ export const ServicesDetailedBlock: React.FC<Props> = ({
|
||||
}) => {
|
||||
const serviceList = services || []
|
||||
const pathname = usePathname()
|
||||
const isDe = pathname?.split('/').filter(Boolean)[0] === 'de'
|
||||
const contactHref = isDe ? '/de/kontakt' : '/kontakt'
|
||||
const contactText = isDe ? 'Kontakt aufnehmen' : 'Skontaktuj się'
|
||||
const localeSegment = pathname?.split('/').filter(Boolean)[0]
|
||||
const isDe = localeSegment === 'de'
|
||||
const isEn = localeSegment === 'en'
|
||||
const contactHref = isEn ? '/en/contact' : (isDe ? '/de/kontakt' : '/kontakt')
|
||||
const contactText = isEn ? 'Contact us' : (isDe ? 'Kontakt aufnehmen' : 'Skontaktuj się')
|
||||
|
||||
return (
|
||||
<section className="py-16 md:py-24 bg-background text-foreground">
|
||||
|
||||
@@ -11,7 +11,9 @@ type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'team' }>
|
||||
|
||||
export const TeamBlock: React.FC<Props> = ({ members: originalMembers, title, subtitle }) => {
|
||||
const pathname = usePathname()
|
||||
const isDe = pathname?.split('/').filter(Boolean)[0] === 'de'
|
||||
const localeSegment = pathname?.split('/').filter(Boolean)[0]
|
||||
const isDe = localeSegment === 'de'
|
||||
const isEn = localeSegment === 'en'
|
||||
|
||||
// MOCK_MEMBERS usunięte na rzecz prawdziwych danych z CMS
|
||||
const members = originalMembers
|
||||
@@ -152,7 +154,7 @@ export const TeamBlock: React.FC<Props> = ({ members: originalMembers, title, su
|
||||
</p>
|
||||
|
||||
<div className="mt-auto flex items-center text-xs font-bold text-foreground/40 group-hover:text-accent transition-colors uppercase tracking-wider">
|
||||
<span>{isDe ? 'Mehr lesen' : 'Czytaj więcej'}</span>
|
||||
<span>{isEn ? 'Read more' : (isDe ? 'Mehr lesen' : 'Czytaj więcej')}</span>
|
||||
<svg className="w-4 h-4 ml-1.5 transform group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
||||
</svg>
|
||||
|
||||
@@ -502,6 +502,7 @@ export interface Page {
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
buttonText: string;
|
||||
image?: (number | null) | Media;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'finalCta';
|
||||
@@ -1204,6 +1205,7 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
subtitle?: T;
|
||||
buttonText?: T;
|
||||
image?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user