Skip to content

Commit 0c00c0a

Browse files
feat: implement asynchronous language fetching and update templates for language selection
1 parent 349074c commit 0c00c0a

File tree

11 files changed

+83
-87
lines changed

11 files changed

+83
-87
lines changed

app/api/endpoints/meta.py

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,50 @@
88
router = APIRouter()
99

1010

11+
async def fetch_languages_list():
12+
"""
13+
Fetch and format languages list from TMDB.
14+
Returns a list of language dictionaries with iso_639_1, language, and country.
15+
"""
16+
tmdb = get_tmdb_service()
17+
tasks = [
18+
tmdb.get_primary_translations(),
19+
tmdb.get_languages(),
20+
tmdb.get_countries(),
21+
]
22+
primary_translations, languages, countries = await asyncio.gather(*tasks)
23+
24+
language_map = {lang["iso_639_1"]: lang["english_name"] for lang in languages}
25+
country_map = {country["iso_3166_1"]: country["english_name"] for country in countries}
26+
27+
result = []
28+
for element in primary_translations:
29+
# element looks like "en-US"
30+
parts = element.split("-")
31+
if len(parts) != 2:
32+
continue
33+
34+
lang_code, country_code = parts
35+
language_name = language_map.get(lang_code)
36+
country_name = country_map.get(country_code)
37+
38+
if language_name and country_name:
39+
result.append(
40+
{
41+
"iso_639_1": element,
42+
"language": language_name,
43+
"country": country_name,
44+
}
45+
)
46+
result.sort(key=lambda x: (x["iso_639_1"] != "en-US", x["language"]))
47+
return result
48+
49+
1150
@router.get("/api/languages")
1251
async def get_languages():
1352
try:
14-
tmdb = get_tmdb_service()
15-
tasks = [
16-
tmdb.get_primary_translations(),
17-
tmdb.get_languages(),
18-
tmdb.get_countries(),
19-
]
20-
primary_translations, languages, countries = await asyncio.gather(*tasks)
21-
22-
language_map = {lang["iso_639_1"]: lang["english_name"] for lang in languages}
23-
24-
country_map = {country["iso_3166_1"]: country["english_name"] for country in countries}
25-
26-
result = []
27-
for element in primary_translations:
28-
# element looks like "en-US"
29-
parts = element.split("-")
30-
if len(parts) != 2:
31-
continue
32-
33-
lang_code, country_code = parts
34-
35-
language_name = language_map.get(lang_code)
36-
country_name = country_map.get(country_code)
37-
38-
if language_name and country_name:
39-
result.append(
40-
{
41-
"iso_639_1": element,
42-
"language": language_name,
43-
"country": country_name,
44-
}
45-
)
46-
return result
47-
53+
languages = await fetch_languages_list()
54+
return languages
4855
except Exception as e:
4956
logger.error(f"Failed to fetch languages: {e}")
5057
raise HTTPException(status_code=502, detail="Failed to fetch languages from TMDB")

app/core/app.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from jinja2 import Environment, FileSystemLoader
99
from loguru import logger
1010

11+
from app.api.endpoints.meta import fetch_languages_list
1112
from app.api.main import api_router
1213
from app.services.token_store import token_store
1314

@@ -64,12 +65,20 @@ async def lifespan(app: FastAPI):
6465
@app.get("/configure", response_class=HTMLResponse)
6566
@app.get("/{token}/configure", response_class=HTMLResponse)
6667
async def configure_page(request: Request, token: str | None = None):
68+
languages = []
69+
try:
70+
languages = await fetch_languages_list()
71+
except Exception as e:
72+
logger.warning(f"Failed to fetch languages for template: {e}")
73+
languages = [{"iso_639_1": "en-US", "language": "English", "country": "US"}]
74+
6775
template = jinja_env.get_template("index.html")
6876
html_content = template.render(
6977
request=request,
7078
app_version=__version__,
7179
app_host=settings.HOST_NAME,
7280
announcement_html=settings.ANNOUNCEMENT_HTML or "",
81+
languages=languages,
7382
)
7483
return HTMLResponse(content=html_content, media_type="text/html")
7584

