Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.git
.github
.vscode
.dart_tool
.packages
build
example
*.md
LICENSE
.gitignore
.pubignore
analysis_options.yaml
*.txt.template
53 changes: 53 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Docker

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
docker:
name: "Build & Push"
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: 📦 Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: 🏷️ Extract version from pubspec.yaml
id: version
run: |
VERSION=$(grep -oP '^version:\s*\K\S+' pubspec.yaml)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Image version: $VERSION"

- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: 🔧 Set up QEMU
uses: docker/setup-qemu-action@v3

- name: 🔑 Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: 🐳 Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/sheety:latest
${{ secrets.DOCKERHUB_USERNAME }}/sheety:${{ steps.version.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
66 changes: 66 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,64 @@
"group": "dart"
}
},
{
"label": "docker:build",
"detail": "Build Docker image locally",
"icon": {
"color": "terminal.ansiBlue",
"id": "package"
},
"type": "shell",
"command": "docker build -t plugfox/sheety:latest -t plugfox/sheety:${input:dockerTag} .",
"dependsOn": [],
"args": [],
"group": {
"kind": "none",
"isDefault": false
},
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}"
},
"isBackground": false,
"presentation": {
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"group": "docker"
}
},
{
"label": "docker:push",
"detail": "Build multi-platform image and push to DockerHub (plugfox)",
"icon": {
"color": "terminal.ansiBlue",
"id": "cloud-upload"
},
"type": "shell",
"command": "docker buildx build --platform linux/amd64,linux/arm64 -t plugfox/sheety:latest -t plugfox/sheety:${input:dockerTag} --push .",
"dependsOn": [],
"args": [],
"group": {
"kind": "none",
"isDefault": false
},
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}"
},
"isBackground": false,
"presentation": {
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"group": "docker"
}
},
{
"label": "dart:format",
"detail": "Format all files in the project",
Expand Down Expand Up @@ -61,5 +119,13 @@
"group": "dart"
}
}
],
"inputs": [
{
"id": "dockerTag",
"type": "promptString",
"description": "Docker image version tag (e.g. 0.4.2)",
"default": "0.4.2"
}
]
}
46 changes: 46 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ==============================================================================
# Stage 1: Build AOT-compiled binaries
# ==============================================================================
FROM dart:stable AS build

WORKDIR /app

# Cache dependencies
COPY pubspec.yaml pubspec.lock ./
RUN dart pub get --no-example

# Copy source and compile
COPY bin/ bin/
RUN dart compile exe bin/generate.dart -o /app/bin/generate && \
dart compile exe bin/localize.dart -o /app/bin/localize

# Collect runtime dependencies (glibc, ld-linux, etc.)
RUN mkdir -p /runtime/lib /runtime/lib64 && \
# Use ldd to find all shared libraries needed by compiled binaries
(ldd /app/bin/generate /app/bin/localize || true) \
| grep -oP '(?<==> )\S+' | sort -u | while read lib; do \
dir="/runtime$(dirname "$lib")"; \
mkdir -p "$dir"; \
cp "$lib" "$dir/"; \
done && \
# Copy the dynamic linker
cp /lib64/ld-linux-* /runtime/lib64/ 2>/dev/null || \
cp /lib/ld-linux-* /runtime/lib/ 2>/dev/null || true

# ==============================================================================
# Stage 2: Minimal scratch image
# ==============================================================================
FROM scratch

# CA certificates for HTTPS (Google Sheets API, OpenAI API)
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt

# Runtime libraries (glibc, ld-linux)
COPY --from=build /runtime/ /

# Compiled binaries
COPY --from=build /app/bin/generate /bin/generate
COPY --from=build /app/bin/localize /bin/localize

ENTRYPOINT ["/bin/generate"]
116 changes: 106 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@

1. **Dart SDK >= 3.7.0 < 4.0.0**
2. **Service Account** with **Google Sheets API** enabled:

