Implement comprehensive homepage improvements and SEO optimization

Major homepage and SEO enhancements based on optimization document:

**Homepage Content Updates:**
- Updated H1 titles with SEO-optimized text for both RO/EN
- Enhanced hero descriptions with targeted keywords
- Improved feature descriptions for better clarity
- Updated daily verse section with keyword-rich titles
- Added new footer description with SEO focus

**SEO Implementation:**
- Added dynamic metadata generation with locale-specific SEO
- Implemented Open Graph tags for social media sharing
- Added Twitter Card metadata for enhanced sharing
- Integrated Schema.org JSON-LD structured data
- Set up hreflang tags for international SEO
- Added canonical URLs to prevent duplicate content
- Included targeted keywords for both languages

**Technical Improvements:**
- Migrated from Docker to PM2 deployment
- Removed Docker files and updated deployment scripts
- Updated README with PM2 instructions
- Fixed console log cleanup for production
- Added proper favicon with Next.js app directory
- Increased memory limit to 4GB for better performance
- Updated port configuration to 0.0.0.0:3010
- Set Romanian (/ro) as default locale with proper redirects

**Translation Updates:**
- Enhanced Romanian translations with SEO-optimized content
- Updated English translations with matching SEO improvements
- Added new 'seo' namespace for metadata translations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Assistant
2025-09-22 18:40:21 +00:00
parent b24251eb2d
commit d4b0062521
18 changed files with 256 additions and 249 deletions

View File

@@ -1,56 +0,0 @@
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js

View File

@@ -54,12 +54,12 @@ O aplicație web completă pentru studiul Bibliei cu capabilități de chat AI
- **AI**: Azure OpenAI API cu fallback la Ollama - **AI**: Azure OpenAI API cu fallback la Ollama
- **Security**: JWT, bcrypt, rate limiting, input validation - **Security**: JWT, bcrypt, rate limiting, input validation
- **Testing**: Jest, React Testing Library, TypeScript - **Testing**: Jest, React Testing Library, TypeScript
- **DevOps**: Docker, Docker Compose, Nginx, SSL support - **DevOps**: PM2, Nginx, SSL support
- **Performance**: Caching, indexing, optimization scripts - **Performance**: Caching, indexing, optimization scripts
## Instalare Rapidă ## Instalare Rapidă
### Folosind Docker (Recomandat) ### Folosind PM2 (Recomandat)
1. Clonează repository-ul: 1. Clonează repository-ul:
```bash ```bash
@@ -83,18 +83,25 @@ JWT_SECRET=your-secure-jwt-secret
NEXTAUTH_SECRET=your-secure-nextauth-secret NEXTAUTH_SECRET=your-secure-nextauth-secret
``` ```
4. Pornește aplicația: 4. Instalează dependențele și construiește aplicația:
```bash ```bash
docker-compose up -d npm ci
npm run build
``` ```
5. Rulează migrațiile și importă datele biblice: 5. Rulează migrațiile și importă datele biblice:
```bash ```bash
docker-compose exec app npx prisma migrate deploy npx prisma migrate deploy
docker-compose exec app npm run import-bible npx prisma generate
npm run import-bible
``` ```
6. Accesează aplicația la: http://localhost:3000 6. Pornește aplicația cu PM2:
```bash
pm2 start ecosystem.config.js --env production
```
7. Accesează aplicația la: http://localhost:3000
### Instalare Manuală ### Instalare Manuală
@@ -149,7 +156,7 @@ npm run dev
│ └── db.ts # Conexiunea la baza de date │ └── db.ts # Conexiunea la baza de date
├── prisma/ # Schema și migrații Prisma ├── prisma/ # Schema și migrații Prisma
├── scripts/ # Scripturi de utilitate ├── scripts/ # Scripturi de utilitate
└── docker/ # Configurații Docker └── ecosystem.config.js # Configurație PM2
``` ```
## Configurare AI ## Configurare AI
@@ -171,12 +178,20 @@ Pentru rularea locală de modele AI:
## Deployment în Producție ## Deployment în Producție
### Folosind Docker ### Folosind PM2
1. Copiază `.env.example` la `.env.production` și configurează-l 1. Copiază `.env.example` la `.env` și configurează-l pentru producție
2. Construiește și pornește serviciile: 2. Rulează scriptul de deployment:
```bash ```bash
docker-compose -f docker-compose.prod.yml up -d ./deploy.sh
```
Sau manual:
```bash
npm ci
npm run build
pm2 restart ghidul-biblic || pm2 start ecosystem.config.js --env production
pm2 save
``` ```
### Configurare SSL ### Configurare SSL
@@ -194,7 +209,7 @@ sudo certbot --nginx -d yourdomain.com
## Monitorizare ## Monitorizare
- **Health Check**: `/api/health` - **Health Check**: `/api/health`
- **Logs**: `docker-compose logs -f app` - **Logs**: `pm2 logs ghidul-biblic`
- **Metrici**: Implementate prin endpoint-uri dedicate - **Metrici**: Implementate prin endpoint-uri dedicate
## Contribuții ## Contribuții

