Dockerfile, docker-compose, Gitea CI, Makefile, i18n (en/fa)
Some checks failed
CI / build (push) Failing after 27s

This commit is contained in:
2026-06-23 20:23:04 +03:30
parent 7176f9b81d
commit 0bb5db46d0
8 changed files with 200 additions and 0 deletions

7
.env.example Normal file
View File

@@ -0,0 +1,7 @@
BOT_TOKEN=YOUR_TELEGRAM_BOT_TOKEN
HTTP_PROXY=
DB_PATH=db.sqlite3
BOT_WEBHOOK_URL=
BOT_LISTEN=:8080
BOT_TLS_CERT=
BOT_TLS_KEY=

27
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,27 @@
name: CI
on:
push:
branches: [main, master]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Check formatting
run: test -z "$(gofmt -d .)" || exit 1
- name: Vet
run: go vet ./...
- name: Build
run: go build ./...
- name: Test
run: go test ./...

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /usr/local/bin/worktime-bot ./cmd/bot/
FROM alpine:3.18
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /data
COPY --from=builder /usr/local/bin/worktime-bot /usr/local/bin/worktime-bot
COPY --from=builder /src/db/schema.sql ./db/schema.sql
VOLUME ["/data"]
CMD ["worktime-bot"]

32
Makefile Normal file
View File

@@ -0,0 +1,32 @@
.PHONY: build run test clean docker docker-run lint vet
BINARY=worktime-bot
BUILD_DIR=.build
build: $(BUILD_DIR)/$(BINARY)
$(BUILD_DIR)/$(BINARY): cmd/bot/*.go internal/**/*.go pkg/**/*.go go.mod go.sum
mkdir -p $(BUILD_DIR)
go build -o $(BUILD_DIR)/$(BINARY) ./cmd/bot/
run: build
$(BUILD_DIR)/$(BINARY)
test:
go test ./...
lint: vet
vet:
go vet ./...
clean:
rm -rf $(BUILD_DIR)
docker:
docker compose build
docker-run: docker
docker compose up -d
.PHONY: all
all: build test lint

15
docker-compose.yml Normal file
View File

@@ -0,0 +1,15 @@
services:
worktime-bot:
build: .
env_file:
- .env
environment:
- BOT_TOKEN=${BOT_TOKEN}
- HTTP_PROXY=${HTTP_PROXY-}
- DB_PATH=/data/db.sqlite3
volumes:
- worktime_data:/data
volumes:
worktime_data:
driver: local

16
pkg/i18n/en.json Normal file
View File

@@ -0,0 +1,16 @@
{
"unknown_command": "Unknown command",
"clock_in": "Clock In",
"clock_out": "Clock Out",
"error": "Error",
"no_permission": "You do not have permission",
"remote_switched_on": "Remote mode enabled",
"remote_switched_off": "Remote mode disabled",
"already_clocked_in": "You are already clocked in",
"already_clocked_out": "You are already clocked out",
"break_started": "Break started",
"remote_enabled": "Remote mode enabled",
"remote_disabled": "Remote mode disabled",
"dayoff_set": "Today marked as day off",
"dayoff_removed": "Day off removed"
}

16
pkg/i18n/fa.json Normal file
View File

@@ -0,0 +1,16 @@
{
"unknown_command": "دستور نامشخص",
"clock_in": "ورودی کاری",
"clock_out": "خروج کاری",
"error": "خطا",
"no_permission": "دسترسی ندارید",
"remote_switched_on": "وضعیت راه دور فعال شد",
"remote_switched_off": "وضعیت راه دور غیرفعال شد",
"already_clocked_in": "شما قبلاً ورود زده‌اید",
"already_clocked_out": "شما قبلاً خروج زده‌اید",
"break_started": "استراحت شروع شد",
"remote_enabled": "وضعیت راه دور فعال شد",
"remote_disabled": "وضعیت راه دور غیرفعال شد",
"dayoff_set": "امروز به عنوان تعطیل ثبت شد",
"dayoff_removed": "تعطیلی برداشته شد"
}

67
pkg/i18n/translator.go Normal file
View File

@@ -0,0 +1,67 @@
package i18n
import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"sync"
)
// Translator handles translation loading and retrieval.
type Translator struct {
mu sync.RWMutex
messages map[string]map[string]string // lang -> key -> message
}
// NewTranslator loads JSON locale files en.json and fa.json from the i18n directory.
// defaultLang sets the default fallback language (e.g., "en").
func NewTranslator(defaultLang string) (*Translator, error) {
if defaultLang == "" {
defaultLang = "en"
}
t := &Translator{
messages: make(map[string]map[string]string),
}
// Load known locale files: en.json and fa.json located in the same package directory.
for _, lang := range []string{"en", "fa"} {
filePath := filepath.Join("i18n", lang+".json")
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
var m map[string]string
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
}
t.messages[lang] = m
}
// Ensure default language exists.
if _, ok := t.messages[defaultLang]; !ok {
return nil, fmt.Errorf("default language %s not found", defaultLang)
}
return t, nil
}
// Get returns the translated string for a given key and language.
// Falls back to English if the key/language is missing.
func (t *Translator) Get(key, lang string) string {
if lang == "" {
lang = "en"
}
t.mu.RLock()
defer t.mu.RUnlock()
if msgs, ok := t.messages[lang]; ok {
if v, ok := msgs[key]; ok {
return v
}
}
// fallback to English messages
if msgs, ok := t.messages["en"]; ok {
if v, ok := msgs[key]; ok {
return v
}
}
// If not found, return the key itself.
return key
}