app/core/config.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,7 @@ class Settings(BaseSettings):
2929
REDIS_TOKEN_KEY: str = "watchly:token:"
3030
TOKEN_SALT: str = "change-me"
3131
TOKEN_TTL_SECONDS: int = 0 # 0 = never expire
32-
ANNOUNCEMENT_HTML: str = (
33-
'<img src="https://elfhosted.com/images/logo.svg" height="64px" width="64px" style="float: left;'
34-
' margin-right: 10px;"><p>This is the public instance of <a'
35-
' href="https://github.com/TimilsinaBimal/Watchly">Watchly</a>, sponsored by <a'
36-
' href="https://store.elfhosted.com/">ElfHosted</a> ❤️ <br>See our FREE <a'
37-
' href="https://stremio-addons-guide.elfhosted.com">Stremio Addons Guide</a> for more great addons and'
38-
" features!</p>"
39-
)
32+
ANNOUNCEMENT_HTML: str = ""
4033
AUTO_UPDATE_CATALOGS: bool = True
4134
CATALOG_REFRESH_INTERVAL_SECONDS: int = 43200 # 12 hours
4235
APP_ENV: Literal["development", "production", "vercel"] = "production"
@@ -52,5 +45,4 @@ class Settings(BaseSettings):
5245

5346
settings = Settings()
5447

55-
# Get version from version.py (single source of truth)
5648
APP_VERSION = __version__

app/static/js/modules/catalog.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function createCatalogItem(cat, index) {
8282
</div>
8383
<label class="switch relative inline-flex items-center cursor-pointer flex-shrink-0 ml-auto sm:ml-0">
8484
<input type="checkbox" class="sr-only peer" ${cat.enabled ? 'checked' : ''} data-catalog-id="${cat.id}">
85-
<div class="w-11 h-6 bg-slate-700 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-white/20 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-white peer-checked:after:bg-black peer-checked:after:border-black"></div>
85+
<div class="w-11 h-6 bg-neutral-700 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-white/20 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-white peer-checked:after:bg-black peer-checked:after:border-black"></div>
8686
</label>
8787
</div>
8888
<div class="catalog-desc hidden sm:block text-xs text-slate-500 mt-2 ml-8 pl-1">${escapeHtml(cat.description || '')}</div>
@@ -152,7 +152,7 @@ function setupRenameLogic(item, cat) {
152152
const renameBtn = item.querySelector('.rename-btn');
153153

154154
const editActions = document.createElement('div');
155-
editActions.className = 'edit-actions hidden absolute right-1 top-1/2 -translate-y-1/2 flex gap-1 bg-slate-900 pl-2 z-10';
155+
editActions.className = 'edit-actions hidden absolute right-1 top-1/2 -translate-y-1/2 flex gap-1 bg-neutral-900 pl-2 z-10';
156156
editActions.innerHTML = `
157157
<button type="button" class="edit-btn save p-1 text-green-500 hover:bg-green-500/10 rounded transition" title="Save"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></button>
158158
<button type="button" class="edit-btn cancel p-1 text-red-500 hover:bg-red-500/10 rounded transition" title="Cancel"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>

app/static/js/modules/form.js

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ function renderGenreList(container, genres, namePrefix) {
144144
<label class="flex items-center gap-3 p-2 rounded-lg hover:bg-white/5 cursor-pointer transition group">
145145
<div class="relative flex items-center">
146146
<input type="checkbox" name="${namePrefix}" value="${genre.id}"
147-
class="peer appearance-none w-5 h-5 border-2 border-slate-600 rounded bg-slate-900 checked:bg-white checked:border-white transition-colors">
147+
class="peer appearance-none w-5 h-5 border-2 border-slate-600 rounded bg-neutral-900 checked:bg-white checked:border-white transition-colors">
148148
<svg class="absolute w-3.5 h-3.5 text-black left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 peer-checked:opacity-100 pointer-events-none transition-opacity"
149149
fill="none" stroke="currentColor" viewBox="0 0 24 24">
150150
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path>
@@ -155,26 +155,8 @@ function renderGenreList(container, genres, namePrefix) {
155155
`).join('');
156156
}
157157

