# Specification: Translation Dictionary for Living Christianity

## Overview

"Sanasto Wiki" is a web-based dictionary application for simultaneous translators in the living Christianity. The application provides publicly accessible translations while restricting editing and commenting to invited contributors.

---

## Core Concepts

### Supported Languages

Initial seed data:

| Code | Name | Native Name | Sort Order |
|------|------|-------------|------------|
| fi | Finnish | Suomi | 1 |
| en | English | English | 2 |
| sv | Swedish | Svenska | 3 |
| no | Norwegian | Norsk | 4 |
| de | German | Deutsch | 5 |
| ru | Russian | Русский | 6 |


Language codes are fixed because translations are stored in columns on `entries`. The `supported_languages` table controls labels, ordering, and whether a language is active. To add a language, you must add a new column to `entries` and add a corresponding row to `supported_languages`.

### Entry
An entry represents a translatable unit which may be:

- `word` — Single word
- `phrase` — Multi-word expression or idiom, sentence
- `proper_name` — Person's name (for consistent transliteration)
- `title` — Book, publication, or hymn title
- `reference` — Biblical or doctrinal term
- `other` — Any other translatable text unit

Each entry has translations in multiple languages.

### Suggested Meaning
When translators disagree on a translation or want to suggest alternatives (regional variations, contextual meanings, etc.), they can submit a suggested meaning for community review.

---

## Technical Stack

* Framework: Rails 8
* Database: SQLite with FTS5
* Authentication: Rails 8 built-in authentication
* Authorization: Invitation-only for contributors
* Deployment: Kamal

---

## Setup / First User

When setup has not been completed, the `/setup` route is accessible for creating the initial admin account. Completion is tracked in the database. The first user created will be the system's default contact email (accessible via `User.first.email`).

For detailed setup instructions, see [SETUP_GUIDE.md](docs/SETUP_GUIDE.md).

**Quick Start:**
1. Deploy the application
2. Navigate to `/setup`
3. Create your admin account
4. Start inviting contributors

---

## Authentication Flow

### Public Access
- No authentication required for browsing and searching entries
- Read-only access to all dictionary content

### Invitation-Only Registration
- Admins send email invitations with unique tokens
- Invited users register by setting name and password
- `invitation_token` cleared after successful registration

### Login & Sessions
- Email + password authentication
- Session-based (encrypted cookies, Rails default)
- Optional "remember me" for extended sessions

### Authorization
- **Contributor**: Create/edit entries, add comments, suggest translations
- **Reviewer**: All contributor actions + review suggestions, verify entries
- **Admin**: All actions + invite users, manage roles

### Security
- Passwords hashed with bcrypt
- Rate limiting on login attempts
- Invitation tokens expire after 14 days

---

## Database Schema
```
# db/schema.rb

ActiveRecord::Schema[8.0].define(version: 2025_01_22_100000) do
  create_table "entries", force: :cascade do |t|
    t.integer "category", null: false  # word, phrase, proper_name, title, reference, other
    
    # Language columns
    t.string "fi"  # Finnish
    t.string "en"  # English
    t.string "sv"  # Swedish
    t.string "no"  # Norwegian
    t.string "ru"  # Russian
    t.string "de"  # German
    
    t.text "notes"
    t.boolean "verified", default: false
    t.integer "created_by_id"
    t.integer "updated_by_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    
    t.index ["category"], name: "index_entries_on_category"
  end

  create_table "suggested_meanings", force: :cascade do |t|
    t.integer "entry_id", null: false
    t.string "language_code", null: false
    t.string "alternative_translation", null: false
    t.text "context"
    t.text "reasoning"
    t.string "source"
    t.string "region"
    t.integer "status", default: 0  # pending, accepted, rejected
    t.integer "submitted_by_id"
    t.integer "reviewed_by_id"
    t.datetime "reviewed_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    
    t.index ["entry_id"], name: "index_suggested_meanings_on_entry_id"
    t.index ["language_code"], name: "index_suggested_meanings_on_language_code"
    t.index ["status"], name: "index_suggested_meanings_on_status"
  end

  create_table "comments", force: :cascade do |t|
    t.integer "user_id", null: false
    t.string "commentable_type", null: false
    t.integer "commentable_id", null: false
    t.text "body", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    
    t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable"
  end

  create_table "users", force: :cascade do |t|
    t.string "email", null: false
    t.string "password_digest", null: false
    t.string "name"
    t.integer "role", default: 0  # contributor, reviewer, admin
    t.string "primary_language"
    t.string "invitation_token"
    t.datetime "invitation_sent_at"
    t.datetime "invitation_accepted_at"
    t.integer "invited_by_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true
  end

  create_table "entry_versions", force: :cascade do |t|
    t.integer "entry_id", null: false
    t.integer "user_id", null: false
    t.json "changes_made", null: false
    t.string "change_type"  # create, update, verify
    t.datetime "created_at", null: false
    
    t.index ["entry_id"], name: "index_entry_versions_on_entry_id"
  end

  create_table "supported_languages", force: :cascade do |t|
    t.string "code", null: false
    t.string "name", null: false
    t.string "native_name", null: false
    t.integer "sort_order", default: 0
    t.boolean "active", default: true
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    
    t.index ["code"], name: "index_supported_languages_on_code", unique: true
  end

  add_foreign_key "suggested_meanings", "entries"
  add_foreign_key "suggested_meanings", "supported_languages", column: "language_code", primary_key: "code"
  add_foreign_key "suggested_meanings", "users", column: "submitted_by_id"
  add_foreign_key "suggested_meanings", "users", column: "reviewed_by_id"
  add_foreign_key "comments", "users"
  add_foreign_key "entries", "users", column: "created_by_id"
  add_foreign_key "entries", "users", column: "updated_by_id"
  add_foreign_key "entry_versions", "entries"
  add_foreign_key "entry_versions", "users"
end
```

