Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3a5ee5a
Add Turkish interface language
muhammedkoramaz Sep 24, 2026
b495660
Merge branch 'develop' into feature/turkish-translation
artcc Sep 24, 2026
3ca48ce
Merge pull request #345 from muhammedkoramaz/feature/turkish-translation
artcc Sep 24, 2026
eb73ba3
Add Turkish support for dashboard banners and release notes
artcc Sep 24, 2026
6364ab8
Localize UI labels and defer review prompt form rendering
artcc Sep 24, 2026
89c97ec
Add Swedish locale support across the stack
artcc Sep 24, 2026
b8771fe
Format dashboard banner locale literal
artcc Sep 24, 2026
367ac26
Add Danish locale support across backend and frontend
artcc Sep 24, 2026
7976a30
Consolidate changelog entries for new language support
artcc Sep 24, 2026
d9b3ce5
Add Finnish locale support across backend and frontend
artcc Sep 24, 2026
c500b35
Add Croatian locale support across backend and frontend
artcc Sep 24, 2026
4b88916
Update native-language help labels and locale-specific titles
artcc Sep 24, 2026
e741aa8
Improve locale-aware formatting and translations
artcc Sep 24, 2026
f6ddcee
Stabilize ReviewPrompt test with deferred review fetch
artcc Sep 24, 2026
d22e56e
Localize review, billing, and error messaging
artcc Sep 24, 2026
1b5d206
Improve locale handling and voice message localization
artcc Sep 24, 2026
0e32759
Refine locale detection and pluralized translations
artcc Sep 24, 2026
9482362
Merge pull request #346 from artcc/feature/languages
artcc Sep 25, 2026
caab870
Update changelog release date for 1.9.25
artcc Sep 25, 2026
980a352
Sort banner locales and mark incomplete translations
artcc Sep 25, 2026
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AGENTS.md — FreeLingo

**Current version: 1.9.20**
**Current version: 1.9.25**

## Project

Expand Down
27 changes: 25 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,31 @@

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.9.25] - 2026-09-25

### Added

- Turkish (`tr`), Swedish (`sv`), Danish (`da`), Finnish (`fi`), and Croatian (`hr`) interface and native-language options across registration, Settings, and administration, with translated UI, learning feedback, voice-session titles, and emails. These are not study languages.
- Dashboard announcements can use these five languages as source or translations alongside the original ten.

### Changed

- Dashboard announcements with ten through fourteen translations remain readable; saving requires all fifteen and completing a translation creates a new revision. The editor loads the chosen source language's text before translating.
- What's New presents the five interface languages (Croatian, Danish, Finnish, Swedish, and Turkish) in each of the fifteen interface locales alongside a general improvements note.
- Interface text, resource counts, accessible controls, payment-recovery and error messages, administrator badges, review ratings, learning-language names, dates, numbers, and prices follow the selected interface language. Study help displays the user's native-language name, and voice-session dates follow native-language conventions.
- The review prompt waits for the existing-review lookup before showing the rating form. The Dutch, Polish, Romanian, and Russian FAQs explain the default public-registration setting.

### Fixed

- Saving the interface language synchronizes locale cookies and applies the selection when the displayed language differs, including when that selection was already saved in the profile.
- Initial locale detection preserves an existing supported interface-language cookie and ignores `Accept-Language` entries with zero or out-of-range quality weights.
- Post-assessment voice-trial prompts and Premium continuation messages are translated in French, Portuguese, German, Italian, Polish, Dutch, Romanian, and Russian.
- Voice transcription, tutor-response, speech-generation, and transport errors display localized messages instead of raw backend or WebSocket diagnostics.
- Croatian feedback comment counts use the correct plural forms, including counts such as 21 and 31.
- Password reset displays the current password requirements and validates the 10-25 character length consistently with the backend, with translated errors across all fifteen interface locales.
- Transactional emails declare the language of their rendered content in HTML, including the English fallback for unsupported locales.