158-
// Language Selection
159-
async function initializeLanguageSelect() {
158+
function initializeLanguageSelect() {
160159
if (!languageSelect) return;
161-
try {
162-
const languagesResponse = await fetch('/api/languages');
163-
if (!languagesResponse.ok) throw new Error('Failed to fetch languages');
164-
const languages = await languagesResponse.json();
165-
languages.sort((a, b) => {
166-
if (a.iso_639_1 === 'en-US') return -1;
167-
if (b.iso_639_1 === 'en-US') return 1;
168-
return a.language.localeCompare(b.language);
169-
});
170-
languageSelect.innerHTML = languages.map(lang => {
171-
const code = lang.iso_639_1;
172-
const fullLabel = escapeHtml(lang.language) + ' (' + escapeHtml(lang.country) + ')';
173-
return '<option value="' + escapeHtml(code) + '"' + (code === 'en-US' ? ' selected' : '') + '>' + fullLabel + '</option>';
174-
}).join('');
175-
} catch (err) {
176-
languageSelect.innerHTML = '<option value="en-US">English (US)</option>';
177-
}
178160
}
179161

180162
// Password Toggles

app/templates/components/modal_donation.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ <h3 class="text-xl font-bold text-white">Support Watchly 💙</h3>
8181

8282
<!-- Fun Fact -->
8383
<div class="pt-3 border-t border-white/10">
84-
<div class="bg-slate-800/30 border border-slate-700/50 rounded-lg p-3">
84+
<div class="bg-neutral-800/30 border border-slate-700/50 rounded-lg p-3">
8585
<p class="text-xs text-slate-400 leading-relaxed">
8686
<span class="font-semibold text-slate-300">Fun fact:</span> Momo is a delicious steamed spicy
8787
dumpling popular in Nepal. Just like how momo brings joy to
@@ -97,7 +97,7 @@ <h3 class="text-xl font-bold text-white">Support Watchly 💙</h3>
9797
<!-- Ko-fi Modal -->
9898
<div id="kofi-modal" class="fixed inset-0 z-50 hidden">
9999
<!-- Backdrop -->
100-
<div class="absolute inset-0 bg-slate-950/80 backdrop-blur-sm transition-opacity" id="kofi-backdrop"></div>
100+
<div class="absolute inset-0 bg-neutral-950/80 backdrop-blur-sm transition-opacity" id="kofi-backdrop"></div>
101101

102102
<!-- Modal Content -->
103103
<div class="absolute inset-0 flex items-center justify-center p-4">
@@ -128,7 +128,7 @@ <h3 class="text-xl font-bold text-white">Support Watchly 💙</h3>
128128
<!-- Confirmation Modal -->
129129
<div id="confirmModal"
130130
class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm">
131-
<div class="bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl max-w-md w-full transform transition-all scale-95 opacity-0"
131+
<div class="bg-neutral-900 border border-slate-700 rounded-2xl shadow-2xl max-w-md w-full transform transition-all scale-95 opacity-0"
132132
id="confirmModalContent">
133133
<div class="p-6 space-y-4">
134134
<div class="flex items-start gap-4">
@@ -147,7 +147,7 @@ <h3 class="text-lg font-bold text-white mb-1" id="confirmModalTitle">Confirm Act
147147
</div>
148148
<div class="flex gap-3 pt-2">
149149
<button id="confirmModalCancel"
150-
class="flex-1 px-4 py-2.5 bg-slate-800 hover:bg-slate-700 text-white rounded-xl font-medium transition">
150+
class="flex-1 px-4 py-2.5 bg-neutral-800 hover:bg-neutral-700 text-white rounded-xl font-medium transition">
151151
Cancel
152152
</button>
153153
<button id="confirmModalConfirm"

app/templates/components/section_config.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ <h2 class="text-3xl font-bold text-white mb-2">Preferences</h2>
1010
<label class="block text-sm font-medium text-slate-400 uppercase tracking-wider">Language</label>
1111
<div class="relative">
1212
<select id="languageSelect"
13-
class="w-full appearance-none bg-slate-900 hover:bg-slate-800/80 border border-slate-700 rounded-xl px-4 py-3.5 text-white focus:ring-2 focus:ring-white/20 focus:border-white/30 outline-none transition-all cursor-pointer">
14-
<!-- JS Populated -->
13+
class="w-full appearance-none bg-neutral-900 hover:bg-neutral-800/80 border border-slate-700 rounded-xl px-4 py-3.5 text-white focus:ring-2 focus:ring-white/20 focus:border-white/30 outline-none transition-all cursor-pointer">
14+
{% for lang in languages %}
15+
<option value="{{ lang.iso_639_1 }}" {% if lang.iso_639_1 == 'en-US' %}selected{% endif %}>
16+
{{ lang.language }} ({{ lang.country }})
17+
</option>
18+
{% endfor %}
1519
</select>
1620
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-4 text-slate-400">
1721
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -59,7 +63,7 @@ <h2 class="text-3xl font-bold text-white mb-2">Preferences</h2>
5963
</svg>
6064
</div>
6165
<input type="text" id="rpdbKey"
62-
class="w-full bg-slate-900 border border-slate-700 rounded-xl pl-11 pr-4 py-3.5 text-white placeholder-slate-500 focus:ring-2 focus:ring-white/20 focus:border-white/30 outline-none transition-all font-mono text-sm"
66+
class="w-full bg-neutral-900 border border-slate-700 rounded-xl pl-11 pr-4 py-3.5 text-white placeholder-slate-500 focus:ring-2 focus:ring-white/20 focus:border-white/30 outline-none transition-all font-mono text-sm"
6367
placeholder="Paste your RPDB API key here">
6468
</div>
6569
<p class="text-xs text-slate-500">Enable ratings on posters via <a

app/templates/components/section_install.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ <h3 class="text-3xl font-bold text-white mb-2">You're all set!</h3>
7474
class="bg-neutral-950 rounded-xl p-4 mb-8 border border-white/10 font-mono text-sm text-slate-200 break-all relative group">
7575
<div id="addonUrl">https://...</div>
7676
<div
77-
class="absolute inset-0 bg-slate-900/90 backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center rounded-xl cursor-default">
77+
class="absolute inset-0 bg-neutral-900/90 backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center rounded-xl cursor-default">
7878
<span class="text-white font-sans text-sm font-medium">Private Token - Do Not Share</span>
7979
</div>
8080
</div>

app/templates/components/section_login.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ <h2 class="text-3xl font-bold text-white mb-2">Connect to Stremio</h2>
2626
<label class="text-xs text-slate-400">Email</label>
2727
<input id="emailInput" type="email" autocomplete="email" inputmode="email"
2828
spellcheck="false" required placeholder="[email protected]"
29-
class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3.5 text-white placeholder-slate-500 focus:ring-2 focus:ring-white/20 focus:border-white/30 outline-none transition-all">
29+
class="w-full bg-neutral-900 border border-slate-700 rounded-xl px-4 py-3.5 text-white placeholder-slate-500 focus:ring-2 focus:ring-white/20 focus:border-white/30 outline-none transition-all">
3030
<label class="text-xs text-slate-400">Password</label>
3131
<div class="relative">
3232
<input id="passwordInput" type="password" autocomplete="current-password"
3333
placeholder="Your Stremio password"
34-
class="w-full bg-slate-900 border border-slate-700 rounded-xl pl-4 pr-12 py-3.5 text-white placeholder-slate-500 focus:ring-2 focus:ring-white/20 focus:border-white/30 outline-none transition-all">
34+
class="w-full bg-neutral-900 border border-slate-700 rounded-xl pl-4 pr-12 py-3.5 text-white placeholder-slate-500 focus:ring-2 focus:ring-white/20 focus:border-white/30 outline-none transition-all">
3535
<button type="button"
3636
class="toggle-btn absolute right-2 top-1/2 -translate-y-1/2 bg-white/10 hover:bg-white/20 text-white p-2 rounded-lg"
3737
aria-label="Show password" title="Show" data-target="passwordInput">

0 commit comments

Comments
 (0)