View File

@@ -1,7 +1,7 @@
import '../globals.css' import '../globals.css'
import type { Metadata } from 'next' import type { Metadata } from 'next'
import { NextIntlClientProvider } from 'next-intl' import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server' import { getMessages, getTranslations } from 'next-intl/server'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { MuiThemeProvider } from '@/components/providers/theme-provider' import { MuiThemeProvider } from '@/components/providers/theme-provider'
import { AuthProvider } from '@/components/auth/auth-provider' import { AuthProvider } from '@/components/auth/auth-provider'
@@ -9,9 +9,66 @@ import { Navigation } from '@/components/layout/navigation'
import FloatingChat from '@/components/chat/floating-chat' import FloatingChat from '@/components/chat/floating-chat'
import { merriweather, lato } from '@/lib/fonts' import { merriweather, lato } from '@/lib/fonts'
export const metadata: Metadata = { export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
title: 'Ghid Biblic - Biblical Guide', const { locale } = await params
description: 'A comprehensive Bible study application with AI chat capabilities', const t = await getTranslations({ locale, namespace: 'seo' })
const currentUrl = locale === 'ro' ? 'https://ghidulbiblic.ro/ro/' : 'https://ghidulbiblic.ro/en/'
const alternateUrl = locale === 'ro' ? 'https://ghidulbiblic.ro/en/' : 'https://ghidulbiblic.ro/ro/'
return {
title: t('title'),
description: t('description'),
keywords: t('keywords'),
alternates: {
canonical: currentUrl,
languages: {
'ro': 'https://ghidulbiblic.ro/ro/',
'en': 'https://ghidulbiblic.ro/en/',
'x-default': 'https://ghidulbiblic.ro/'
}
},
openGraph: {
title: t('ogTitle'),
description: t('ogDescription'),
url: currentUrl,
siteName: locale === 'ro' ? 'Ghid Biblic' : 'Biblical Guide',
locale: locale,
type: 'website',
images: [
{
url: `https://ghidulbiblic.ro/og-image-${locale}.jpg`,
width: 1200,
height: 630,
alt: t('ogTitle'),
},
],
},
twitter: {
card: 'summary_large_image',
site: '@ghidbiblic',
title: t('twitterTitle'),
description: t('twitterDescription'),
images: [`https://ghidulbiblic.ro/og-image-${locale}.jpg`],
},
other: {
'application/ld+json': JSON.stringify({
"@context": "https://schema.org",
"@type": "MobileApplication",
"name": locale === 'ro' ? "Ghid Biblic" : "Biblical Guide",
"url": "https://ghidulbiblic.ro",
"description": t('description'),
"applicationCategory": "EducationApplication",
"operatingSystem": "iOS, Android, Web",
"inLanguage": [locale],
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
}
})
}
}
} }
export async function generateStaticParams() { export async function generateStaticParams() {

View File

@@ -44,6 +44,7 @@ export default function Home() {
const theme = useTheme() const theme = useTheme()
const router = useRouter() const router = useRouter()
const t = useTranslations('home') const t = useTranslations('home')
const tSeo = useTranslations('seo')
const locale = useLocale() const locale = useLocale()
const [userCount, setUserCount] = useState(2847) const [userCount, setUserCount] = useState(2847)
const [expandedFaq, setExpandedFaq] = useState<string | false>(false) const [expandedFaq, setExpandedFaq] = useState<string | false>(false)
@@ -595,7 +596,7 @@ export default function Home() {
{t('footer.brand')} {t('footer.brand')}
</Typography> </Typography>
<Typography variant="body2" color="grey.400" sx={{ maxWidth: 300 }}> <Typography variant="body2" color="grey.400" sx={{ maxWidth: 300 }}>
{t('footer.description')} {tSeo('footer')}
</Typography> </Typography>
</Box> </Box>

View File

@@ -1,6 +1,4 @@
@tailwind base; @import "tailwindcss";
@tailwind components;
@tailwind utilities;
:root { :root {
--foreground-rgb: 0, 0, 0; --foreground-rgb: 0, 0, 0;

32
app/icon.tsx Normal file
View File

@@ -0,0 +1,32 @@
import { ImageResponse } from 'next/og'
export const size = {
width: 32,
height: 32,
}
export const contentType = 'image/png'
export default function Icon() {
return new ImageResponse(
(
<div
style={{
fontSize: 24,
background: '#333',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontWeight: 'bold',
}}
>
B
</div>
),
{
...size,
}
)
}

View File

@@ -38,7 +38,6 @@ export function AuthProvider({ children }: AuthProviderProps) {
// Check if token is expired before making request // Check if token is expired before making request
if (isTokenExpired(token)) { if (isTokenExpired(token)) {
console.log('Token expired in refreshUser, clearing auth state')
localStorage.removeItem('authToken') localStorage.removeItem('authToken')
setUser(null) setUser(null)
setIsLoading(false) setIsLoading(false)
@@ -62,14 +61,6 @@ export function AuthProvider({ children }: AuthProviderProps) {
const data = await response.json() const data = await response.json()
setUser(data.user) setUser(data.user)
} else { } else {
// Token is invalid or expired, get error details
try {
const errorData = await response.json()
console.log('Server returned 401 error:', errorData)
} catch (e) {
console.log('Server returned 401 without JSON body, status:', response.status)
}
console.log('Token expired or invalid, clearing auth state')
localStorage.removeItem('authToken') localStorage.removeItem('authToken')
setUser(null) setUser(null)
} }
@@ -83,81 +74,18 @@ export function AuthProvider({ children }: AuthProviderProps) {
} }
} }
// Debug database schema
const debugSchema = async () => {
try {
const response = await fetch('/api/debug/schema')
const debug = await response.json()
console.log('Database schema info:', debug)
} catch (e) {
console.log('Schema debug failed:', e)
}
}
// Debug user lookup
const debugUser = async (userId: string) => {
try {
const response = await fetch('/api/debug/user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId })
})
const debug = await response.json()
console.log('User debug info:', debug)
} catch (e) {
console.log('User debug failed:', e)
}
}
// Debug token validation
const debugToken = async (token: string) => {
try {
const response = await fetch('/api/debug/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
})
const debug = await response.json()
console.log('Token debug info:', debug)
// Log more details about the token payload
if (debug.payload) {
console.log('Token payload:', debug.payload)
// Debug user lookup
if (debug.payload.userId) {
debugUser(debug.payload.userId)
}
}
if (debug.verificationResult) {
console.log('Verification result:', debug.verificationResult)
}
} catch (e) {
console.log('Token debug failed:', e)
}
}
// Clear expired tokens and sync state immediately on mount // Clear expired tokens and sync state immediately on mount
useEffect(() => { useEffect(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
const token = localStorage.getItem('authToken') const token = localStorage.getItem('authToken')
console.log('Auth mount check - token exists:', !!token)
if (token) { if (token) {
console.log('Token preview:', token.substring(0, 50) + '...')
// Debug the database schema and token on server side
debugSchema()
debugToken(token)
const expired = isTokenExpired(token) const expired = isTokenExpired(token)
console.log('Token expired:', expired)
if (expired) { if (expired) {
console.log('Clearing expired token and user state on mount')
localStorage.removeItem('authToken') localStorage.removeItem('authToken')
setUser(null) setUser(null)
} }
} else if (user) { } else if (user) {
console.log('No token but user exists in store, clearing user state')
setUser(null) setUser(null)
} }
clearExpiredToken() clearExpiredToken()
@@ -168,23 +96,19 @@ export function AuthProvider({ children }: AuthProviderProps) {
useEffect(() => { useEffect(() => {
if (!initialized && typeof window !== 'undefined') { if (!initialized && typeof window !== 'undefined') {
const token = localStorage.getItem('authToken') const token = localStorage.getItem('authToken')
console.log('Initialization flow - token exists:', !!token)
if (token) { if (token) {
// Check if token is expired before making request // Check if token is expired before making request
if (isTokenExpired(token)) { if (isTokenExpired(token)) {
console.log('Token expired during initialization, clearing auth state')
localStorage.removeItem('authToken') localStorage.removeItem('authToken')
setUser(null) setUser(null)
setIsLoading(false) setIsLoading(false)
} else { } else {
console.log('Token is valid, calling refreshUser()')
// Token appears valid, try to refresh user data // Token appears valid, try to refresh user data
// refreshUser will handle server-side validation failures // refreshUser will handle server-side validation failures
refreshUser() refreshUser()
} }
} else { } else {
console.log('No token found, clearing user state')
// No token, clear any stale user data // No token, clear any stale user data
setUser(null) setUser(null)
setIsLoading(false) setIsLoading(false)

View File

@@ -138,7 +138,6 @@ export default function FloatingChat() {
const checkAuthStatus = useCallback(async () => { const checkAuthStatus = useCallback(async () => {
try { try {
const token = localStorage.getItem('authToken') const token = localStorage.getItem('authToken')
console.log('Chat - checkAuthStatus: token from localStorage:', token ? 'present' : 'null')
if (token) { if (token) {
// Verify token with the server // Verify token with the server
const response = await fetch('/api/auth/me', { const response = await fetch('/api/auth/me', {
@@ -146,20 +145,16 @@ export default function FloatingChat() {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }
}) })
console.log('Chat - auth verification response:', response.ok)
if (response.ok) { if (response.ok) {
setAuthToken(token) setAuthToken(token)
setIsAuthenticated(true) setIsAuthenticated(true)
console.log('Chat - Auth state set: authenticated')
} else { } else {
localStorage.removeItem('authToken') localStorage.removeItem('authToken')
setIsAuthenticated(false) setIsAuthenticated(false)
setAuthToken(null) setAuthToken(null)
console.log('Chat - Auth verification failed, cleared state')
} }
} else { } else {
console.log('Chat - No token in localStorage')
setIsAuthenticated(false) setIsAuthenticated(false)
setAuthToken(null) setAuthToken(null)
} }

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# Deploy script for Biblical Guide production server # Deploy script for Biblical Guide production server
# Fetches latest code from production branch and deploys # Fetches latest code from production branch and deploys with PM2
set -e set -e
@@ -11,6 +11,7 @@ echo "🚀 Starting deployment..."
REPO_URL="https://git.noru1.ro/andrei/ghidul-biblic.git" REPO_URL="https://git.noru1.ro/andrei/ghidul-biblic.git"
BRANCH="production" BRANCH="production"
APP_NAME="ghidul-biblic" APP_NAME="ghidul-biblic"
PORT="3010"
# Colors for output # Colors for output
RED='\033[0;31m' RED='\033[0;31m'
@@ -60,10 +61,6 @@ fi
print_success "Environment variables validated" print_success "Environment variables validated"
# Stop existing containers
print_status "Stopping existing containers..."
docker compose down || true
# Fetch latest code from production branch # Fetch latest code from production branch
print_status "Fetching latest code from $BRANCH branch..." print_status "Fetching latest code from $BRANCH branch..."
git fetch origin $BRANCH git fetch origin $BRANCH
@@ -76,40 +73,54 @@ CURRENT_COMMIT=$(git rev-parse --short HEAD)
COMMIT_MSG=$(git log -1 --pretty=format:"%s") COMMIT_MSG=$(git log -1 --pretty=format:"%s")
print_status "Current commit: $CURRENT_COMMIT - $COMMIT_MSG" print_status "Current commit: $CURRENT_COMMIT - $COMMIT_MSG"
# Build and start the application # Install dependencies if package.json changed
print_status "Building and starting application..." if git diff --name-only HEAD~1 HEAD | grep -q "package.json\|package-lock.json"; then
docker compose up --build -d print_status "Dependencies changed, installing..."
npm ci
print_success "Dependencies installed"
fi
# Build the application
print_status "Building application..."
npm run build
print_success "Application built successfully"
# Restart with PM2
print_status "Restarting application with PM2..."
pm2 restart $APP_NAME || pm2 start ecosystem.config.js --env production
# Save PM2 configuration
pm2 save
print_success "Application restarted with PM2"
# Wait for application to be ready # Wait for application to be ready
print_status "Waiting for application to start..." print_status "Waiting for application to start..."
sleep 10 sleep 5
# Health check # Health check
print_status "Performing health check..." print_status "Performing health check..."
for i in {1..30}; do for i in {1..30}; do
if curl -f http://localhost:3010/api/health >/dev/null 2>&1; then if curl -f http://localhost:$PORT/api/health >/dev/null 2>&1; then
print_success "Application is healthy and running on port 3010" print_success "Application is healthy and running on port $PORT"
break break
fi fi
if [ $i -eq 30 ]; then if [ $i -eq 30 ]; then
print_error "Health check failed after 30 attempts" print_error "Health check failed after 30 attempts"
print_status "Showing container logs:" print_status "Showing PM2 logs:"
docker compose logs --tail=50 pm2 logs $APP_NAME --lines 20
exit 1 exit 1
fi fi
sleep 2 sleep 2
done done
# Show running containers # Show PM2 status
print_status "Running containers:" print_status "PM2 Status:"
docker compose ps pm2 status
# Cleanup old images (keep last 3)
print_status "Cleaning up old Docker images..."
docker image prune -f >/dev/null 2>&1 || true
print_success "🎉 Deployment completed successfully!" print_success "🎉 Deployment completed successfully!"
print_status "Application is now running at: http://localhost:3010" print_status "Application is now running at: http://localhost:$PORT"
print_status "API health endpoint: http://localhost:3010/api/health" print_status "API health endpoint: http://localhost:$PORT/api/health"

View File

@@ -1,23 +0,0 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
ports:
- "3010:3000"
environment:
DATABASE_URL: ${DATABASE_URL}
AZURE_OPENAI_KEY: ${AZURE_OPENAI_KEY}
AZURE_OPENAI_ENDPOINT: ${AZURE_OPENAI_ENDPOINT}
AZURE_OPENAI_DEPLOYMENT: ${AZURE_OPENAI_DEPLOYMENT}
OLLAMA_API_URL: ${OLLAMA_API_URL}
JWT_SECRET: ${JWT_SECRET}
NEXTAUTH_URL: ${NEXTAUTH_URL}
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
NODE_ENV: production
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3

28
ecosystem.config.js Normal file
View File

@@ -0,0 +1,28 @@
module.exports = {
apps: [
{
name: 'ghidul-biblic',
script: 'npm',
args: 'start',
cwd: '/root/ghidul-biblic',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '4G',
env: {
NODE_ENV: 'production',
PORT: 3010,
HOSTNAME: '0.0.0.0',
},
env_production: {
NODE_ENV: 'production',
PORT: 3010,
HOSTNAME: '0.0.0.0',
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true
}
]
};

13
i18n.ts
View File

@@ -1,14 +1,21 @@
import {getRequestConfig} from 'next-intl/server'; import {getRequestConfig} from 'next-intl/server';
import ro from './messages/ro.json';
import en from './messages/en.json';
// Can be imported from a shared config // Can be imported from a shared config
export const locales = ['ro', 'en']; export const locales = ['ro', 'en'] as const;
const messages = {
ro,
en
} as const;
export default getRequestConfig(async ({locale}) => { export default getRequestConfig(async ({locale}) => {
// Ensure locale has a value, default to 'ro' if undefined // Ensure locale has a value, default to 'ro' if undefined
const validLocale = locale || 'ro'; const validLocale = (locale || 'ro') as keyof typeof messages;
return { return {
locale: validLocale, locale: validLocale,
messages: (await import(`./messages/${validLocale}.json`)).default messages: messages[validLocale]
}; };
}); });

View File

@@ -2,16 +2,13 @@ export function isTokenExpired(token: string): boolean {
try { try {
const payload = JSON.parse(atob(token.split('.')[1])) as { exp?: number } const payload = JSON.parse(atob(token.split('.')[1])) as { exp?: number }
if (!payload || !payload.exp) { if (!payload || !payload.exp) {
console.log('Token has no expiration data')
return true return true
} }
const currentTime = Math.floor(Date.now() / 1000) const currentTime = Math.floor(Date.now() / 1000)
const isExpired = payload.exp < currentTime const isExpired = payload.exp < currentTime
console.log(`Token expiration check: exp=${payload.exp}, now=${currentTime}, expired=${isExpired}`)
return isExpired return isExpired
} catch (error) { } catch (error) {
console.log('Token validation error:', error)
return true return true
} }
} }
@@ -19,11 +16,8 @@ export function isTokenExpired(token: string): boolean {
export function clearExpiredToken(): void { export function clearExpiredToken(): void {
const token = localStorage.getItem('authToken') const token = localStorage.getItem('authToken')
if (token && isTokenExpired(token)) { if (token && isTokenExpired(token)) {
console.log('Clearing expired token from localStorage')
localStorage.removeItem('authToken') localStorage.removeItem('authToken')
} else if (token) { } else if (token) {
console.log('Token exists and is valid')
} else { } else {
console.log('No token in localStorage')
} }
} }

View File

@@ -33,33 +33,33 @@
}, },
"home": { "home": {
"hero": { "hero": {
"title": "Biblical Guide", "title": "Biblical Guide Online Bible Study with AI Chat, Daily Verses, and Prayer Community",
"subtitle": "Explore Scripture with artificial intelligence", "subtitle": "Online Bible Study with AI assistance",
"description": "A modern platform for Bible study, with intelligent AI chat, advanced search, and a prayer community that supports you on your spiritual journey.", "description": "Biblical Guide is an online Bible study app. Read Scripture, ask questions with AI-powered chat, search verses instantly, and join a global prayer community that supports your spiritual growth.",
"cta": { "cta": {
"readBible": "Start reading", "readBible": "Start reading",
"askAI": "Ask AI" "askAI": "Try it free now AI Bible chat"
}, },
"liveCounter": "Join thousands of believers studying God's word right now" "liveCounter": "Join thousands of believers who use Biblical Guide to study, understand, and apply God's Word in their everyday lives"
}, },
"features": { "features": {
"title": "Discover the features", "title": "Discover the features",
"subtitle": "Everything you need for a complete Bible study experience", "subtitle": "Everything you need for a complete Bible study experience",
"bible": { "bible": {
"title": "Read the Bible", "title": "Read the Bible online",
"description": "Explore Scripture with a modern and easy-to-use interface" "description": "access all 66 books with a modern and intuitive interface"
}, },
"chat": { "chat": {
"title": "AI Chat", "title": "AI Bible Chat",
"description": "Ask questions about Scripture and receive clear answers" "description": "ask Scripture questions and get clear, accurate answers"
}, },
"prayers": { "prayers": {
"title": "Prayers", "title": "Prayer Community",
"description": "Share prayers and pray together with the community" "description": "share requests and join others in prayer"
}, },
"search": { "search": {
"title": "Search", "title": "Verse Search",
"description": "Search for verses and passages throughout Scripture" "description": "quickly find verses, keywords, and topics across the Bible"
} }
}, },
"stats": { "stats": {
@@ -80,7 +80,7 @@
"tryButton": "Try it yourself" "tryButton": "Try it yourself"
}, },
"dailyVerse": { "dailyVerse": {
"title": "Today's Verse", "title": "Daily Bible Verse receive encouragement from Scripture every day, straight to your inbox",
"date": "January 15, 2024", "date": "January 15, 2024",
"verse": "For I know the plans I have for you, declares the Lord, plans to prosper you and not to harm you, plans to give you hope and a future.", "verse": "For I know the plans I have for you, declares the Lord, plans to prosper you and not to harm you, plans to give you hope and a future.",
"reference": "Jeremiah 29:11", "reference": "Jeremiah 29:11",
@@ -481,5 +481,15 @@
"back": "Back", "back": "Back",
"next": "Next", "next": "Next",
"previous": "Previous" "previous": "Previous"
},
"seo": {
"title": "Biblical Guide Online Bible Study with AI, Daily Verses & Prayer Community",
"description": "Biblical Guide is an online Bible study app with AI-powered chat, instant verse search, and a global prayer community. Get daily Bible verses and Scripture-based answers to your questions.",
"keywords": "online Bible study, AI Bible chat, daily Bible verse, Bible study app, prayer community, read the Bible online, verse search, Scripture study",
"ogTitle": "Biblical Guide Online Bible Study with AI",
"ogDescription": "Read the Bible online, ask questions with AI chat, and join a prayer community. Get daily Bible verses in your inbox.",
"twitterTitle": "Biblical Guide Online Bible Study with AI",
"twitterDescription": "Online Bible study app with AI chat, daily verses, and prayer community.",
"footer": "Biblical Guide online Bible study app with AI chat, daily verses, and prayer community."
} }
} }

View File

@@ -33,33 +33,33 @@
}, },
"home": { "home": {
"hero": { "hero": {
"title": "Ghid Biblic", "title": "Ghid Biblic Studiu biblic online cu AI, versete zilnice și comunitate de rugăciune",
"subtitle": "Explorează Scriptura cu ajutorul inteligenței artificiale", "subtitle": "Studiu biblic online cu asistență AI",
"description": "O platformă modernă pentru studiul Bibliei, cu chat AI inteligent, căutare avansată și o comunitate de rugăciune care te sprijină în călătoria ta spirituală.", "description": "Ghid Biblic este o aplicație de studiu biblic online. Citește Scriptura, pune întrebări cu ajutorul chatului AI, caută versete rapid și alătură-te unei comunități de rugăciune care te sprijină zilnic.",
"cta": { "cta": {
"readBible": "Începe să citești", "readBible": "Începe să citești",
"askAI": "Întreabă AI" "askAI": "Încearcă acum gratuit - Chat AI"
}, },
"liveCounter": "Alătură-te la mii de credincioși care studiază Cuvântul lui Dumnezeu chiar acum" "liveCounter": "Alătură-te miilor de credincioși care folosesc Ghid Biblic pentru a înțelege și aplica Cuvântul lui Dumnezeu în viața de zi cu zi"
}, },
"features": { "features": {
"title": "Descoperă funcționalitățile", "title": "Descoperă funcționalitățile",
"subtitle": "Totul de ce ai nevoie pentru o experiență completă de studiu biblic", "subtitle": "Totul de ce ai nevoie pentru o experiență completă de studiu biblic",
"bible": { "bible": {
"title": "Citește Biblia", "title": "Citește Biblia online",
"description": "Explorează Scriptura cu o interfață modernă și ușor de folosit" "description": "toate cele 66 de cărți biblice într-o interfață modernă și intuitivă"
}, },
"chat": { "chat": {
"title": "Chat cu AI", "title": "Chat AI biblic",
"description": "Pune întrebări despre Scriptură și primește răspunsuri clare" "description": "întreabă Scriptura și primește răspunsuri clare, fundamentate pe versete"
}, },
"prayers": { "prayers": {
"title": "Rugăciuni", "title": "Comunitate de rugăciune",
"description": "Partajează rugăciuni și roagă-te împreună cu comunitatea" "description": "trimite și primește cereri de rugăciune"
}, },
"search": { "search": {
"title": "Căutare", "title": "Căutare versete",
"description": "Caută versete și pasaje din întreaga Scriptură" "description": "sește rapid pasaje, cuvinte cheie și teme biblice"
} }
}, },
"stats": { "stats": {
@@ -80,7 +80,7 @@
"tryButton": "Încearcă și tu" "tryButton": "Încearcă și tu"
}, },
"dailyVerse": { "dailyVerse": {
"title": "Versetul de Astăzi", "title": "Versetul biblic al zilei primește zilnic inspirație din Scriptură direct în inbox",
"date": "15 ianuarie 2024", "date": "15 ianuarie 2024",
"verse": "Căci Eu știu gândurile pe care le am cu privire la voi, zice Domnul, gânduri de pace și nu de rău, ca să vă dau un viitor și o speranță.", "verse": "Căci Eu știu gândurile pe care le am cu privire la voi, zice Domnul, gânduri de pace și nu de rău, ca să vă dau un viitor și o speranță.",
"reference": "Ieremia 29:11", "reference": "Ieremia 29:11",
@@ -481,5 +481,15 @@
"back": "Înapoi", "back": "Înapoi",
"next": "Următorul", "next": "Următorul",
"previous": "Anterior" "previous": "Anterior"
},
"seo": {
"title": "Ghid Biblic Studiu Biblic Online cu AI, Versete Zilnice și Comunitate de Rugăciune",
"description": "Ghid Biblic este o aplicație de studiu biblic online cu chat AI, căutare rapidă de versete și o comunitate de rugăciune. Primește versete zilnice și răspunsuri clare din Scriptură.",
"keywords": "studiu biblic online, aplicație biblică, chat AI biblic, versete biblice zilnice, comunitate de rugăciune, citește Biblia online, căutare versete, Biblia Română",
"ogTitle": "Ghid Biblic Studiu Biblic Online cu AI",
"ogDescription": "Citește Biblia online, pune întrebări prin chat AI și alătură-te unei comunități de rugăciune. Primește versete zilnice în inbox.",
"twitterTitle": "Ghid Biblic Studiu Biblic Online cu AI",
"twitterDescription": "Aplicație biblică online cu chat AI, versete zilnice și comunitate de rugăciune.",
"footer": "Ghid Biblic aplicație de studiu biblic online, cu chat AI, versete zilnice și comunitate de rugăciune."
} }
} }

View File

@@ -1,12 +1,13 @@
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server' import type { NextRequest } from 'next/server'
// Avoid importing Node-only modules in Middleware (Edge runtime)
import createIntlMiddleware from 'next-intl/middleware' import createIntlMiddleware from 'next-intl/middleware'
import { locales } from './i18n'
// Internationalization configuration // Internationalization configuration
const intlMiddleware = createIntlMiddleware({ const intlMiddleware = createIntlMiddleware({
locales: ['ro', 'en'], locales: [...locales],
defaultLocale: 'ro' defaultLocale: 'ro',
localePrefix: 'always'
}) })
// Note: Avoid using Prisma or any Node-only APIs in Middleware. // Note: Avoid using Prisma or any Node-only APIs in Middleware.
@@ -71,10 +72,11 @@ export const config = {
// Match all pathnames except for // Match all pathnames except for
// - api routes // - api routes
// - _next (Next.js internals) // - _next (Next.js internals)
// - _vercel
// - static files (images, etc.) // - static files (images, etc.)
'/((?!api|_next|_vercel|.*\\..*).*)', // - favicon.ico, robots.txt, sitemap.xml
// However, match all pathnames within `/api`, except for the Middleware to run there '/((?!api|_next|_vercel|.*\\..*|favicon.ico|robots.txt|sitemap.xml).*)',
'/api/:path*', // Match internationalized pathnames
'/dashboard/:path*' '/(ro|en)/:path*'
], ],
} }

View File

@@ -2,8 +2,10 @@ const withNextIntl = require('next-intl/plugin')('./i18n.ts');
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: 'standalone',
typedRoutes: false, typedRoutes: false,
trailingSlash: false,
poweredByHeader: false,
compress: true,
} }
module.exports = withNextIntl(nextConfig) module.exports = withNextIntl(nextConfig)

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B