---

## Initial Data

See 'public/Kristillisyyden sanasto ver 23.5.2013.xlsx'

---

## User Roles & Permissions

| Action | Public | Contributor | Reviewer | Admin |
|--------|--------|-------------|----------|-------|
| View entries | ✓ | ✓ | ✓ | ✓ |
| Search entries | ✓ | ✓ | ✓ | ✓ |
| Create entry | | ✓ | ✓ | ✓ |
| Edit entry | | ✓ | ✓ | ✓ |
| Add comment | | ✓ | ✓ | ✓ |
| Submit suggested meaning | | ✓ | ✓ | ✓ |
| Review suggested meanings | | | ✓ | ✓ |
| Mark entry as verified | | | ✓ | ✓ |
| Invite new users | | | | ✓ |
| Manage users | | | | ✓ |

---

## Features

### Public Features

**Search & Browse**
- Full-text search across all languages
- Filter by category
- Alphabetical browsing per language
- View entry with all translations
- Download the entries table as an XLSX file, without :id attribute

### Contributor Features

**Entry Management**
- Create new entries with translations in known languages
- Edit existing translations
- Leave empty translations for others to fill in
- Add notes/context to entries
- View edit history

**Discussion**
- Add comments to entries
- Submit alternative translations as suggested meanings
- Participate in translation discussions

### Reviewer Features

**Quality Control**
- Review and approve/reject suggested meanings
- Mark entries as verified
- Merge duplicate entries

### Admin Features

**User Management**
- Send invitations via email
- Assign/change user roles
- Deactivate users

**System Management**
- Bulk import/export (CSV)
- Database backup

---

## User Interface

### Pages

1. **Home/Search** — Search box, quick stats, recent entries
2. **Browse** — Alphabetical listing with language selection
3. **Entry View** — Single entry with all translations, comments, suggested meanings
4. **Entry Edit** — Form for editing translations (contributors only)
5. **New Entry** — Form for creating entries (contributors only)
6. **Suggested Meanings Queue** — List for reviewers
7. **User Profile** — Personal settings, contribution history
8. **Admin Dashboard** — User management, invitations, stats

### Design Principles
- Clean, readable typography (translations must be easy to read quickly)
- Mobile-friendly (translators may use during services)
- Fast search (primary use case)
- Clear visual distinction between verified and unverified entries

---

## API (Optional Future)

REST API for potential mobile app or integration:
```
GET    /api/entries
GET    /api/entries/:id
GET    /api/entries/search?q=:query&lang=:code
POST   /api/entries (authenticated)
PATCH  /api/entries/:id (authenticated)
```

## Deployment

Server: Single VPS with Kamal
Database: SQLite with Litestream for replication/backup
Assets: Propshaft (Rails 8 default)
Background Jobs: Solid Queue (for invitation emails)


## Future Considerations

Export to PDF/print format for offline use
Audio pronunciation recordings
Mobile app
Offline mode with sync
Additional languages via migration
