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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ scripts/sort_contibutors/contributors_file.*
scripts/sort_contibutors/.tools/
scripts/sort_contibutors/bin/

## translations
scripts/do_translate/explanations_bundle*
scripts/do_translate/bin/

#optimizations
.github/scripts/binary_version.txt

Expand Down
47 changes: 47 additions & 0 deletions scripts/do_translate/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
BINARY_NAME := translate
BUILD_DIR := bin
GO := go

LANG_CODE ?= nl

.PHONY: build run construct destruct lint test vet security clean

build:
mkdir -p $(BUILD_DIR)
$(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/translator

run: build
./$(BUILD_DIR)/$(BINARY_NAME) $(ARGS)

# File mode: bundle all explanation files into one file for Google Translate
construct: build
./$(BUILD_DIR)/$(BINARY_NAME) --construct $(ARGS)

# File mode: recreate files with a language suffix, e.g. make destruct LANG_CODE=nl
destruct: build
./$(BUILD_DIR)/$(BINARY_NAME) --destruct --language $(LANG_CODE) $(ARGS)

lint:
@command -v golangci-lint >/dev/null 2>&1 || { \
echo "golangci-lint not found. Install from https://golangci-lint.run/usage/install/"; \
exit 1; \
}
golangci-lint run ./...

test:
$(GO) test ./...

vet:
$(GO) vet ./...

security:
@command -v gosec >/dev/null 2>&1 || { \
echo "gosec not found. Installing with: go install github.com/securego/gosec/v2/cmd/gosec@latest"; \
GOBIN=$$(pwd)/.tools $(GO) install github.com/securego/gosec/v2/cmd/gosec@latest || exit 1; \
PATH=$$(pwd)/.tools:$$PATH gosec ./...; \
exit $$?; \
}
gosec ./...

clean:
rm -rf $(BUILD_DIR) .tools
120 changes: 120 additions & 0 deletions scripts/do_translate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# File Translator

A Go CLI for translating text files using Google Cloud Translation API.

## Requirements

- Go 1.27 or newer
- A Google Cloud project
- Cloud Translation API enabled
- Credentials with permission to use Cloud Translation

## Install dependencies

go mod tidy

## The cheap manual way

Spet 1: make the binaries:

`make`

Step 2: create the explanations_bundle.txt:

`./bin/translate --construct`

Step 3: save the contexts in a docx file

`cat explanations_bundle.txt | pbcopy`

Step 4: Open word and paste the contents, save it to the disk as a docx file

Step 5: open https://translate.google.com/?sl=en&tl=nl&op=docs select a target langauge, upload the doc and complete the translation process and download the file.

Step 6: open the translated doc and copy the contents to a txt file. e.g. `explanations_bundle_nl.txt` and make sure the `@@@@ FILE:` part is not translated (if it is, reconstruct it to FILE again).

step 7: now deconstruct the new file (e.g. `explanations_bundle_nl`) with

` ./bin/translate -bundle explanations_bundle_nl.txt -destruct -language nl`

Made a mistake? if you copied the wrong nl files in the `-destruct` from step 7, use the following command to remove them again:

` rm ../../src/main/resources/explanations/*nl.adoc `

## Configure authentication when doing cloud based translation

Using the Google Cloud CLI:

gcloud auth application-default login
gcloud config set project YOUR_PROJECT_ID

Enable the Translation API:

gcloud services enable translate.googleapis.com

Set the project ID:

export GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID

## Usage

Translate English to Dutch:

go run ./cmd/translator \
-input input.txt \
-output output.nl.txt \
-source en \
-target nl

Let Google detect the source language:

go run ./cmd/translator \
-input input.txt \
-output output.de.txt \
-target de

Specify a project explicitly:

go run ./cmd/translator \
-project YOUR_PROJECT_ID \
-input input.txt \
-output output.fr.txt \
-target fr

## File mode (manual translation via Google Translate)

Combine all explanation `.adoc` files into one file (run from this directory;
use `-dir` to override the explanations path, `-bundle` for the bundle name):

go run ./cmd/translator --construct

Paste the bundle into Google Translate (keep the `@@@@ FILE: ... @@@@` lines),
save the result over the bundle, then recreate the files with a language suffix
(e.g. `challenge1_nl.adoc`):

go run ./cmd/translator --destruct --language nl

## Build

go build -o bin/translator ./cmd/translator

Then run:

./bin/translator \
-input input.txt \
-output output.nl.txt \
-target nl

## Supported language codes

Examples:

- en: English
- nl: Dutch
- de: German
- fr: French
- es: Spanish
- it: Italian
- pt: Portuguese
- ja: Japanese
- zh: Chinese
Binary file added scripts/do_translate/bin/translate
Binary file not shown.
106 changes: 106 additions & 0 deletions scripts/do_translate/cmd/translator/filemode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package main

import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)

var (
translatedName = regexp.MustCompile(`_[a-z]{2}(-[A-Za-z]{2})?\.adoc$`)
markerLine = regexp.MustCompile(
`^\W*FILE:\s*([\w.\-]+\.adoc)\W*$`,
)
langCode = regexp.MustCompile(`^[a-zA-Z]{2,3}([-_][A-Za-z]{2})?$`)
)

// construct merges all explanation adoc files into one bundle file.
func construct(dir, bundle string) error {
entries, err := os.ReadDir(dir)
if err != nil {
return fmt.Errorf("read dir: %w", err)
}
var names []string
for _, e := range entries {
n := e.Name()
if e.IsDir() || !strings.HasSuffix(n, ".adoc") ||
translatedName.MatchString(n) {
continue
}
names = append(names, n)
}
sort.Strings(names)

var sb strings.Builder
for _, n := range names {
content, err := os.ReadFile(filepath.Join(dir, n))
if err != nil {
return fmt.Errorf("read %s: %w", n, err)
}
fmt.Fprintf(&sb, "@@@@ FILE: %s @@@@\n", n)
sb.WriteString(strings.TrimRight(string(content), "\n"))
sb.WriteString("\n\n")
}
if err := os.WriteFile(bundle, []byte(sb.String()), 0644); err != nil {
return fmt.Errorf("write bundle: %w", err)
}
fmt.Printf("Combined %d files into %s\n", len(names), bundle)
return nil
}

// destruct splits a (translated) bundle back into <name>_<lang>.adoc files.
func destruct(dir, bundle, lang string) error {
if !langCode.MatchString(lang) {
return fmt.Errorf("invalid language code %q", lang)
}
data, err := os.ReadFile(bundle)
if err != nil {
return fmt.Errorf("read bundle: %w", err)
}

var (
current string
buf []string
count int
)
flush := func() error {
if current == "" {
return nil
}
out := strings.TrimRight(strings.Join(buf, "\n"), "\n") + "\n"
name := strings.TrimSuffix(current, ".adoc") +
"_" + lang + ".adoc"
if err := os.WriteFile(
filepath.Join(dir, name), []byte(out), 0644,
); err != nil {
return fmt.Errorf("write %s: %w", name, err)
}
count++
return nil
}

for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimRight(line, "\r")
if m := markerLine.FindStringSubmatch(
strings.TrimSpace(line),
); m != nil {
if err := flush(); err != nil {
return err
}
current, buf = filepath.Base(m[1]), nil
continue
}
buf = append(buf, line)
}
if err := flush(); err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no FILE markers found in %s", bundle)
}
fmt.Printf("Recreated %d files in %s\n", count, dir)
return nil
}
Loading
Loading