chore: add remaining db migration and translation scripts

This commit is contained in:
MichalC
2026-09-18 21:52:43 +02:00
parent ab8a148823
commit c8193cf166
21 changed files with 4099 additions and 0 deletions
+2367
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', locale: 'pl', limit: 100 })
for (const p of pages.docs) {
if (!p.layout) continue;
const blocks = p.layout.map(b => b.blockType).join(', ')
console.log(`Page: ${p.slug} | Blocks: ${blocks}`)
}
process.exit(0)
}
run().catch(console.error)
+13
View File
@@ -0,0 +1,13 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const homePl = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
const homeEn = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'en' })
console.log("PL Layout length:", homePl.docs[0]?.layout?.length)
console.log("EN Layout length:", homeEn.docs[0]?.layout?.length)
process.exit(0)
}
run().catch(console.error)
+15
View File
@@ -0,0 +1,15 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const home = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
const blocks = home.docs[0].layout?.map(b => b.blockType)
console.log("HOME PL BLOCKS:", blocks)
const homeEn = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'en' })
console.log("HOME EN BLOCKS:", homeEn.docs[0].layout?.map(b => b.blockType))
process.exit(0)
}
run().catch(console.error)
+10
View File
@@ -0,0 +1,10 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const enPage = await payload.find({ collection: 'pages', locale: 'en', where: { slug: { equals: 'kontakt' } } })
console.log("EN KONTAKT BLOCKS:", enPage.docs[0]?.layout?.map(b => b.blockType))
process.exit(0)
}
run().catch(console.error)
+16
View File
@@ -0,0 +1,16 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
async function run() {
const payload = await getPayload({ config: configPromise })
const header = await payload.findGlobal({ slug: 'header', locale: 'pl' })
const footer = await payload.findGlobal({ slug: 'footer', locale: 'pl' })
const pages = await payload.find({ collection: 'pages', locale: 'pl' })
fs.writeFileSync('content_dump.json', JSON.stringify({ header, footer, pages }, null, 2))
console.log("Dumped to content_dump.json")
process.exit(0)
}
run().catch(console.error)
+13
View File
@@ -0,0 +1,13 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
async function run() {
const payload = await getPayload({ config: configPromise })
const home = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
if (home.docs[0]) {
fs.writeFileSync('scripts/home_dump.json', JSON.stringify(home.docs[0].layout, null, 2))
}
process.exit(0)
}
run().catch(console.error)
+15
View File
@@ -0,0 +1,15 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
const dump = pages.docs.filter(p => !['home'].includes(p.slug)).map(p => ({
slug: p.slug,
layout: p.layout
}))
fs.writeFileSync('scripts/remaining_dump.json', JSON.stringify(dump, null, 2))
process.exit(0)
}
run().catch(console.error)
+18
View File
@@ -0,0 +1,18 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', locale: 'all', limit: 100 })
for (const p of pages.docs) {
if (!p.layout) continue;
for (const b of p.layout) {
if (b.blockType === 'contactSection') {
console.log(`Page ${p.slug} has contactSection in some locale.`)
break;
}
}
}
process.exit(0)
}
run().catch(console.error)
+124
View File
@@ -0,0 +1,124 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
import path from 'path'
// Helper to remove ids deeply from layout blocks so Payload recreates them
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 })
// 1. Ensure we have an uploaded image to use
const mediaDir = path.join(process.cwd(), 'media')
const files = fs.readdirSync(mediaDir).filter(f => f.endsWith('.webp'))
let heroImageId: number | null = null
let aboutImageId: number | null = null
if (files.length > 0) {
try {
const created1 = await payload.create({
collection: 'media',
data: { alt: 'Hero background' },
file: {
data: fs.readFileSync(path.join(mediaDir, files[0])),
mimetype: 'image/webp',
name: 'hero-bg.webp',
size: fs.statSync(path.join(mediaDir, files[0])).size
}
})
heroImageId = created1.id as number
console.log(`Uploaded hero image ID: ${heroImageId}`)
if (files.length > 1) {
const created2 = await payload.create({
collection: 'media',
data: { alt: 'About section image' },
file: {
data: fs.readFileSync(path.join(mediaDir, files[1])),
mimetype: 'image/webp',
name: 'about-img.webp',
size: fs.statSync(path.join(mediaDir, files[1])).size
}
})
aboutImageId = created2.id as number
console.log(`Uploaded about image ID: ${aboutImageId}`)
}
} catch(e) {
console.error("Media upload error", e)
}
}
// 2. Load original layouts from snapshot
const snapshotPath = path.join(process.cwd(), 'scripts', 'content-snapshot.json')
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'))
const homeSnapshot = snapshot.pages.find((p: any) => p.pl.slug === 'home')
if (!homeSnapshot) {
console.error("Home snapshot not found")
process.exit(1)
}
const homeDoc = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
if (!homeDoc.docs[0]) {
console.error("Home page not found in DB")
process.exit(1)
}
const homeId = homeDoc.docs[0].id
const updateLayout = async (loc: 'pl' | 'de' | 'en', baseLoc: 'pl' | 'de') => {
let layout = removeIds(homeSnapshot[baseLoc].layout)
// Inject images and EN translations
layout = layout.map((block: any) => {
if (block.blockType === 'hero') {
block.image = heroImageId
if (loc === 'en') {
block.title = 'Your Trusted Accounting Office in Germany'
block.titleAccent = 'We speak English and Polish'
block.subtitle = 'Professional bookkeeping, tax settlements, and business consulting for companies in Germany.'
block.primaryCta = 'Contact Us'
block.secondaryCta = { text: 'Our Services', url: '/uslugi' }
}
} else if (block.blockType === 'about') {
block.image = aboutImageId || heroImageId
}
return block
})
try {
await payload.update({
collection: 'pages',
id: homeId,
locale: loc,
data: {
title: loc === 'en' ? 'Accounting Services in Germany' : homeSnapshot[baseLoc].title,
layout
}
})
console.log(`Successfully fixed home page layout for ${loc}`)
} catch(e) {
console.error(`Failed to fix ${loc} home page`, e)
}
}
await updateLayout('pl', 'pl')
await updateLayout('de', 'de')
await updateLayout('en', 'pl') // Clone EN from PL base
process.exit(0)
}
run().catch(console.error)
+43
View File
@@ -0,0 +1,43 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const home = await payload.find({ collection: 'pages', where: { slug: { equals: 'home' } }, locale: 'pl' })
if (home.docs[0] && home.docs[0].layout) {
const enLayout = home.docs[0].layout.map(block => {
// Remove id so Payload generates a new one
const { id, blockName, ...rest } = block as any
if (rest.blockType === 'hero') {
return {
...rest,
title: 'Your Trusted Accounting Office in Germany',
titleAccent: 'We speak English and Polish',
subtitle: 'Professional bookkeeping, tax settlements, and business consulting for companies in Germany.',
primaryCta: 'Contact Us',
secondaryCta: { text: 'Our Services', url: '/uslugi' }
}
}
return rest
})
try {
await payload.update({
collection: 'pages',
id: home.docs[0].id,
locale: 'en',
data: {
title: 'Accounting Services in Germany',
layout: enLayout
}
})
console.log("Successfully fixed EN home page")
} catch (e) {
console.error("Error fixing EN home page", e)
}
}
process.exit(0)
}
run().catch(console.error)
+69
View File
@@ -0,0 +1,69 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const plPages = await payload.find({ collection: 'pages', locale: 'pl', where: { slug: { equals: 'kontakt' } } })
const plPage = plPages.docs[0]
if (!plPage) return;
const layout = plPage.layout || []
const enLayout = layout.map(block => {
const b = JSON.parse(JSON.stringify(block))
// strip populated
for (const key of Object.keys(b)) {
if (b[key] && typeof b[key] === 'object' && b[key].id !== undefined) {
b[key] = b[key].id
}
}
if (b.blockType === 'pageHeader') {
b.title = "Contact"
b.subtitle = "Do you have questions about tax settlement in Germany? Contact us — we will be happy to help."
}
if (b.blockType === 'contactSection') {
b.address = "TO BE CONFIRMED BY CLIENT"
b.hours = "Mon-Fri: 9:00 - 17:00\nBy appointment"
}
return b
})
await payload.update({
collection: 'pages',
id: plPage.id,
locale: 'en',
data: { layout: enLayout }
})
console.log("Forced EN layout for kontakt")
const deLayout = layout.map(block => {
const b = JSON.parse(JSON.stringify(block))
// strip populated
for (const key of Object.keys(b)) {
if (b[key] && typeof b[key] === 'object' && b[key].id !== undefined) {
b[key] = b[key].id
}
}
if (b.blockType === 'pageHeader') {
b.title = "Kontakt"
b.subtitle = "Haben Sie Fragen zur Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne weiter."
}
if (b.blockType === 'contactSection') {
b.address = "TO BE CONFIRMED BY CLIENT"
b.hours = "Mo-Fr: 9:00 - 17:00\nNach Vereinbarung"
}
return b
})
await payload.update({
collection: 'pages',
id: plPage.id,
locale: 'de',
data: { layout: deLayout }
})
console.log("Forced DE layout for kontakt")
process.exit(0)
}
run().catch(console.error)
+309
View File
@@ -0,0 +1,309 @@
[
{
"id": "6aac6aa8a53b4a5a4f6600ed",
"title": "Odbierz zwrot podatku z Niemiec",
"titleAccent": "Rozliczymy Cię po polsku",
"subtitle": "Ponad 20 lat doświadczenia w rozliczeniach dla Polaków pracujących w Niemczech. Szybko, rzetelnie i w pełni po polsku.",
"primaryCta": "Napisz na WhatsApp",
"image": {
"id": 16,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:53.986Z",
"createdAt": "2026-09-17T22:35:53.986Z",
"url": "/api/media/file/d0c63f69-9a73-48ba-a03b-15dafd55b5cf-3.webp",
"thumbnailURL": null,
"filename": "d0c63f69-9a73-48ba-a03b-15dafd55b5cf-3.webp",
"mimeType": "image/webp",
"filesize": 69160,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"imageSecondary": {
"id": 1,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:31:47.560Z",
"createdAt": "2026-09-17T22:31:47.559Z",
"url": "/api/media/file/38670a24-644b-42db-ac36-fd77f78352ba-2.webp",
"thumbnailURL": null,
"filename": "38670a24-644b-42db-ac36-fd77f78352ba-2.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "hero",
"secondaryCta": {
"text": "Zobacz jak to działa",
"url": "#jak-to-dziala"
}
},
{
"id": "6aac6aa8a53b4a5a4f6600f2",
"blockName": null,
"items": [
{
"id": "6aac6aa8a53b4a5a4f6600ee",
"text": "Działamy w ramach b.b.h.",
"icon": "shield"
},
{
"id": "6aac6aa8a53b4a5a4f6600ef",
"text": "Doradztwo 100% po polsku",
"icon": "message"
},
{
"id": "6aac6aa8a53b4a5a4f6600f0",
"text": "Wsparcie w Urzędzie Skarbowym",
"icon": "check"
},
{
"id": "6aac6aa8a53b4a5a4f6600f1",
"text": "Wieloletnie doświadczenie",
"icon": "star"
}
],
"blockType": "trustBar"
},
{
"id": "6aac6aa8a53b4a5a4f6600f5",
"eyebrow": null,
"title": "Rozliczenia bez bariery językowej",
"description": "Niemieckie prawo podatkowe bywa skomplikowane. Przeprowadzimy Cię przez cały proces w Twoim ojczystym języku, dbając o maksymalny zwrot i zero stresu.",
"image": {
"id": 16,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:53.986Z",
"createdAt": "2026-09-17T22:35:53.986Z",
"url": "/api/media/file/d0c63f69-9a73-48ba-a03b-15dafd55b5cf-3.webp",
"thumbnailURL": null,
"filename": "d0c63f69-9a73-48ba-a03b-15dafd55b5cf-3.webp",
"mimeType": "image/webp",
"filesize": 69160,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"features": [
{
"id": "6aac6aa8a53b4a5a4f6600f3",
"text": "Bez barier językowych"
},
{
"id": "6aac6aa8a53b4a5a4f6600f4",
"text": "Indywidualne podejście"
}
],
"blockType": "uniqueValue"
},
{
"id": "6aac6aa8a53b4a5a4f6600f9",
"title": null,
"subtitle": null,
"blockName": null,
"items": [
{
"id": "6aac6aa8a53b4a5a4f6600f6",
"stat": "20+",
"label": "Lat doświadczenia",
"description": "w prawie podatkowym i rozliczeniach dla Polaków w Niemczech"
},
{
"id": "6aac6aa8a53b4a5a4f6600f7",
"stat": "100%",
"label": "Obsługa po polsku",
"description": "bezpośredni kontakt i wyjaśnienie każdej kwestii podatkowej"
},
{
"id": "6aac6aa8a53b4a5a4f6600f8",
"stat": "§4 Nr. 11",
"label": "Stowarzyszenie b.b.h.",
"description": "oficjalna podstawa prawna doradztwa zgodnie ze StBerG"
}
],
"blockType": "stats"
},
{
"id": "6aac6aa8a53b4a5a4f6600fd",
"eyebrow": "Usługi",
"title": "Czym się zajmujemy",
"blockName": null,
"services": [
{
"id": "6aac6aa8a53b4a5a4f6600fa",
"title": "Deklaracje podatkowe",
"description": "Kompleksowe sporządzenie i złożenie zeznania rocznego (Einkommensteuererklärung).",
"linkText": "Zobacz więcej",
"linkUrl": null
},
{
"id": "6aac6aa8a53b4a5a4f6600fb",
"title": "Kontrola decyzji",
"description": "Szczegółowa weryfikacja decyzji podatkowej (Steuerbescheid) z urzędu skarbowego.",
"linkText": "Zobacz więcej",
"linkUrl": null
},
{
"id": "6aac6aa8a53b4a5a4f6600fc",
"title": "Ulgi i dopłaty",
"description": "Pomoc w uzyskaniu Kindergeld, Riester-Rente oraz odliczaniu kosztów dojazdów i podwójnego gospodarstwa.",
"linkText": "Zobacz więcej",
"linkUrl": null
}
],
"blockType": "servicesList"
},
{
"id": "6aac6aa8a53b4a5a4f660101",
"eyebrow": null,
"title": "Jak to działa",
"subtitle": "Prosty i przejrzysty proces rozliczenia w 3 krokach — bez zbędnych formalności.",
"ctaText": "Zobacz cały proces",
"ctaUrl": "/pl/jak-sie-umowic",
"image": {
"id": 15,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:53.848Z",
"createdAt": "2026-09-17T22:35:53.848Z",
"url": "/api/media/file/ac4e2098-0b88-4f6f-98ed-72461a36784c-3.webp",
"thumbnailURL": null,
"filename": "ac4e2098-0b88-4f6f-98ed-72461a36784c-3.webp",
"mimeType": "image/webp",
"filesize": 55450,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"steps": [
{
"id": "6aac6aa8a53b4a5a4f6600fe",
"title": "Napisz na WhatsApp",
"description": "Wyślij krótką wiadomość lub zadzwoń, by rozpocząć proces."
},
{
"id": "6aac6aa8a53b4a5a4f6600ff",
"title": "Przekaż dokumenty",
"description": "Prześlij nam wymagane dokumenty bezpiecznie online lub dostarcz osobiście."
},
{
"id": "6aac6aa8a53b4a5a4f660100",
"title": "Odbierz zwrot",
"description": "Zajmiemy się resztą i poinformujemy Cię o spodziewanym zwrocie z urzędu skarbowego."
}
],
"blockType": "process"
},
{
"id": "6aac6aa8a53b4a5a4f660102",
"eyebrow": "Zaufanie",
"title": "Zaufanie i bezpieczeństwo",
"description": "Działamy w ramach b.b.h. Lohnsteuerhilfeverein e.V. (Beratungsstelle). Oferujemy profesjonalną pomoc w sprawach podatku dochodowego w ramach uprawnień wynikających z §4 Nr. 11 StBerG.",
"logo": null,
"linkText": "Poznaj stowarzyszenie",
"linkUrl": "/pl/o-nas",
"blockName": null,
"trustPoints": [],
"blockType": "affiliation"
},
{
"id": "6aac6aa8a53b4a5a4f660106",
"title": "Opinie klientów",
"blockName": null,
"testimonials": [
{
"id": "6aac6aa8a53b4a5a4f660103",
"quote": "Pan Janusz pomógł mi odzyskać podatek za 3 ostatnie lata. Wszystko jasno wytłumaczone po polsku.",
"author": "Piotr, Monachium",
"source": null
},
{
"id": "6aac6aa8a53b4a5a4f660104",
"quote": "Szybki kontakt przez WhatsApp i pełne wsparcie przy odwołaniu od decyzji Finanzamt. Polecam!",
"author": "Anna, Berlin",
"source": null
},
{
"id": "6aac6aa8a53b4a5a4f660105",
"quote": "Ogromna wiedza o ulgach dla pracowników budowlanych. Zwrot był znacznie wyższy niż się spodziewałem.",
"author": "Krzysztof, Dortmund",
"source": null
}
],
"blockType": "testimonials"
},
{
"id": "6aac6aa8a53b4a5a4f66010f",
"title": "Częste pytania",
"ctaText": null,
"ctaUrl": null,
"blockName": null,
"questions": [
{
"id": "6aac6aa8a53b4a5a4f660107",
"category": null,
"question": "Czy muszę przyjeżdżać osobiście do biura?",
"answer": "Nie, wszystkie formalności możemy załatwić w 100% zdalnie. Dokumenty przesyłasz online przez bezpieczne kanały, a konsultacje prowadzimy telefonicznie lub przez WhatsApp."
},
{
"id": "6aac6aa8a53b4a5a4f660108",
"category": null,
"question": "Do kiedy muszę złożyć deklarację podatkową w Niemczech?",
"answer": "Standardowy termin to 31 sierpnia kolejnego roku, jednak korzystając z usług doradcy podatkowego, zyskujesz dodatkowy czas – nawet do końca lutego lub maja następnego roku (w zależności od ustaleń na dany rok)."
},
{
"id": "6aac6aa8a53b4a5a4f660109",
"category": null,
"question": "Jak długo czeka się na zwrot podatku z urzędu skarbowego?",
"answer": "Czas oczekiwania na decyzję i przelew zależy od urzędu skarbowego – najczęściej wynosi on od 3 do 8 tygodni po złożeniu kompletnej deklaracji."
},
{
"id": "6aac6aa8a53b4a5a4f66010a",
"category": null,
"question": "Czy załatwiacie również zasiłek Kindergeld?",
"answer": "Tak, pomagamy w formalnościach związanych z pozyskaniem zasiłku rodzinnego Kindergeld dla pracowników zatrudnionych na terenie Niemiec."
},
{
"id": "6aac6aa8a53b4a5a4f66010b",
"category": null,
"question": "Jakie dokumenty są potrzebne do rozliczenia?",
"answer": "Do podstawowego rozliczenia potrzebujesz Lohnsteuerbescheinigung (karty podatkowej od pracodawcy), zaświadczenia o dochodach w Polsce (EU/EWR) oraz dokumentów potwierdzających koszty (np. dojazdy, podwójne gospodarstwo)."
},
{
"id": "6aac6aa8a53b4a5a4f66010c",
"category": null,
"question": "Czy muszę przychodzić osobiście do biura?",
"answer": "Nie, cały proces możemy przeprowadzić w 100% online. Dokumenty możesz przesłać nam bezpiecznie przez WhatsApp, e-mail lub pocztą tradycyjną."
},
{
"id": "6aac6aa8a53b4a5a4f66010d",
"category": null,
"question": "Ile kosztuje rozliczenie podatku z Niemiec?",
"answer": "Koszt rozliczenia ustalamy indywidualnie, bazując na stopniu skomplikowania sprawy i rodzaju deklarowanych dochodów. Zawsze informujemy o cenie przed rozpoczęciem prac."
},
{
"id": "6aac6aa8a53b4a5a4f66010e",
"category": null,
"question": "Czy pomagacie przy wnioskach o Kindergeld?",
"answer": "Tak, pomagamy w formalnościach związanych z pozyskaniem zasiłku rodzinnego Kindergeld dla pracowników zatrudnionych na terenie Niemiec."
}
],
"blockType": "faqTeaser"
},
{
"id": "6aac6aa8a53b4a5a4f660110",
"eyebrow": "Kontakt",
"title": "Gotowy na wyższy zwrot podatku?",
"subtitle": "Napisz do nas już teraz. Odpowiemy najszybciej jak to możliwe.",
"buttonText": "Rozpocznij na WhatsApp",
"blockName": null,
"blockType": "finalCta"
}
]
+10
View File
@@ -0,0 +1,10 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const forms = await payload.find({ collection: 'forms' })
console.log("FORMS:", forms.docs.map(f => ({ id: f.id, title: f.title })))
process.exit(0)
}
run().catch(console.error)
+473
View File
@@ -0,0 +1,473 @@
[
{
"slug": "uslugi",
"layout": [
{
"id": "6aac6b4ada77e55afbdd567a",
"title": "Kompleksowe rozliczenia podatkowe dla Polaków w Niemczech",
"subtitle": "Profesjonalne sporządzanie deklaracji, weryfikacja decyzji podatkowych i bieżące wsparcie w kontaktach z niemieckim urzędem skarbowym.",
"image": {
"id": 10,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:52.987Z",
"createdAt": "2026-09-17T22:35:52.987Z",
"url": "/api/media/file/38670a24-644b-42db-ac36-fd77f78352ba-3.webp",
"thumbnailURL": null,
"filename": "38670a24-644b-42db-ac36-fd77f78352ba-3.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aac6b4ada77e55afbdd5682",
"title": "Czym się zajmujemy",
"subtitle": "Zapewniamy rzetelne i kompleksowe wsparcie na każdym etapie rozliczenia podatku dochodowego w Niemczech.",
"blockName": null,
"services": [
{
"id": "6aac6b4ada77e55afbdd567b",
"title": "Sporządzenie deklaracji podatkowej (Einkommensteuererklärung)",
"anchor": "sporzadzenie-deklaracji-podatkowej-einkommensteuererklarung",
"description": "Przygotowujemy kompletne zeznanie podatkowe, uwzględniając wszystkie przysługujące Ci ulgi, koszty uzyskania przychodu oraz odliczenia. Dbamy o maksymalizację Twojego zwrotu i pełną zgodność z niemieckim prawem.",
"icon": "fileText"
},
{
"id": "6aac6b4ada77e55afbdd567c",
"title": "Wstępne obliczenie przewidywanego zwrotu",
"anchor": "wstepne-obliczenie-przewidywanego-zwrotu",
"description": "Przed wysłaniem dokumentów do urzędu wyliczamy dokładną kwotę spodziewanego zwrotu lub ewentualnej dopłaty. Dzięki temu wiesz dokładnie, czego się spodziewać jeszcze przed wydaniem decyzji przez urzędu skarbowego.",
"icon": "calculator"
},
{
"id": "6aac6b4ada77e55afbdd567d",
"title": "Kontrola decyzji podatkowej (Steuerbescheid)",
"anchor": "kontrola-decyzji-podatkowej-steuerbescheid",
"description": "Szczegółowo weryfikujemy otrzymaną decyzję z urzędu skarbowego pod kątem ewentualnych błędów urzędników. Sprawdzamy, czy urząd uwzględnił wszystkie zgłoszone koszty i ulgi.",
"icon": "shieldCheck"
},
{
"id": "6aac6b4ada77e55afbdd567e",
"title": "Składanie odwołań (Einspruch)",
"anchor": "skladanie-odwolan-einspruch",
"description": "W przypadku niekorzystnej lub błędnej decyzji urzędu sporządzamy oficjalne odwołanie i reprezentujemy Cię w postępowaniu wyjaśniającym, skutecznie dochodząc Twoich praw.",
"icon": "scale"
},
{
"id": "6aac6b4ada77e55afbdd567f",
"title": "Całoroczne doradztwo podatkowe",
"anchor": "caloroczne-doradztwo-podatkowe",
"description": "Pozostajemy do Twojej dyspozycji przez cały rok podatkowy. Odpowiadamy na pytania, pomagamy w bieżącej korespondencji z urzędem i doradzamy przy zmianie sytuacji życiowej.",
"icon": "helpCircle"
},
{
"id": "6aac6b4ada77e55afbdd5680",
"title": "Doradztwo przy wyborze klasy podatkowej (Steuerklasse)",
"anchor": "doradztwo-przy-wyborze-klasy-podatkowej-steuerklasse",
"description": "Doradzamy optymalny dobór klas podatkowych dla małżeństw i osób pracujących w Niemczech. Pomagamy wybrać wariant (np. III/V lub IV/IV), który przyniesie największe korzyści finansowe.",
"icon": "users"
},
{
"id": "6aac6b4ada77e55afbdd5681",
"title": "Ulgi, dopłaty i odliczenia (Kindergeld, dojazdy, podwójne gospodarstwo)",
"anchor": "ulgi-doplaty-i-odliczenia-kindergeld-dojazdy-podwojne-gospodarstwo",
"description": "Pomagamy w uzyskaniu Kindergeld, Riester-Rente oraz prawidłowym odliczeniu kosztów podwójnego gospodarstwa domowego (doppelter Haushalt) i dojazdów do pracy (Pendlerpauschale).",
"icon": "coins"
}
],
"blockType": "servicesDetailed"
},
{
"id": "6aac6b4ada77e55afbdd568c",
"title": "Dla kogo — zakres uprawnień doradczych wg §4 Nr. 11 StBerG",
"subtitle": "Jako punkt doradczy b.b.h. Lohnsteuerhilfeverein e.V. działamy ściśle w ramach ustawowych uprawnień dla stowarzyszeń pomocy podatkowej.",
"eligibleTitle": "Kto może skorzystać z naszej pomocy",
"limitationsTitle": "Dopuszczalne przychody dodatkowe (najem, kapitał, inne)",
"limitationsText": "Osoby uzyskujące dodatkowe przychody (np. z najmu i dzierżawy, kapitałów pieniężnych, innych źródeł) — doradztwo jest możliwe wyłącznie wtedy, gdy łączne przychody z tych źródeł nie przekraczają 18 000 € (dla osób samotnych) lub 36 000 € (dla małżeństw rozliczających się wspólnie) w roku podatkowym.",
"excludedTitle": "Kogo NIE obejmują nasze uprawnienia (granica ustawowa)",
"legalNotice": "Pomoc podatkowa jest świadczona wyłącznie na rzecz członków stowarzyszenia b.b.h. Lohnsteuerhilfeverein e.V. zgodnie z § 4 Nr. 11 Steuerberatungsgesetz (StBerG).",
"blockName": null,
"eligibleItems": [
{
"id": "6aac6b4ada77e55afbdd5683",
"text": "Pracownicy zatrudnieni na umowę o pracę (Arbeitnehmer) oraz pracownicy tymczasowi"
},
{
"id": "6aac6b4ada77e55afbdd5684",
"text": "Urzędnicy państwowi (Beamte)"
},
{
"id": "6aac6b4ada77e55afbdd5685",
"text": "Emeryci i renciści (Rentner und Pensionäre)"
},
{
"id": "6aac6b4ada77e55afbdd5686",
"text": "Osoby bezrobotne oraz pobierające zasiłki socjalne"
},
{
"id": "6aac6b4ada77e55afbdd5687",
"text": "Odbiorcy świadczeń rodzinnych (Kindergeld, Elterngeld) oraz alimentów"
}
],
"excludedItems": [
{
"id": "6aac6b4ada77e55afbdd5688",
"text": "Osoby prowadzące jednoosobową działalność gospodarczą (Gewerbe)"
},
{
"id": "6aac6b4ada77e55afbdd5689",
"text": "Przedsiębiorcy, spółki oraz osoby samozatrudnione (Selbständige)"
},
{
"id": "6aac6b4ada77e55afbdd568a",
"text": "Osoby uzyskujące przychody z działalności rolniczej i leśnej (Land- und Forstwirtschaft)"
},
{
"id": "6aac6b4ada77e55afbdd568b",
"text": "Osoby z przychodami z najmu lub kapitału przekraczającymi limity 18 000 € / 36 000 €"
}
],
"blockType": "eligibility"
},
{
"id": "6aac6b4ada77e55afbdd568d",
"eyebrow": "Kontakt",
"title": "Masz pytania dotyczące Twojego rozliczenia?",
"subtitle": "Skontaktuj się bezpośrednio przez WhatsApp. Przeanalizujemy Twoją sytuację i pomożemy uzyskać należny zwrot.",
"buttonText": "Napisz na WhatsApp",
"image": {
"id": 10,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:35:52.987Z",
"createdAt": "2026-09-17T22:35:52.987Z",
"url": "/api/media/file/38670a24-644b-42db-ac36-fd77f78352ba-3.webp",
"thumbnailURL": null,
"filename": "38670a24-644b-42db-ac36-fd77f78352ba-3.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "finalCta"
}
]
},
{
"slug": "jak-sie-umowic",
"layout": [
{
"id": "6aac6afbef4df35aa15f8d03",
"title": "Jak się umówić",
"subtitle": "Prosty, przejrzysty i w 100% zdalny proces rozliczenia podatkowego. Wszystko załatwisz bez wychodzenia z domu.",
"image": {
"id": 9,
"alt": "About section image",
"updatedAt": "2026-09-17T22:33:12.549Z",
"createdAt": "2026-09-17T22:33:12.549Z",
"url": "/api/media/file/about-img.webp",
"thumbnailURL": null,
"filename": "about-img.webp",
"mimeType": "image/webp",
"filesize": 72208,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aac6afbef4df35aa15f8d07",
"eyebrow": null,
"title": "Rozliczenie w 3 prostych krokach",
"subtitle": "Zapewniamy pełne wsparcie i jasną komunikację na każdym etapie.",
"ctaText": null,
"ctaUrl": null,
"image": {
"id": 8,
"alt": "Hero background",
"updatedAt": "2026-09-17T22:33:12.415Z",
"createdAt": "2026-09-17T22:33:12.414Z",
"url": "/api/media/file/hero-bg.webp",
"thumbnailURL": null,
"filename": "hero-bg.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"steps": [
{
"id": "6aac6afbef4df35aa15f8d04",
"title": "Napisz na WhatsApp",
"description": "Skontaktuj się z nami bezpośrednio przez WhatsApp lub formularz poniżej, aby rozpocząć i ustalić szczegóły Twojej sytuacji podatkowej."
},
{
"id": "6aac6afbef4df35aa15f8d05",
"title": "Przekaż dokumenty",
"description": "Prześlij zaświadczenia o dochodach (Lohnsteuerbescheinigung, EU/EWR), podatkowy numer identyfikacyjny (IdNr) lub PESEL oraz ewentualne potwierdzenia kosztów."
},
{
"id": "6aac6afbef4df35aa15f8d06",
"title": "Odbierz gotowe rozliczenie do podpisu",
"description": "Przygotowujemy kompletną deklarację, wyliczamy przewidywany zwrot z urzędu skarbowego i przesyłamy gotowy dokument do Twojej akceptacji i podpisu."
}
],
"blockType": "process"
},
{
"id": "6aac6afbef4df35aa15f8d08",
"address": "DO POTWIERDZENIA PRZEZ KLIENTA",
"phone": "+49 123 456 789",
"email": "[email protected]",
"hours": "Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu",
"blockName": null,
"blockType": "contactInfo"
},
{
"id": "6aac6afbef4df35aa15f8d09",
"eyebrow": null,
"title": "Chcesz szybko odzyskać podatek?",
"subtitle": "Nie zwlekaj z rozliczeniem. Napisz teraz, a zajmiemy się formalnościami od początku do końca.",
"buttonText": "Napisz na WhatsApp",
"image": {
"id": 9,
"alt": "About section image",
"updatedAt": "2026-09-17T22:33:12.549Z",
"createdAt": "2026-09-17T22:33:12.549Z",
"url": "/api/media/file/about-img.webp",
"thumbnailURL": null,
"filename": "about-img.webp",
"mimeType": "image/webp",
"filesize": 72208,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "finalCta"
}
]
},
{
"slug": "o-nas",
"layout": [
{
"id": "6aac6afbef4df35aa15f8ce2",
"title": "O nas",
"subtitle": "Ponad 20 lat rzetelnego wsparcia podatkowego dla Polaków pracujących w Niemczech.",
"image": {
"id": 7,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:31:48.622Z",
"createdAt": "2026-09-17T22:31:48.622Z",
"url": "/api/media/file/d0c63f69-9a73-48ba-a03b-15dafd55b5cf-2.webp",
"thumbnailURL": null,
"filename": "d0c63f69-9a73-48ba-a03b-15dafd55b5cf-2.webp",
"mimeType": "image/webp",
"filesize": 69160,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aac6afbef4df35aa15f8ce9",
"title": "Janusz Kędzierski",
"image": {
"id": 6,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:31:48.453Z",
"createdAt": "2026-09-17T22:31:48.453Z",
"url": "/api/media/file/ac4e2098-0b88-4f6f-98ed-72461a36784c-2.webp",
"thumbnailURL": null,
"filename": "ac4e2098-0b88-4f6f-98ed-72461a36784c-2.webp",
"mimeType": "image/webp",
"filesize": 55450,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"paragraphs": [
{
"id": "6aac6afbef4df35aa15f8ce3",
"text": "Od ponad dwóch dekad pomagam polskim pracownikom i specjalistom w Niemczech w bezpiecznym, bezstresowym i w pełni legalnym rozliczaniu podatków dochodowych przed urzędem skarbowym."
},
{
"id": "6aac6afbef4df35aa15f8ce4",
"text": "Moim celem jest zdjęcie z Ciebie ciężaru skomplikowanych niemieckich przepisów skarbowych. Cały proces prowadzimy w języku polskim — od wstępnej analizy dokumentów, przez precyzyjne wyliczenia ulg i kosztów uzyskania przychodu, aż po weryfikację końcowej decyzji podatkowej."
}
],
"credentials": [
{
"id": "6aac6afbef4df35aa15f8ce5",
"text": "Ponad 20 lat praktyki w niemieckim prawie podatkowym"
},
{
"id": "6aac6afbef4df35aa15f8ce6",
"text": "Beratungsstellenleiter w b.b.h. Lohnsteuerhilfeverein e.V."
},
{
"id": "6aac6afbef4df35aa15f8ce7",
"text": "Pełna obsługa w języku polskim i niemieckim"
},
{
"id": "6aac6afbef4df35aa15f8ce8",
"text": "Maksymalizacja zwrotu podatku i zero stresu formalnego"
}
],
"blockType": "bio"
},
{
"id": "6aac6afbef4df35aa15f8ceb",
"title": "Nasz zespół",
"subtitle": "Poznaj ekspertów, którzy z zaangażowaniem dbają o Twoje finanse. Każdy z nas wnosi wieloletnie doświadczenie, by zagwarantować Ci spokój i bezpieczeństwo.",
"blockName": null,
"members": [
{
"id": "6aac6afbef4df35aa15f8cea",
"name": "Janusz Kędzierski",
"role": "Właściciel / Beratungsstellenleiter",
"description": "Wieloletni praktyk prawa podatkowego ze specjalizacją w doradztwie dla polskich pracowników w Niemczech.",
"photo": null,
"bookingUrl": "/kontakt"
}
],
"blockType": "team"
},
{
"id": "6aac6afbef4df35aa15f8cec",
"eyebrow": null,
"title": "Gotowy na rozliczenie podatku?",
"subtitle": "Skontaktuj się z nami już dziś. Przeanalizujemy Twoją sytuację i pomożemy uzyskać maksymalny zwrot z niemieckiego urzędu skarbowego.",
"buttonText": "Napisz na WhatsApp",
"image": {
"id": 8,
"alt": "Hero background",
"updatedAt": "2026-09-17T22:33:12.415Z",
"createdAt": "2026-09-17T22:33:12.414Z",
"url": "/api/media/file/hero-bg.webp",
"thumbnailURL": null,
"filename": "hero-bg.webp",
"mimeType": "image/webp",
"filesize": 74328,
"width": 1536,
"height": 1024,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "finalCta"
}
]
},
{
"slug": "faq",
"layout": [
{
"id": "6aac6afbef4df35aa15f8cdf",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
},
{
"slug": "kontakt",
"layout": [
{
"id": "6aac6b4ada77e55afbdd56a0",
"title": "Kontakt",
"subtitle": "Masz pytania dotyczące rozliczenia podatkowego w Niemczech? Skontaktuj się z nami — chętnie pomożemy.",
"image": {
"id": 5,
"alt": "Obraz",
"updatedAt": "2026-09-17T22:31:48.145Z",
"createdAt": "2026-09-17T22:31:48.145Z",
"url": "/api/media/file/9a2983cc-482c-4ff7-9f1d-31679e4dfc56-2.webp",
"thumbnailURL": null,
"filename": "9a2983cc-482c-4ff7-9f1d-31679e4dfc56-2.webp",
"mimeType": "image/webp",
"filesize": 70318,
"width": 1672,
"height": 941,
"focalX": 50,
"focalY": 50
},
"blockName": null,
"blockType": "pageHeader"
},
{
"id": "6aac6b4ada77e55afbdd56a1",
"address": "DO POTWIERDZENIA PRZEZ KLIENTA",
"phone": "+49 123 456 789",
"email": "[email protected]",
"hours": "Pon-Pt: 9:00 - 17:00\nPo wcześniejszym umówieniu",
"blockName": null,
"blockType": "contactInfo"
}
]
},
{
"slug": "stowarzyszenie",
"layout": [
{
"id": "6aac6afbef4df35aa15f8cd6",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
},
{
"slug": "polityka-prywatnosci",
"layout": [
{
"id": "6aac6afbef4df35aa15f8cd3",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
},
{
"slug": "polityka-cookies",
"layout": [
{
"id": "6aac6afbef4df35aa15f8cd0",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
},
{
"slug": "impressum",
"layout": [
{
"id": "6aac6afbef4df35aa15f8ccd",
"text": "Treść w przygotowaniu",
"blockName": null,
"blockType": "TextSection"
}
]
}
]
+77
View File
@@ -0,0 +1,77 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
import fs from 'fs'
import path from 'path'
async function run() {
const payload = await getPayload({ config: configPromise })
const snapshotPath = path.join(process.cwd(), 'scripts', 'content-snapshot.json')
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'))
const kontaktSnap = snapshot.pages.find((p: any) => p.pl.slug === 'kontakt')
if (!kontaktSnap) {
console.error("No kontakt in snapshot")
return
}
// Find actual page ID
const dbPages = await payload.find({ collection: 'pages', where: { slug: { equals: 'kontakt' } } })
const dbPage = dbPages.docs[0]
if (!dbPage) {
console.error("No kontakt in DB")
return
}
for (const loc of ['pl', 'de', 'en']) {
let layout = kontaktSnap[loc]?.layout
if (!layout && loc === 'en') {
// fallback to PL for layout structure
layout = JSON.parse(JSON.stringify(kontaktSnap.pl.layout))
}
if (!layout) continue;
const newLayout = layout.map((block: any) => {
const b = { ...block }
delete b.id // VERY IMPORTANT to avoid SQLite unique constraint errors or table mismatches
// If we find contactInfo, UPGRADE it to contactSection and attach form 1!
if (b.blockType === 'contactInfo' || b.blockType === 'contactSection') {
b.blockType = 'contactSection'
b.form = 1 // We created form 1 earlier
}
// Fix EN translations if this is EN
if (loc === 'en') {
if (b.blockType === 'pageHeader') {
b.title = "Contact"
b.subtitle = "Do you have questions about tax settlement in Germany? Contact us — we will be happy to help."
}
if (b.blockType === 'contactSection') {
b.address = "TO BE CONFIRMED BY CLIENT"
b.hours = "Mon-Fri: 9:00 - 17:00\nBy appointment"
}
}
return b
})
const title = loc === 'en' ? 'Contact' : (loc === 'de' ? 'Kontakt' : 'Kontakt')
await payload.update({
collection: 'pages',
id: dbPage.id,
locale: loc as any,
data: {
title,
layout: newLayout
}
})
console.log(`Restored kontakt in ${loc}`)
}
process.exit(0)
}
run().catch(console.error)
+16
View File
@@ -0,0 +1,16 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const docs = await payload.find({
collection: 'pages',
where: { slug: { equals: 'uslugi' } }, // wait, slug in DE might be different! Let's query by ID 2
locale: 'de'
})
const doc = docs.docs[0]
const cta = doc.layout?.find(b => b.blockType === 'finalCta') as any
console.log("USLUGI DE CTA IMAGE:", typeof cta?.image, cta?.image?.id || cta?.image)
process.exit(0)
}
run().catch(console.error)
+75
View File
@@ -0,0 +1,75 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
// 1. UPDATE GLOBALS
await payload.updateGlobal({
slug: 'footer',
locale: 'en',
data: {
copyright: '© 2024 Accounting Kędzierski. All rights reserved.',
association: {
title: 'Member of b.b.h. Lohnsteuerhilfeverein e.V.'
},
creator: {
label: 'Created by'
}
}
})
console.log("Footer updated")
// 2. UPDATE PAGES (Titles and basic fields)
const translations: Record<string, any> = {
'home': { title: 'Home' },
'uslugi': { title: 'Services' },
'jak-sie-umowic': { title: 'How to make an appointment' },
'o-nas': { title: 'About Us' },
'faq': { title: 'FAQ' },
'kontakt': { title: 'Contact' },
'stowarzyszenie': { title: 'Association' },
'polityka-prywatnosci': { title: 'Privacy Policy' },
'polityka-cookies': { title: 'Cookie Policy' },
'impressum': { title: 'Imprint' }
}
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
for (const page of pages.docs) {
const slug = page.slug || ''
const enData = translations[slug] || { title: page.title }
// For Home, let's keep the hero translations if it's there
if (slug === 'home') {
enData.layout = page.layout?.map(block => {
if (block.blockType === 'hero') {
return {
...block,
title: 'Your Trusted Accounting Office in Germany',
titleAccent: 'We speak English and Polish',
subtitle: 'Professional bookkeeping, tax settlements, and business consulting for companies in Germany.',
primaryCta: 'Contact Us',
secondaryCta: { text: 'Our Services', url: '/uslugi' }
}
}
return block
})
}
try {
await payload.update({
collection: 'pages',
id: page.id,
locale: 'en',
data: enData
})
console.log(`Page updated: ${slug}`)
} catch (e) {
console.error(`Error updating page ${slug}`, e)
}
}
console.log("Done")
process.exit(0)
}
run().catch(console.error)
+271
View File
@@ -0,0 +1,271 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
const translations: Record<string, Record<string, any>> = {
'uslugi': {
en: {
pageHeader: { title: "Comprehensive tax settlements in Germany", subtitle: "Professional tax return preparation, tax assessment verification, and ongoing support." },
servicesDetailed: {
title: "What we do",
subtitle: "We provide reliable and comprehensive support at every stage of income tax settlement in Germany.",
services: [
{ title: "Tax return preparation (Einkommensteuererklärung)", description: "We prepare a complete tax return, taking into account all applicable allowances, deductible expenses, and deductions. We ensure maximum refund and full compliance with German law." },
{ title: "Preliminary calculation of expected refund", description: "Before sending the documents to the tax office, we calculate the exact amount of the expected refund or potential surcharge. You know exactly what to expect." },
{ title: "Tax assessment verification (Steuerbescheid)", description: "We thoroughly verify the received decision from the tax office for any errors by officials. We check if the office has included all reported costs and allowances." },
{ title: "Filing appeals (Einspruch)", description: "In the event of an unfavorable or incorrect decision from the office, we prepare an official appeal and represent you in the clarification proceedings." },
{ title: "Year-round tax consulting", description: "We remain at your disposal throughout the tax year. We answer questions, help in ongoing correspondence with the office and advise when life situations change." },
{ title: "Advice on choosing a tax class (Steuerklasse)", description: "We advise on the optimal selection of tax classes for marriages and people working in Germany. We help to choose the variant (e.g. III/V or IV/IV) that will bring the greatest financial benefits." },
{ title: "Allowances, subsidies and deductions", description: "We help in obtaining Kindergeld, Riester-Rente and correct deduction of the costs of maintaining a double household (doppelter Haushalt) and commuting to work (Pendlerpauschale)." }
]
},
eligibility: {
title: "For whom — scope of advisory powers under §4 No. 11 StBerG",
subtitle: "As an advisory point of b.b.h. Lohnsteuerhilfeverein e.V. we operate strictly within statutory limits for tax assistance associations.",
eligibleTitle: "Who can benefit from our help",
limitationsTitle: "Permissible additional income (rental, capital, other)",
limitationsText: "Persons earning additional income — advice is possible only if the total income from these sources does not exceed €18,000 (singles) or €36,000 (married couples).",
excludedTitle: "Who is NOT covered by our services (statutory limit)",
legalNotice: "Tax assistance is provided only to members of the b.b.h. Lohnsteuerhilfeverein e.V. according to § 4 No. 11 Steuerberatungsgesetz (StBerG).",
eligibleItems: [
{ text: "Employees (Arbeitnehmer) and temporary workers" },
{ text: "Civil servants (Beamte)" },
{ text: "Pensioners and retirees (Rentner und Pensionäre)" },
{ text: "Unemployed persons and social benefit recipients" },
{ text: "Recipients of family benefits (Kindergeld, Elterngeld) and alimony" }
],
excludedItems: [
{ text: "Sole traders (Gewerbe)" },
{ text: "Entrepreneurs, partnerships and self-employed (Selbständige)" },
{ text: "Persons generating income from agriculture and forestry (Land- und Forstwirtschaft)" },
{ text: "Persons with rental or capital income exceeding limits" }
]
}
},
de: {
pageHeader: { title: "Umfassende Steuererklärungen in Deutschland", subtitle: "Professionelle Erstellung, Prüfung von Steuerbescheiden und laufende Unterstützung." },
servicesDetailed: {
title: "Was wir tun",
subtitle: "Wir bieten zuverlässige und umfassende Unterstützung in jeder Phase der Einkommensteuererklärung in Deutschland.",
services: [
{ title: "Erstellung der Einkommensteuererklärung", description: "Wir erstellen eine vollständige Steuererklärung unter Berücksichtigung aller zustehenden Freibeträge, Werbungskosten und Abzüge. Wir sorgen für maximale Rückerstattung und volle Übereinstimmung mit deutschem Recht." },
{ title: "Vorläufige Berechnung der erwarteten Rückerstattung", description: "Bevor wir die Dokumente an das Finanzamt senden, berechnen wir die genaue Höhe der erwarteten Rückerstattung oder einer möglichen Nachzahlung." },
{ title: "Prüfung des Steuerbescheids", description: "Wir prüfen den erhaltenen Bescheid des Finanzamtes eingehend auf Fehler der Sachbearbeiter. Wir prüfen, ob das Amt alle gemeldeten Kosten und Freibeträge berücksichtigt hat." },
{ title: "Einspruchsverfahren", description: "Im Falle eines unvorteilhaften oder falschen Bescheids des Amtes erstellen wir einen offiziellen Einspruch und vertreten Sie im Klärungsverfahren." },
{ title: "Ganzjährige Steuerberatung", description: "Wir stehen Ihnen während des gesamten Steuerjahres zur Verfügung. Wir beantworten Fragen, helfen bei der laufenden Korrespondenz mit dem Amt und beraten bei Veränderungen der Lebenssituation." },
{ title: "Beratung bei der Wahl der Steuerklasse", description: "Wir beraten bei der optimalen Wahl der Steuerklassen für Verheiratete und Arbeitnehmer in Deutschland. Wir helfen bei der Wahl der Variante (z. B. III/V oder IV/IV)." },
{ title: "Freibeträge, Zuschüsse und Abzüge", description: "Wir helfen bei der Erlangung von Kindergeld, Riester-Rente und dem korrekten Abzug der Kosten für eine doppelte Haushaltsführung und Fahrtkosten zur Arbeit (Pendlerpauschale)." }
]
},
eligibility: {
title: "Für wen — Beratungsumfang nach §4 Nr. 11 StBerG",
subtitle: "Als Beratungsstelle des b.b.h. Lohnsteuerhilfeverein e.V. arbeiten wir streng im Rahmen der gesetzlichen Befugnisse.",
eligibleTitle: "Wer unsere Hilfe in Anspruch nehmen kann",
limitationsTitle: "Zulässige Zusatzeinnahmen (Vermietung, Kapital, Sonstige)",
limitationsText: "Personen mit Zusatzeinkünften — eine Beratung ist nur möglich, wenn die Gesamteinnahmen aus diesen Quellen 18.000 € (Alleinstehende) oder 36.000 € (Verheiratete) nicht überschreiten.",
excludedTitle: "Wer NICHT unter unsere Leistungen fällt",
legalNotice: "Die Hilfeleistung in Steuersachen erfolgt ausschließlich für Mitglieder des b.b.h. Lohnsteuerhilfeverein e.V. gem. § 4 Nr. 11 StBerG.",
eligibleItems: [
{ text: "Arbeitnehmer und Leiharbeiter" },
{ text: "Beamte" },
{ text: "Rentner und Pensionäre" },
{ text: "Arbeitslose und Bezieher von Sozialleistungen" },
{ text: "Bezieher von Familienleistungen (Kindergeld, Elterngeld) und Unterhalt" }
],
excludedItems: [
{ text: "Gewerbetreibende" },
{ text: "Unternehmer, Gesellschaften und Selbständige" },
{ text: "Personen mit Einkünften aus Land- und Forstwirtschaft" },
{ text: "Personen mit Miet- oder Kapitaleinkünften, die die Grenzen überschreiten" }
]
}
}
},
'jak-sie-umowic': {
en: {
pageHeader: { title: "How to make an appointment", subtitle: "Simple, transparent, and 100% remote tax settlement process. You can handle everything without leaving your home." },
process: {
title: "Settlement in 3 simple steps",
subtitle: "We provide full support and clear communication at every stage.",
steps: [
{ title: "Message us on WhatsApp", description: "Contact us directly via WhatsApp or the form below to start and discuss the details of your tax situation." },
{ title: "Hand over documents", description: "Send your income certificates (Lohnsteuerbescheinigung, EU/EWR), tax identification number (IdNr), and potential expense confirmations." },
{ title: "Receive ready settlement for signature", description: "We prepare the complete declaration, calculate the expected refund, and send the ready document for your approval and signature." }
]
},
contactInfo: { hours: "Mon-Fri: 9:00 - 17:00\nBy appointment" }
},
de: {
pageHeader: { title: "Termin vereinbaren", subtitle: "Einfacher, transparenter und 100% digitaler Prozess der Steuererklärung. Alles von zu Hause aus." },
process: {
title: "Steuererklärung in 3 einfachen Schritten",
subtitle: "Wir bieten volle Unterstützung und klare Kommunikation in jeder Phase.",
steps: [
{ title: "Auf WhatsApp schreiben", description: "Kontaktieren Sie uns direkt über WhatsApp oder das Formular unten, um zu beginnen und die Details zu besprechen." },
{ title: "Dokumente übergeben", description: "Senden Sie uns Ihre Lohnsteuerbescheinigungen (ggf. EU/EWR), Steueridentifikationsnummer (IdNr) und mögliche Kostenbelege." },
{ title: "Erklärung zur Unterschrift erhalten", description: "Wir erstellen die komplette Erklärung, berechnen die erwartete Erstattung und senden das fertige Dokument zur Freigabe und Unterschrift." }
]
},
contactInfo: { hours: "Mo-Fr: 9:00 - 17:00\nNach Vereinbarung" }
}
},
'o-nas': {
en: {
pageHeader: { title: "About Us", subtitle: "Over 20 years of reliable tax support for Poles working in Germany." },
bio: {
paragraphs: [
{ text: "For over two decades, I have been helping Polish workers and specialists in Germany to safely, stress-free, and fully legally settle income taxes before the tax office." },
{ text: "My goal is to take the burden of complicated German tax regulations off your shoulders. We conduct the entire process in Polish — from initial document analysis, through precise calculations of allowances, to verification of the final tax decision." }
],
credentials: [
{ text: "Over 20 years of practice in German tax law" },
{ text: "Beratungsstellenleiter at b.b.h. Lohnsteuerhilfeverein e.V." },
{ text: "Full service in Polish and German" },
{ text: "Maximizing tax refunds and zero formal stress" }
]
},
team: {
title: "Our Team",
subtitle: "Meet the experts who are committed to your finances. Each of us brings years of experience to guarantee your peace of mind and safety.",
members: [
{ role: "Owner / Beratungsstellenleiter", description: "Long-time tax law practitioner specializing in consulting for Polish workers in Germany." }
]
}
},
de: {
pageHeader: { title: "Über uns", subtitle: "Über 20 Jahre zuverlässige Steuerunterstützung für Polen, die in Deutschland arbeiten." },
bio: {
paragraphs: [
{ text: "Seit über zwei Jahrzehnten helfe ich polnischen Arbeitnehmern und Fachkräften in Deutschland bei der sicheren, stressfreien und völlig legalen Erledigung der Einkommensteuer gegenüber dem Finanzamt." },
{ text: "Mein Ziel ist es, Ihnen die Last der komplizierten deutschen Steuervorschriften von den Schultern zu nehmen. Wir führen den gesamten Prozess auf Polnisch durch — von der ersten Dokumentenanalyse über präzise Berechnungen von Freibeträgen bis hin zur Überprüfung des finalen Steuerbescheids." }
],
credentials: [
{ text: "Über 20 Jahre Praxis im deutschen Steuerrecht" },
{ text: "Beratungsstellenleiter im b.b.h. Lohnsteuerhilfeverein e.V." },
{ text: "Vollständiger Service in Polnisch und Deutsch" },
{ text: "Maximierung der Steuerrückerstattung und null formaler Stress" }
]
},
team: {
title: "Unser Team",
subtitle: "Lernen Sie die Experten kennen, die sich für Ihre Finanzen einsetzen. Jeder von uns bringt jahrelange Erfahrung mit, um Ihre Ruhe und Sicherheit zu garantieren.",
members: [
{ role: "Inhaber / Beratungsstellenleiter", description: "Langjähriger Steuerrechtspraktiker mit Spezialisierung auf die Beratung polnischer Arbeitnehmer in Deutschland." }
]
}
}
}
}
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
for (const page of pages.docs) {
if (!translations[page.slug || '']) continue;
for (const loc of ['en', 'de'] as const) {
const dbPage = await payload.findByID({ collection: 'pages', id: page.id, locale: loc })
if (!dbPage || !dbPage.layout) continue;
const tPage = translations[page.slug!][loc];
let modified = false;
const layout = dbPage.layout.map((block: any) => {
// preserve IDs
for (const key of Object.keys(block)) {
if (block[key] && typeof block[key] === 'object' && block[key].id !== undefined) {
block[key] = block[key].id
}
}
if (block.blockType === 'pageHeader' && tPage.pageHeader) {
block.title = tPage.pageHeader.title;
block.subtitle = tPage.pageHeader.subtitle;
modified = true;
}
if (block.blockType === 'servicesDetailed' && tPage.servicesDetailed) {
block.title = tPage.servicesDetailed.title;
block.subtitle = tPage.servicesDetailed.subtitle;
block.services = block.services.map((s: any, i: number) => ({
...s,
title: tPage.servicesDetailed.services[i]?.title || s.title,
description: tPage.servicesDetailed.services[i]?.description || s.description
}))
modified = true;
}
if (block.blockType === 'eligibility' && tPage.eligibility) {
block.title = tPage.eligibility.title;
block.subtitle = tPage.eligibility.subtitle;
block.eligibleTitle = tPage.eligibility.eligibleTitle;
block.limitationsTitle = tPage.eligibility.limitationsTitle;
block.limitationsText = tPage.eligibility.limitationsText;
block.excludedTitle = tPage.eligibility.excludedTitle;
block.legalNotice = tPage.eligibility.legalNotice;
block.eligibleItems = block.eligibleItems.map((s: any, i: number) => ({
...s, text: tPage.eligibility.eligibleItems[i]?.text || s.text
}))
block.excludedItems = block.excludedItems.map((s: any, i: number) => ({
...s, text: tPage.eligibility.excludedItems[i]?.text || s.text
}))
modified = true;
}
if (block.blockType === 'process' && tPage.process) {
block.title = tPage.process.title;
block.subtitle = tPage.process.subtitle;
block.steps = block.steps.map((s: any, i: number) => ({
...s,
title: tPage.process.steps[i]?.title || s.title,
description: tPage.process.steps[i]?.description || s.description
}))
modified = true;
}
if (block.blockType === 'bio' && tPage.bio) {
block.paragraphs = block.paragraphs.map((s: any, i: number) => ({
...s, text: tPage.bio.paragraphs[i]?.text || s.text
}))
block.credentials = block.credentials.map((s: any, i: number) => ({
...s, text: tPage.bio.credentials[i]?.text || s.text
}))
modified = true;
}
if (block.blockType === 'team' && tPage.team) {
block.title = tPage.team.title;
block.subtitle = tPage.team.subtitle;
block.members = block.members.map((s: any, i: number) => ({
...s,
role: tPage.team.members[i]?.role || s.role,
description: tPage.team.members[i]?.description || s.description
}))
modified = true;
}
if (block.blockType === 'contactInfo' && tPage.contactInfo) {
block.hours = tPage.contactInfo.hours;
modified = true;
}
return block;
});
if (modified) {
await payload.update({
collection: 'pages',
id: page.id,
locale: loc,
data: { layout }
})
console.log(`Fully translated ${page.slug} to ${loc}`)
}
}
}
process.exit(0)
}
run().catch(console.error)
+97
View File
@@ -0,0 +1,97 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
// Translation mapping for FinalCTA to EN
const enCta = {
title: "Have questions about your tax return?",
subtitle: "Contact us directly via WhatsApp. We will analyze your situation and help you get the refund you deserve.",
buttonText: "Message on WhatsApp",
}
// Translation mapping for FinalCTA to DE
const deCta = {
title: "Haben Sie Fragen zu Ihrer Steuererklärung?",
subtitle: "Kontaktieren Sie uns direkt über WhatsApp. Wir analysieren Ihre Situation und helfen Ihnen, die Ihnen zustehende Rückerstattung zu erhalten.",
buttonText: "Auf WhatsApp schreiben",
}
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
for (const page of pages.docs) {
if (page.slug === 'home') continue; // already mostly translated
// EN Update
const enPage = await payload.findByID({ collection: 'pages', id: page.id, locale: 'en' })
if (enPage && enPage.layout) {
let modified = false
const layout = enPage.layout.map((block: any) => {
// Strip populated ids
for (const key of Object.keys(block)) {
if (block[key] && typeof block[key] === 'object' && block[key].id !== undefined) {
block[key] = block[key].id
}
}
if (block.blockType === 'finalCta') {
block.title = enCta.title
block.subtitle = enCta.subtitle
block.buttonText = enCta.buttonText
modified = true
}
if (block.blockType === 'pageHeader') {
if (page.slug === 'uslugi') {
block.title = "Our Services"
block.subtitle = "Professional tax and accounting support in Germany."
} else if (page.slug === 'kontakt') {
block.title = "Contact"
block.subtitle = "Do you have questions about tax settlement in Germany? Contact us — we will be happy to help."
}
modified = true
}
return block
})
if (modified) {
await payload.update({ collection: 'pages', id: page.id, locale: 'en', data: { layout } })
console.log(`Updated EN translations for ${page.slug}`)
}
}
// DE Update
const dePage = await payload.findByID({ collection: 'pages', id: page.id, locale: 'de' })
if (dePage && dePage.layout) {
let modified = false
const layout = dePage.layout.map((block: any) => {
for (const key of Object.keys(block)) {
if (block[key] && typeof block[key] === 'object' && block[key].id !== undefined) {
block[key] = block[key].id
}
}
if (block.blockType === 'finalCta') {
block.title = deCta.title
block.subtitle = deCta.subtitle
block.buttonText = deCta.buttonText
modified = true
}
if (block.blockType === 'pageHeader') {
if (page.slug === 'uslugi') {
block.title = "Unsere Dienstleistungen"
block.subtitle = "Professionelle steuerliche und buchhalterische Betreuung in Deutschland."
} else if (page.slug === 'kontakt') {
block.title = "Kontakt"
block.subtitle = "Haben Sie Fragen zur Steuererklärung in Deutschland? Kontaktieren Sie uns — wir helfen Ihnen gerne weiter."
}
modified = true
}
return block
})
if (modified) {
await payload.update({ collection: 'pages', id: page.id, locale: 'de', data: { layout } })
console.log(`Updated DE translations for ${page.slug}`)
}
}
}
process.exit(0)
}
run().catch(console.error)
+54
View File
@@ -0,0 +1,54 @@
import { getPayload } from 'payload'
import configPromise from '../src/payload.config'
async function run() {
const payload = await getPayload({ config: configPromise })
const pages = await payload.find({ collection: 'pages', limit: 100, locale: 'pl' })
for (const page of pages.docs) {
if (!['faq', 'kontakt', 'stowarzyszenie', 'polityka-prywatnosci', 'polityka-cookies', 'impressum'].includes(page.slug || '')) continue;
for (const loc of ['en', 'de'] as const) {
const dbPage = await payload.findByID({ collection: 'pages', id: page.id, locale: loc })
if (!dbPage || !dbPage.layout) continue;
let modified = false;
const layout = dbPage.layout.map((block: any) => {
// preserve IDs
for (const key of Object.keys(block)) {
if (block[key] && typeof block[key] === 'object' && block[key].id !== undefined) {
block[key] = block[key].id
}
}
if (block.blockType === 'contactInfo') {
block.hours = loc === 'en' ? "Mon-Fri: 9:00 - 17:00\nBy appointment" : "Mo-Fr: 9:00 - 17:00\nNach Vereinbarung"
block.address = "TO BE CONFIRMED BY CLIENT"
modified = true;
}
if (block.blockType === 'TextSection') {
block.text = loc === 'en' ? "Content in preparation" : "Inhalt in Vorbereitung"
modified = true;
}
return block;
});
if (modified) {
await payload.update({
collection: 'pages',
id: page.id,
locale: loc,
data: { layout }
})
console.log(`Fully translated ${page.slug} to ${loc}`)
}
}
}
process.exit(0)
}
run().catch(console.error)