diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 18f3452..0401832 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -86,19 +86,24 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract version from tag + - name: Extract version and determine release type id: version run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then + if [[ $GITHUB_REF == refs/tags/v* ]]; then VERSION=${GITHUB_REF#refs/tags/} echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT + echo "is_release=true" >> $GITHUB_OUTPUT else - echo "version=latest" >> $GITHUB_OUTPUT - echo "version_without_v=latest" >> $GITHUB_OUTPUT + SHORT_SHA=${GITHUB_SHA::7} + echo "version=edge" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "is_release=false" >> $GITHUB_OUTPUT fi - - name: Build and push ${{ matrix.mode.name }} image + # Build and push for releases (with 'latest' tag) + - name: Build and push ${{ matrix.mode.name }} image (release) + if: steps.version.outputs.is_release == 'true' uses: docker/build-push-action@v6 with: push: true @@ -114,3 +119,20 @@ jobs: platforms: linux/amd64,linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max + + # Build and push for main branch (with 'edge' tag) + - name: Build and push ${{ matrix.mode.name }} image (edge) + if: steps.version.outputs.is_release == 'false' + uses: docker/build-push-action@v6 + with: + push: true + build-args: | + SIMPLE_MODE=${{ matrix.mode.simple_mode }} + tags: | + bentopdf/bentopdf${{ matrix.mode.suffix }}:edge + bentopdf/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:edge + ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:sha-${{ steps.version.outputs.short_sha }} + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/update-embedpdf-snippet.yml b/.github/workflows/update-embedpdf-snippet.yml new file mode 100644 index 0000000..58ab27a --- /dev/null +++ b/.github/workflows/update-embedpdf-snippet.yml @@ -0,0 +1,135 @@ +name: Update EmbedPDF Snippet + +on: + workflow_dispatch: + schedule: + - cron: '0 3 * * 1' # Weekly; adjust as needed + +jobs: + update-snippet: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Read current upstream version marker + id: current-version + run: | + if [ -f vendor/embedpdf/.upstream-version ]; then + CUR=$(cat vendor/embedpdf/.upstream-version) + else + CUR="" + fi + echo "version=$CUR" >> "$GITHUB_OUTPUT" + + - name: Read latest upstream version (@embedpdf/core) + id: upstream-version + run: | + LATEST=$(npm view @embedpdf/core version) + echo "version=$LATEST" >> "$GITHUB_OUTPUT" + + - name: Should update? + id: gate + run: | + if [ "${{ steps.upstream-version.outputs.version }}" = "${{ steps.current-version.outputs.version }}" ]; then + echo "run=false" >> "$GITHUB_OUTPUT" + echo "No upstream version change detected." + else + echo "run=true" >> "$GITHUB_OUTPUT" + echo "Updating from '${{ steps.current-version.outputs.version }}' to '${{ steps.upstream-version.outputs.version }}'" + fi + + - name: Setup Node + if: steps.gate.outputs.run == 'true' + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Enable corepack (pnpm) + if: steps.gate.outputs.run == 'true' + run: corepack enable + + - name: Prepare workspace + if: steps.gate.outputs.run == 'true' + run: | + mkdir -p vendor/embedpdf + npm config set cache ./\.npm-cache + + - name: Clone upstream embed-pdf-viewer + if: steps.gate.outputs.run == 'true' + run: git clone https://github.com/embedpdf/embed-pdf-viewer ../embed-pdf-viewer + + - name: Install upstream deps + if: steps.gate.outputs.run == 'true' + working-directory: ../embed-pdf-viewer + run: pnpm install --frozen-lockfile + + - name: Build snippet + if: steps.gate.outputs.run == 'true' + working-directory: ../embed-pdf-viewer + run: pnpm run build:snippet + + - name: Pack snippet tarball + if: steps.gate.outputs.run == 'true' + working-directory: ../embed-pdf-viewer + run: | + npm pack ./snippet --pack-destination ../bentopdf/vendor/embedpdf + ls -l ../bentopdf/vendor/embedpdf + + - name: Sanitize tarball (rename pkg and pin deps) + if: steps.gate.outputs.run == 'true' + env: + UPSTREAM_VERSION: ${{ steps.upstream-version.outputs.version }} + run: | + TARBALL=$(ls vendor/embedpdf/*.tgz | sort | tail -n1) + TMP=$(mktemp -d) + tar -xzf "$TARBALL" -C "$TMP" + PKG="$TMP/package/package.json" + PKG="$PKG" node - <<'NODE' + const fs = require("fs"); + const path = process.env.PKG; + const ver = process.env.UPSTREAM_VERSION || "1.4.1"; + const pkg = JSON.parse(fs.readFileSync(path, "utf8")); + pkg.name = "embedpdf-snippet"; + pkg.dependencies = pkg.dependencies || {}; + for (const k of Object.keys(pkg.dependencies)) { + if (k.startsWith("@embedpdf/")) pkg.dependencies[k] = `^${ver}`; + if (k === "preact") pkg.dependencies[k] = "^10.17.0"; + } + fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); + NODE + NEW=vendor/embedpdf/embedpdf-snippet-${UPSTREAM_VERSION}.tgz + tar -czf "$NEW" -C "$TMP" package + # Remove any older snippet tarballs, keep only the new one + find vendor/embedpdf -maxdepth 1 -name 'embedpdf-snippet-*.tgz' ! -name "$(basename "$NEW")" -delete + rm -rf "$TMP" + ls -l vendor/embedpdf + + - name: Update package.json dependency path + if: steps.gate.outputs.run == 'true' + run: | + TARBALL=$(ls vendor/embedpdf/embedpdf-snippet-*.tgz | sort | tail -n1) + node -e "const fs=require('fs');const pkg=require('./package.json');const tar=process.argv[1];pkg.dependencies['embedpdf-snippet']='file:'+tar;fs.writeFileSync('package.json',JSON.stringify(pkg,null,2)+'\n');" "$TARBALL" + + - name: Refresh lockfile + if: steps.gate.outputs.run == 'true' + run: npm install --package-lock-only + + - name: Write upstream version marker + if: steps.gate.outputs.run == 'true' + run: | + echo "${{ steps.upstream-version.outputs.version }}" > vendor/embedpdf/.upstream-version + + - name: Create Pull Request + if: steps.gate.outputs.run == 'true' + uses: peter-evans/create-pull-request@v6 + with: + commit-message: 'chore: update embedpdf snippet' + title: 'Update vendored EmbedPDF snippet' + body: | + - Build snippet from upstream embed-pdf-viewer via `npm run build:snippet` + - Pack tarball into `vendor/embedpdf/` and point dependency to it + - Refresh package-lock.json + branch: chore/update-embedpdf-snippet + delete-branch: true diff --git a/.gitignore b/.gitignore index 55ff64b..341cb20 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.npm-cache # Editor directories and files .vscode/* diff --git a/Dockerfile b/Dockerfile index 1e2290f..373b41e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,12 @@ +# Global variable declaration: +# Build to serve under Subdirectory BASE_URL if provided, eg: "ARG BASE_URL=/pdf/", otherwise leave blank: "ARG BASE_URL=" +ARG BASE_URL= + # Build stage FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ +COPY vendor ./vendor RUN npm ci COPY . . @@ -9,44 +14,28 @@ COPY . . # Pass SIMPLE_MODE environment variable if provided ARG SIMPLE_MODE=false ENV SIMPLE_MODE=$SIMPLE_MODE -RUN npm run build -- --mode production + +# global arg to local arg +ARG BASE_URL +ENV BASE_URL=$BASE_URL + +RUN if [ -z "$BASE_URL" ]; then \ + npm run build -- --mode production; \ + else \ + npm run build -- --base=${BASE_URL} --mode production; \ + fi # Production stage FROM nginxinc/nginx-unprivileged:stable-alpine-slim LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf" -COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html +# global arg to local arg +ARG BASE_URL + +COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html${BASE_URL%/} COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp EXPOSE 8080 CMD ["nginx", "-g", "daemon off;"] - - -# Old Dockerfile for Root User -# # Build stage -# FROM node:20-alpine AS builder - -# WORKDIR /app - -# COPY package*.json ./ -# RUN npm ci - -# COPY . . - -# # Build without type checking (vite build only) -# # Pass SIMPLE_MODE environment variable if provided -# ARG SIMPLE_MODE=false -# ENV SIMPLE_MODE=$SIMPLE_MODE -# RUN npm run build -- --mode production - -# # Production stage -# FROM nginx:alpine - -# COPY --from=builder /app/dist /usr/share/nginx/html -# COPY nginx.conf /etc/nginx/nginx.conf - -# EXPOSE 8080 - -# CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 77d8ba4..4ea93d0 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ --- +## 📢 Join Us on Discord + +[![Discord](https://img.shields.io/badge/Discord-Join%20Server-7289da?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/Bgq3Ay3f2w) + +Have questions, feature requests, or want to chat with the community? Join our Discord server! + +--- + ## 📜 Licensing BentoPDF is dual-licensed: @@ -139,6 +147,20 @@ BentoPDF offers a comprehensive suite of tools to handle all your PDF needs. --- +## 🌍 Translations + +BentoPDF is available in multiple languages: + +| Language | Status | +|----------|--------| +| English | [![English](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/en/common.json) | +| German | [![German](https://img.shields.io/badge/In_Progress-yellow?style=flat-square)](public/locales/de/common.json) | +| Vietnamese | [![Vietnamese](https://img.shields.io/badge/Complete-green?style=flat-square)](public/locales/vi/common.json) | + +Want to help translate BentoPDF into your language? Check out our [Translation Guide](TRANSLATION.md)! + +--- + ## 🚀 Getting Started You can run BentoPDF locally for development or personal use. @@ -187,47 +209,19 @@ The easiest way to self-host is to download the pre-built distribution file from 3. Extract the zip file 4. Serve the extracted folder with your preferred web server -**Navigate to the extracted Folder:** +**Serve the extracted folder (requires Node.js):** + ```bash # Navigate to the extracted folder cd dist-1.7.3 # Replace with your version + +# Start a local server +npx http-server -c-1 ``` -**Serve with Python:** +The website will be accessible at: `http://localhost:8080/` -```bash -# For Python 3 -python -m http.server 8000 -``` - -The website can be accessible at: ```http://[::1]:8000/``` - -**Serve with Node.js:** - -```bash -# Install a simple server -npx serve . - -# Or if you have serve installed globally -npm install -g serve -serve . -``` - -The website can be accessible at: ```http://localhost:3000/``` - -**Serve with other tools:** - -You can also use other static file servers like: -- Go: `go run main.go` with a simple Go server -- PHP: `php -S localhost:8000` -- Ruby: `ruby -run -e httpd . -p 8000` - -The website can be accessible at: -- Go: ```http://localhost:8080/``` (default) or as specified -- PHP: ```http://localhost:8000/``` -- Ruby: ```http://localhost:8000/``` (default port can be changed) - -Simply serve the extracted folder using any static file server, and BentoPDF will work completely client-side without any server-side dependencies. +> **Note:** The `-c-1` flag disables caching for development. **Build from Source (Advanced):** @@ -247,10 +241,10 @@ npm run build # Package the distribution for hosting (optional) npm run package -# Serve the dist folder -npx serve dist +# Preview the build locally +npm run preview -The website can be accessible at: http://localhost:3000/ +# The website will be accessible at: http://localhost:4173/ ``` @@ -279,6 +273,36 @@ The website can be accessible at: ```http://localhost:3000/tools/bentopdf/``` The `npm run package` command creates a `dist-{version}.zip` file that you can use for self-hosting. +**Docker Subdirectory Deployment:** + +BentoPDF's Docker image also supports the `BASE_URL` build argument for subdirectory deployments: + +```bash +# Build for subdirectory deployment +docker build --build-arg BASE_URL=/bentopdf/ -t bentopdf . + +# Run the container +docker run -p 3000:8080 bentopdf + +# The app will be accessible at http://localhost:3000/bentopdf/ +``` + +**Combined with Simple Mode:** + +```bash +# Build with both BASE_URL and SIMPLE_MODE +docker build \ + --build-arg BASE_URL=/tools/pdf/ \ + --build-arg SIMPLE_MODE=true \ + -t bentopdf-simple . + +docker run -p 3000:8080 bentopdf-simple +``` + +> **Important**: +> - Always include trailing slashes in `BASE_URL` (e.g., `/bentopdf/` not `/bentopdf`) +> - The default value is `/` for root deployment + ### 🚀 Run with Docker Compose (Recommended) For a more robust setup with auto-restart capabilities: @@ -437,7 +461,7 @@ BentoPDF was originally built using **HTML**, **CSS**, and **vanilla JavaScript* - **PDF to Office**: Converts PDF files into editable Word, Excel, and PowerPoint formats. - **Office to PDF**: Converts Word, Excel, and PowerPoint documents into optimized PDFs. -Contributions and discussions on the roadmap are welcome! Join the conversation via [Discord](https://discord.gg/AP2Y97juZT). +Contributions and discussions on the roadmap are welcome! Join the conversation via [Discord](https://discord.gg/Bgq3Ay3f2w). --- diff --git a/TRANSLATION.md b/TRANSLATION.md new file mode 100644 index 0000000..00354a5 --- /dev/null +++ b/TRANSLATION.md @@ -0,0 +1,482 @@ +# 🌍 Translation Guide for BentoPDF + +This guide will help you add new languages or improve existing translations for BentoPDF. + +## Table of Contents + +- [Overview](#overview) +- [Quick Start](#quick-start) +- [Adding a New Language](#adding-a-new-language) +- [Translation File Structure](#translation-file-structure) +- [Where Translations Are Used](#where-translations-are-used) +- [Testing Your Translations](#testing-your-translations) +- [Translation Guidelines](#translation-guidelines) +- [Common Issues](#common-issues) + +--- + +## Overview + +BentoPDF uses **i18next** for internationalization (i18n). Currently supported languages: + +- **English** (`en`) - Default +- **German** (`de`) +- **Vietnamese** (`vi`) + +The app automatically detects the language from the URL path: +- `/en/` → English +- `/de/` → German +- `/vi/` → Vietnamese + +--- + +## Quick Start + +**To improve existing translations:** + +1. Navigate to `public/locales/{language}/common.json` +2. Find the key you want to update +3. Change the translation value +4. Save and test + +**To add a new language (e.g., Spanish):** + +1. Copy `public/locales/en/common.json` to `public/locales/es/common.json` +2. Translate all values in `es/common.json` +3. Add Spanish to `supportedLanguages` in `src/js/i18n/i18n.ts` +4. Add Spanish name to `languageNames` in `src/js/i18n/i18n.ts` +5. Test thoroughly + +--- + +## Adding a New Language + +Let's add **French** as an example: + +### Step 1: Create Translation File + +```bash +# Create the directory +mkdir -p public/locales/fr + +# Copy the English template +cp public/locales/en/common.json public/locales/fr/common.json +``` + +### Step 2: Translate the JSON File + +Open `public/locales/fr/common.json` and translate all the values: + +```json +{ + "nav": { + "home": "Accueil", + "about": "À propos", + "contact": "Contact", + "allTools": "Tous les outils" + }, + "hero": { + "title": "Votre boîte à outils PDF gratuite et sécurisée", + "subtitle": "Fusionnez, divisez, compressez et modifiez des PDF directement dans votre navigateur." + } + // ... continue translating all keys +} +``` + +⚠️ **Important**: Only translate the **values**, NOT the keys! + +✅ **Correct:** +```json +"home": "Accueil" +``` + +❌ **Wrong:** +```json +"accueil": "Accueil" +``` + +### Step 3: Register the Language + +Edit `src/js/i18n/i18n.ts`: + +```typescript +// Add 'fr' to supported languages +export const supportedLanguages = ['en', 'de', 'fr'] as const; +export type SupportedLanguage = (typeof supportedLanguages)[number]; + +// Add French display name +export const languageNames: Record = { + en: 'English', + de: 'Deutsch', + fr: 'Français', // ← Add this +}; +``` + +### Step 4: Test Your Translation + +```bash +# Start the dev server +npm run dev + +# Visit the French version +# http://localhost:5173/fr/ +``` + +--- + +## Translation File Structure + +The `common.json` file is organized into logical sections: + +```json +{ + "nav": { + // Navigation menu items + }, + "hero": { + // Homepage hero section + }, + "features": { + // Features section + }, + "tools": { + // Tool names and descriptions + }, + "upload": { + // File upload UI + }, + "settings": { + // Settings modal and keyboard shortcuts + }, + "faq": { + // FAQ section + }, + "footer": { + // Footer links and text + }, + "compliance": { + // Security compliance information + }, + "testimonials": { + // User testimonials + }, + "support": { + // Support section + }, + "alert": { + // Alert and error messages + } +} +``` + +### Key Naming Convention + +- Use **camelCase** for keys: `"deletePage"` not `"delete_page"` +- Use **nested objects** for organization: `"nav.home"` is represented as: + ```json + { + "nav": { + "home": "Home" + } + } + ``` +- Be descriptive: `"shortcutsWarning"` is better than `"warning1"` + +--- + +## Where Translations Are Used + +### 1. HTML Templates (`data-i18n` attribute) + +```html + +Home +``` + +The `data-i18n` attribute tells i18next which translation to use. + +### 2. Tool Definitions + +Tool names and descriptions are defined in `src/js/config/tools.ts` and use a special namespace: + +```typescript +{ + name: 'Merge PDF', // Used for shortcuts only + subtitle: 'Combine multiple PDFs into one file.', +} +``` + +In translations: +```json +{ + "tools": { + "mergePdf": { + "name": "PDF zusammenführen", + "subtitle": "Mehrere PDFs in eine Datei kombinieren." + } + } +} +``` + +### 3. Dynamic JavaScript (`t()` function) + +For translations that need to be applied dynamically: + +```typescript +import { t } from './i18n/i18n'; + +const message = t('alert.error'); +console.log(message); // "Error" or "Fehler" depending on language +``` + +### 4. Placeholders + +For input placeholders: + +```html + +``` + +In `common.json`: +```json +{ + "tools": { + "searchPlaceholder": "Nach einem Tool suchen..." + } +} +``` + +--- + +## Testing Your Translations + +### Manual Testing + +1. **Start development server:** + ```bash + npm run dev + ``` + +2. **Visit each language:** + - English: `http://localhost:5173/en/` + - German: `http://localhost:5173/de/` + - Vietnamese: `http://localhost:5173/vi/` + - Your new language: `http://localhost:5173/fr/` + +3. **Check these pages:** + - Homepage (`/`) + - About page (`/about.html`) + - Contact page (`/contact.html`) + - FAQ page (`/faq.html`) + - Tool pages (e.g., `/merge-pdf.html`) + +4. **Test these interactions:** + - Click the language switcher in the footer + - Navigate between pages + - Open the settings modal (click gear icon next to search) + - Try a tool to see upload messages + +### Automated Checks + +Check for missing translations: + +```bash +# This will show any missing keys +node scripts/check-translations.js +``` + +*(If this script doesn't exist, you may need to create it or manually compare JSON files)* + +### Browser Testing + +Test in different browsers: +- Chrome/Edge +- Firefox +- Safari + +--- + +## Translation Guidelines + +### 1. Keep the Tone Consistent + +BentoPDF is **friendly, clear, and professional**. Match this tone in your translations. + +✅ **Good:** +```json +"hero.title": "Ihr kostenloses und sicheres PDF-Toolkit" +``` + +❌ **Too formal:** +```json +"hero.title": "Ihr gebührenfreies und gesichertes Werkzeug für PDF-Dokumente" +``` + +### 2. Preserve Formatting + +Some strings contain HTML or special characters: + +```json +{ + "faq.analytics.answer": "We care about your privacy. BentoPDF does not track personal information. We use Simple Analytics solely to see anonymous visit counts." +} +``` + +When translating, **keep the HTML tags intact**: + +```json +{ + "faq.analytics.answer": "Wir schätzen Ihre Privatsphäre. BentoPDF verfolgt keine persönlichen Informationen. Wir verwenden Simple Analytics ausschließlich, um anonyme Besucherzahlen zu sehen." +} +``` + +### 3. Handle Plurals and Gender + +If your language has complex plural rules or gender distinctions, consult the [i18next pluralization guide](https://www.i18next.com/translation-function/plurals). + +Example: +```json +{ + "pages": "page", + "pages_plural": "pages" +} +``` + +### 4. Don't Translate Brand Names or Legal Terms + +Keep these as-is: +- BentoPDF +- PDF +- GitHub +- Discord +- Chrome, Firefox, Safari, etc. +- Terms and Conditions +- Privacy Policy +- Licensing + +### 5. Technical Terms + +For technical terms, use commonly accepted translations in your language: +- "Merge" → "Fusionner" (French), "Zusammenführen" (German) +- "Split" → "Diviser" (French), "Teilen" (German) +- "Compress" → "Compresser" (French), "Komprimieren" (German) + +If unsure, check how other PDF tools translate these terms in your language. + +### 6. String Length + +Some UI elements have limited space. Try to keep translations **similar in length** to the English version. + +If a translation is much longer, test it visually to ensure it doesn't break the layout. + +--- + +## Common Issues + +### Issue: Translations Not Showing Up + +**Solution:** +1. Clear your browser cache +2. Hard refresh (Ctrl+F5 or Cmd+Shift+R) +3. Check browser console for errors +4. Verify the JSON file is valid (no syntax errors) + +### Issue: Some Text Still in English + +**Possible causes:** +1. Missing translation key in your language file +2. Missing `data-i18n` attribute in HTML +3. Hardcoded text in JavaScript + +**Solution:** +- Compare your language file with `en/common.json` to find missing keys +- Search the codebase for hardcoded strings + +### Issue: JSON Syntax Error + +**Symptoms:** +``` +SyntaxError: Unexpected token } in JSON at position 1234 +``` + +**Solution:** +- Use a JSON validator: https://jsonlint.com/ +- Common mistakes: + - Trailing comma after last item + - Missing or extra quotes + - Unescaped quotes inside strings (use `\"`) + +### Issue: Language Switcher Not Showing New Language + +**Solution:** +Make sure you added the language to both arrays in `i18n.ts`: +```typescript +export const supportedLanguages = ['en', 'de', 'fr']; // ← Add here +export const languageNames = { + en: 'English', + de: 'Deutsch', + fr: 'Français', // ← And here +}; +``` + +--- + +## File Checklist + +When adding a new language, make sure these files are updated: + +- [ ] `public/locales/{lang}/common.json` - Main translation file +- [ ] `src/js/i18n/i18n.ts` - Add to `supportedLanguages` and `languageNames` +- [ ] Test all pages: homepage, about, contact, FAQ, tool pages +- [ ] Test settings modal and shortcuts +- [ ] Test language switcher in footer +- [ ] Verify URL routing works (`/{lang}/`) + +--- + +## Getting Help + +If you have questions or need help: + +1. Check existing translations in `public/locales/de/common.json` for reference +2. Open an issue on [GitHub](https://github.com/alam00000/bentopdf/issues) +3. Join our [Discord server](https://discord.gg/Bgq3Ay3f2w) + +--- + +## Contributing Your Translation + +Once you've completed a translation: + +1. **Test thoroughly** (see [Testing Your Translations](#testing-your-translations)) +2. **Fork the repository** on GitHub +3. **Create a new branch**: `git checkout -b add-french-translation` +4. **Commit your changes**: `git commit -m "Add French translation"` +5. **Push to your fork**: `git push origin add-french-translation` +6. **Open a Pull Request** with: + - Description of the language added + - Screenshots showing the translation in action + - Confirmation that you've tested all pages + +Thank you for contributing to BentoPDF! 🎉 + +--- + +## Translation Progress + +Current translation coverage: + +| Language | Code | Status | Maintainer | +|----------|------|--------|------------| +| English | `en` | ✅ Complete | Core team | +| German | `de` | 🚧 In Progress | Core team | +| Vietnamese | `vi` | ✅ Complete | Community | +| Your Language | `??` | 🚧 In Progress | You? | + +--- + +**Last Updated**: December 2025 diff --git a/about.html b/about.html index 3aca48d..42bb2c6 100644 --- a/about.html +++ b/about.html @@ -5,6 +5,14 @@ About Bentopdf - Fast, Private, and Free PDF Tools + + + + + + + + @@ -13,10 +21,7 @@