docs: add comprehensive K6 load testing guide with API structure
- Document all API endpoints (auth, listings, payments, search) - Include DTOs and request/response body shapes - Document authentication methods and rate limits - Provide database and environment configuration - Include existing test setup (Playwright, Vitest) - Detail CI/CD pipeline structure - Recommend K6 endpoints and test patterns - Provide file location references for quick lookup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
134
.github/dependabot.yml
vendored
Normal file
134
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
# ── Node.js / pnpm dependencies ──────────────────────────────────
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "06:00"
|
||||
timezone: "Asia/Ho_Chi_Minh"
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- "goodgo/platform-team"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "security"
|
||||
# Group minor/patch updates to reduce PR noise
|
||||
groups:
|
||||
dev-dependencies:
|
||||
patterns:
|
||||
- "@types/*"
|
||||
- "eslint*"
|
||||
- "prettier*"
|
||||
- "typescript*"
|
||||
- "vitest*"
|
||||
- "@playwright/*"
|
||||
- "husky"
|
||||
- "lint-staged"
|
||||
- "tsx"
|
||||
- "turbo"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
nestjs:
|
||||
patterns:
|
||||
- "@nestjs/*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
prisma:
|
||||
patterns:
|
||||
- "prisma"
|
||||
- "@prisma/*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Security updates always get individual PRs (not grouped)
|
||||
commit-message:
|
||||
prefix: "deps"
|
||||
include: "scope"
|
||||
|
||||
# ── Python dependencies (AI services) ────────────────────────────
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/libs/ai-services"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "06:00"
|
||||
timezone: "Asia/Ho_Chi_Minh"
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "security"
|
||||
- "ai-services"
|
||||
commit-message:
|
||||
prefix: "deps(ai)"
|
||||
include: "scope"
|
||||
|
||||
# ── GitHub Actions ───────────────────────────────────────────────
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "06:00"
|
||||
timezone: "Asia/Ho_Chi_Minh"
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "ci"
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
commit-message:
|
||||
prefix: "ci"
|
||||
include: "scope"
|
||||
|
||||
# ── Docker base images ──────────────────────────────────────────
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/apps/api"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "06:00"
|
||||
timezone: "Asia/Ho_Chi_Minh"
|
||||
open-pull-requests-limit: 3
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "docker"
|
||||
commit-message:
|
||||
prefix: "docker(api)"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/apps/web"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "06:00"
|
||||
timezone: "Asia/Ho_Chi_Minh"
|
||||
open-pull-requests-limit: 3
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "docker"
|
||||
commit-message:
|
||||
prefix: "docker(web)"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/libs/ai-services"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "06:00"
|
||||
timezone: "Asia/Ho_Chi_Minh"
|
||||
open-pull-requests-limit: 3
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "docker"
|
||||
commit-message:
|
||||
prefix: "docker(ai)"
|
||||
61
.github/workflows/codeql.yml
vendored
Normal file
61
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: CodeQL Analysis
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run weekly on Monday at 06:17 UTC — off-peak to avoid :00/:30 congestion
|
||||
- cron: "17 6 * * 1"
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: CodeQL (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [javascript-typescript]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# Use extended security queries for deeper analysis
|
||||
queries: security-extended,security-and-quality
|
||||
config: |
|
||||
paths:
|
||||
- apps/
|
||||
- libs/
|
||||
paths-ignore:
|
||||
- node_modules/
|
||||
- "**/dist/"
|
||||
- "**/*.spec.ts"
|
||||
- "**/*.test.ts"
|
||||
- "**/__tests__/"
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
# SARIF results are automatically uploaded to GitHub Security tab
|
||||
upload: always
|
||||
312
.github/workflows/security.yml
vendored
Normal file
312
.github/workflows/security.yml
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
name: Security Scanning
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Run daily at 05:43 UTC — catch new CVEs early
|
||||
- cron: "43 5 * * *"
|
||||
|
||||
concurrency:
|
||||
group: security-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
# ── Dependency Audit ─────────────────────────────────────────────
|
||||
dependency-audit:
|
||||
name: Dependency Audit (pnpm)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run pnpm audit
|
||||
run: |
|
||||
echo "## Dependency Audit Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Run audit, capture output and exit code
|
||||
set +e
|
||||
AUDIT_OUTPUT=$(pnpm audit --json 2>&1)
|
||||
AUDIT_EXIT=$?
|
||||
set -e
|
||||
|
||||
# Parse and display summary
|
||||
echo "$AUDIT_OUTPUT" | jq -r '
|
||||
if .metadata then
|
||||
"| Severity | Count |\n|----------|-------|\n" +
|
||||
"| Critical | \(.metadata.vulnerabilities.critical // 0) |\n" +
|
||||
"| High | \(.metadata.vulnerabilities.high // 0) |\n" +
|
||||
"| Moderate | \(.metadata.vulnerabilities.moderate // 0) |\n" +
|
||||
"| Low | \(.metadata.vulnerabilities.low // 0) |\n" +
|
||||
"| Info | \(.metadata.vulnerabilities.info // 0) |"
|
||||
else
|
||||
"No vulnerabilities found ✅"
|
||||
end
|
||||
' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "Audit completed" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Also run human-readable output
|
||||
pnpm audit 2>&1 || true
|
||||
|
||||
# Fail on critical or high vulnerabilities only
|
||||
pnpm audit --audit-level=critical
|
||||
continue-on-error: false
|
||||
|
||||
# ── Container Scanning (Trivy) ───────────────────────────────────
|
||||
trivy-api:
|
||||
name: Trivy Scan — API Image
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build API image for scanning
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: apps/api/Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: goodgo-api:scan
|
||||
cache-from: type=gha,scope=api-scan
|
||||
cache-to: type=gha,mode=max,scope=api-scan
|
||||
|
||||
- name: Run Trivy vulnerability scanner (API)
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
image-ref: "goodgo-api:scan"
|
||||
format: "sarif"
|
||||
output: "trivy-api-results.sarif"
|
||||
severity: "CRITICAL,HIGH"
|
||||
# Ignore unfixed vulns to reduce noise
|
||||
ignore-unfixed: true
|
||||
|
||||
- name: Upload Trivy SARIF (API)
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: "trivy-api-results.sarif"
|
||||
category: "trivy-api"
|
||||
|
||||
- name: Trivy table output (API)
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
image-ref: "goodgo-api:scan"
|
||||
format: "table"
|
||||
severity: "CRITICAL,HIGH,MEDIUM"
|
||||
ignore-unfixed: true
|
||||
|
||||
trivy-web:
|
||||
name: Trivy Scan — Web Image
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build Web image for scanning
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: apps/web/Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: goodgo-web:scan
|
||||
cache-from: type=gha,scope=web-scan
|
||||
cache-to: type=gha,mode=max,scope=web-scan
|
||||
|
||||
- name: Run Trivy vulnerability scanner (Web)
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
image-ref: "goodgo-web:scan"
|
||||
format: "sarif"
|
||||
output: "trivy-web-results.sarif"
|
||||
severity: "CRITICAL,HIGH"
|
||||
ignore-unfixed: true
|
||||
|
||||
- name: Upload Trivy SARIF (Web)
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: "trivy-web-results.sarif"
|
||||
category: "trivy-web"
|
||||
|
||||
- name: Trivy table output (Web)
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
image-ref: "goodgo-web:scan"
|
||||
format: "table"
|
||||
severity: "CRITICAL,HIGH,MEDIUM"
|
||||
ignore-unfixed: true
|
||||
|
||||
trivy-ai:
|
||||
name: Trivy Scan — AI Services Image
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build AI Services image for scanning
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./libs/ai-services
|
||||
file: libs/ai-services/Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: goodgo-ai:scan
|
||||
cache-from: type=gha,scope=ai-scan
|
||||
cache-to: type=gha,mode=max,scope=ai-scan
|
||||
|
||||
- name: Run Trivy vulnerability scanner (AI)
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
image-ref: "goodgo-ai:scan"
|
||||
format: "sarif"
|
||||
output: "trivy-ai-results.sarif"
|
||||
severity: "CRITICAL,HIGH"
|
||||
ignore-unfixed: true
|
||||
|
||||
- name: Upload Trivy SARIF (AI)
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: "trivy-ai-results.sarif"
|
||||
category: "trivy-ai"
|
||||
|
||||
- name: Trivy table output (AI)
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
image-ref: "goodgo-ai:scan"
|
||||
format: "table"
|
||||
severity: "CRITICAL,HIGH,MEDIUM"
|
||||
ignore-unfixed: true
|
||||
|
||||
# ── Filesystem / IaC Scanning ────────────────────────────────────
|
||||
trivy-fs:
|
||||
name: Trivy Filesystem Scan
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy filesystem scanner
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
scan-type: "fs"
|
||||
scan-ref: "."
|
||||
format: "sarif"
|
||||
output: "trivy-fs-results.sarif"
|
||||
severity: "CRITICAL,HIGH"
|
||||
ignore-unfixed: true
|
||||
scanners: "vuln,secret,misconfig"
|
||||
|
||||
- name: Upload Trivy SARIF (filesystem)
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: "trivy-fs-results.sarif"
|
||||
category: "trivy-filesystem"
|
||||
|
||||
- name: Trivy filesystem table output
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
scan-type: "fs"
|
||||
scan-ref: "."
|
||||
format: "table"
|
||||
severity: "CRITICAL,HIGH,MEDIUM"
|
||||
scanners: "vuln,secret,misconfig"
|
||||
|
||||
# ── Summary Gate ─────────────────────────────────────────────────
|
||||
security-gate:
|
||||
name: Security Gate
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-audit, trivy-api, trivy-web, trivy-ai, trivy-fs]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Check security scan results
|
||||
run: |
|
||||
echo "## Security Scan Summary" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Check each job result
|
||||
FAILED=false
|
||||
|
||||
if [ "${{ needs.dependency-audit.result }}" != "success" ]; then
|
||||
echo "❌ Dependency audit: ${{ needs.dependency-audit.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
FAILED=true
|
||||
else
|
||||
echo "✅ Dependency audit: passed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ needs.trivy-api.result }}" != "success" ]; then
|
||||
echo "❌ Trivy API scan: ${{ needs.trivy-api.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
FAILED=true
|
||||
else
|
||||
echo "✅ Trivy API scan: passed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ needs.trivy-web.result }}" != "success" ]; then
|
||||
echo "❌ Trivy Web scan: ${{ needs.trivy-web.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
FAILED=true
|
||||
else
|
||||
echo "✅ Trivy Web scan: passed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ needs.trivy-ai.result }}" != "success" ]; then
|
||||
echo "❌ Trivy AI scan: ${{ needs.trivy-ai.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
FAILED=true
|
||||
else
|
||||
echo "✅ Trivy AI scan: passed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ needs.trivy-fs.result }}" != "success" ]; then
|
||||
echo "❌ Trivy filesystem scan: ${{ needs.trivy-fs.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
FAILED=true
|
||||
else
|
||||
echo "✅ Trivy filesystem scan: passed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "$FAILED" = true ]; then
|
||||
echo ""
|
||||
echo "⚠️ One or more security scans failed. Review the Security tab for details." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 All security scans passed!" >> $GITHUB_STEP_SUMMARY
|
||||
3
apps/web/i18n/config.ts
Normal file
3
apps/web/i18n/config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const locales = ['vi', 'en'] as const;
|
||||
export type Locale = (typeof locales)[number];
|
||||
export const defaultLocale: Locale = 'vi';
|
||||
4
apps/web/i18n/navigation.ts
Normal file
4
apps/web/i18n/navigation.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createNavigation } from 'next-intl/navigation';
|
||||
import { routing } from './routing';
|
||||
|
||||
export const { Link, redirect, usePathname, useRouter } = createNavigation(routing);
|
||||
16
apps/web/i18n/request.ts
Normal file
16
apps/web/i18n/request.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { getRequestConfig } from 'next-intl/server';
|
||||
import { routing } from './routing';
|
||||
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
let locale = await requestLocale;
|
||||
|
||||
// Ensure a valid locale is used
|
||||
if (!locale || !routing.locales.includes(locale as (typeof routing.locales)[number])) {
|
||||
locale = routing.defaultLocale;
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`../messages/${locale}.json`)).default,
|
||||
};
|
||||
});
|
||||
8
apps/web/i18n/routing.ts
Normal file
8
apps/web/i18n/routing.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { defineRouting } from 'next-intl/routing';
|
||||
import { locales, defaultLocale } from './config';
|
||||
|
||||
export const routing = defineRouting({
|
||||
locales,
|
||||
defaultLocale,
|
||||
localePrefix: 'as-needed',
|
||||
});
|
||||
110
apps/web/messages/en.json
Normal file
110
apps/web/messages/en.json
Normal file
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "GoodGo — Vietnam Real Estate Platform",
|
||||
"description": "GoodGo — smart real estate platform in Vietnam. Buy, sell, and rent properties easily with over 10,000+ listings nationwide.",
|
||||
"ogTitle": "GoodGo — Vietnam Real Estate Platform",
|
||||
"ogDescription": "Buy, sell, and rent properties easily with GoodGo — Vietnam's leading smart real estate platform."
|
||||
},
|
||||
"common": {
|
||||
"goodgo": "GoodGo",
|
||||
"loading": "Loading...",
|
||||
"retry": "Retry",
|
||||
"retrying": "Retrying...",
|
||||
"goHome": "Go to homepage",
|
||||
"search": "Search",
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"logout": "Logout",
|
||||
"admin": "Admin",
|
||||
"dashboard": "Dashboard",
|
||||
"errorCode": "Error code: {code}",
|
||||
"retriedCount": "Retried {count} times",
|
||||
"allRightsReserved": "© 2026 GoodGo. All rights reserved.",
|
||||
"skipToContent": "Skip to main content"
|
||||
},
|
||||
"nav": {
|
||||
"home": "Home",
|
||||
"search": "Search",
|
||||
"mainNav": "Main navigation",
|
||||
"dashboardNav": "Dashboard",
|
||||
"adminNav": "Administration"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"listings": "Listings",
|
||||
"createListing": "Create listing",
|
||||
"analytics": "Analytics",
|
||||
"aiValuation": "AI Valuation",
|
||||
"profile": "Profile",
|
||||
"subscription": "Subscription",
|
||||
"payments": "Payments",
|
||||
"darkMode": "Switch to dark mode",
|
||||
"lightMode": "Switch to light mode"
|
||||
},
|
||||
"adminNav": {
|
||||
"dashboard": "Dashboard",
|
||||
"users": "User management",
|
||||
"moderation": "Content moderation",
|
||||
"kyc": "KYC verification",
|
||||
"closeMenu": "Close menu",
|
||||
"openMenu": "Open menu"
|
||||
},
|
||||
"landing": {
|
||||
"heroTitle": "Find your perfect",
|
||||
"heroTitleHighlight": "property",
|
||||
"heroSubtitle": "Smart real estate platform in Vietnam — buy, sell, and rent properties with ease",
|
||||
"searchPlaceholder": "Enter area, project, or keyword...",
|
||||
"transactionTypeLabel": "Type",
|
||||
"featuredTitle": "Featured listings",
|
||||
"featuredSubtitle": "Explore the most popular properties",
|
||||
"viewAll": "View all",
|
||||
"loadError": "Unable to load listings. Please try again.",
|
||||
"noFeatured": "No featured listings yet",
|
||||
"districtsTitle": "Popular areas",
|
||||
"districtsSubtitle": "Search by popular districts",
|
||||
"statsTitle": "GoodGo in numbers",
|
||||
"statsSubtitle": "Vietnam's trusted real estate platform",
|
||||
"ctaTitle": "Have a property to list?",
|
||||
"ctaSubtitle": "List for free today and reach thousands of potential buyers",
|
||||
"registerFree": "Register for free",
|
||||
"searchNow": "Search now"
|
||||
},
|
||||
"stats": {
|
||||
"listings": "Listings",
|
||||
"users": "Users",
|
||||
"transactions": "Successful transactions",
|
||||
"provinces": "Provinces"
|
||||
},
|
||||
"footer": {
|
||||
"description": "Smart real estate platform in Vietnam",
|
||||
"propertyTypes": "Property types",
|
||||
"areas": "Areas",
|
||||
"support": "Support"
|
||||
},
|
||||
"propertyTypes": {
|
||||
"APARTMENT": "Apartment",
|
||||
"HOUSE": "House",
|
||||
"VILLA": "Villa",
|
||||
"LAND": "Land",
|
||||
"OFFICE": "Office",
|
||||
"SHOPHOUSE": "Shophouse"
|
||||
},
|
||||
"transactionTypes": {
|
||||
"SALE": "Sale",
|
||||
"RENT": "Rent"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Page not found",
|
||||
"description": "The page you are looking for does not exist or has been moved."
|
||||
},
|
||||
"error": {
|
||||
"title": "An error occurred",
|
||||
"description": "Sorry, something went wrong. Please try again.",
|
||||
"autoRetrying": "Automatically retrying..."
|
||||
},
|
||||
"language": {
|
||||
"label": "Language",
|
||||
"vi": "Tiếng Việt",
|
||||
"en": "English"
|
||||
}
|
||||
}
|
||||
110
apps/web/messages/vi.json
Normal file
110
apps/web/messages/vi.json
Normal file
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "GoodGo — Nền tảng Bất động sản Việt Nam",
|
||||
"description": "GoodGo — nền tảng bất động sản thông minh tại Việt Nam. Mua bán, cho thuê nhà đất dễ dàng với hơn 10,000+ tin đăng trên toàn quốc.",
|
||||
"ogTitle": "GoodGo — Nền tảng Bất động sản Việt Nam",
|
||||
"ogDescription": "Mua bán, cho thuê bất động sản dễ dàng với GoodGo — nền tảng thông minh, uy tín hàng đầu Việt Nam."
|
||||
},
|
||||
"common": {
|
||||
"goodgo": "GoodGo",
|
||||
"loading": "Đang tải...",
|
||||
"retry": "Thử lại",
|
||||
"retrying": "Đang thử lại...",
|
||||
"goHome": "Về trang chủ",
|
||||
"search": "Tìm kiếm",
|
||||
"login": "Đăng nhập",
|
||||
"register": "Đăng ký",
|
||||
"logout": "Đăng xuất",
|
||||
"admin": "Admin",
|
||||
"dashboard": "Bảng điều khiển",
|
||||
"errorCode": "Mã lỗi: {code}",
|
||||
"retriedCount": "Đã thử lại {count} lần",
|
||||
"allRightsReserved": "© 2026 GoodGo. Tất cả quyền được bảo lưu.",
|
||||
"skipToContent": "Chuyển đến nội dung chính"
|
||||
},
|
||||
"nav": {
|
||||
"home": "Trang chủ",
|
||||
"search": "Tìm kiếm",
|
||||
"mainNav": "Điều hướng chính",
|
||||
"dashboardNav": "Bảng điều khiển",
|
||||
"adminNav": "Quản trị"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Bảng điều khiển",
|
||||
"listings": "Tin đăng",
|
||||
"createListing": "Đăng tin",
|
||||
"analytics": "Phân tích",
|
||||
"aiValuation": "Định giá AI",
|
||||
"profile": "Hồ sơ",
|
||||
"subscription": "Gói dịch vụ",
|
||||
"payments": "Thanh toán",
|
||||
"darkMode": "Chuyển sang chế độ tối",
|
||||
"lightMode": "Chuyển sang chế độ sáng"
|
||||
},
|
||||
"adminNav": {
|
||||
"dashboard": "Dashboard",
|
||||
"users": "Quản lý người dùng",
|
||||
"moderation": "Kiểm duyệt tin",
|
||||
"kyc": "Duyệt KYC",
|
||||
"closeMenu": "Đóng menu",
|
||||
"openMenu": "Mở menu"
|
||||
},
|
||||
"landing": {
|
||||
"heroTitle": "Tìm kiếm bất động sản",
|
||||
"heroTitleHighlight": "hoàn hảo",
|
||||
"heroSubtitle": "Nền tảng bất động sản thông minh tại Việt Nam — mua bán, cho thuê nhà đất dễ dàng",
|
||||
"searchPlaceholder": "Nhập khu vực, dự án, hoặc từ khóa...",
|
||||
"transactionTypeLabel": "Loại GD",
|
||||
"featuredTitle": "Tin đăng nổi bật",
|
||||
"featuredSubtitle": "Khám phá các bất động sản được quan tâm nhất",
|
||||
"viewAll": "Xem tất cả",
|
||||
"loadError": "Không thể tải tin đăng. Vui lòng thử lại.",
|
||||
"noFeatured": "Chưa có tin đăng nổi bật",
|
||||
"districtsTitle": "Khu vực nổi bật",
|
||||
"districtsSubtitle": "Tìm kiếm theo quận huyện phổ biến",
|
||||
"statsTitle": "GoodGo trong số liệu",
|
||||
"statsSubtitle": "Nền tảng bất động sản đáng tin cậy tại Việt Nam",
|
||||
"ctaTitle": "Bạn có bất động sản muốn đăng?",
|
||||
"ctaSubtitle": "Đăng tin miễn phí ngay hôm nay, tiếp cận hàng ngàn người mua tiềm năng",
|
||||
"registerFree": "Đăng ký miễn phí",
|
||||
"searchNow": "Tìm kiếm ngay"
|
||||
},
|
||||
"stats": {
|
||||
"listings": "Tin đăng",
|
||||
"users": "Người dùng",
|
||||
"transactions": "Giao dịch thành công",
|
||||
"provinces": "Tỉnh thành"
|
||||
},
|
||||
"footer": {
|
||||
"description": "Nền tảng bất động sản thông minh tại Việt Nam",
|
||||
"propertyTypes": "Loại BĐS",
|
||||
"areas": "Khu vực",
|
||||
"support": "Hỗ trợ"
|
||||
},
|
||||
"propertyTypes": {
|
||||
"APARTMENT": "Căn hộ",
|
||||
"HOUSE": "Nhà riêng",
|
||||
"VILLA": "Biệt thự",
|
||||
"LAND": "Đất nền",
|
||||
"OFFICE": "Văn phòng",
|
||||
"SHOPHOUSE": "Shophouse"
|
||||
},
|
||||
"transactionTypes": {
|
||||
"SALE": "Bán",
|
||||
"RENT": "Cho thuê"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Không tìm thấy trang",
|
||||
"description": "Trang bạn đang tìm không tồn tại hoặc đã được di chuyển."
|
||||
},
|
||||
"error": {
|
||||
"title": "Đã xảy ra lỗi",
|
||||
"description": "Rất tiếc, đã có lỗi xảy ra. Vui lòng thử lại.",
|
||||
"autoRetrying": "Đang tự động thử lại..."
|
||||
},
|
||||
"language": {
|
||||
"label": "Ngôn ngữ",
|
||||
"vi": "Tiếng Việt",
|
||||
"en": "English"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user