- Create a new project in [Google Cloud Console](https://console.cloud.google.com/).
- Enable **Google Sheets API** for that project.
- Create a **Service Account** and download its credentials JSON (e.g., `credentials.json`).
Expand Down Expand Up @@ -151,12 +150,10 @@ dart pub global run sheety_localization:generate \
## Integration Steps

1. **Prepare your Google Sheet**

- Create a new or reuse an existing Google Sheet.
- For each sheet/tab within that spreadsheet, set up the header in the first row as follows: `label | description | meta | en | ru | es | de | ... (other locales)`.

- Each subsequent row represents one localized string:

- **`label`**: Unique key (used as a Dart getter name).
- **`description`**: Short human-readable note (e.g., `"Login button text"`).
- **`meta`**: JSON string with any metadata (e.g., `{"plural":"one","other":"{count} items"}`).
Expand All @@ -165,7 +162,6 @@ dart pub global run sheety_localization:generate \
2. **Create a Google Cloud project & Service Account**

> **Note:** [How to use client libraries to access Google APIs](https://cloud.google.com/docs/authentication/client-libraries#creating_a_service_account)

- Go to [Google Cloud Console](https://console.cloud.google.com/).
- Create a new project or select an existing one.
- Enable the **Google Sheets API** for that project.
Expand All @@ -174,7 +170,6 @@ dart pub global run sheety_localization:generate \
- Download the JSON key file (e.g., `credentials.json`).

3. **Share your spreadsheet with the Service Account**

- In Google Sheets, click **Share** (top right).
- Add your Service Account's email (from the JSON file) with **Viewer** or **Editor** rights.

Expand Down Expand Up @@ -242,9 +237,7 @@ dart pub global run sheety_localization:generate \
This formula translates the English text to Russian whenever `E2` is empty. Replace `"ru"` with any target locale code.

7. **(Optional) Conditional formatting to highlight missing or machine-translated cells**

- Set up **Conditional Formatting** in Google Sheets:

- Select all cells in a language column (e.g., column `E` for Russian).
- Use a rule like `=ISFORMULA(E2)` to gray out any cell where a formula (i.e., machine translation) is used.
- Use `=E2=""` to highlight cells that are still empty (missing translation).
Expand All @@ -267,7 +260,6 @@ dart pub global run sheety_localization:generate \
```

- This will produce:

- `lib/localization.dart` (exports all generated classes)
- One ARB file per sheet (e.g., `app_en.arb`, `app_ru.arb`, etc.) under `lib/src/l10n/`
- One Dart file per locale under `lib/src/generated/` (e.g., `app_localizations_en.dart`, `app_localizations_ru.dart`, etc.)
Expand Down Expand Up @@ -427,6 +419,112 @@ dart pub global run sheety_localization:localize \

---

## Docker

A minimal Docker image with AOT-compiled `generate` and `localize` binaries is available on DockerHub. No Dart SDK installation required.

### Pull

```bash
docker pull plugfox/sheety:latest
```

### Generate localization files

```bash
docker run --rm \
-v $(pwd)/credentials.json:/credentials.json:ro \
-v $(pwd)/lib:/output \
plugfox/sheety:latest \
--credentials=/credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--lib=/output \
--arb=src/l10n \
--gen=src/generated \
--prefix=app \
--no-format
```

> **Note:** `--no-format` is recommended inside Docker since `dart format` is not available in the minimal image. Format the generated files on the host afterwards.

The default entrypoint is `/bin/generate`. Mount your `credentials.json` as read-only and the output directory as a volume.

### AI-powered localization (translate missing cells)

Override the entrypoint to use `/bin/localize`:

```bash
docker run --rm \
--entrypoint /bin/localize \
-v $(pwd)/credentials.json:/credentials.json:ro \
plugfox/sheety:latest \
--credentials=/credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--token=<YOUR_OPENAI_API_KEY> \
--model=gpt-5-mini \
--batch=3 \
--workers=6
```

Alternatively, pass the API key via a file:

```bash
docker run --rm \
--entrypoint /bin/localize \
-v $(pwd)/credentials.json:/credentials.json:ro \
-v $(pwd)/openai_token.txt:/token.txt:ro \
plugfox/sheety:latest \
--credentials=/credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--token-file=/token.txt
```

### Custom system prompt for localization

```bash
docker run --rm \
--entrypoint /bin/localize \
-v $(pwd)/credentials.json:/credentials.json:ro \
-v $(pwd)/my_prompt.txt:/prompt.txt:ro \
plugfox/sheety:latest \
--credentials=/credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--token=<YOUR_OPENAI_API_KEY> \
--prompt=/prompt.txt
```

### Print help

```bash
# generate help
docker run --rm plugfox/sheety --help

# localize help
docker run --rm --entrypoint /bin/localize plugfox/sheety:latest --help
```

### Building the image locally

```bash
# Clone the repository
git clone https://github.com/DoctorinaAI/sheety_localization.git

# Navigate into the project directory
cd sheety_localization

# Local build
docker build -t username/sheety:latest .

# Multi-platform build and push to DockerHub
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t username/sheety:latest \
-t username/sheety:0.4.2 \
--push .
```

---

## Tips & Best Practices

- **Keep your ARB files under version control** (for reference and manual tweaks).
Expand Down Expand Up @@ -495,13 +593,11 @@ If you want to auto-fill missing translations from English to Russian:
## Example: Conditional Formatting Rules

1. **Gray out machine translations**

- Select entire column (e.g., column `E` for `ru`).
- Add a custom formula rule: `=ISFORMULA(E2)`.
- Set fill color to light gray.

2. **Highlight empty cells (missing translations)**

- Select entire column (e.g., `E`).
- Add a custom formula rule: `=E2=""`.
- Set fill color to red (or any noticeable color).
Loading