## [1.9.20] - 2026-09-24

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
![Next.js](https://img.shields.io/badge/next.js-16-black?style=flat-square)
![Python](https://img.shields.io/badge/python-3.14-blue?style=flat-square)
![Self-hosted](https://img.shields.io/badge/self--hosted-yes-orange?style=flat-square)
![Version](https://img.shields.io/badge/version-1.9.20-brightgreen?style=flat-square)
![Version](https://img.shields.io/badge/version-1.9.25-brightgreen?style=flat-square)

<p align="left">
<img src="assets/logo_large.png" alt="FreeLingo logo" />
Expand Down Expand Up @@ -71,7 +71,7 @@ freelingo/
├── data/ # Shared data files
├── docs/ # GitHub Pages landing site
├── frontend/ # Next.js (React)
├── messages/ # i18n translation files (de, en, es, fr, it, nl, pl, pt, ro, ru)
├── messages/ # i18n catalogs (da, de, en, es, fi, fr, hr, it, nl, pl, pt, ro, ru, sv, tr)
├── specs/ # Specification files
├── AGENTS.md # AI assistant instructions
├── CHANGELOG.md # Version history
Expand Down
8 changes: 5 additions & 3 deletions backend/app/routers/admin_dashboard_banner.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@

router = APIRouter(prefix="/api/admin/dashboard-banner", tags=["admin"])

_TRANSLATION_PROMPT = """Translate this dashboard announcement faithfully into all ten requested locales.
Return en, es, fr, pt, de, it, ru, nl, pl, and ro. Preserve the original tone and meaning.
_TRANSLATION_PROMPT = """Translate this dashboard announcement faithfully into all fifteen requested locales.
Return en, es, fr, pt, de, it, ru, nl, pl, ro, tr, sv, da, fi, and hr. Preserve the original tone and meaning.
Do not add claims, details, formatting, Markdown, or HTML. Every field must contain plain text only.
For the source locale, copy each provided field exactly.

Expand All @@ -31,7 +31,9 @@
"""


@router.get("", response_model=DashboardBannerAdminResponse | None)
@router.get(
"", response_model=DashboardBannerAdminResponse | None, response_model_exclude_unset=True
)
@limiter.limit("60/minute")
async def get_dashboard_banner(
request: Request,
Expand Down
35 changes: 35 additions & 0 deletions backend/app/routers/lessons.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,41 @@
"good_pronunciation": "Pronuntie buna!",
"target_phrase": "Fraza tinta a fost: {answer}",
},
"tr": {
"correct": "Doğru!",
"correct_answer": "Doğru cevap: {answer}",
"free_write_unavailable": "Yazılı cevap şu anda değerlendirilemedi.",
"good_pronunciation": "Telaffuzunuz iyi!",
"target_phrase": "Hedef ifade şuydu: {answer}",
},
"sv": {
"correct": "Rätt!",
"correct_answer": "Rätt svar: {answer}",
"free_write_unavailable": "Det gick inte att bedöma det skriftliga svaret just nu.",
"good_pronunciation": "Bra uttal!",
"target_phrase": "Målfrasen var: {answer}",
},
"da": {
"correct": "Rigtigt!",
"correct_answer": "Det rigtige svar er: {answer}",
"free_write_unavailable": "Det skriftlige svar kunne ikke vurderes lige nu.",
"good_pronunciation": "God udtale!",
"target_phrase": "Målfrasen var: {answer}",
},
"fi": {
"correct": "Oikein!",
"correct_answer": "Oikea vastaus: {answer}",
"free_write_unavailable": "Kirjoitettua vastausta ei voitu arvioida juuri nyt.",
"good_pronunciation": "Hyvä ääntäminen!",
"target_phrase": "Tavoiteilmaus oli: {answer}",
},
"hr": {
"correct": "Točno!",
"correct_answer": "Točan odgovor: {answer}",
"free_write_unavailable": "Pisani odgovor trenutačno nije bilo moguće ocijeniti.",
"good_pronunciation": "Dobar izgovor!",
"target_phrase": "Ciljani izraz bio je: {answer}",
},
}


Expand Down
31 changes: 17 additions & 14 deletions backend/app/schemas/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,24 @@

from app.core.config import settings

# Native-language codes accepted when creating users through the admin API.
# These match the registration and profile options, not the study-language catalog.
SUPPORTED_LANGUAGES = {
"en",
"es",
"fr",
"pt",
"de",
"it",
"zh",
"ja",
"ko",
"ar",
"ru",
"nl",
"pl",
"ro",
"da", # Danish
"de", # German
"en", # English
"es", # Spanish
"fi", # Finnish
"fr", # French
"hr", # Croatian
"it", # Italian
"nl", # Dutch
"pl", # Polish
"pt", # Portuguese
"ro", # Romanian
"ru", # Russian
"sv", # Swedish
"tr", # Turkish
}


Expand Down
10 changes: 10 additions & 0 deletions backend/app/schemas/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ def validate_password_strength(v: str) -> str:
"nl",
"pl",
"ro",
"tr",
"sv",
"da",
"fi",
"hr",
}

SUPPORTED_TARGET_LANGUAGES: set[str] = {
Expand Down Expand Up @@ -65,6 +70,11 @@ def get_available_languages() -> list[str]:
"nl",
"ro",
"ru",
"tr",
"sv",
"da",
"fi",
"hr",
}

VALID_LEARNING_GOALS: set[str] = {
Expand Down
21 changes: 18 additions & 3 deletions backend/app/schemas/dashboard_banner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

from pydantic import BaseModel, ConfigDict, Field, field_validator

DashboardBannerLocale = Literal["en", "es", "fr", "pt", "de", "it", "ru", "nl", "pl", "ro"]
DashboardBannerLocale = Literal[
"en", "es", "fr", "pt", "de", "it", "ru", "nl", "pl", "ro", "tr", "sv", "da", "fi", "hr"
]


class DashboardBannerTranslation(BaseModel):
Expand All @@ -24,7 +26,7 @@ def strip_nonblank_text(cls, value: str) -> str:
return value


class DashboardBannerTranslations(BaseModel):
class DashboardBannerStoredTranslations(BaseModel):
model_config = ConfigDict(extra="forbid")

en: DashboardBannerTranslation
Expand All @@ -37,6 +39,19 @@ class DashboardBannerTranslations(BaseModel):
nl: DashboardBannerTranslation
pl: DashboardBannerTranslation
ro: DashboardBannerTranslation
tr: DashboardBannerTranslation | None = None
sv: DashboardBannerTranslation | None = None
da: DashboardBannerTranslation | None = None
fi: DashboardBannerTranslation | None = None
hr: DashboardBannerTranslation | None = None


class DashboardBannerTranslations(DashboardBannerStoredTranslations):
tr: DashboardBannerTranslation
sv: DashboardBannerTranslation
da: DashboardBannerTranslation
fi: DashboardBannerTranslation
hr: DashboardBannerTranslation


class DashboardBannerTranslateRequest(DashboardBannerTranslation):
Expand All @@ -57,7 +72,7 @@ class DashboardBannerUpdate(BaseModel):

class DashboardBannerPublicResponse(BaseModel):
revision: int
translations: DashboardBannerTranslations
translations: DashboardBannerStoredTranslations


class DashboardBannerAdminResponse(DashboardBannerPublicResponse):
Expand Down
Loading
Loading