Compare commits

...

286 Commits

Author SHA1 Message Date
Patrick
a198bb24f2 fix: missing font (#332)
## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

Cannot run without the font missing.
```
Unhandled exception. System.InvalidOperationException: Default font family name can't be null or empty.
   at Avalonia.Media.FontManager.GetDefaultFontFamilyName(FontManagerOptions options)
   at Avalonia.Media.FontManager..ctor(IFontManagerImpl platformImpl)
   at Avalonia.Media.FontManager.get_Current()
   at Avalonia.AppBuilder.<>c__DisplayClass71_0.<ConfigureFonts>b__0(AppBuilder appBuilder)
   at Avalonia.AppBuilder.SetupUnsafe()
   at Avalonia.AppBuilder.Setup()
   at Avalonia.AppBuilder.SetupWithLifetime(IApplicationLifetime lifetime)
   at Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(AppBuilder builder, String[] args, Action`1 lifetimeBuilder)
   at Jellyfin2Samsung.Program.Main(String[] args) in Samsung-Jellyfin-Installer/Jellyfin2Samsung-CrossOS/Program.cs:line 25
```

## Fixes
Adds missing packages to the nix shell.

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-05-04 14:30:45 +02:00
github-actions[bot]
82142edf2e chore: update version table in README [skip ci] 2026-05-04 12:28:29 +00:00
Patrick Stel
fccd1f2acb GitHub Auth for SDB 2026-05-04 14:24:44 +02:00
Jarda-H
12a815a9ee fix: missing font 2026-05-03 23:16:54 +02:00
github-actions[bot]
fef471797d chore: update version table in README [skip ci] 2026-04-23 13:00:08 +00:00
Patrick
abf2dfda8f New Crowdin updates (#328) 2026-04-23 10:39:59 +02:00
Patrick
39fc2179d0 New translations en.json (Portuguese, Brazilian) 2026-04-23 10:31:54 +02:00
Patrick
4321f08f0f New translations en.json (Vietnamese) 2026-04-23 10:31:53 +02:00
Patrick
0f9bd70a3d New translations en.json (English) 2026-04-23 10:31:52 +02:00
Patrick
7ab26caa6a New translations en.json (Chinese Simplified) 2026-04-23 10:31:50 +02:00
Patrick
d65c1371bf New translations en.json (Ukrainian) 2026-04-23 10:31:48 +02:00
Patrick
2fc0739be9 New translations en.json (Turkish) 2026-04-23 10:31:47 +02:00
Patrick
ac4a8f6910 New translations en.json (Swedish) 2026-04-23 10:31:45 +02:00
Patrick
92e505c135 New translations en.json (Serbian (Cyrillic)) 2026-04-23 10:31:44 +02:00
Patrick
1850c487f0 New translations en.json (Russian) 2026-04-23 10:31:42 +02:00
Patrick
89ac524f06 New translations en.json (Portuguese) 2026-04-23 10:31:41 +02:00
Patrick
75cafea579 New translations en.json (Polish) 2026-04-23 10:31:39 +02:00
Patrick
f4d61e36f8 New translations en.json (Norwegian) 2026-04-23 10:31:38 +02:00
Patrick
ddfe2c4983 New translations en.json (Dutch) 2026-04-23 10:31:37 +02:00
Patrick
224c9391aa New translations en.json (Korean) 2026-04-23 10:31:35 +02:00
Patrick
3c6cec56c9 New translations en.json (Japanese) 2026-04-23 10:31:34 +02:00
Patrick
6747fdfc35 New translations en.json (Italian) 2026-04-23 10:31:32 +02:00
Patrick
8e678367f5 New translations en.json (Hungarian) 2026-04-23 10:31:31 +02:00
Patrick
3223d73592 New translations en.json (Hebrew) 2026-04-23 10:31:30 +02:00
Patrick
955418340d New translations en.json (Finnish) 2026-04-23 10:31:28 +02:00
Patrick
8a9ed0eb20 New translations en.json (Greek) 2026-04-23 10:31:27 +02:00
Patrick
9f4c9a7907 New translations en.json (German) 2026-04-23 10:31:25 +02:00
github-actions[bot]
9f90810b94 chore: update version table in README [skip ci] 2026-04-23 08:31:24 +00:00
Patrick
ef23c823df New translations en.json (Danish) 2026-04-23 10:31:24 +02:00
Patrick
cbe0ce8b21 New translations en.json (Czech) 2026-04-23 10:31:23 +02:00
Patrick
c381ecccd1 New translations en.json (Catalan) 2026-04-23 10:31:21 +02:00
Patrick
d1fa426815 New translations en.json (Arabic) 2026-04-23 10:31:20 +02:00
Patrick
377dca8528 New translations en.json (Afrikaans) 2026-04-23 10:31:18 +02:00
Patrick
932f0fa862 New translations en.json (Spanish) 2026-04-23 10:31:17 +02:00
Patrick
fc62f2c034 New translations en.json (French) 2026-04-23 10:31:15 +02:00
Patrick
8c997ce7fd New translations en.json (Romanian) 2026-04-23 10:31:14 +02:00
Patrick Stel
598ded836a version bump 2026-04-23 10:27:29 +02:00
Patrick Stel
9c34f8408e Reversed IP Warning 2026-04-23 10:20:41 +02:00
Patrick
2a2ee67d52 New Crowdin updates (#325) 2026-04-16 09:36:00 +02:00
Patrick
2e5ee8d428 Merge branch 'beta' into l10n_beta 2026-04-16 09:35:00 +02:00
Patrick
1ef3a31f13 New translations en.json (Portuguese, Brazilian) 2026-04-16 05:15:25 +02:00
Patrick
8757689b91 New translations en.json (Vietnamese) 2026-04-16 05:15:24 +02:00
Patrick
37a7c414bd New translations en.json (English) 2026-04-16 05:15:23 +02:00
Patrick
c3659394d5 New translations en.json (Chinese Simplified) 2026-04-16 05:15:21 +02:00
Patrick
3381869811 New translations en.json (Ukrainian) 2026-04-16 05:15:20 +02:00
Patrick
32265caf39 New translations en.json (Turkish) 2026-04-16 05:15:19 +02:00
Patrick
bfc759bfd8 New translations en.json (Swedish) 2026-04-16 05:15:17 +02:00
Patrick
245d47a9e0 New translations en.json (Serbian (Cyrillic)) 2026-04-16 05:15:16 +02:00
Patrick
095d19e8f9 New translations en.json (Russian) 2026-04-16 05:15:15 +02:00
Patrick
527d5ca08a New translations en.json (Portuguese) 2026-04-16 05:15:14 +02:00
Patrick
2da3ee4df2 New translations en.json (Polish) 2026-04-16 05:15:13 +02:00
Patrick
1fe99bd974 New translations en.json (Norwegian) 2026-04-16 05:15:11 +02:00
Patrick
d58ae3ded3 New translations en.json (Dutch) 2026-04-16 05:15:10 +02:00
Patrick
c002f88a7b New translations en.json (Korean) 2026-04-16 05:15:09 +02:00
Patrick
29e746dd09 New translations en.json (Japanese) 2026-04-16 05:15:08 +02:00
Patrick
94307d58db New translations en.json (Italian) 2026-04-16 05:15:06 +02:00
Patrick
b667132efe New translations en.json (Hungarian) 2026-04-16 05:15:05 +02:00
Patrick
8ff66448ad New translations en.json (Hebrew) 2026-04-16 05:15:04 +02:00
Patrick
6085e11046 New translations en.json (Finnish) 2026-04-16 05:15:03 +02:00
Patrick
97e684ea30 New translations en.json (Greek) 2026-04-16 05:15:02 +02:00
Patrick
c19d411677 New translations en.json (German) 2026-04-16 05:15:00 +02:00
Patrick
1786591380 New translations en.json (Danish) 2026-04-16 05:14:59 +02:00
Patrick
2d1eb06048 New translations en.json (Czech) 2026-04-16 05:14:58 +02:00
Patrick
ca18e54097 New translations en.json (Catalan) 2026-04-16 05:14:56 +02:00
Patrick
6ac842b2d0 New translations en.json (Arabic) 2026-04-16 05:14:55 +02:00
Patrick
23d7213bc2 New translations en.json (Afrikaans) 2026-04-16 05:14:54 +02:00
Patrick
fcdea48ba8 New translations en.json (Spanish) 2026-04-16 05:14:53 +02:00
Patrick
9f75dbfefe New translations en.json (French) 2026-04-16 05:14:52 +02:00
Patrick
3ac9aa3bdf New translations en.json (Romanian) 2026-04-16 05:14:50 +02:00
kingchenc
75ac9d6e2e fix: resolve "undefined" server display on Samsung TVs and add mDNS (.local) warning
Fixes #319 — Server appears as "undefined" on Samsung TV after network interruptions
when using mDNS (.local) hostnames.

Root cause:
The injected jellyfin_credentials in localStorage were missing the `Name` property.
When the TV couldn't reach the server (mDNS resolution failure), Jellyfin Web had
no cached server name to display, resulting in literal "undefined".

Changes:
- Add `Name` property to injected auto-login credentials using the real ServerName
  from /System/Info/Public, with fallback to the server URL
- Store JellyfinServerName in AppSettings (fetched during server validation and
  authentication, cleared on logout)
- Add the server's LocalAddress (IP-based) as a fallback entry in config.json so
  the TV can still connect when mDNS resolution fails
- Add a visible warning banner in the UI when a .local hostname is detected,
  advising users to use a direct IP address instead
- Add lblMdnsWarning localization key to all 28 language files (translated for
  da, nl, en, fr, de, pt, tr — English fallback for remaining languages)
2026-04-16 04:51:00 +02:00
github-actions[bot]
a128e1be6b chore: update version table in README [skip ci] 2026-04-11 18:29:12 +00:00
github-actions[bot]
74dbde4276 chore: update version table in README [skip ci] 2026-04-11 18:08:21 +00:00
Patrick
912bc84687 Update AppSettings.cs 2026-04-11 20:04:53 +02:00
Patrick
598274ec08 Add NixOS instructions to README
Added instructions for using NixOS with the Samsung2Jellyfin branch.
2026-04-11 20:01:32 +02:00
Patrick
3db936c767 add shell.nix for NixOS Unstable support (#321)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-04-11 19:55:44 +02:00
Confused-Engineer
ffc314e6c4 Merge pull request #1 from Confused-Engineer/Confused-Engineer-nix
Upload shell.nix
2026-04-11 10:17:15 -04:00
Confused-Engineer
2d6f5492f8 Upload shell.nix 2026-04-11 10:16:46 -04:00
PatrickSt1991
bafcc8721b Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-04-11 14:08:03 +02:00
PatrickSt1991
d7424138c6 Clear CustomWgt path after install 2026-04-11 14:06:56 +02:00
Patrick Stel
fa19289e76 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-04-10 15:50:49 +02:00
Patrick Stel
3f45b55f02 Single call instead of double preventing connection drop 2026-04-10 15:50:38 +02:00
github-actions[bot]
3916205d56 chore: update version table in README [skip ci] 2026-04-10 12:44:38 +00:00
Patrick Stel
d822bd2e9f Feature: Save NetworkInterface
Improvement: Subnet check and IP Search
2026-04-10 11:27:58 +02:00
Patrick
717674ed2f Bump the nuget group with 1 update (#314)
Pinned Tmds.DBus.Protocol at 0.21.3.

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Tmds.DBus.Protocol&package-manager=nuget&previous-version=0.21.2&new-version=0.21.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/network/alerts).

</details>
2026-04-09 21:11:20 +02:00
dependabot[bot]
32b95a72cf Bump the nuget group with 1 update
Bumps Tmds.DBus.Protocol from 0.21.2 to 0.21.3

---
updated-dependencies:
- dependency-name: Tmds.DBus.Protocol
  dependency-version: 0.21.3
  dependency-type: direct:production
  dependency-group: nuget
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 16:51:20 +00:00
Patrick
404496e818 Update FileHelper.cs
Removed obsolete file copy for custom WGT and TPK files
2026-04-09 18:49:00 +02:00
github-actions[bot]
11ed846c1a chore: update version table in README [skip ci] 2026-04-05 18:22:07 +00:00
PatrickSt1991
d02526c1d6 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-04-05 20:18:57 +02:00
PatrickSt1991
56486c1ec8 Changed IsEnbaled for settings to ServerIpSet 2026-04-05 20:18:48 +02:00
Patrick
a46a352359 New Crowdin updates (#305) 2026-03-30 13:18:45 +02:00
github-actions[bot]
2d0a6525da chore: update version table in README [skip ci] 2026-03-28 06:42:50 +00:00
PatrickSt1991
753fda3407 version bump 2026-03-28 07:39:21 +01:00
Patrick
387c2b501f feat(V17): trailer fallback for non-English metadata languages via TMDB + DuckDuckGo Lite (#304)
Fixes a long-standing Jellyfin upstream bug (jellyfin/jellyfin#12817)
where movies configured with non-English metadata languages (German,
Spanish, French, etc.) show no trailer button because TMDB returns empty
video data for non-English language queries.

## Problem

When Jellyfin fetches metadata with e.g. language=de, TMDB returns 0
trailers even though English trailers exist in their database. Jellyfin
makes no fallback to English. The issue was closed upstream as "not
planned" — unfixed for 2-3 years, affecting every non-English Jellyfin
user.

## Solution

Client-side trailer fallback system built into the existing YouTube fix
(V16 -> V17). No Jellyfin server changes required.

When a movie detail page has no trailer button:

1. TMDB: search for trailer in user's language (via
include_video_language parameter)
2. DuckDuckGo Lite: search "{title} Trailer {language}
site:youtube.com", match by title
3. TMDB: English trailer (cached from step 1, no extra API call)
4. DuckDuckGo Lite: English fallback (cached from step 2, no extra API
call)

Max 2 external HTTP requests (1x TMDB + 1x DDG). Steps 3+4 use cached
results.

## Why DuckDuckGo Lite instead of Google/YouTube search

Google and YouTube aggressively block automated requests with CAPTCHAs,
rate limiting, and bot detection. DuckDuckGo Lite (lite.duckduckgo.com)
returns plain HTML with no JavaScript, no API key required, no CAPTCHAs,
and no bot blocking. DDG Lite has been stable for years with minimal
changes to its HTML structure, making it a reliable fallback for trailer
discovery.

## Technical details

- Language detected from Jellyfin user settings
(document.documentElement.lang), not TV system language
- TMDB API key extracted at runtime from Jellyfin server plugin
configuration (GET /Plugins -> GET /Plugins/{id}/Configuration), falls
back to Jellyfin's default key from TmdbUtils.cs
- DDG results parsed with title extraction: language-matched results
prioritized, non-matched results cached as English fallback
- Trailer button injected as native Jellyfin UI element (same CSS
classes, Material Icons)
- Fullscreen overlay player reuses existing player.html bridge
- Closable via Tizen back button, Escape, Backspace, or video end
- httpsGet helper with 8s timeout and double-callback guard
- 22 non-English languages supported in DDG language map

## Fallback flow

```
Movie detail page loaded
  |
  +-- Trailer button exists? -> Yes -> normal existing fix (CustomPlayer)
  |
  +-- No -> Service: /trailer?tmdbId=X&lang={Jellyfin language}&tmdbKey={runtime key}
              |
              +-- 1. TMDB: trailer in user's language?
              |     -> Found -> play with CustomPlayer
              |
              +-- 2. DDG Lite: "{title} Trailer {language} site:youtube.com"
              |     -> Language matched in title? -> play with CustomPlayer
              |
              +-- 3. TMDB: English trailer (cached from step 1, no extra call)
              |     -> Found -> play with CustomPlayer
              |
              +-- 4. DDG Lite: English fallback (cached from step 2, no extra call)
              |     -> Found -> play with CustomPlayer
              |
              +-- Nothing found -> no trailer available
```

# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [-] Documentation update
- [-] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [-] I have tested my changes manually  
- [-] I have added necessary documentation (if applicable)  
- [-] I have verified that the installer still works with the underlying
CLI
2026-03-28 07:36:55 +01:00
Patrick
016f17ab04 New translations en.json (Portuguese, Brazilian) 2026-03-26 09:43:56 +01:00
Patrick
8e971fc6aa New translations en.json (Vietnamese) 2026-03-26 09:43:55 +01:00
Patrick
ac423c7c04 New translations en.json (English) 2026-03-26 09:43:54 +01:00
Patrick
ec2b9293ee New translations en.json (Chinese Simplified) 2026-03-26 09:43:52 +01:00
Patrick
8cee7d4b97 New translations en.json (Ukrainian) 2026-03-26 09:43:51 +01:00
Patrick
8ecf4a9b8a New translations en.json (Turkish) 2026-03-26 09:43:50 +01:00
Patrick
2e20e6f7b7 New translations en.json (Swedish) 2026-03-26 09:43:49 +01:00
Patrick
eb4ca257f1 New translations en.json (Serbian (Cyrillic)) 2026-03-26 09:43:48 +01:00
Patrick
e6845eebdd New translations en.json (Russian) 2026-03-26 09:43:46 +01:00
Patrick
ebb4995748 New translations en.json (Portuguese) 2026-03-26 09:43:44 +01:00
Patrick
505a2d3be4 New translations en.json (Polish) 2026-03-26 09:43:43 +01:00
Patrick
e31beed93f New translations en.json (Norwegian) 2026-03-26 09:43:42 +01:00
Patrick
dc682cfc5f New translations en.json (Dutch) 2026-03-26 09:43:41 +01:00
Patrick
56f77379c7 New translations en.json (Korean) 2026-03-26 09:43:40 +01:00
Patrick
e09155e9d2 New translations en.json (Japanese) 2026-03-26 09:43:38 +01:00
Patrick
641c2d0f5f New translations en.json (Italian) 2026-03-26 09:43:37 +01:00
Patrick
3a98bc6cf9 New translations en.json (Hungarian) 2026-03-26 09:43:36 +01:00
Patrick
e8491798a5 New translations en.json (Hebrew) 2026-03-26 09:43:35 +01:00
Patrick
bb1bda5051 New translations en.json (Finnish) 2026-03-26 09:43:34 +01:00
Patrick
d6a8d28fbb New translations en.json (Greek) 2026-03-26 09:43:33 +01:00
Patrick
79b1d68643 New translations en.json (German) 2026-03-26 09:43:32 +01:00
Patrick
e852cbc4f1 New translations en.json (Danish) 2026-03-26 09:43:30 +01:00
Patrick
3dcf9a2878 New translations en.json (Czech) 2026-03-26 09:43:29 +01:00
Patrick
6012413efb New translations en.json (Catalan) 2026-03-26 09:43:28 +01:00
Patrick
2aeb46c718 New translations en.json (Arabic) 2026-03-26 09:43:27 +01:00
Patrick
fe0b0b7bcb New translations en.json (Afrikaans) 2026-03-26 09:43:26 +01:00
Patrick
2232312f83 New translations en.json (Spanish) 2026-03-26 09:43:25 +01:00
Patrick
f66bfc69cb New translations en.json (French) 2026-03-26 09:43:24 +01:00
Patrick
0ef964e535 New translations en.json (Romanian) 2026-03-26 09:43:23 +01:00
Patrick
6a3e93d4d6 feat: add GitHub PAT authentication for API requests (#302)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

Adds optional GitHub Personal Access Token (PAT) authentication to avoid
API rate limiting when fetching releases. The token is resolved from
three sources in priority order:

1. App settings (`GitHubToken`)
2. `GITHUB_TOKEN` environment variable
3. GitHub CLI (`gh auth token`)

Also improves the rate limit error message with hints on how to
configure a PAT, and adds trace logging so users can see which token
source was used in the debug log.

Related issues (all closed, this PR provides a proper long-term fix via
authenticated requests):
- Relates to #287 — Custom WGT option missing when GitHub rate limit is
reached
- Relates to #235 — Initialization failed due to rate limiting
- Relates to #236 — Catch rate limit exception during initialization
- Relates to #170 — Fix Rate Limit

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style
- [x] I have tested my changes manually
- [ ] I have added necessary documentation (if applicable)
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

- Token is only attached to requests targeting `api.github.com`,
`raw.githubusercontent.com`, and `github.com`
- When no token is configured, the app continues to work with
unauthenticated requests (existing behavior)
- Debug logs now show `[GitHubAuth]` entries indicating which token
source was used
2026-03-26 09:08:04 +01:00
kingchenc
84a4e964a3 feat(V17): extract DDG language map to TrailerLanguageMap.cs with full Jellyfin language coverage
Extracts the inline DDG language map from FixYouTube.cs into a dedicated TrailerLanguageMap.cs file
and expands coverage from 22 languages to all 93 Jellyfin-supported locales.

## Changes

- New file: TrailerLanguageMap.cs — static class with all Jellyfin language codes mapped to
  DDG search keywords (English name + native name per language)
- FixYouTube.cs: inline langMap replaced with __LANG_MAP__ placeholder, injected via .Replace()
  from TrailerLanguageMap.JsObject at build time

## Why

Previously, if a user had a language not in the 22-entry inline map (e.g., Hindi, Bengali, Georgian,
Kazakh, etc.), the DDG search would fall back to an empty string — searching just "{title} Trailer
site:youtube.com" with no language qualifier. This made language matching impossible for those users.

Now every Jellyfin-supported language resolves to proper search keywords. For example:
- hi -> "Hindi हिन्दी"
- ka -> "Georgian ქართული"
- bn -> "Bengali বাংলা"
- kk -> "Kazakh Қазақша"
2026-03-25 22:33:17 +01:00
kingchenc
7bdbc061cc feat(V17): trailer fallback for non-English metadata languages via TMDB + DuckDuckGo Lite
Fixes a long-standing Jellyfin upstream bug (jellyfin/jellyfin#12817) where movies configured with
non-English metadata languages (German, Spanish, French, etc.) show no trailer button because TMDB
returns empty video data for non-English language queries.

## Problem

When Jellyfin fetches metadata with e.g. language=de, TMDB returns 0 trailers even though English
trailers exist in their database. Jellyfin makes no fallback to English. The issue was closed upstream
as "not planned" — unfixed for 2-3 years, affecting every non-English Jellyfin user.

## Solution

Client-side trailer fallback system built into the existing YouTube fix (V16 -> V17). No Jellyfin
server changes required.

When a movie detail page has no trailer button:

1. TMDB: search for trailer in user's language (via include_video_language parameter)
2. DuckDuckGo Lite: search "{title} Trailer {language} site:youtube.com", match by title
3. TMDB: English trailer (cached from step 1, no extra API call)
4. DuckDuckGo Lite: English fallback (cached from step 2, no extra API call)

Max 2 external HTTP requests (1x TMDB + 1x DDG). Steps 3+4 use cached results.

## Why DuckDuckGo Lite instead of Google/YouTube search

Google and YouTube aggressively block automated requests with CAPTCHAs, rate limiting, and bot
detection. DuckDuckGo Lite (lite.duckduckgo.com) returns plain HTML with no JavaScript, no API key
required, no CAPTCHAs, and no bot blocking. DDG Lite has been stable for years with minimal changes
to its HTML structure, making it a reliable fallback for trailer discovery.

## Technical details

- Language detected from Jellyfin user settings (document.documentElement.lang), not TV system language
- TMDB API key extracted at runtime from Jellyfin server plugin configuration (GET /Plugins -> GET
  /Plugins/{id}/Configuration), falls back to Jellyfin's default key from TmdbUtils.cs
- DDG results parsed with title extraction: language-matched results prioritized, non-matched results
  cached as English fallback
- Trailer button injected as native Jellyfin UI element (same CSS classes, Material Icons)
- Fullscreen overlay player reuses existing player.html bridge
- Closable via Tizen back button, Escape, Backspace, or video end
- httpsGet helper with 8s timeout and double-callback guard
- 22 non-English languages supported in DDG language map

## Fallback flow

```
Movie detail page loaded
  |
  +-- Trailer button exists? -> Yes -> normal existing fix (CustomPlayer)
  |
  +-- No -> Service: /trailer?tmdbId=X&lang={Jellyfin language}&tmdbKey={runtime key}
              |
              +-- 1. TMDB: trailer in user's language?
              |     -> Found -> play with CustomPlayer
              |
              +-- 2. DDG Lite: "{title} Trailer {language} site:youtube.com"
              |     -> Language matched in title? -> play with CustomPlayer
              |
              +-- 3. TMDB: English trailer (cached from step 1, no extra call)
              |     -> Found -> play with CustomPlayer
              |
              +-- 4. DDG Lite: English fallback (cached from step 2, no extra call)
              |     -> Found -> play with CustomPlayer
              |
              +-- Nothing found -> no trailer available
```
2026-03-25 21:08:02 +01:00
Dávid Balatoni
f6c6ff074c feat: add GitHub PAT input UI to settings 2026-03-25 19:39:49 +01:00
Dávid Balatoni
ee1a9140cd feat: add trace logging for GitHub token resolution source 2026-03-24 10:36:15 +01:00
Dávid Balatoni
4e27791dfe fix: improve rate limit error message with PAT setup hints 2026-03-21 20:39:09 +01:00
Dávid Balatoni
5518f7f20e feat: wire GitHubAuthHandler into HttpClient singleton 2026-03-21 20:39:05 +01:00
Dávid Balatoni
cea2bda15b feat: add GitHubToken setting to AppSettings 2026-03-21 20:38:46 +01:00
Dávid Balatoni
da91012e06 feat: add GitHub auth handler for authenticated API requests 2026-03-21 20:38:11 +01:00
Patrick
1e33f057bf Update README.md 2026-03-21 14:08:31 +01:00
Patrick
eab08e271d Update Crowdin configuration file 2026-03-21 13:41:36 +01:00
PatrickSt1991
b20803cc5b Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-03-21 13:35:23 +01:00
PatrickSt1991
b3eb8d007b removed bom from pt.json
Dynamic Localization to work with transifex
2026-03-21 13:23:44 +01:00
Patrick
35990672d2 Add Transifex configuration for localization 2026-03-21 11:50:16 +01:00
Patrick
cc666e4b68 Set checkout reference to 'beta' in workflow 2026-03-21 11:31:46 +01:00
Patrick
70bdf89b4a Sync master-only docs and workflow into beta (#300)
# Pull Request Template

## Branch
- [ ] I branched off beta (not master) to develop this feature/fix

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-03-21 11:26:12 +01:00
Patrick
a6263a2468 Merge branch 'master' into beta 2026-03-21 11:25:59 +01:00
github-actions[bot]
eead46aeb6 chore: update version table in README [skip ci] 2026-03-21 10:13:29 +00:00
PatrickSt1991
b7d1c921d3 Sync master-only docs and workflow into beta 2026-03-21 11:08:23 +01:00
Patrick
8dbb0de4a2 Beta -> Prod (#299)
Added Litefin
2026-03-21 11:02:44 +01:00
github-actions[bot]
868df40a6b chore: update version table in README [skip ci] 2026-03-21 07:01:07 +00:00
Patrick
0b77c32b5e Revise contributor links in README
Updated links and added new contributor information.
2026-03-21 07:59:01 +01:00
Patrick
c0e1f0333f Add LiteFin to the pool (#298)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

This includes LiteFin as a new package.

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---
2026-03-21 07:57:14 +01:00
PatrickSt1991
78d0ede171 Added litefin and filter for wgt and tpk in selected releases 2026-03-21 07:56:12 +01:00
Patrick
03647d6360 Update AppSettings.cs 2026-03-21 07:15:32 +01:00
Patrick
f17a8404f3 Update AddLatestRelease.cs
wgt and tpk filter
2026-03-21 07:13:30 +01:00
Patrick
3d0f89e6a8 Add fetch call for LiteFinRelease 2026-03-21 07:02:11 +01:00
Patrick
116351376a Add Moonlite to jellyfinOverrides dictionary 2026-03-21 06:57:38 +01:00
Patrick
7dc7ecc6e3 Add Litefin constant for image URL 2026-03-21 06:55:21 +01:00
Patrick
f663eae6d9 Add LiteFinRelease property to AppSettings 2026-03-21 06:49:52 +01:00
Patrick
bfed3237e7 Update Constants.cs 2026-03-15 07:44:56 +01:00
Patrick
fbec97cfad Add Portuguese to available languages in README 2026-03-04 12:54:01 +01:00
Patrick
a48b49e9fc Update pull request template to include branch info
Added a section to specify the branch used for development.
2026-03-04 12:50:33 +01:00
Patrick
3b4e31e09e Change rebase target from master to beta branch 2026-03-04 12:48:59 +01:00
Patrick
9159ee38ac Add Portuguese localization to pt.json (#293)
# Pull Request Template

## Description

Added Portuguese localization for various application messages.

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-03-04 12:43:25 +01:00
PatrickSt1991
0bdd8cd6ce Add @eduardomozart as contributor 2026-03-04 12:39:37 +01:00
PatrickSt1991
de7edefc9c added Portugees, thanks @eduardomozart 2026-03-04 12:35:06 +01:00
PatrickSt1991
35cb7cf2e6 Add @eduardomozart as contributor 2026-03-04 12:34:49 +01:00
Patrick
ef7b2abb31 Custom WGT Fixes (#291)
# Pull Request Template

## Description

Custom WGT option is now always added even when the GitHub rate limit is
reached
Fixes #287 

Download and Install button is placed inside Try / Catch, should fix the
button being stuck after failed custom wgt
Fixes #285 

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-03-03 09:08:51 +01:00
PatrickSt1991
5ed25031ca Place CustomWGT in try catch #285
Always add Custom WGT option also when rate limit is hit #287
2026-03-03 08:59:50 +01:00
Patrick
37d61eed78 Moonfin URL update (#290)
# Pull Request Template

## Description

This PR is to update the URL for the new Moonfin repo since Tizen and
webOS have been merged

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-03-03 08:55:46 +01:00
PatrickSt1991
9aa4073995 Add @RadicalMuffinMan as contributor 2026-03-03 08:28:18 +01:00
PatrickSt1991
ecf6937f55 Moonfin URL update 2026-03-03 08:18:52 +01:00
Patrick
23ffe2d046 feat: YouTube Service dynamic service port (#284)
# Pull Request Template

## Description

- Loosens the restrictions on Jellyfin Installations to allow
jellyfin-secondary to be installed next to jellyfin main.
- Sets dynamic port for YouTube Service fix to allow multiple service
for side-by-side running.

Fixes #280 

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-02-22 11:23:43 +01:00
Patrick
1c6ac7a282 Update Linux release status in stable-release.yml
Removed AppImage from the Linux release status message.
2026-02-22 09:35:42 +01:00
Patrick
5f3f8a6895 Update Linux status in beta prerelease workflow 2026-02-22 09:30:51 +01:00
PatrickSt1991
4e9ab2dcb8 Allow secondary Jellyfin to pass even if main is installed
Dynamic port change on youtube trailer service fix for secondary jellyfin
New function to extract appid from config
2026-02-22 09:22:32 +01:00
Patrick
c87b7bef65 Beta (#282)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-02-21 15:59:59 +01:00
Patrick
60d30c3f05 Add Transport Tycoon Deluxe (#281)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-02-21 15:43:37 +01:00
Patrick
0cab66098e Add TTD constant for image URL 2026-02-21 15:42:11 +01:00
Patrick
030cf24786 Add TransportTycoonDeluxe preview image 2026-02-21 15:41:03 +01:00
github-actions[bot]
1c12ad6af7 chore: update version table in README [skip ci] 2026-02-21 14:33:06 +00:00
Patrick
3e5d3bfe74 feat: Package required version check (#278)
# Pull Request Template

## Description

Feature added for validating required version against TV Tizen OS
version.
Improved functionality for checking if appId needs to be replaced or
already exists.

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-02-21 15:25:27 +01:00
PatrickSt1991
d802bddf52 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-02-21 15:18:01 +01:00
skelzer
3f28090e9b Contribution by @skelzer 2026-02-21 15:17:23 +01:00
Patrick
faab2a68df Update TizenCertificateService.cs 2026-02-19 16:40:39 +01:00
github-actions[bot]
6f37097e97 chore: update version table in README [skip ci] 2026-02-18 11:34:32 +00:00
PatrickSt1991
9d4f6b5630 version bump 2026-02-18 12:31:01 +01:00
PatrickSt1991
2c2bb5de80 Package required version check with Tizen OS 2026-02-18 12:02:39 +01:00
Patrick Stel
cd7d17e2b0 NotInstalled Error code added
Catch not installed error code and continue installation flow
2026-02-17 14:31:54 +01:00
Patrick
29381bca2e Beta expected app (#275) 2026-02-16 15:34:12 +01:00
Patrick
4f0232ab6a Fix app ID retrieval in CheckForInstalledApp method 2026-02-16 15:32:22 +01:00
Patrick
28b39c39f5 Implement ReadWgtApplicationId method
Add method to read WGT application ID from config.xml
2026-02-16 15:29:40 +01:00
Patrick
f278f938c0 Refactor CheckForInstalledApp to use expectedAppId (#274)
expectedAppId check instead of using constants

# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-02-16 15:20:36 +01:00
Patrick
7d4c94bd62 Refactor CheckForInstalledApp to use expectedAppId
expectedAppId check instead of using constants
2026-02-16 15:19:40 +01:00
PatrickSt1991
e36755de2f Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-02-14 08:25:54 +01:00
PatrickSt1991
c15405cebb Resolves #265 - Assume installed when app_list is not returned by TV 2026-02-14 08:25:33 +01:00
Patrick
17153786c5 Adds Doom preview image (#272)
Dooms added to the community packages, the PR add the preview image.
2026-02-10 12:57:02 +01:00
Patrick
e5ab14c48e Add doom preview
Adds doom preview image
2026-02-10 12:51:43 +01:00
Patrick
1643ea5758 Update BuildInfoViewModel.cs 2026-02-10 12:50:53 +01:00
Patrick
7906bca1b2 Update Constants.cs 2026-02-10 12:49:48 +01:00
Patrick
9cf3de81ae Refactor stable release workflow and remove AppImage build
Updated the stable release workflow to copy all files from the publish directory and modified the maintainer and description fields in the control file. Removed the AppImage build section.
2026-02-09 12:26:00 +01:00
Patrick
eb10033d19 Beta (#270)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-02-09 12:24:57 +01:00
PatrickSt1991
91c3aa9549 Revert program.cs logs folder logic 2026-02-09 12:21:03 +01:00
PatrickSt1991
c63adfa618 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-02-09 12:11:24 +01:00
PatrickSt1991
4471e27855 Revert "Folder location fix for Linux"
This reverts commit e377ddfd49.
2026-02-09 12:08:40 +01:00
Patrick
7ef1c516e1 Update maintainer and remove AppImage build process
Updated maintainer information and removed AppImage build steps.
2026-02-09 12:03:32 +01:00
github-actions[bot]
2daac548cf chore: update version table in README [skip ci] 2026-02-09 10:13:24 +00:00
PatrickSt1991
e377ddfd49 Folder location fix for Linux 2026-02-09 11:06:10 +01:00
PatrickSt1991
ae4f2ccb2f Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-02-09 10:26:50 +01:00
PatrickSt1991
64d610d6b2 Different location for Linux 2026-02-09 10:26:42 +01:00
Patrick
88f4755c3d Add dependency installation for AppImage
Install necessary dependencies for AppImage build.
2026-02-09 09:51:17 +01:00
Patrick
d401e750d5 Use recursive copy for publish output in workflows
Updated copy commands to use recursive flag for full directory structure.
2026-02-09 09:47:27 +01:00
Patrick
cd55172fb0 Refactor beta prerelease workflow for AppImage build
Updated the workflow to copy all published files and set up the AppImage structure correctly.
2026-02-09 08:09:37 +01:00
github-actions[bot]
2e495db51d chore: update version table in README [skip ci] 2026-01-30 17:52:26 +00:00
Patrick
cdbc9959c5 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta (#262)
# Pull Request Template

## Description

Introduce a app preview in the build information 

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [X] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes
none
2026-01-30 18:44:24 +01:00
Patrick
60e0148ee2 Update stable-release.yml 2026-01-30 15:56:45 +01:00
github-actions[bot]
b68114e6db chore: update version table in README [skip ci] 2026-01-29 18:37:50 +00:00
PatrickSt1991
51af88217e Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-01-29 19:34:12 +01:00
PatrickSt1991
c4a4e947ba App preview added 2026-01-29 19:34:02 +01:00
Patrick
fd7058ca57 Add CFBundleShortVersionString and CFBundleIconFile 2026-01-29 11:31:00 +01:00
Patrick
a41a36aad3 Update beta-prerelease.yml 2026-01-29 11:17:43 +01:00
Patrick
1c8bbb7251 Update beta-prerelease.yml 2026-01-29 09:02:04 +01:00
Patrick
7a1729530a Fix duplicate overwrite_files entry
Removed duplicate 'overwrite_files' entry in beta-prerelease.yml.
2026-01-29 08:49:53 +01:00
Patrick
efdb3406fa Refactor macOS icon conversion in workflow
Updated macOS icon conversion process to use librsvg for reliable SVG to PNG conversion.
2026-01-29 08:49:08 +01:00
Patrick
821fac94f8 Enhance stable-release workflow with manual trigger
Added workflow_dispatch event to allow manual triggering with inputs for ref and allow_existing_release.
2026-01-28 13:35:26 +01:00
Patrick
3069290214 Update stable release workflow to allow asset updates 2026-01-28 13:31:47 +01:00
github-actions[bot]
9065cf2249 chore: update version table in README [skip ci] 2026-01-28 12:27:57 +00:00
Patrick
e58a198711 Refactor stable release workflow
Refactor stable release workflow to improve clarity and organization, including version validation, changelog generation, and artifact packaging for Windows, Linux, and macOS.
2026-01-28 13:20:14 +01:00
Patrick
4a3e54bd47 YouTube Trailer fix, multiple bug fixes (#260)
# Pull Request Template

## Description

This version implements a Tizen native fix for YouTube trailers errors,
it creates a web application service that runs behind jellyfin.
Passing the file:// restriction and patching youtube-plugin.js

Along side multiple bug fixes

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-01-28 13:01:02 +01:00
Patrick
9e6eaa2316 Merge branch 'master' into beta 2026-01-28 12:46:41 +01:00
PatrickSt1991
a1cac4ae17 return fix, iframe layover fix
gerneral youtube trailer fix
2026-01-28 12:17:39 +01:00
github-actions[bot]
a48e3b5d24 chore: update version table in README [skip ci] 2026-01-27 20:06:41 +00:00
PatrickSt1991
cc231e39fd version bump 2026-01-27 21:03:00 +01:00
PatrickSt1991
9879412518 fetchReleases instead of release
Added prefix option and int to tell how much releases to fetch
2026-01-27 20:57:47 +01:00
PatrickSt1991
0af0f2d1aa Fixed config.json error when package isnt jellyifin 2026-01-27 20:07:06 +01:00
PatrickSt1991
e91a0f23d6 YouTube fix on Tizen 9 2026-01-27 19:29:36 +01:00
PatrickSt1991
d4fc9b6071 new finding 2026-01-26 21:11:43 +01:00
PatrickSt1991
0979d05234 Wokring resolver 2026-01-25 20:53:52 +01:00
PatrickSt1991
d1078087f5 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-01-24 18:27:16 +01:00
PatrickSt1991
63f37a5cfa add esbuild - my bad sorry
beta build service for youtube
2026-01-24 18:23:33 +01:00
PatrickSt1991
867c7c2c6e Added default for Github Assets 2026-01-23 16:12:14 +01:00
Patrick
c572e2b8ed Update beta-prerelease.yml 2026-01-22 19:50:14 +01:00
Patrick
0400671fc4 Update beta-prerelease.yml 2026-01-22 19:48:27 +01:00
Patrick
d58baa4012 Update beta-prerelease.yml 2026-01-22 19:45:36 +01:00
Patrick
2ca2f4c7f0 Update beta-prerelease.yml 2026-01-22 18:15:40 +01:00
Patrick
8d7ab3429a Update beta-prerelease.yml 2026-01-22 12:11:45 +01:00
Patrick
096d7a3e57 Generate Info.plist for Jellyfin2Samsung apps
Added Info.plist generation for both ARM64 and x64 Jellyfin2Samsung apps.
2026-01-22 12:04:04 +01:00
Patrick
276be8cabb Update beta release workflow for version handling
Refactor beta release workflow to remove SKIP_RELEASE logic and improve version extraction.
2026-01-22 11:53:59 +01:00
Patrick
8eef524927 Refactor beta release workflow and version extraction
Removed SKIP_RELEASE logic and related conditions from the beta release workflow. Improved version extraction and release note generation steps.
2026-01-22 11:47:57 +01:00
Patrick
754a47b4dd Refactor AppImage and .app bundle creation steps
Updated AppImage build process and improved .app bundle creation.
2026-01-22 11:30:11 +01:00
Patrick
581f1bb238 Update VERSION_TAG assignment in beta prerelease workflow 2026-01-22 09:36:43 +01:00
github-actions[bot]
669d3896a0 chore: update version table in README [skip ci] 2026-01-22 08:36:24 +00:00
Patrick
a8def26087 Append '-beta' to version in workflow 2026-01-22 09:36:05 +01:00
Patrick
3ea51370cd stip beta tag (#249)
Small PR that strips beta tag so github action can build new release
2026-01-22 09:27:13 +01:00
github-actions[bot]
f280c76a27 chore: update version table in README [skip ci] 2026-01-22 08:26:39 +00:00
Patrick
d59c5e42c5 stip beta tag 2026-01-22 09:23:15 +01:00
kingchenc
04d6f5c228 Fix version 2026-01-21 21:09:00 +01:00
kingchenc
9cd6006841 Implement CI/CD beta workflow, GitHub Atom auto-updater, and critical bug fixes (#247)
So far, everything looks good :)
2026-01-21 21:01:17 +01:00
Patrick
14adc6e806 Merge branch 'master' into beta 2026-01-21 13:41:26 +01:00
PatrickSt1991
e1b9d5b03d possible null references popping up in github action 2026-01-21 13:34:00 +01:00
PatrickSt1991
71cf3d62a8 move functions to htmlutils and filter out beta releases 2026-01-21 13:14:34 +01:00
PatrickSt1991
fed80d8f55 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-01-21 12:56:03 +01:00
PatrickSt1991
b36a83038f Write api response to debug log 2026-01-21 12:55:49 +01:00
Patrick
f17efcbefc Add empty line before platform status table in YAML 2026-01-21 11:54:47 +01:00
Patrick
fe16834604 Update beta-prerelease.yml 2026-01-21 11:49:22 +01:00
Patrick
ac5fb567d0 Set bash shell for Create DMG step
Specify bash shell for Create DMG step in workflow.
2026-01-21 11:21:30 +01:00
Patrick
f38c345cc6 Refactor AppImage comment and add macOS packaging
Updated comments and added macOS CLI tarball packaging step.
2026-01-21 11:13:41 +01:00
Patrick
b6ac5b600c Refactor beta-prerelease.yml for clarity and fixes
Updated comments for clarity and fixed AppImage build process to not require FUSE. Adjusted macOS build steps and removed unnecessary comments.
2026-01-21 11:04:23 +01:00
Patrick
36a9a267bc Refactor beta release workflow for clarity and structure 2026-01-21 10:58:41 +01:00
Patrick
7350a0c92d Refactor beta release workflow for improved clarity
Refactor beta release workflow to streamline version extraction, skip logic, and artifact packaging for Linux and macOS. Update commands for building and publishing across platforms.
2026-01-21 10:53:33 +01:00
Patrick
a575a4353b Refactor macOS CLI publishing and update release notes
Updated macOS CLI publishing steps and added universal binary creation. Modified release notes and packaging for macOS and Linux.
2026-01-21 10:46:52 +01:00
PatrickSt1991
2adebebb74 Fixed DeletePreviousInstall setting
Forcefully delete previous installation if tryOverwrite is true but Author certificate mismatch
2026-01-21 08:31:01 +01:00
Patrick
9c8ae5c572 Update beta-prerelease.yml 2026-01-20 22:25:11 +01:00
Patrick
7af3f099cb Update beta-prerelease.yml 2026-01-20 22:19:38 +01:00
Patrick
ad0c3ff186 Rename DMG creation step and improve cleanup
Updated DMG creation step to be safer and cleaner.
2026-01-20 15:19:50 +01:00
Patrick
405573bc36 Update beta-prerelease.yml 2026-01-19 20:49:53 +01:00
Patrick
224fb37350 Update icon download and conversion process in workflow 2026-01-19 20:44:12 +01:00
Patrick
62fc93f10f Update download icon step to use bash shell 2026-01-19 20:37:48 +01:00
Patrick
dea4f4e6c7 Enhance beta release workflow for macOS app
Added macOS app publishing steps and icon conversion.
2026-01-19 20:31:21 +01:00
PatrickSt1991
6a0a6df965 Moved Jellyfin config guard up to installerService
NetworkInterfaces as selectable instead of automatic
2026-01-19 20:05:42 +01:00
kingchenc
9c38699702 Add cancellation token to stop background tasks when update dialog shows
Description

  Fixed UI freeze when update dialog appears during startup. Previously, network scanning and GitHub release loading continued running in the background while the update dialog was displayed, causing the window to freeze.

Changes:
  - Added _initializationCts to cancel initialization tasks
  - Pass cancellation token to LoadReleasesAsync() and LoadDevicesAsync()
  - Cancel background tasks before showing update dialog
  - Resume initialization after user dismisses dialog (Cancel/Skip/Manual)
  - Added cancellation check in DeviceHelper.ScanForDevicesAsync() loop
2026-01-19 13:38:51 +01:00
PatrickSt1991
36b112cd96 Added spacer to prevent youtube falling of the view 2026-01-18 20:01:52 +01:00
kingchenc
0f68d108b1 Add auto-updater with GitHub Atom feed (no rate limit)
Summary
  - Add auto-update checker using GitHub Atom feed (bypasses API rate limits)
  - Show update dialog on startup with two options: "Open Releases" (manual) or "Update Now" (automatic)
  - Automatic updates: download, extract, replace files, restart app
  - Support for Windows, Linux, and macOS

Changes
  - New services: `UpdaterService`, `UpdateDialogService`
  - New dialog: `UpdateDialog` with version info and release notes
  - New settings: `CheckForUpdatesOnStartup`, `SkippedUpdateVersion`
  - Localization: All 6 languages (EN, DE, NL, FR, DA, TR)

Technical Details
  - Uses `/releases.atom` endpoint instead of REST API (no rate limit)
  - Falls back to API only for download URLs when update is available
  - Update script handles file replacement while app is closed
  - Version comparison with semver support
2026-01-18 15:03:23 +01:00
kingchenc
a97d4dccf3 Fix for when no Theme is selected
2026-01-18 14:31:19.230 [Binding]An error occurred binding 'Text' to 'SelectedJellyTheme.Name' at 'SelectedJellyTheme': 'Value is null.' (TextBlock #11601606)
2026-01-18 14:45:36 +01:00
Patrick
2a054c761f Add stable release workflow for Jellyfin2Samsung
This workflow automates the stable release process for the Jellyfin2Samsung project, including validation, changelog generation, and packaging of artifacts.
2026-01-18 14:25:10 +01:00
Patrick
faa67aa904 Update beta-prerelease.yml
Overwrite beta on new commit with same version number
2026-01-18 14:00:20 +01:00
github-actions[bot]
432e50abdf chore: update version table in README [skip ci] 2026-01-18 12:51:56 +00:00
PatrickSt1991
de86a50773 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-01-18 13:50:36 +01:00
PatrickSt1991
2e9f1fd350 version bump 2026-01-18 13:49:57 +01:00
PatrickSt1991
a76b2cc3da Replaced DeviceInfo.Duid with tvIpAddress, SDBClient expects TVIP instead DUID 2026-01-18 13:48:44 +01:00
Patrick
b7e2f85251 Refactor beta release workflow to improve clarity 2026-01-18 13:20:30 +01:00
Patrick
95f166124c Refactor beta release workflow and update comments 2026-01-18 13:12:38 +01:00
Patrick
073cd3ebd2 Add GitHub Actions workflow for beta pre-release 2026-01-18 13:04:56 +01:00
80 changed files with 7998 additions and 791 deletions

View File

@@ -84,7 +84,7 @@ We welcome pull requests! Please:
```bash
git remote add upstream https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer.git
git fetch upstream
git rebase upstream/master
git rebase upstream/beta
```
---

View File

@@ -1,5 +1,8 @@
# Pull Request Template
## Branch
- [ ] I branched off beta (not master) to develop this feature/fix
## Description
Please include a summary of the change and which issue is fixed. Also include relevant motivation and context.

View File

@@ -13,145 +13,292 @@ env:
CONFIGURATION: Release
jobs:
# ======================================================
# RELEASE + WINDOWS + LINUX
# ======================================================
beta-release:
runs-on: ubuntu-latest
outputs:
VERSION: ${{ env.VERSION }}
VERSION_TAG: ${{ env.VERSION_TAG }}
steps:
# --------------------------------------------------
# Checkout (full history needed for changelog & tags)
# --------------------------------------------------
- uses: actions/checkout@v4
with:
fetch-depth: 0
# --------------------------------------------------
# Extract version from AppSettings.cs (pure Linux tools)
# --------------------------------------------------
# ---------------- VERSION ----------------
- name: Extract version
shell: bash
run: |
VERSION=$(curl -fsSL \
https://raw.githubusercontent.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/refs/heads/beta/Jellyfin2Samsung-CrossOS/Helpers/AppSettings.cs \
| grep -oE 'v[0-9]+(\.[0-9]+){1,3}')
VERSION=$(grep -oE 'v[0-9]+(\.[0-9]+){1,3}' \
Jellyfin2Samsung-CrossOS/Helpers/AppSettings.cs)
if [ -z "$VERSION" ]; then
echo "❌ Failed to extract version"
echo "❌ Version not found"
exit 1
fi
echo "VERSION=${VERSION#v}" >> $GITHUB_ENV
echo "VERSION_TAG=${VERSION}-beta" >> $GITHUB_ENV
# --------------------------------------------------
# Skip if this beta version already exists
# --------------------------------------------------
- name: Skip if version already released
shell: bash
run: |
if git tag --list | grep -q "^${VERSION_TAG}$"; then
echo " ${VERSION_TAG} already exists — skipping"
exit 0
fi
# --------------------------------------------------
# Generate changelog from commits
# --------------------------------------------------
# ---------------- RELEASE NOTES ----------------
- name: Generate changelog
shell: bash
run: |
LAST_TAG=$(git tag --sort=-creatordate | grep beta | head -n 1)
if [ -z "$LAST_TAG" ]; then
RANGE="HEAD"
else
RANGE="$LAST_TAG..HEAD"
fi
RANGE="${LAST_TAG:+$LAST_TAG..HEAD}"
{
echo "## ✅ What's New & Improved"
git log $RANGE --pretty=format:"- %s"
git log ${RANGE:-HEAD} --pretty=format:"- %s"
} > CHANGELOG.md
# --------------------------------------------------
# Setup .NET SDK
# --------------------------------------------------
- name: Prepare release notes
run: |
DATE=$(date +'%Y-%m-%d')
{
echo "## 📦 [${VERSION_TAG}] ${DATE}"
echo ""
cat CHANGELOG.md
echo ""
echo ""
echo "| Platform | Status | Notes |"
echo "|----------|--------|-------|"
echo "| 🍎 macOS (.app + dmg) | ⚠️ Beta | ARM64 + Intel |"
echo "| 🍎 macOS (CLI) | ⚠️ Beta | Per-arch tar.gz |"
echo "| 🐧 Linux | ⚠️ Beta | tar.gz / .deb |"
echo "| 🪟 Windows | ⚠️ Beta | CI-built |"
} > RELEASE_NOTES.md
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# --------------------------------------------------
# Publish (Linux runner, cross-compile)
# --------------------------------------------------
- name: Publish Windows
# ---------------- WINDOWS ----------------
- name: Build Windows
run: |
dotnet publish \
-c $CONFIGURATION \
-r win-x64 \
-p:SelfContained=true \
-p:UseAppHost=true \
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release -r win-x64 -p:SelfContained=true \
-o publish/win-x64
- name: Publish macOS
run: |
dotnet publish \
-c $CONFIGURATION \
-r osx-x64 \
-p:SelfContained=true \
-p:UseAppHost=true \
-o publish/osx-x64
mkdir -p dist
(cd publish/win-x64 && zip -r "../../dist/${PRODUCT_NAME}-${VERSION_TAG}-win-x64.zip" .)
- name: Publish Linux
# ---------------- LINUX ----------------
- name: Build Linux
run: |
dotnet publish \
-c $CONFIGURATION \
-r linux-x64 \
-p:SelfContained=true \
-p:UseAppHost=true \
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release -r linux-x64 -p:SelfContained=true \
-o publish/linux-x64
# --------------------------------------------------
# Package artifacts (tar / zip — Linux native)
# --------------------------------------------------
- name: Package artifacts
shell: bash
run: |
mkdir -p dist
tar -czf "dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.tar.gz" \
-C publish/linux-x64 .
zip -r "dist/${PRODUCT_NAME}-${VERSION_TAG}-win-x64.zip" publish/win-x64
tar -czf "dist/${PRODUCT_NAME}-${VERSION_TAG}-osx-x64.tar.gz" -C publish/osx-x64 .
tar -czf "dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.tar.gz" -C publish/linux-x64 .
# ---------------- LINUX ICON ----------------
- name: Linux icon
run: |
sudo apt-get update
sudo apt-get install -y librsvg2-bin
rsvg-convert -w 256 -h 256 \
jellyfin-tizen-logo.svg -o jellyfin.png
# --------------------------------------------------
# Create GitHub Pre-Release
# --------------------------------------------------
- name: Create GitHub Pre-Release
uses: softprops/action-gh-release@v2
# ---------------- DEB ----------------
- name: Build .deb
run: |
sudo apt-get install -y dpkg-dev
PKG=deb
mkdir -p $PKG/opt/jellyfin2samsung \
$PKG/usr/share/{applications,icons/hicolor/256x256/apps}
cp -R publish/linux-x64/. $PKG/opt/jellyfin2samsung/
chmod +x $PKG/opt/jellyfin2samsung/Jellyfin2Samsung
# 🔥 quick+dirty: allow writes where your app writes
mkdir -p \
$PKG/opt/jellyfin2samsung/Logs \
$PKG/opt/jellyfin2samsung/Downloads \
$PKG/opt/jellyfin2samsung/Assets/TizenSDB \
$PKG/opt/jellyfin2samsung/Assets/Certificate
chmod -R 777 \
$PKG/opt/jellyfin2samsung/Logs \
$PKG/opt/jellyfin2samsung/Downloads \
$PKG/opt/jellyfin2samsung/Assets/TizenSDB \
$PKG/opt/jellyfin2samsung/Assets/Certificate
cp jellyfin.png $PKG/usr/share/icons/hicolor/256x256/apps/jellyfin2samsung.png
cat > $PKG/usr/share/applications/jellyfin2samsung.desktop <<EOF
[Desktop Entry]
Name=Jellyfin2Samsung
Exec=/opt/jellyfin2samsung/Jellyfin2Samsung
Icon=jellyfin2samsung
Type=Application
Categories=Utility;
EOF
mkdir -p $PKG/DEBIAN
cat > $PKG/DEBIAN/control <<EOF
Package: jellyfin2samsung
Version: ${VERSION}
Architecture: amd64
Maintainer: MadeByPatrick
Description: Jellyfin2Samsung
EOF
dpkg-deb --build $PKG
mv $PKG.deb dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.deb
# ---------------- RELEASE (UPDATE SAME TAG EVERY COMMIT) ----------------
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.VERSION_TAG }}
name: ${{ env.VERSION_TAG }}
prerelease: true
overwrite_files: true
files: dist/*
body: |
## 📦 [${{ env.VERSION_TAG }}] $(date +'%Y-%m-%d')
body_path: RELEASE_NOTES.md
$(cat CHANGELOG.md)
# ======================================================
# MACOS (PER-ARCH CLI + APP + DMG)
# ======================================================
macos-app:
runs-on: macos-latest
needs: beta-release
---
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
⚠️ **Disclaimer — Platform Support Status**
# ---------------- ICON ----------------
# ---------------- ICON ----------------
- name: macOS icon (SVG -> ICNS)
run: |
# Reliable SVG -> PNG conversion on GitHub macOS runners
brew install librsvg
| Platform | Status | Notes |
|----------|--------|-------|
| 🍎 macOS | ⚠️ Beta | CI-built |
| 🐧 Linux | ⚠️ Beta | CI-built |
| 🪟 Windows | ⚠️ Beta | CI-built |
# If the SVG is already in your repo, keep this as-is.
# If not, uncomment the curl line below and remove the local reference.
# curl -fsSL \
# https://raw.githubusercontent.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/refs/heads/master/jellyfin-tizen-logo.svg \
# -o jellyfin-tizen-logo.svg
---
rsvg-convert -w 1024 -h 1024 jellyfin-tizen-logo.svg -o jellyfin.png
### 🛡️ Security Notice
Antivirus warnings may occur and are likely **false positives**.
rm -rf icon.iconset
mkdir icon.iconset
for s in 16 32 128 256 512; do
sips -z $s $s jellyfin.png --out icon.iconset/icon_${s}x${s}.png
sips -z $((s*2)) $((s*2)) jellyfin.png --out icon.iconset/icon_${s}x${s}@2x.png
done
---
iconutil -c icns icon.iconset -o jellyfin.icns
### 🙌 Thanks for testing **Samsung Jellyfin Installer (Beta)**
💬 Feedback welcome via GitHub issues & discussions.
# ---------------- ARM64 ----------------
- name: macOS ARM64 publish
run: |
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release -r osx-arm64 -p:SelfContained=true \
-o publish/osx-arm64
mkdir -p dist
tar -czf \
dist/${PRODUCT_NAME}-${{ needs.beta-release.outputs.VERSION_TAG }}-macos-arm64.tar.gz \
-C publish/osx-arm64 .
APP=Jellyfin2Samsung-arm64.app/Contents
mkdir -p $APP/MacOS $APP/Resources
cp -R publish/osx-arm64/* $APP/MacOS/
chmod +x $APP/MacOS/Jellyfin2Samsung
cp jellyfin.icns $APP/Resources/
cat > $APP/Info.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Jellyfin2Samsung</string>
<key>CFBundleExecutable</key><string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key><string>org.madebypatrick.jellyfin2samsung</string>
<key>CFBundleVersion</key><string>${{ needs.beta-release.outputs.VERSION }}</string>
<key>CFBundleShortVersionString</key><string>${{ needs.beta-release.outputs.VERSION }}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>CFBundleIconFile</key><string>jellyfin.icns</string>
</dict>
</plist>
EOF
codesign --deep --force --sign - Jellyfin2Samsung-arm64.app
rm -rf dmg-arm64
mkdir -p dmg-arm64
cp -R Jellyfin2Samsung-arm64.app dmg-arm64/Jellyfin2Samsung.app
ln -s /Applications dmg-arm64/Applications
hdiutil create \
-volname Jellyfin2Samsung \
-srcfolder dmg-arm64 \
-format UDZO \
dist/${PRODUCT_NAME}-${{ needs.beta-release.outputs.VERSION_TAG }}-macos-arm64.dmg
# ---------------- INTEL ----------------
- name: macOS Intel publish
run: |
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release -r osx-x64 -p:SelfContained=true \
-o publish/osx-x64
tar -czf \
dist/${PRODUCT_NAME}-${{ needs.beta-release.outputs.VERSION_TAG }}-macos-x64.tar.gz \
-C publish/osx-x64 .
APP=Jellyfin2Samsung-x64.app/Contents
mkdir -p $APP/MacOS $APP/Resources
cp -R publish/osx-x64/* $APP/MacOS/
chmod +x $APP/MacOS/Jellyfin2Samsung
cp jellyfin.icns $APP/Resources/
cat > $APP/Info.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Jellyfin2Samsung</string>
<key>CFBundleExecutable</key><string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key><string>org.madebypatrick.jellyfin2samsung</string>
<key>CFBundleVersion</key><string>${{ needs.beta-release.outputs.VERSION }}</string>
<key>CFBundleShortVersionString</key><string>${{ needs.beta-release.outputs.VERSION }}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>CFBundleIconFile</key><string>jellyfin.icns</string>
</dict>
</plist>
EOF
codesign --deep --force --sign - Jellyfin2Samsung-x64.app
rm -rf dmg-x64
mkdir -p dmg-x64
cp -R Jellyfin2Samsung-x64.app dmg-x64/Jellyfin2Samsung.app
ln -s /Applications dmg-x64/Applications
hdiutil create \
-volname Jellyfin2Samsung \
-srcfolder dmg-x64 \
-format UDZO \
dist/${PRODUCT_NAME}-${{ needs.beta-release.outputs.VERSION_TAG }}-macos-x64.dmg
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.beta-release.outputs.VERSION_TAG }}
name: ${{ needs.beta-release.outputs.VERSION_TAG }}
prerelease: true
overwrite_files: true
files: dist/*

373
.github/workflows/stable-release.yml vendored Normal file
View File

@@ -0,0 +1,373 @@
name: Stable Release
on:
pull_request:
branches: [master]
types: [closed]
workflow_dispatch:
inputs:
ref:
description: "Git ref to build (branch/tag/SHA). Default: master"
required: false
default: "master"
allow_existing_release:
description: "If true, update assets even when the stable tag already exists"
required: false
type: boolean
default: true
permissions:
contents: write
env:
PRODUCT_NAME: Jellyfin2Samsung
PROJECT_DIR: Jellyfin2Samsung-CrossOS
CONFIGURATION: Release
jobs:
# ======================================================
# STABLE RELEASE + WINDOWS + LINUX (tar.gz + deb)
# ======================================================
stable-release:
if: |
(github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'master' &&
github.event.pull_request.head.ref == 'beta') ||
(github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.version.outputs.VERSION }}
VERSION_TAG: ${{ steps.version.outputs.VERSION_TAG }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
# ---------------- VERSION (NO -beta allowed) ----------------
- name: Validate AppVersion (must be stable)
id: version
shell: bash
run: |
FILE="${PROJECT_DIR}/Helpers/AppSettings.cs"
VERSION_LINE=$(grep 'AppVersion' "$FILE" || true)
if echo "$VERSION_LINE" | grep -q '\-beta"'; then
echo "❌ AppVersion still contains '-beta'"
echo " Stable releases must use a clean version like v2.0.0.1"
exit 1
fi
VERSION=$(echo "$VERSION_LINE" | grep -oE 'v[0-9]+(\.[0-9]+){1,3}')
if [ -z "$VERSION" ]; then
echo "❌ Failed to extract stable version"
exit 1
fi
echo "VERSION=${VERSION#v}" >> "$GITHUB_ENV"
echo "VERSION_TAG=${VERSION}" >> "$GITHUB_ENV"
echo "VERSION=${VERSION#v}" >> "$GITHUB_OUTPUT"
echo "VERSION_TAG=${VERSION}" >> "$GITHUB_OUTPUT"
# --------------------------------------------------
# Guard (log-only): create or update assets
# --------------------------------------------------
- name: Stable release exists? (allow updating assets)
shell: bash
run: |
if git tag --list | grep -qx "$VERSION_TAG"; then
echo " Stable release $VERSION_TAG already exists — will update assets"
else
echo " Stable release $VERSION_TAG does not exist — will create it"
fi
# --------------------------------------------------
# Optional: in manual runs, allow user to DISABLE updating an existing release
# --------------------------------------------------
- name: Stop if release exists and manual updating is disabled
if: github.event_name == 'workflow_dispatch' && inputs.allow_existing_release == false
shell: bash
run: |
if git tag --list | grep -qx "$VERSION_TAG"; then
echo " Release $VERSION_TAG already exists and allow_existing_release=false — exiting"
exit 0
fi
# ---------------- CHANGELOG (from last stable tag) ----------------
- name: Generate changelog
shell: bash
run: |
LAST_STABLE=$(git tag \
| grep -E '^v[0-9]+(\.[0-9]+){1,3}$' \
| sort -V \
| tail -n 1)
if [ -z "$LAST_STABLE" ]; then
RANGE="HEAD"
else
RANGE="$LAST_STABLE..HEAD"
fi
{
echo "## ✅ What's New & Improved"
git log $RANGE --pretty=format:"- %s"
} > CHANGELOG.md
# ---------------- RELEASE NOTES ----------------
- name: Prepare release notes
shell: bash
run: |
DATE=$(date +'%Y-%m-%d')
{
echo "## 📦 [${VERSION_TAG}] ${DATE}"
echo ""
cat CHANGELOG.md
echo ""
echo ""
echo "| Platform | Status | Notes |"
echo "|----------|--------|-------|"
echo "| 🍎 macOS (.app + dmg) | ✅ Stable | ARM64 + Intel |"
echo "| 🍎 macOS (CLI) | ✅ Stable | Per-arch tar.gz |"
echo "| 🐧 Linux | ✅ Stable | tar.gz / .deb |"
echo "| 🪟 Windows | ✅ Stable | CI-built |"
echo ""
echo "---"
echo ""
echo "### 🛡️ Security Notice"
echo "Antivirus warnings may occur and are likely **false positives**."
} > RELEASE_NOTES.md
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# ---------------- WINDOWS ----------------
- name: Build Windows
run: |
dotnet publish ${PROJECT_DIR}/Jellyfin2Samsung.csproj \
-c $CONFIGURATION -r win-x64 -p:SelfContained=true \
-o publish/win-x64
mkdir -p dist
(cd publish/win-x64 && zip -r "../../dist/${PRODUCT_NAME}-${VERSION_TAG}-win-x64.zip" .)
# ---------------- LINUX ----------------
- name: Build Linux
run: |
dotnet publish ${PROJECT_DIR}/Jellyfin2Samsung.csproj \
-c $CONFIGURATION -r linux-x64 -p:SelfContained=true \
-o publish/linux-x64
tar -czf "dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.tar.gz" \
-C publish/linux-x64 .
# ---------------- LINUX ICON ----------------
- name: Linux icon
run: |
sudo apt-get update
sudo apt-get install -y librsvg2-bin
rsvg-convert -w 256 -h 256 \
jellyfin-tizen-logo.svg -o jellyfin.png
# ---------------- DEB ----------------
- name: Build .deb
run: |
sudo apt-get install -y dpkg-dev
PKG=deb
mkdir -p $PKG/opt/jellyfin2samsung \
$PKG/usr/share/{applications,icons/hicolor/256x256/apps}
cp -R publish/linux-x64/. $PKG/opt/jellyfin2samsung/
chmod +x $PKG/opt/jellyfin2samsung/Jellyfin2Samsung
# 🔥 quick+dirty: allow writes where your app writes
mkdir -p \
$PKG/opt/jellyfin2samsung/Logs \
$PKG/opt/jellyfin2samsung/Downloads \
$PKG/opt/jellyfin2samsung/Assets/TizenSDB \
$PKG/opt/jellyfin2samsung/Assets/Certificate
chmod -R 777 \
$PKG/opt/jellyfin2samsung/Logs \
$PKG/opt/jellyfin2samsung/Downloads \
$PKG/opt/jellyfin2samsung/Assets/TizenSDB \
$PKG/opt/jellyfin2samsung/Assets/Certificate
cp jellyfin.png $PKG/usr/share/icons/hicolor/256x256/apps/jellyfin2samsung.png
cat > $PKG/usr/share/applications/jellyfin2samsung.desktop <<EOF
[Desktop Entry]
Name=Jellyfin2Samsung
Exec=/opt/jellyfin2samsung/Jellyfin2Samsung
Icon=jellyfin2samsung
Type=Application
Categories=Utility;
EOF
mkdir -p $PKG/DEBIAN
cat > $PKG/DEBIAN/control <<EOF
Package: jellyfin2samsung
Version: ${VERSION}
Architecture: amd64
Maintainer: MadeByPatrick
Description: Jellyfin2Samsung
EOF
dpkg-deb --build $PKG
mv $PKG.deb dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.deb
# ---------------- CREATE/UPDATE STABLE RELEASE ----------------
- name: Create/Update GitHub Release (stable)
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.VERSION_TAG }}
name: ${{ env.VERSION_TAG }}
prerelease: false
overwrite_files: true
files: dist/*
body_path: RELEASE_NOTES.md
# ======================================================
# MACOS (PER-ARCH CLI + APP + DMG)
# ======================================================
macos-app:
runs-on: macos-latest
needs: stable-release
if: needs.stable-release.result == 'success'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# ---------------- ICON ----------------
- name: macOS icon
run: |
sips -s format png jellyfin-tizen-logo.svg --out jellyfin.png
sips -z 1024 1024 jellyfin.png --out jellyfin.png
mkdir icon.iconset
for s in 16 32 128 256 512; do
sips -z $s $s jellyfin.png --out icon.iconset/icon_${s}x${s}.png
sips -z $((s*2)) $((s*2)) jellyfin.png --out icon.iconset/icon_${s}x${s}@2x.png
done
iconutil -c icns icon.iconset -o jellyfin.icns
# ---------------- ARM64 ----------------
- name: macOS ARM64 publish
run: |
dotnet publish ${PROJECT_DIR}/Jellyfin2Samsung.csproj \
-c $CONFIGURATION -r osx-arm64 -p:SelfContained=true \
-o publish/osx-arm64
mkdir -p dist
tar -czf \
dist/${PRODUCT_NAME}-${{ needs.stable-release.outputs.VERSION_TAG }}-macos-arm64.tar.gz \
-C publish/osx-arm64 .
APP=Jellyfin2Samsung-arm64.app/Contents
mkdir -p $APP/MacOS $APP/Resources
cp -R publish/osx-arm64/* $APP/MacOS/
chmod +x $APP/MacOS/Jellyfin2Samsung
cp jellyfin.icns $APP/Resources/
cat > $APP/Info.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Jellyfin2Samsung</string>
<key>CFBundleExecutable</key><string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key><string>org.madebypatrick.jellyfin2samsung</string>
<key>CFBundleVersion</key><string>${{ needs.stable-release.outputs.VERSION }}</string>
<key>CFBundleShortVersionString</key><string>${{ needs.stable-release.outputs.VERSION }}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>CFBundleIconFile</key><string>jellyfin.icns</string>
</dict>
</plist>
EOF
codesign --deep --force --sign - Jellyfin2Samsung-arm64.app
rm -rf dmg-arm64
mkdir -p dmg-arm64
cp -R Jellyfin2Samsung-arm64.app dmg-arm64/Jellyfin2Samsung.app
ln -s /Applications dmg-arm64/Applications
hdiutil create \
-volname Jellyfin2Samsung \
-srcfolder dmg-arm64 \
-format UDZO \
dist/${PRODUCT_NAME}-${{ needs.stable-release.outputs.VERSION_TAG }}-macos-arm64.dmg
# ---------------- INTEL ----------------
- name: macOS Intel publish
run: |
dotnet publish ${PROJECT_DIR}/Jellyfin2Samsung.csproj \
-c $CONFIGURATION -r osx-x64 -p:SelfContained=true \
-o publish/osx-x64
tar -czf \
dist/${PRODUCT_NAME}-${{ needs.stable-release.outputs.VERSION_TAG }}-macos-x64.tar.gz \
-C publish/osx-x64 .
APP=Jellyfin2Samsung-x64.app/Contents
mkdir -p $APP/MacOS $APP/Resources
cp -R publish/osx-x64/* $APP/MacOS/
chmod +x $APP/MacOS/Jellyfin2Samsung
cp jellyfin.icns $APP/Resources/
cat > $APP/Info.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Jellyfin2Samsung</string>
<key>CFBundleExecutable</key><string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key><string>org.madebypatrick.jellyfin2samsung</string>
<key>CFBundleVersion</key><string>${{ needs.stable-release.outputs.VERSION }}</string>
<key>CFBundleShortVersionString</key><string>${{ needs.stable-release.outputs.VERSION }}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>CFBundleIconFile</key><string>jellyfin.icns</string>
</dict>
</plist>
EOF
codesign --deep --force --sign - Jellyfin2Samsung-x64.app
rm -rf dmg-x64
mkdir -p dmg-x64
cp -R Jellyfin2Samsung-x64.app dmg-x64/Jellyfin2Samsung.app
ln -s /Applications dmg-x64/Applications
hdiutil create \
-volname Jellyfin2Samsung \
-srcfolder dmg-x64 \
-format UDZO \
dist/${PRODUCT_NAME}-${{ needs.stable-release.outputs.VERSION_TAG }}-macos-x64.dmg
# ---------------- UPLOAD MACOS ASSETS TO SAME STABLE RELEASE ----------------
- name: Upload macOS assets to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.stable-release.outputs.VERSION_TAG }}
name: ${{ needs.stable-release.outputs.VERSION_TAG }}
prerelease: false
overwrite_files: true
files: dist/*

View File

@@ -22,6 +22,7 @@ jobs:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: beta
- name: Fetch latest releases
id: releases
uses: actions/github-script@v7

1
.gitignore vendored
View File

@@ -178,7 +178,6 @@ DocProject/Help/html
# Click-Once directory
publish/
esbuild/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml

View File

@@ -72,11 +72,17 @@ namespace Jellyfin2Samsung
services.AddSingleton<ITizenCertificateService, TizenCertificateService>();
services.AddSingleton<ITizenInstallerService, TizenInstallerService>();
services.AddSingleton<IThemeService, ThemeService>();
services.AddSingleton<IUpdaterService, UpdaterService>();
services.AddSingleton<IUpdateDialogService, UpdateDialogService>();
// HttpClient (configured ONCE)
// HttpClient (configured ONCE, with GitHub auth if available)
services.AddSingleton(sp =>
{
var client = new HttpClient
var appSettings = sp.GetRequiredService<AppSettings>();
var token = Helpers.Core.GitHubAuthHandler.ResolveToken(appSettings);
var handler = new Helpers.Core.GitHubAuthHandler(token);
var client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
@@ -84,7 +90,6 @@ namespace Jellyfin2Samsung
client.DefaultRequestHeaders.UserAgent.ParseAdd("SamsungJellyfinInstaller/1.1");
client.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github+json");
return client;
});

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -47,6 +47,7 @@
"lblForceLogin": "Tving Samsung-certifikat",
"DeveloperModeRequired": "Samsung TV er ikke i developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP matcher ikke denne enheds lokale IP, vil du fortsætte?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Omvendt IP (for arabisk/hebraisk)",
"ServerIP": "Server IP",
"Theme": "Tema",
@@ -113,6 +114,8 @@
"lblServerUrl": "Server-URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Avancerede indstillinger",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Stil",
"lblCssSettings": "Brugerdefinerede CSS-indstillinger",
"lblCustomCss": "Brugerdefineret CSS-kode",
@@ -133,7 +136,20 @@
"lblMainSettings": "Programindstillinger",
"lblRefreshUsers": "Opdater brugere",
"lblUserSelectionHint": "Valgte brugere konfigureres under installationen",
"lblYtDlpServer": "Lokal streamingserver",
"lblValidateStream": "Valider server",
"subnetMismatch": "Enhederne er i forskellige undernetværk (netværk)"
"subnetMismatch": "Enhederne er i forskellige undernetværk (netværk)",
"UpdateAvailable": "Opdatering tilgængelig",
"UpdateCurrentVersion": "Nuværende version:",
"UpdateLatestVersion": "Nyeste version:",
"UpdateReleaseNotes": "Udgivelsesnoter:",
"UpdateManual": "Åbn udgivelser",
"UpdateAutomatic": "Opdater nu",
"UpdateSkip": "Spring denne version over",
"UpdateDownloading": "Downloader opdatering...",
"UpdateApplying": "Anvender opdatering",
"UpdateApplyingMessage": "Applikationen genstarter for at fuldføre opdateringen.",
"UpdateError": "Opdatering mislykkedes",
"UpdateCheckFailed": "Kunne ikke tjekke for opdateringer",
"IncompatiblePackage": "Pakkeversionen er ikke kompatibel med enheden.",
"IncompatiblePackageDetailed": "Pakkeversion {0} er ikke kompatibel med Tizen OS {1}",
"lblMdnsWarning": "Advarsel: Du bruger et mDNS-hostnavn (.local). Samsung TV'er kan ikke pålideligt opløse .local-adresser, hvilket kan få serveren til at vises som \"undefined\" på dit TV — især efter netværksafbrydelser. Brug en direkte IP-adresse (f.eks. 192.168.1.100) i stedet for en stabil forbindelse."
}

View File

@@ -47,6 +47,7 @@
"lblForceLogin": "Samsung-Zertifikat erzwingen",
"DeveloperModeRequired": "Samsung TV ist nicht im Entwicklermodus...",
"DeveloperIPMismatch": "Samsung Developer Mode IP stimmt nicht mit der lokalen IP dieses Geräts überein, möchten Sie fortfahren?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (für Arabisch/Hebräisch)",
"ServerIP": "Server-IP",
"Theme": "Theme",
@@ -91,7 +92,7 @@
"lblEnableDevLogs": "TV-Debugging aktivieren",
"lblOpenDebugWindow": "TV-Log öffnen",
"lblStartLog": "Start",
"lblStopLog": "Stoppen",
"lblStopLog": "Stopp",
"lblSaveLogs": "Speichern",
"lblLaunchOnInstall": "Nach der Installation öffnen",
"insufficientSpace": "Auf Ihrem Gerät ist nicht genügend Speicherplatz vorhanden. Bitte entfernen Sie einige Apps und versuchen Sie es erneut ...",
@@ -113,6 +114,8 @@
"lblServerUrl": "Server-URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Erweiterte Einstellungen",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Benutzerdefinierte CSS-Einstellungen",
"lblCustomCss": "Benutzerdefinierter CSS-Code",
@@ -133,7 +136,20 @@
"lblMainSettings": "Anwendungseinstellungen",
"lblRefreshUsers": "Benutzer aktualisieren",
"lblUserSelectionHint": "Ausgewählte Benutzer werden bei der Installation konfiguriert",
"lblYtDlpServer": "Lokaler Streaming-Server",
"lblValidateStream": "Server validieren",
"subnetMismatch": "Die Geräte befinden sich in unterschiedlichen Subnetzen (Netzwerken)."
"subnetMismatch": "Die Geräte befinden sich in unterschiedlichen Subnetzen (Netzwerken).",
"UpdateAvailable": "Update verfügbar",
"UpdateCurrentVersion": "Aktuelle Version:",
"UpdateLatestVersion": "Neueste Version:",
"UpdateReleaseNotes": "Versionshinweise:",
"UpdateManual": "Releases öffnen",
"UpdateAutomatic": "Jetzt aktualisieren",
"UpdateSkip": "Diese Version überspringen",
"UpdateDownloading": "Update wird heruntergeladen...",
"UpdateApplying": "Update wird angewendet",
"UpdateApplyingMessage": "Die Anwendung wird neu gestartet, um das Update abzuschließen.",
"UpdateError": "Update fehlgeschlagen",
"UpdateCheckFailed": "Update-Überprüfung fehlgeschlagen",
"IncompatiblePackage": "Paketversion ist nicht mit dem Gerät kompatibel.",
"IncompatiblePackageDetailed": "Paketversion {0} nicht kompatibel mit Tizen OS {1}",
"lblMdnsWarning": "Warnung: Du verwendest einen mDNS-Hostnamen (.local). Samsung TVs können .local-Adressen nicht zuverlässig auflösen, was dazu führen kann, dass der Server auf deinem TV als \"undefined\" angezeigt wird — besonders nach Netzwerkunterbrechungen. Verwende stattdessen eine direkte IP-Adresse (z.B. 192.168.1.100) für eine stabile Verbindung."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -47,6 +47,7 @@
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
@@ -113,6 +114,8 @@
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
@@ -133,7 +136,20 @@
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"lblYtDlpServer": "Local streaming server",
"lblValidateStream": "Validate Server",
"subnetMismatch": "Devices are in different subnets (network)"
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -47,6 +47,7 @@
"lblForceLogin": "Certificat Samsung obligatoire",
"DeveloperModeRequired": "Le téléviseur Samsung n'est pas en mode développeur...",
"DeveloperIPMismatch": "L'adresse IP du mode développeur Samsung ne correspond pas à l'adresse IP locale de cet appareil. Souhaitez-vous continuer ?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Adresse IP inversée (pour l'arabe/l'hébreu)",
"ServerIP": "IP du serveur",
"Theme": "Thème",
@@ -91,7 +92,7 @@
"lblEnableDevLogs": "Activer le débogage TV",
"lblOpenDebugWindow": "Ouvrir le journal TV",
"lblStartLog": "Commencer",
"lblStopLog": "Arrêt",
"lblStopLog": "Arrêter",
"lblSaveLogs": "Sauvegarder",
"lblLaunchOnInstall": "Ouvrir après l'installation",
"insufficientSpace": "Votre appareil ne dispose pas d'espace suffisant, veuillez supprimer certaines applications et réessayer...",
@@ -113,6 +114,8 @@
"lblServerUrl": "URL du serveur",
"lblConnectionStatus": "Statut serveur",
"lblAdvancedSettings": "Paramètres avancés",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "Style CSS",
"lblCssSettings": "Paramètres CSS personnalisés",
"lblCustomCss": "Code CSS personnalisé",
@@ -133,7 +136,20 @@
"lblMainSettings": "Paramètres de l'application",
"lblRefreshUsers": "Actualiser utilisateurs",
"lblUserSelectionHint": "Les utilisateurs sélectionnés seront configurés lors de l'installation",
"lblYtDlpServer": "Serveur de streaming local",
"lblValidateStream": "Valider le serveur",
"subnetMismatch": "Les appareils se trouvent dans différents sous-réseaux (réseau)."
"subnetMismatch": "Les appareils se trouvent dans différents sous-réseaux (réseau).",
"UpdateAvailable": "Mise à jour disponible",
"UpdateCurrentVersion": "Version actuelle:",
"UpdateLatestVersion": "Dernière version:",
"UpdateReleaseNotes": "Notes de version:",
"UpdateManual": "Ouvrir les versions",
"UpdateAutomatic": "Mettre à jour",
"UpdateSkip": "Ignorer cette version",
"UpdateDownloading": "Téléchargement...",
"UpdateApplying": "Application de la mise à jour",
"UpdateApplyingMessage": "L'application va redémarrer pour terminer la mise à jour.",
"UpdateError": "Échec de la mise à jour",
"UpdateCheckFailed": "Échec de la vérification des mises à jour",
"IncompatiblePackage": "Version du package non compatible avec l'appareil.",
"IncompatiblePackageDetailed": "La version du package {0} n'est pas compatible avec Tizen OS {1}",
"lblMdnsWarning": "Attention : Vous utilisez un nom d'hôte mDNS (.local). Les TV Samsung ne peuvent pas résoudre de manière fiable les adresses .local, ce qui peut faire apparaître le serveur comme \"undefined\" sur votre TV — surtout après des interruptions réseau. Utilisez plutôt une adresse IP directe (ex. 192.168.1.100) pour une connexion stable."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -47,6 +47,7 @@
"lblForceLogin": "Forceer Samsung-certificaat",
"DeveloperModeRequired": "Samsung TV staat niet in ontwikkelaarsmodus...",
"DeveloperIPMismatch": "Samsung ontwikkelaarsmodus IP komt niet overeen met het lokale IP van dit apparaat, wilt u doorgaan?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Omgekeerd IP-adres (voor Arabisch/Hebreeuws)",
"ServerIP": "Server IP",
"Theme": "Thema",
@@ -113,6 +114,8 @@
"lblServerUrl": "Server-URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Geavanceerde instellingen",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Stijl",
"lblCssSettings": "Aangepaste CSS-instellingen",
"lblCustomCss": "Aangepaste CSS-code",
@@ -133,7 +136,20 @@
"lblMainSettings": "Toepassingsinstellingen",
"lblRefreshUsers": "Gebruikers vernieuwen",
"lblUserSelectionHint": "Geselecteerde gebruikers worden geconfigureerd tijdens de installatie",
"lblYtDlpServer": "Lokale streaming server",
"lblValidateStream": "Valideer de server",
"subnetMismatch": "De apparaten bevinden zich in verschillende subnetten (netwerken)."
"subnetMismatch": "De apparaten bevinden zich in verschillende subnetten (netwerken).",
"UpdateAvailable": "Update beschikbaar",
"UpdateCurrentVersion": "Huidige versie:",
"UpdateLatestVersion": "Nieuwste versie:",
"UpdateReleaseNotes": "Release-opmerkingen:",
"UpdateManual": "Releases openen",
"UpdateAutomatic": "Nu bijwerken",
"UpdateSkip": "Deze versie overslaan",
"UpdateDownloading": "Update downloaden...",
"UpdateApplying": "Update toepassen",
"UpdateApplyingMessage": "De applicatie wordt opnieuw gestart om de update te voltooien.",
"UpdateError": "Update mislukt",
"UpdateCheckFailed": "Controle op updates mislukt",
"IncompatiblePackage": "Pakketversie niet compatibel met apparaat.",
"IncompatiblePackageDetailed": "Pakketversie {0} niet compatibel met Tizen OS {1}",
"lblMdnsWarning": "Waarschuwing: Je gebruikt een mDNS-hostnaam (.local). Samsung TV's kunnen .local-adressen niet betrouwbaar omzetten, waardoor de server als \"undefined\" op je TV kan verschijnen — vooral na netwerkonderbrekingen. Gebruik in plaats daarvan een direct IP-adres (bijv. 192.168.1.100) voor een stabiele verbinding."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Aviso: Você está usando um nome de host mDNS (.local). TVs Samsung não conseguem resolver endereços .local de forma confiável, o que pode fazer o servidor aparecer como \"undefined\" na sua TV — especialmente após interrupções de rede. Use um endereço IP direto (ex. 192.168.1.100) para uma conexão estável."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -47,6 +47,7 @@
"lblForceLogin": "Samsung sertifikasını zorla",
"DeveloperModeRequired": "Samsung TV geliştirici modunda değil...",
"DeveloperIPMismatch": "Samsung Geliştirici modu IP'si bu cihazın yerel IP'siyle eşleşmiyor, devam etmek istiyor musunuz?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Ters IP (Arapça/İbranice için)",
"ServerIP": "Sunucu IP",
"Theme": "Tema",
@@ -113,6 +114,8 @@
"lblServerUrl": "Sunucu URL",
"lblConnectionStatus": "Sunucu Durumu",
"lblAdvancedSettings": "Gelişmiş Ayarlar",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Stili",
"lblCssSettings": "Özel CSS Ayarları",
"lblCustomCss": "Özel CSS Kodu",
@@ -133,7 +136,20 @@
"lblMainSettings": "Uygulama Ayarları",
"lblRefreshUsers": "Kullanıcıları Yenile",
"lblUserSelectionHint": "Seçilen kullanıcılar kurulum sırasında yapılandırılacak",
"lblYtDlpServer": "Yerel yayın sunucusu",
"lblValidateStream": "Sunucuyu Doğrula",
"subnetMismatch": "Cihazlar farklı alt ağlarda (ağda) bulunmaktadır."
"subnetMismatch": "Cihazlar farklı alt ağlarda (ağda) bulunmaktadır.",
"UpdateAvailable": "Güncelleme mevcut",
"UpdateCurrentVersion": "Mevcut sürüm:",
"UpdateLatestVersion": "Son sürüm:",
"UpdateReleaseNotes": "Sürüm notları:",
"UpdateManual": "Sürümleri aç",
"UpdateAutomatic": "Şimdi güncelle",
"UpdateSkip": "Bu sürümü atla",
"UpdateDownloading": "Güncelleme indiriliyor...",
"UpdateApplying": "Güncelleme uygulanıyor",
"UpdateApplyingMessage": "Güncellemeyi tamamlamak için uygulama yeniden başlatılacak.",
"UpdateError": "Güncelleme başarısız",
"UpdateCheckFailed": "Güncelleme kontrolü başarısız",
"IncompatiblePackage": "Paket sürümü cihazla uyumlu değil.",
"IncompatiblePackageDetailed": "Paket sürümü {0}, Tizen OS {1} ile uyumlu değil",
"lblMdnsWarning": "Uyarı: Bir mDNS ana bilgisayar adı (.local) kullanıyorsunuz. Samsung TV'ler .local adreslerini güvenilir şekilde çözümleyemez, bu da sunucunun TV'nizde \"undefined\" olarak görünmesine neden olabilir — özellikle ağ kesintilerinden sonra. Kararlı bir bağlantı için bunun yerine doğrudan bir IP adresi (ör. 192.168.1.100) kullanın."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -2,6 +2,7 @@ using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text.Json;
@@ -35,6 +36,10 @@ namespace Jellyfin2Samsung.Helpers.API
string jsonContent = await response.Content.ReadAsStringAsync();
var jsonObject = JsonNode.Parse(jsonContent);
var logFilePath = Path.Combine(AppContext.BaseDirectory, "Logs", $"debug_tv_api_{DateTime.Now:yyyy-MM-dd_HH-mm-ss-fff}.log");
await File.WriteAllTextAsync(logFilePath, jsonContent);
var deviceNode = jsonObject?["device"];
if (deviceNode == null)
{

View File

@@ -35,6 +35,7 @@ namespace Jellyfin2Samsung.Helpers
public string Certificate { get; set; } = "Jelly2Sams";
public bool DeletePreviousInstall { get; set; } = false;
public string UserCustomIP { get; set; } = "";
public string SavedNetworkInterfaceName { get; set; } = "";
public bool ForceSamsungLogin { get; set; } = false;
public bool RTLReading { get; set; } = false;
public string JellyfinIP { get; set; } = "";
@@ -45,6 +46,7 @@ namespace Jellyfin2Samsung.Helpers
public string JellyfinAccessToken { get; set; } = "";
public string JellyfinServerId { get; set; } = "";
public string JellyfinServerLocalAddress { get; set; } = "";
public string JellyfinServerName { get; set; } = "";
public string AudioLanguagePreference { get; set; } = "";
public string SubtitleLanguagePreference { get; set; } = "";
public bool EnableBackdrops { get; set; } = false;
@@ -72,18 +74,25 @@ namespace Jellyfin2Samsung.Helpers
public bool PatchYoutubePlugin { get; set; } = false;
public string CustomCss { get; set; } = "";
public bool DarkMode { get; set; } = false;
public string GitHubToken { get; set; } = "";
public string LocalYoutubeServer { get; set; } = string.Empty;
// ----- Updater settings -----
public bool CheckForUpdatesOnStartup { get; set; } = true;
public string SkippedUpdateVersion { get; set; } = string.Empty;
public DateTime? LastUpdateCheck { get; set; } = null;
// ----- Application-scoped settings (readonly at runtime) -----
public string ReleasesUrl { get; set; } = "https://api.github.com/repos/jeppevinkel/jellyfin-tizen-builds/releases";
public string AuthorEndpoint { get; set; } = "https://dev.tizen.samsung.com/apis/v2/authors";
public string AppVersion { get; set; } = "v2.0.0.0";
public string AppVersion { get; set; } = "v2.2.0.9";
public string TizenSdb { get; set; } = "https://api.github.com/repos/PatrickSt1991/tizen-sdb/releases";
public string JellyfinAvRelease { get; set; } = "https://api.github.com/repos/PatrickSt1991/tizen-jellyfin-avplay/releases";
public string JellyfinAvReleaseFork { get; set; } = "https://api.github.com/repos/asamahy/tizen-jellyfin-avplay/releases";
public string JellyfinLegacy { get; set; } = "https://api.github.com/repos/jeppevinkel/jellyfin-tizen-builds/releases/tags/2024-10-27-1821";
public string CommunityRelease { get; set; } = "https://api.github.com/repos/PatrickSt1991/tizen-community-packages/releases";
public string MoonfinRelease { get; set; } = "https://api.github.com/repos/Moonfin-Client/Tizen/releases";
public string MoonfinRelease { get; set; } = "https://api.github.com/repos/Moonfin-Client/Smart-TV/releases";
public string LiteFinRelease { get; set; } = "https://api.github.com/repos/MoazSalem/litefin/releases";
public string ReleaseInfo { get; set; } = "https://raw.githubusercontent.com/jeppevinkel/jellyfin-tizen-builds/refs/heads/master/README.md";
public string CommunityInfo { get; set; } = "https://raw.githubusercontent.com/PatrickSt1991/tizen-community-packages/refs/heads/main/README.md";
public AppSettings() { }

View File

@@ -1,6 +1,8 @@
using Jellyfin2Samsung.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
@@ -16,49 +18,86 @@ namespace Jellyfin2Samsung.Helpers.Core
_httpClient = httpClient;
}
public async Task<GitHubRelease?> GetLatestReleaseAsync(string url, string displayName)
public async Task<List<GitHubRelease>> GetReleasesAsync(string url, string prefix, string displayName, int take = 1)
{
if (take < 1) take = 1;
try
{
using var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync();
var json = await response.Content.ReadAsStringAsync();
// Try to parse as array first (normal GitHub releases endpoint)
try
{
var releases = await JsonSerializer.DeserializeAsync<List<GitHubRelease>>(
stream,
var releases = JsonSerializer.Deserialize<List<GitHubRelease>>(
json,
JsonSerializerOptionsProvider.Default);
var latest = releases?.Count > 0 ? releases[0] : null;
if (releases == null || releases.Count == 0)
return new List<GitHubRelease>();
if (latest != null)
latest.Name = displayName;
releases = releases
.Select(r =>
{
r.Assets = r.Assets?
.Where(a =>
!string.IsNullOrWhiteSpace(a.FileName) &&
(a.FileName.EndsWith(".wgt", StringComparison.OrdinalIgnoreCase) ||
a.FileName.EndsWith(".tpk", StringComparison.OrdinalIgnoreCase)))
.ToList() ?? new List<Asset>();
return latest;
return r;
})
.Where(r => r.Assets.Count > 0)
.ToList();
if (releases.Count == 0)
return new List<GitHubRelease>();
var result = releases.Count > take ? releases.GetRange(0, take) : releases;
foreach (var r in result)
{
r.Name = string.IsNullOrWhiteSpace(displayName)
? $"{prefix}{r.Name}"
: displayName;
}
return result;
}
catch (JsonException)
{
// If array parsing fails, try parsing as single object
stream.Position = 0;
var latest = await JsonSerializer.DeserializeAsync<GitHubRelease>(
stream,
var latest = JsonSerializer.Deserialize<GitHubRelease>(
json,
JsonSerializerOptionsProvider.Default);
if (latest != null)
latest.Name = displayName;
if (latest == null)
return new List<GitHubRelease>();
return latest;
latest.Assets = latest.Assets?
.Where(a =>
!string.IsNullOrWhiteSpace(a.FileName) &&
(a.FileName.EndsWith(".wgt", StringComparison.OrdinalIgnoreCase) ||
a.FileName.EndsWith(".tpk", StringComparison.OrdinalIgnoreCase)))
.ToList() ?? new List<Asset>();
if (latest.Assets.Count == 0)
return new List<GitHubRelease>();
latest.Name = string.IsNullOrWhiteSpace(displayName)
? $"{prefix}{latest.Name}"
: displayName;
return new List<GitHubRelease> { latest };
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine($"Failed to fetch release from {url}: {ex}");
return null;
return new List<GitHubRelease>();
}
}
}
}
}

View File

@@ -17,6 +17,23 @@ namespace Jellyfin2Samsung.Helpers.Core
public const string CustomWgtFile = "Custom WGT File";
}
/// <summary>
/// preview image URLS for different apps.
/// </summary>
public static class PreviewImages
{
public const string Jellyfin = "https://jellyfin.org/assets/images/10.8-home-4a73a92bf90d1eeffa5081201ca9c7bb.png";
public const string Moonfin = "https://iili.io/fs8W4Re.png";
public const string Moonlight = "https://iili.io/fsvn6mJ.png";
public const string Fireplace = "https://raw.githubusercontent.com/thonythony/fireplace/refs/heads/master/icon.jpg";
public const string TVApp = "https://iili.io/fsvaHsn.png";
public const string Twitch = "https://iili.io/fsvUNu2.md.gif";
public const string ClubInfoBoard = "https://iili.io/fsviHQV.png";
public const string Doom = "https://iili.io/fyofVqu.png";
public const string TTD = "https://iili.io/qFBP2vn.png";
public const string Litefin = "https://iili.io/qerI0la.png";
}
/// <summary>
/// Tizen installation error codes returned by the SDB tool.
/// </summary>
@@ -30,6 +47,7 @@ namespace Jellyfin2Samsung.Helpers.Core
public const string InstallCompleted = "install completed";
public const string ResignFailed = "Re-sign failed";
public const string Failed = "failed";
public const string NotInstalled = "failed[132]";
}
/// <summary>
@@ -174,6 +192,19 @@ namespace Jellyfin2Samsung.Helpers.Core
public const string AlphaNumeric = Alpha + "0123456789";
}
/// <summary>
/// Updater related constants.
/// </summary>
public static class Updater
{
public const string RepoOwner = "Jellyfin2Samsung";
public const string RepoName = "Samsung-Jellyfin-Installer";
public const string AtomFeedUrl = "https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases.atom";
public const string ReleasesPageUrl = "https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases";
public const string LatestReleaseApiUrl = "https://api.github.com/repos/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases/latest";
public const int UpdateCheckTimeoutSeconds = 10;
}
/// <summary>
/// Localization keys used for status messages.
/// </summary>
@@ -209,6 +240,22 @@ namespace Jellyfin2Samsung.Helpers.Core
public const string InvalidDeviceIp = "InvalidDeviceIp";
public const string LblOther = "lblOther";
public const string IpNotListed = "IpNotListed";
public const string IncompatiblePackage = "IncompatiblePackage";
public const string IncompatiblePackageDetailed = "IncompatiblePackageDetailed";
// Updater localization keys
public const string UpdateAvailable = "UpdateAvailable";
public const string UpdateCurrentVersion = "UpdateCurrentVersion";
public const string UpdateLatestVersion = "UpdateLatestVersion";
public const string UpdateReleaseNotes = "UpdateReleaseNotes";
public const string UpdateManual = "UpdateManual";
public const string UpdateAutomatic = "UpdateAutomatic";
public const string UpdateSkip = "UpdateSkip";
public const string UpdateDownloading = "UpdateDownloading";
public const string UpdateApplying = "UpdateApplying";
public const string UpdateApplyingMessage = "UpdateApplyingMessage";
public const string UpdateError = "UpdateError";
public const string UpdateCheckFailed = "UpdateCheckFailed";
}
/// <summary>

View File

@@ -17,61 +17,40 @@ namespace Jellyfin2Samsung.Helpers.Core
private static readonly string[] tpkItem = ["*.tpk"];
private static readonly string[] allItem = ["*.wgt", "*.tpk"];
public async Task<string?> BrowseWgtFilesAsync(IStorageProvider storageProvider)
public async Task<string?> BrowseWgtFilesAsync(IStorageProvider storageProvider)
{
var fileTypes = new List<FilePickerFileType>
{
new("WGT Files")
{
var fileTypes = new List<FilePickerFileType>
{
new("WGT Files")
{
Patterns = wgtItem
},
new("TPK Files")
{
Patterns = tpkItem
},
new("All Supported Files")
{
Patterns = allItem
}
};
var options = new FilePickerOpenOptions
{
Title = "Select WGT/TPK File",
FileTypeFilter = fileTypes,
AllowMultiple = true
};
var files = await storageProvider.OpenFilePickerAsync(options);
if (files?.Any() == true)
{
var newPaths = new List<string>();
foreach (var file in files)
{
var originalPath = file.Path.LocalPath;
var directory = Path.GetDirectoryName(originalPath);
var baseName = Path.GetFileNameWithoutExtension(originalPath);
var extension = Path.GetExtension(originalPath);
var randomSuffix = new string(Enumerable.Range(0, 4)
.Select(_ => Constants.CharacterSets.Alpha[Random.Shared.Next(Constants.CharacterSets.Alpha.Length)])
.ToArray());
var newFileName = $"{baseName}{randomSuffix}{extension}";
var newFilePath = Path.Combine(directory ?? Environment.CurrentDirectory, newFileName);
File.Copy(originalPath, newFilePath, overwrite: true);
newPaths.Add(newFilePath);
}
return string.Join(";", newPaths);
}
return null;
Patterns = wgtItem
},
new("TPK Files")
{
Patterns = tpkItem
},
new("All Supported Files")
{
Patterns = allItem
}
};
var options = new FilePickerOpenOptions
{
Title = "Select WGT/TPK File",
FileTypeFilter = fileTypes,
AllowMultiple = true
};
var files = await storageProvider.OpenFilePickerAsync(options);
if (files?.Any() == true)
return string.Join(";", files.Select(f => f.Path.LocalPath));
return null;
}
public List<ExtensionEntry> ParseExtensions(string output)
{
var extensions = new List<ExtensionEntry>();
@@ -111,6 +90,16 @@ namespace Jellyfin2Samsung.Helpers.Core
var match = RegexPatterns.WgtConfig.TizenApplicationId.Match(configContent);
return match.Success ? match.Groups["pkg"].Value : null;
}
public static async Task<string?> ReadExtractedWgtPackageId(string workspaceRoot)
{
var configPath = Path.Combine(workspaceRoot, "config.xml");
if (!File.Exists(configPath))
return null;
var configContent = await File.ReadAllTextAsync(configPath, Encoding.UTF8);
var match = RegexPatterns.WgtConfig.TizenApplicationId.Match(configContent);
return match.Success ? match.Groups["pkg"].Value : null;
}
public static async Task<bool> ModifyWgtPackageId(string wgtPath)
{
if (!File.Exists(wgtPath))
@@ -164,5 +153,60 @@ namespace Jellyfin2Samsung.Helpers.Core
sb.Append(Constants.CharacterSets.AlphaNumeric[Random.Shared.Next(Constants.CharacterSets.AlphaNumeric.Length)]);
return sb.ToString();
}
public static async Task<string?> ReadWgtApplicationId(string wgtPath)
{
if (!File.Exists(wgtPath))
return null;
using var memoryStream = new MemoryStream();
using (var originalStream = File.OpenRead(wgtPath))
await originalStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Read, true);
var configEntry = archive.GetEntry("config.xml");
if (configEntry == null)
return null;
string configContent;
using (var reader = new StreamReader(configEntry.Open(), Encoding.UTF8))
configContent = await reader.ReadToEndAsync();
// Prefer using a RegexPatterns entry if you want; otherwise this is safe and specific:
var match = Regex.Match(
configContent,
@"<tizen:application\b[^>]*\bid\s*=\s*""(?<id>[^""]+)""",
RegexOptions.IgnoreCase);
return match.Success ? match.Groups["id"].Value : null;
}
public static async Task<string?> ReadWgtRequiredVersion(string wgtPath)
{
if (!File.Exists(wgtPath))
return null;
using var memoryStream = new MemoryStream();
using (var originalStream = File.OpenRead(wgtPath))
await originalStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Read, true);
var configEntry = archive.GetEntry("config.xml");
if (configEntry == null)
return null;
string configContent;
using (var reader = new StreamReader(configEntry.Open(), Encoding.UTF8))
configContent = await reader.ReadToEndAsync();
var match = Regex.Match(
configContent,
@"<tizen:application\b[^>]*\brequired_version\s*=\s*""(?<version>[^""]+)""",
RegexOptions.IgnoreCase);
return match.Success ? match.Groups["version"].Value : null;
}
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Core
{
public class GitHubAuthHandler : DelegatingHandler
{
private readonly string? _token;
public GitHubAuthHandler(string? token)
: base(new HttpClientHandler())
{
_token = token;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!string.IsNullOrEmpty(_token) && IsGitHubRequest(request.RequestUri))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
}
var response = await base.SendAsync(request, cancellationToken);
// Token is expired or revoked — retry unauthenticated for public endpoints
if (response.StatusCode == HttpStatusCode.Unauthorized &&
request.Headers.Authorization != null)
{
Trace.TraceWarning("[GitHubAuth] Token rejected (401) — retrying without authorization");
var retry = new HttpRequestMessage(request.Method, request.RequestUri);
foreach (var header in request.Headers)
{
if (!string.Equals(header.Key, "Authorization", StringComparison.OrdinalIgnoreCase))
retry.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
response = await base.SendAsync(retry, cancellationToken);
}
return response;
}
private static bool IsGitHubRequest(Uri? uri)
{
if (uri == null) return false;
var host = uri.Host;
return host.Equals("api.github.com", StringComparison.OrdinalIgnoreCase)
|| host.Equals("raw.githubusercontent.com", StringComparison.OrdinalIgnoreCase)
|| host.Equals("github.com", StringComparison.OrdinalIgnoreCase);
}
public static string? ResolveToken(AppSettings settings)
{
// 1. Explicit setting
if (!string.IsNullOrWhiteSpace(settings.GitHubToken))
{
Trace.TraceInformation("[GitHubAuth] Using token from app settings");
return settings.GitHubToken.Trim();
}
// 2. Environment variable
var envToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
if (!string.IsNullOrWhiteSpace(envToken))
{
Trace.TraceInformation("[GitHubAuth] Using token from GITHUB_TOKEN environment variable");
return envToken.Trim();
}
// 3. GitHub CLI (gh auth token)
try
{
var psi = new ProcessStartInfo
{
FileName = "gh",
Arguments = "auth token",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(psi);
if (process != null)
{
var output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit(5000);
if (process.ExitCode == 0 && !string.IsNullOrWhiteSpace(output))
{
Trace.TraceInformation("[GitHubAuth] Using token from GitHub CLI (gh auth token)");
return output;
}
}
}
catch
{
// gh CLI not installed or not authenticated — ignore
}
// 4. No token available — unauthenticated requests
Trace.TraceInformation("[GitHubAuth] No token found — using unauthenticated requests");
return null;
}
}
}

View File

@@ -1,4 +1,8 @@
namespace Jellyfin2Samsung.Helpers.Core
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace Jellyfin2Samsung.Helpers.Core
{
public static class HtmlUtils
{
@@ -9,12 +13,10 @@
return html.Replace("<head>", "<head><base href=\".\">");
}
public static string RewriteLocalPaths(string html)
{
return RegexPatterns.Html.LocalPaths.Replace(html, "$1=\"$2\"");
}
public static string CleanAndApplyCsp(string html)
{
html = RegexPatterns.Html.CspMeta.Replace(html, "");
@@ -39,5 +41,52 @@
.Replace("\n", "\\n")
.Replace("\r", "\\r");
}
public static string RemoveMarkdownTable(string html)
{
if (string.IsNullOrEmpty(html))
return html;
var tablePattern = @"(\|[^\n]+\|\s*\n)+";
return Regex.Replace(html, tablePattern, string.Empty, RegexOptions.Multiline);
}
public static string StripHtml(string html)
{
if (string.IsNullOrEmpty(html))
return string.Empty;
// Simple HTML stripping - replace common tags
var text = html
.Replace("<br>", "\n")
.Replace("<br/>", "\n")
.Replace("<br />", "\n")
.Replace("</p>", "\n")
.Replace("</li>", "\n")
.Replace("<li>", "• ");
// Remove all remaining HTML tags
while (text.Contains('<') && text.Contains('>'))
{
var start = text.IndexOf('<');
var end = text.IndexOf('>', start);
if (end > start)
text = text.Remove(start, end - start + 1);
else
break;
}
// Decode common HTML entities
text = text
.Replace("&nbsp;", " ")
.Replace("&amp;", "&")
.Replace("&lt;", "<")
.Replace("&gt;", ">")
.Replace("&quot;", "\"")
.Replace("&#39;", "'");
// Clean up whitespace
var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
return string.Join("\n", lines.Select(l => l.Trim()).Where(l => !string.IsNullOrEmpty(l)));
}
}
}

View File

@@ -21,9 +21,10 @@ namespace Jellyfin2Samsung.Helpers.Core
private readonly IDialogService _dialogService = dialogService;
private readonly INetworkService _networkService = networkService;
public async Task<string?> DownloadReleaseAsync(GitHubRelease release, Asset selectedAsset, ProgressCallback? progress = null)
public async Task<string?> DownloadReleaseAsync(GitHubRelease release, Asset? selectedAsset, ProgressCallback? progress = null)
{
if (release?.PrimaryDownloadUrl == null) return null;
if (selectedAsset?.DownloadUrl == null) return null;
try
{
@@ -38,8 +39,10 @@ namespace Jellyfin2Samsung.Helpers.Core
return null;
}
}
public async Task<bool> InstallPackageAsync(string packagePath, NetworkDevice selectedDevice, CancellationToken cancellationToken, ProgressCallback? progress = null, Action? onSamsungLoginStarted = null)
public async Task<bool> InstallPackageAsync(string? packagePath, NetworkDevice selectedDevice, CancellationToken cancellationToken, ProgressCallback? progress = null, Action? onSamsungLoginStarted = null)
{
if(selectedDevice.DeveloperIP == null) return false;
var localIps = _networkService.GetRelevantLocalIPs()
.Select(ip => ip.ToString())
.ToList();
@@ -92,6 +95,25 @@ namespace Jellyfin2Samsung.Helpers.Core
selectedDevice.IpAddress = selectedDevice.DeveloperIP;
}
if (ipMismatch)
{
bool isReversedIp = localIps
.Select(ip => _networkService.InvertIPAddress(ip))
.Contains(selectedDevice.DeveloperIP);
if (isReversedIp)
{
bool continueExecution = await _dialogService.ShowConfirmationAsync(
"IP Reversed",
"DeveloperIPReversed".Localized(),
"keyContinue".Localized(),
"keyStop".Localized());
if (!continueExecution)
return false;
ipMismatch = false;
}
}
if (ipMismatch)
{
bool continueExecution = await _dialogService.ShowConfirmationAsync("IP Mismatch", "DeveloperIPMismatch".Localized(), "keyContinue".Localized(), "keyStop".Localized());
@@ -141,8 +163,10 @@ namespace Jellyfin2Samsung.Helpers.Core
return false;
}
}
public async Task<bool> InstallCustomPackagesAsync(string[] packagePaths, NetworkDevice device, CancellationToken cancellationToken, Action<string> onProgress, Action? onSamsungLoginStarted = null)
public async Task<bool> InstallCustomPackagesAsync(string[] packagePaths, NetworkDevice? device, CancellationToken cancellationToken, Action<string> onProgress, Action? onSamsungLoginStarted = null)
{
if (device == null) return false;
onProgress("UsingCustomWGT".Localized());
var allSuccessful = true;

View File

@@ -41,8 +41,9 @@ namespace Jellyfin2Samsung.Helpers.Jellyfin
// Apply YouTube plugin patch if enabled
if (AppSettings.Default.PatchYoutubePlugin)
{
await _youTube.CorsAsync(ws);
await _youTube.FixAsync(ws);
await _youTube.PatchPluginAsync(ws);
await _youTube.UpdateCorsAsync(ws);
await _youTube.CreateYouTubeResolverAsync(ws);
}
// Always update server address

File diff suppressed because it is too large Load Diff

View File

@@ -76,6 +76,18 @@ namespace Jellyfin2Samsung.Helpers.Jellyfin.Patches
if (!servers.Any(s => s?.GetValue<string>() == serverUrl))
servers.Add(serverUrl);
// Add LocalAddress (IP-based) as fallback when the primary URL uses mDNS (.local)
// Samsung TVs (Tizen) cannot reliably resolve mDNS hostnames, especially after network disruptions
var localAddress = UrlHelper.NormalizeServerUrl(AppSettings.Default.JellyfinServerLocalAddress);
if (!string.IsNullOrEmpty(localAddress) &&
localAddress != serverUrl &&
UrlHelper.IsValidHttpUrl(localAddress) &&
!servers.Any(s => s?.GetValue<string>() == localAddress))
{
servers.Add(localAddress);
Trace.WriteLine($"[UpdateServerAddress] Added LocalAddress fallback: {localAddress}");
}
await File.WriteAllTextAsync(path, config.ToJsonString());
}
@@ -92,6 +104,7 @@ namespace Jellyfin2Samsung.Helpers.Jellyfin.Patches
var serverUrl = UrlHelper.NormalizeServerUrl(AppSettings.Default.JellyfinFullUrl);
var serverId = AppSettings.Default.JellyfinServerId;
var localAddress = AppSettings.Default.JellyfinServerLocalAddress;
var serverName = AppSettings.Default.JellyfinServerName;
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(serverUrl))
{
@@ -99,19 +112,21 @@ namespace Jellyfin2Samsung.Helpers.Jellyfin.Patches
return;
}
// If server ID is not stored, try to fetch it now
if (string.IsNullOrEmpty(serverId))
// If server ID or server name is not stored, try to fetch it now
if (string.IsNullOrEmpty(serverId) || string.IsNullOrEmpty(serverName))
{
Trace.WriteLine("[InjectAutoLogin] Server ID not cached, fetching from server...");
Trace.WriteLine("[InjectAutoLogin] Server ID/Name not cached, fetching from server...");
var serverInfo = await _apiClient.GetPublicSystemInfoAsync(serverUrl);
if (serverInfo != null && !string.IsNullOrEmpty(serverInfo.Id))
{
serverId = serverInfo.Id;
localAddress = serverInfo.LocalAddress ?? "";
serverName = serverInfo.ServerName ?? "";
AppSettings.Default.JellyfinServerId = serverId;
AppSettings.Default.JellyfinServerLocalAddress = localAddress;
AppSettings.Default.JellyfinServerName = serverName;
AppSettings.Default.Save();
Trace.WriteLine($"[InjectAutoLogin] Fetched and stored server ID: {serverId}");
Trace.WriteLine($"[InjectAutoLogin] Fetched and stored server ID: {serverId}, Name: {serverName}");
}
else
{
@@ -138,6 +153,7 @@ namespace Jellyfin2Samsung.Helpers.Jellyfin.Patches
credentialsScript.AppendLine($" var serverUrl = '{HtmlUtils.EscapeJsString(serverUrl)}';");
credentialsScript.AppendLine($" var serverId = '{HtmlUtils.EscapeJsString(serverId)}';");
credentialsScript.AppendLine($" var localAddress = '{HtmlUtils.EscapeJsString(localAddress)}';");
credentialsScript.AppendLine($" var serverName = '{HtmlUtils.EscapeJsString(serverName)}';");
credentialsScript.AppendLine($" var userId = '{HtmlUtils.EscapeJsString(userId)}';");
credentialsScript.AppendLine($" var accessToken = '{HtmlUtils.EscapeJsString(accessToken)}';");
credentialsScript.AppendLine();
@@ -145,6 +161,7 @@ namespace Jellyfin2Samsung.Helpers.Jellyfin.Patches
credentialsScript.AppendLine(" // Using real server ID (GUID) from /System/Info/Public");
credentialsScript.AppendLine(" var credentials = {");
credentialsScript.AppendLine(" Servers: [{");
credentialsScript.AppendLine(" Name: serverName || serverUrl,");
credentialsScript.AppendLine(" ManualAddress: serverUrl,");
credentialsScript.AppendLine(" LocalAddress: localAddress || serverUrl,");
credentialsScript.AppendLine(" Id: serverId,");
@@ -157,7 +174,7 @@ namespace Jellyfin2Samsung.Helpers.Jellyfin.Patches
credentialsScript.AppendLine(" // Store in localStorage");
credentialsScript.AppendLine(" localStorage.setItem('jellyfin_credentials', JSON.stringify(credentials));");
credentialsScript.AppendLine();
credentialsScript.AppendLine(" console.log('[Auto-Login] Credentials injected for server: ' + serverUrl + ' with ID: ' + serverId);");
credentialsScript.AppendLine(" console.log('[Auto-Login] Credentials injected for server: ' + serverName + ' (' + serverUrl + ') with ID: ' + serverId);");
credentialsScript.AppendLine(" } catch(e) {");
credentialsScript.AppendLine(" console.error('[Auto-Login] Failed to inject credentials:', e);");
credentialsScript.AppendLine(" }");

View File

@@ -0,0 +1,103 @@
namespace Jellyfin2Samsung.Helpers.Jellyfin.Fixes
{
/// <summary>
/// Maps ISO 639 language codes to search keywords for DuckDuckGo Lite trailer search.
/// Covers all languages supported by Jellyfin's localization system.
/// Each entry contains the English name + native name for maximum DDG search match rate.
/// </summary>
public static class TrailerLanguageMap
{
public const string JsObject = @"{
ab:'Abkhaz Аԥсуа',
af:'Afrikaans',
ar:'Arabic العربية',
as:'Assamese অসমীয়া',
be:'Belarusian Беларуская',
bg:'Bulgarian Български',
bn:'Bengali বাংলা',
ca:'Catalan Català',
chr:'Cherokee ᏣᎳᎩ',
cs:'Czech Český',
cy:'Welsh Cymraeg',
da:'Danish Dansk',
de:'Deutsch German',
el:'Greek Ελληνικά',
en:'English',
enm:'English',
eo:'Esperanto',
es:'Spanish Español',
et:'Estonian Eesti',
eu:'Basque Euskara',
fa:'Persian Farsi فارسی',
fi:'Finnish Suomi',
fil:'Filipino Tagalog',
fo:'Faroese Føroyskt',
fr:'French Français',
ga:'Irish Gaeilge',
gl:'Galician Galego',
gsw:'Swiss German Schweizerdeutsch',
he:'Hebrew עברית',
hi:'Hindi हिन्दी',
hr:'Croatian Hrvatski',
ht:'Haitian Creole Kreyòl',
hu:'Hungarian Magyar',
hy:'Armenian Հայերեն',
id:'Indonesian Bahasa',
is:'Icelandic Íslenska',
it:'Italian Italiano',
ja:'Japanese 日本語',
jbo:'Lojban',
ka:'Georgian ქართული',
kab:'Kabyle Taqbaylit',
kk:'Kazakh Қазақша',
km:'Khmer ភាសាខ្មែរ',
kn:'Kannada ಕನ್ನಡ',
ko:'Korean 한국어',
kw:'Cornish Kernewek',
ky:'Kyrgyz Кыргызча',
lb:'Luxembourgish Lëtzebuergesch',
lt:'Lithuanian Lietuvių',
lv:'Latvian Latviešu',
lzh:'Chinese 中文',
mi:'Maori Māori',
mk:'Macedonian Македонски',
ml:'Malayalam മലയാളം',
mn:'Mongolian Монгол',
mr:'Marathi मराठी',
ms:'Malay Melayu',
mt:'Maltese Malti',
my:'Burmese Myanmar မြန်မာ',
nb:'Norwegian Norsk',
ne:'Nepali नेपाली',
nl:'Dutch Nederlands',
nn:'Norwegian Norsk',
oc:'Occitan',
or:'Odia ଓଡ଼ିଆ',
pa:'Punjabi ਪੰਜਾਬੀ',
pl:'Polish Polski',
pr:'English',
pt:'Portuguese Português',
ro:'Romanian Română',
ru:'Russian Русский',
si:'Sinhala සිංහල',
sk:'Slovak Slovenský',
sl:'Slovenian Slovenščina',
sn:'Shona',
sq:'Albanian Shqip',
sr:'Serbian Српски',
sv:'Swedish Svenska',
sw:'Swahili Kiswahili',
ta:'Tamil தமிழ்',
te:'Telugu తెలుగు',
th:'Thai ไทย',
tr:'Turkish Türkçe',
ug:'Uyghur ئۇيغۇرچە',
uk:'Ukrainian Українська',
ur:'Urdu اردو',
uz:'Uzbek Oʻzbekcha',
vi:'Vietnamese Tiếng Việt',
zh:'Chinese 中文',
zu:'Zulu isiZulu'
}";
}
}

View File

@@ -30,6 +30,9 @@ namespace Jellyfin2Samsung.Helpers.Tizen.Devices
foreach (NetworkDevice device in networkDevices)
{
// Check for cancellation before processing each device
cancellationToken.ThrowIfCancellationRequested();
if (await _networkService.IsPortOpenAsync(device.IpAddress, 8001, cancellationToken))
{
try

View File

@@ -15,7 +15,7 @@ namespace Jellyfin2Samsung.Interfaces
string GetLocalIPAddress();
string InvertIPAddress(string ipAddress);
Task<string?> GetManufacturerFromIp(string ipAddress);
Task<string?> GetPrimaryOutboundIPAddressAsync();
bool IsDifferentSubnet(string ip1, string ip2);
Task<IReadOnlyList<NetworkInterfaceOption>> GetNetworkInterfaceOptionsAsync();
}
}

View File

@@ -0,0 +1,62 @@
using Jellyfin2Samsung.Models;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Interfaces
{
/// <summary>
/// Represents the user's choice in the update dialog.
/// </summary>
public enum UpdateDialogChoice
{
/// <summary>
/// User cancelled or closed the dialog.
/// </summary>
Cancel,
/// <summary>
/// User chose to open the releases page manually.
/// </summary>
Manual,
/// <summary>
/// User chose to download and install the update automatically.
/// </summary>
Automatic,
/// <summary>
/// User chose to skip this update.
/// </summary>
Skip
}
/// <summary>
/// Service for showing update-related dialogs.
/// </summary>
public interface IUpdateDialogService
{
/// <summary>
/// Shows the update available dialog with options for manual or automatic update.
/// </summary>
/// <param name="updateInfo">Information about the available update.</param>
/// <returns>The user's choice.</returns>
Task<UpdateDialogChoice> ShowUpdateAvailableDialogAsync(UpdateCheckResult updateInfo);
/// <summary>
/// Shows a progress dialog while downloading the update.
/// </summary>
/// <param name="progress">Progress reporter (0-100).</param>
/// <returns>True if download completed, false if cancelled.</returns>
Task<bool> ShowDownloadProgressAsync(System.IProgress<int> progress);
/// <summary>
/// Shows a message that the update is being applied and the app will restart.
/// </summary>
Task ShowApplyingUpdateMessageAsync();
/// <summary>
/// Shows an error message related to the update process.
/// </summary>
/// <param name="errorMessage">The error message to display.</param>
Task ShowUpdateErrorAsync(string errorMessage);
}
}

View File

@@ -0,0 +1,56 @@
using Jellyfin2Samsung.Models;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Interfaces
{
/// <summary>
/// Service for checking and applying application updates via GitHub releases.
/// Uses the Atom feed endpoint to avoid API rate limiting.
/// </summary>
public interface IUpdaterService
{
/// <summary>
/// Checks if a newer version of the application is available.
/// </summary>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>Update check result containing version information and download URLs.</returns>
Task<UpdateCheckResult> CheckForUpdateAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Downloads the latest release to a temporary location.
/// </summary>
/// <param name="downloadUrl">The URL to download the release from.</param>
/// <param name="progress">Progress callback reporting download percentage (0-100).</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>Path to the downloaded file.</returns>
Task<string> DownloadUpdateAsync(
string downloadUrl,
IProgress<int>? progress = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Applies the downloaded update by extracting, replacing files, and scheduling a restart.
/// </summary>
/// <param name="downloadedFilePath">Path to the downloaded update archive.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>True if the update was successfully prepared and app should restart.</returns>
Task<bool> ApplyUpdateAsync(string downloadedFilePath, CancellationToken cancellationToken = default);
/// <summary>
/// Opens the GitHub releases page in the default browser.
/// </summary>
void OpenReleasesPage();
/// <summary>
/// Gets the URL of the GitHub releases page.
/// </summary>
string ReleasesPageUrl { get; }
/// <summary>
/// Gets the current application version.
/// </summary>
string CurrentVersion { get; }
}
}

View File

@@ -94,6 +94,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Tmds.DBus.Protocol" Version="0.21.3" />
<AvaloniaResource Update="Assets\esbuild\linux-x64\esbuild">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AvaloniaResource>

View File

@@ -6,5 +6,6 @@ namespace Jellyfin2Samsung.Models
{
[ObservableProperty] private string fileName = string.Empty;
[ObservableProperty] private string description = string.Empty;
[ObservableProperty] private string repoUrl = string.Empty;
}
}

View File

@@ -0,0 +1,73 @@
using System;
namespace Jellyfin2Samsung.Models
{
/// <summary>
/// Represents a release entry parsed from the GitHub Atom feed.
/// The Atom feed does not have rate limits unlike the REST API.
/// </summary>
public class GitHubAtomEntry
{
/// <summary>
/// The unique ID of the release (e.g., "tag:github.com,2008:Repository/123456/v1.0.0").
/// </summary>
public string Id { get; set; } = string.Empty;
/// <summary>
/// The release title/name.
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// When the release was last updated.
/// </summary>
public DateTime? Updated { get; set; }
/// <summary>
/// The link to the release page.
/// </summary>
public string Link { get; set; } = string.Empty;
/// <summary>
/// The release content/description (HTML).
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// The author who published the release.
/// </summary>
public string AuthorName { get; set; } = string.Empty;
/// <summary>
/// Extracts the tag name (version) from the release ID or link.
/// </summary>
public string TagName
{
get
{
// Try to extract from link first: https://github.com/owner/repo/releases/tag/v1.0.0
if (!string.IsNullOrEmpty(Link))
{
const string tagMarker = "/releases/tag/";
var tagIndex = Link.IndexOf(tagMarker, StringComparison.OrdinalIgnoreCase);
if (tagIndex >= 0)
{
return Link.Substring(tagIndex + tagMarker.Length);
}
}
// Fallback: extract from ID: tag:github.com,2008:Repository/123456/v1.0.0
if (!string.IsNullOrEmpty(Id))
{
var lastSlash = Id.LastIndexOf('/');
if (lastSlash >= 0 && lastSlash < Id.Length - 1)
{
return Id.Substring(lastSlash + 1);
}
}
return string.Empty;
}
}
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
@@ -40,6 +41,10 @@ namespace Jellyfin2Samsung.Models
[JsonPropertyName("size")]
public long Size { get; set; }
[JsonIgnore]
public bool IsDefault => FileName.Equals("Jellyfin.wgt", StringComparison.OrdinalIgnoreCase);
[JsonIgnore]
public string DisplayText => $"{FileName} ({FormatFileSize(Size)})";

View File

@@ -32,4 +32,12 @@
public string Name = "";
public bool Activated;
}
public class NetworkInterfaceOption
{
public string Name { get; set; } = string.Empty;
public string IpAddress { get; set; } = string.Empty;
public string DisplayText => $"{Name} - {IpAddress}";
}
}

View File

@@ -0,0 +1,9 @@
using Avalonia.Media;
namespace Jellyfin2Samsung.Models;
public sealed class ProviderOption
{
public string DisplayName { get; init; } = "";
public IImage? PreviewImage { get; init; } // can be a Bitmap later
}

View File

@@ -0,0 +1,86 @@
using System;
namespace Jellyfin2Samsung.Models
{
/// <summary>
/// Result of checking for application updates.
/// </summary>
public class UpdateCheckResult
{
/// <summary>
/// Whether a newer version is available.
/// </summary>
public bool IsUpdateAvailable { get; set; }
/// <summary>
/// The current installed version.
/// </summary>
public string CurrentVersion { get; set; } = string.Empty;
/// <summary>
/// The latest available version.
/// </summary>
public string LatestVersion { get; set; } = string.Empty;
/// <summary>
/// URL to download the update for the current platform.
/// </summary>
public string? DownloadUrl { get; set; }
/// <summary>
/// URL to the GitHub releases page.
/// </summary>
public string ReleasesPageUrl { get; set; } = string.Empty;
/// <summary>
/// Release title/name.
/// </summary>
public string ReleaseTitle { get; set; } = string.Empty;
/// <summary>
/// Release notes or description.
/// </summary>
public string ReleaseNotes { get; set; } = string.Empty;
/// <summary>
/// When the release was published.
/// </summary>
public DateTime? PublishedAt { get; set; }
/// <summary>
/// Error message if the check failed.
/// </summary>
public string? ErrorMessage { get; set; }
/// <summary>
/// Whether the check completed successfully.
/// </summary>
public bool IsSuccess => string.IsNullOrEmpty(ErrorMessage);
/// <summary>
/// Creates a failed result with an error message.
/// </summary>
public static UpdateCheckResult Failed(string errorMessage, string currentVersion)
{
return new UpdateCheckResult
{
IsUpdateAvailable = false,
CurrentVersion = currentVersion,
ErrorMessage = errorMessage
};
}
/// <summary>
/// Creates a result indicating no update is available.
/// </summary>
public static UpdateCheckResult NoUpdateAvailable(string currentVersion)
{
return new UpdateCheckResult
{
IsUpdateAvailable = false,
CurrentVersion = currentVersion,
LatestVersion = currentVersion
};
}
}
}

View File

@@ -32,4 +32,4 @@ namespace Jellyfin2Samsung
.WithInterFont()
.LogToTrace();
}
}
}

View File

@@ -5,100 +5,120 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace Jellyfin2Samsung.Services
{
public class LocalizationService : ILocalizationService
{
private const string DefaultLanguage = "en";
private const string LocalizationFolderUri = "avares://Jellyfin2Samsung/Assets/Localization/";
private Dictionary<string, string> _currentStrings = new();
private readonly Dictionary<string, Dictionary<string, string>> _allStrings = new();
private string _currentLanguage = "en";
private string _currentLanguage = DefaultLanguage;
public string CurrentLanguage => _currentLanguage;
public IEnumerable<string> AvailableLanguages => _allStrings.Keys;
public IEnumerable<string> AvailableLanguages => _allStrings.Keys.OrderBy(x => x);
public event EventHandler? LanguageChanged;
public LocalizationService()
{
LoadLanguagesAsync();
LoadLanguages();
}
private void LoadLanguagesAsync()
private void LoadLanguages()
{
var languages = new[] { "en", "da", "nl", "fr", "de", "tr" };
_allStrings.Clear();
foreach (var lang in languages)
var folderUri = new Uri(LocalizationFolderUri);
try
{
try
{
var uri = new Uri($"avares://Jellyfin2Samsung/Assets/Localization/{lang}.json");
var asset = AssetLoader.Open(uri);
var assetUris = AssetLoader.GetAssets(folderUri, null);
using var reader = new StreamReader(asset);
var json = reader.ReadToEnd();
var strings = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
if (strings != null)
{
_allStrings[lang] = strings;
}
}
catch (Exception ex)
foreach (var assetUri in assetUris)
{
System.Diagnostics.Trace.WriteLine($"Failed to load language {lang}: {ex}");
if (!assetUri.AbsolutePath.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
continue;
var fileName = Path.GetFileNameWithoutExtension(assetUri.AbsolutePath);
if (string.IsNullOrWhiteSpace(fileName))
continue;
TryLoadLanguage(fileName);
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine($"Failed to enumerate localization assets: {ex}");
}
// Always make sure English is attempted as fallback language
if (!_allStrings.ContainsKey(DefaultLanguage))
{
TryLoadLanguage(DefaultLanguage);
}
// Set initial language based on system culture
var systemLang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
string configLang = AppSettings.Default.Language;
var configLang = AppSettings.Default.Language;
if (string.IsNullOrEmpty(configLang))
var initialLang =
!string.IsNullOrWhiteSpace(configLang) && _allStrings.ContainsKey(configLang)
? configLang
: _allStrings.ContainsKey(systemLang)
? systemLang
: DefaultLanguage;
SetLanguage(initialLang);
}
private void TryLoadLanguage(string lang)
{
try
{
if (_allStrings.ContainsKey(systemLang))
var uri = new Uri($"{LocalizationFolderUri}{lang}.json");
using var asset = AssetLoader.Open(uri);
using var reader = new StreamReader(asset);
var json = reader.ReadToEnd();
var strings = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
if (strings != null)
{
SetLanguage(systemLang);
}
else
{
SetLanguage("en");
_allStrings[lang] = strings;
}
}
else
catch (Exception ex)
{
SetLanguage(configLang);
System.Diagnostics.Trace.WriteLine($"Failed to load language {lang}: {ex}");
}
}
public string GetString(string key)
{
if (_currentStrings.TryGetValue(key, out var value))
{
return value;
}
// Fallback to English if key not found in current language
if (_currentLanguage != "en" && _allStrings.TryGetValue("en", out var englishStrings))
{
if (englishStrings.TryGetValue(key, out var englishValue))
{
return englishValue;
}
}
if (_allStrings.TryGetValue(DefaultLanguage, out var englishStrings) &&
englishStrings.TryGetValue(key, out var englishValue))
return englishValue;
// Return the key itself if no translation found
return key;
}
public void SetLanguage(string languageCode)
{
if (_allStrings.TryGetValue(languageCode, out var strings))
if (!_allStrings.TryGetValue(languageCode, out var strings))
{
_currentLanguage = languageCode;
_currentStrings = strings;
LanguageChanged?.Invoke(this, EventArgs.Empty);
languageCode = DefaultLanguage;
strings = _allStrings.GetValueOrDefault(DefaultLanguage, new Dictionary<string, string>());
}
_currentLanguage = languageCode;
_currentStrings = strings;
LanguageChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@@ -69,18 +69,20 @@ namespace Jellyfin2Samsung.Services
public async Task<IEnumerable<NetworkDevice>> FindTizenTvsAsync(CancellationToken cancellationToken = default, bool virtualScan = false)
{
var foundDevices = new List<NetworkDevice>();
var localIps = GetRelevantLocalIPs(virtualScan);
var localInfos = GetLocalNetworkInfos(virtualScan);
var lockObject = new object();
// Group by network prefix to avoid scanning the same network multiple times
var uniqueNetworks = localIps
.Select(ip => GetNetworkPrefix(ip))
.Distinct()
// Deduplicate by actual network address so overlapping interfaces don't double-scan
var uniqueNetworks = localInfos
.Select(info => (
Network: GetNetworkAddress(info.Address, info.Mask),
Broadcast: GetBroadcastAddress(info.Address, info.Mask)
))
.DistinctBy(r => r.Network.ToString())
.ToList();
await Task.WhenAll(uniqueNetworks.SelectMany(networkPrefix =>
Enumerable.Range(1, 254)
.Select(i => $"{networkPrefix}.{i}")
await Task.WhenAll(uniqueNetworks.SelectMany(range =>
GetHostAddresses(range.Network, range.Broadcast)
.Select(async ip =>
{
try
@@ -168,10 +170,103 @@ namespace Jellyfin2Samsung.Services
}
}
private string GetNetworkPrefix(IPAddress ip)
// Returns all local interface IPs with their actual subnet masks.
// Falls back to /24 for the user-supplied custom IP since its mask can't be discovered.
private List<(IPAddress Address, IPAddress Mask)> GetLocalNetworkInfos(bool virtualScan = false)
{
var infos = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.Where(ni =>
virtualScan ||
ni.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
ni.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
.SelectMany(ni => ni.GetIPProperties().UnicastAddresses)
.Where(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork)
.Where(ua => !IPAddress.IsLoopback(ua.Address))
.Where(ua => ua.IPv4Mask != null && !ua.IPv4Mask.Equals(IPAddress.Any))
.Select(ua => (Address: ua.Address, Mask: ua.IPv4Mask))
.ToList();
if (!string.IsNullOrEmpty(AppSettings.Default.UserCustomIP) &&
IPAddress.TryParse(AppSettings.Default.UserCustomIP, out var customIp))
{
// Reuse the mask from a local interface whose network contains the custom IP;
// otherwise fall back to /24 so we still scan the right /24 segment.
var fallback = IPAddress.Parse("255.255.255.0");
var matchingMask = infos
.FirstOrDefault(i =>
GetNetworkAddress(i.Address, i.Mask).Equals(GetNetworkAddress(customIp, i.Mask)))
.Mask ?? fallback;
infos.Add((customIp, matchingMask));
}
return infos;
}
private static IPAddress GetNetworkAddress(IPAddress ip, IPAddress mask)
{
var ipBytes = ip.GetAddressBytes();
var maskBytes = mask.GetAddressBytes();
var result = new byte[4];
for (int i = 0; i < 4; i++)
result[i] = (byte)(ipBytes[i] & maskBytes[i]);
return new IPAddress(result);
}
private static IPAddress GetBroadcastAddress(IPAddress ip, IPAddress mask)
{
var ipBytes = ip.GetAddressBytes();
var maskBytes = mask.GetAddressBytes();
var result = new byte[4];
for (int i = 0; i < 4; i++)
result[i] = (byte)(ipBytes[i] | (byte)~maskBytes[i]);
return new IPAddress(result);
}
// Enumerates usable host addresses for a subnet (excludes network and broadcast addresses).
// Caps at 1022 hosts (/22) to keep scans practical; larger subnets are narrowed to the
// /24 block that contains the network address.
private static IEnumerable<string> GetHostAddresses(IPAddress networkAddress, IPAddress broadcastAddress)
{
uint netInt = IpToUInt(networkAddress);
uint broadInt = IpToUInt(broadcastAddress);
uint hostCount = broadInt - netInt - 1;
if (hostCount > 1022)
{
// Narrow to /24 to avoid scanning thousands of addresses
var bytes = networkAddress.GetAddressBytes();
netInt = IpToUInt(new IPAddress(new byte[] { bytes[0], bytes[1], bytes[2], 0 }));
broadInt = netInt + 255;
}
for (uint i = netInt + 1; i < broadInt; i++)
yield return UIntToIp(i);
}
private static uint IpToUInt(IPAddress ip)
{
var bytes = ip.GetAddressBytes();
return $"{bytes[0]}.{bytes[1]}.{bytes[2]}";
if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
return BitConverter.ToUInt32(bytes, 0);
}
private static string UIntToIp(uint value)
{
var bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
return $"{bytes[0]}.{bytes[1]}.{bytes[2]}.{bytes[3]}";
}
// Looks up the subnet mask assigned to a local interface IP.
private static IPAddress? GetMaskForLocalIp(IPAddress target)
{
return NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.SelectMany(ni => ni.GetIPProperties().UnicastAddresses)
.Where(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork)
.FirstOrDefault(ua => ua.Address.Equals(target))
?.IPv4Mask;
}
public async Task<string?> GetManufacturerFromIp(string ipAddress)
@@ -246,49 +341,54 @@ namespace Jellyfin2Samsung.Services
Array.Reverse(parts);
return string.Join(".", parts);
}
public async Task<string?> GetPrimaryOutboundIPAddressAsync()
{
return await Task.Run(() =>
{
foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
{
var ipProps = ni.GetIPProperties();
var gateway = ipProps.GatewayAddresses
.FirstOrDefault(g =>
g.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(g.Address));
if (gateway != null)
{
var ipv4 = ipProps.UnicastAddresses
.FirstOrDefault(ua =>
ua.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ua.Address));
if (ipv4 != null)
return ipv4.Address.ToString();
}
}
return null;
});
}
public bool IsDifferentSubnet(string ip1, string ip2)
{
if (!IPAddress.TryParse(ip1, out var a) ||
!IPAddress.TryParse(ip2, out var b))
return false; // or true, depending on how strict you want to be
if (!IPAddress.TryParse(ip1, out var a) || !IPAddress.TryParse(ip2, out var b))
return false;
// Use the actual mask from the local interface; fall back to /24 if not found
var mask = GetMaskForLocalIp(a) ?? IPAddress.Parse("255.255.255.0");
var aBytes = a.GetAddressBytes();
var bBytes = b.GetAddressBytes();
var maskBytes = mask.GetAddressBytes();
// /24 subnet → first 3 octets must match
return aBytes[0] != bBytes[0]
|| aBytes[1] != bBytes[1]
|| aBytes[2] != bBytes[2];
for (int i = 0; i < 4; i++)
{
if ((aBytes[i] & maskBytes[i]) != (bBytes[i] & maskBytes[i]))
return true;
}
return false;
}
public Task<IReadOnlyList<NetworkInterfaceOption>> GetNetworkInterfaceOptionsAsync()
{
return Task.Run(() =>
{
var result = new List<NetworkInterfaceOption>();
foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
{
if (ni.OperationalStatus != OperationalStatus.Up)
continue;
foreach (var ua in ni.GetIPProperties().UnicastAddresses)
{
if (ua.Address.AddressFamily != AddressFamily.InterNetwork)
continue;
if (IPAddress.IsLoopback(ua.Address))
continue;
result.Add(new NetworkInterfaceOption
{
Name = ni.Name,
IpAddress = ua.Address.ToString()
});
}
}
return (IReadOnlyList<NetworkInterfaceOption>)result;
});
}
}
}

View File

@@ -1,4 +1,4 @@
using Jellyfin2Samsung.Extensions;
using Jellyfin2Samsung.Extensions;
using Jellyfin2Samsung.Helpers;
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Helpers.Tizen.Certificate;
@@ -12,6 +12,7 @@ using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -92,7 +93,9 @@ namespace Jellyfin2Samsung.Services
private static byte[] GenerateAuthorCsr(AsymmetricCipherKeyPair keyPair)
{
var subject = new X509Name("C=, ST=, L=, O=, OU=, CN=Jelly2Sams");
var subject = new X509Name(
new ArrayList { X509Name.C, X509Name.ST, X509Name.L, X509Name.O, X509Name.OU, X509Name.CN },
new ArrayList { "", "", "", "", "", "Jelly2Sams" });
var csr = new Pkcs10CertificationRequest("SHA256withRSA", subject, keyPair.Public, null, keyPair.Private);
using var ms = new MemoryStream();
@@ -107,7 +110,9 @@ namespace Jellyfin2Samsung.Services
private static byte[] GenerateDistributorCsr(AsymmetricCipherKeyPair keyPair, string duid, string userEmail)
{
var subject = new X509Name($"CN=TizenSDK, OU=, O=, L=, ST=, C=, emailAddress={userEmail}");
var subject = new X509Name(
new ArrayList { X509Name.CN, X509Name.OU, X509Name.O, X509Name.L, X509Name.ST, X509Name.C, X509Name.EmailAddress },
new ArrayList { "TizenSDK", "", "", "", "", "", userEmail });
var generalNames = new GeneralNames(new[]
{
new GeneralName(GeneralName.UniformResourceIdentifier, "URN:tizen:packageid="),
@@ -323,4 +328,4 @@ namespace Jellyfin2Samsung.Services
return PlatformService.GetX509KeyStorageFlags();
}
}
}
}

View File

@@ -23,7 +23,6 @@ namespace Jellyfin2Samsung.Services
private readonly IDialogService _dialogService;
private readonly AppSettings _appSettings;
private readonly JellyfinPackagePatcher _jellyfinWebPackagePatcher;
private readonly JellyfinApiClient _jellyfinApiClient;
private readonly ProcessHelper _processHelper;
public string? TizenSdbPath { get; private set; }
@@ -41,7 +40,6 @@ namespace Jellyfin2Samsung.Services
_dialogService = dialogService;
_appSettings = appSettings;
_jellyfinWebPackagePatcher = jellyfinWebPackagePatcher;
_jellyfinApiClient = jellyfinApiClient;
_processHelper = processHelper;
}
@@ -76,7 +74,7 @@ namespace Jellyfin2Samsung.Services
return TizenSdbPath;
}
string downloadedFile = await DownloadTizenSdbAsync(latestVersion);
string downloadedFile = await DownloadTizenSdbAsync();
if (existingFile != null && File.Exists(existingFile))
{
@@ -154,33 +152,31 @@ namespace Jellyfin2Samsung.Services
}
}
public async Task<string> DownloadTizenSdbAsync(string version = null)
public async Task<string> DownloadTizenSdbAsync()
{
try
{
var json = await _httpClient.GetStringAsync(AppSettings.Default.TizenSdb);
var releases = JsonSerializer.Deserialize<List<GitHubRelease>>(json, JsonSerializerOptionsProvider.Default);
var firstRelease = releases?.FirstOrDefault();
if (firstRelease == null)
throw new InvalidOperationException("No releases found");
var firstRelease = (releases?.FirstOrDefault()) ?? throw new InvalidOperationException("No releases found");
string nameMatch = PlatformService.GetAssetPlatformIdentifier();
var matchedAsset = firstRelease.Assets.FirstOrDefault(a =>
!string.IsNullOrEmpty(a.FileName) &&
a.FileName.Contains(nameMatch, StringComparison.OrdinalIgnoreCase));
if (matchedAsset == null)
throw new InvalidOperationException($"No matching asset found for {nameMatch}");
return await DownloadPackageAsync(matchedAsset.DownloadUrl);
return matchedAsset == null
? throw new InvalidOperationException($"No matching asset found for {nameMatch}")
: await DownloadPackageAsync(matchedAsset.DownloadUrl);
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
throw new TimeoutException(
"GitHub rate limit reached while checking for Tizen SDB.\n\n" +
"Please try again later.",
"To avoid this, set a GitHub Personal Access Token (PAT) in settings,\n" +
"or set the GITHUB_TOKEN environment variable,\n" +
"or install the GitHub CLI (gh) and run 'gh auth login'.\n\n" +
"Alternatively, please try again later.",
ex
);
}
@@ -228,7 +224,7 @@ namespace Jellyfin2Samsung.Services
return InstallResult.FailureResult(Constants.LocalizationKeys.InstallTizenSdb.Localized());
}
}
try
{
// Step 1: Prepare device and check for existing installations
@@ -246,8 +242,22 @@ namespace Jellyfin2Samsung.Services
return InstallResult.FailureResult(Constants.LocalizationKeys.TvNameNotFound.Localized());
}
// Step 3: Handle certificate selection/generation
// Step 3: Check is WGT is compatible with TV's Tizen version
var requiredTizenVersion = await FileHelper.ReadWgtRequiredVersion(packageUrl);
if (requiredTizenVersion != null)
{
var requiredVersion = new Version(requiredTizenVersion);
if (deviceInfo.TizenVersion < requiredVersion)
{
progress?.Invoke(Constants.LocalizationKeys.IncompatiblePackage.Localized());
Trace.WriteLine($"Package requires Tizen {requiredVersion} but device has {deviceInfo.TizenVersion}");
return InstallResult.FailureResult(string.Format(Constants.LocalizationKeys.IncompatiblePackageDetailed.Localized(), requiredTizenVersion, deviceInfo.TizenVersion));
}
}
// Step 4: Handle certificate selection/generation
var certificateResult = await HandleCertificateAsync(
tvIpAddress,
deviceInfo,
packageUrl,
progress,
@@ -257,12 +267,17 @@ namespace Jellyfin2Samsung.Services
if (!certificateResult.Success)
return certificateResult.InstallResult;
// Step 4: Apply Jellyfin configuration if needed
await ApplyConfigurationAsync(packageUrl, progress);
// Step 5: Apply Jellyfin configuration if needed
if (packageUrl.Contains(Constants.AppIdentifiers.JellyfinAppName, StringComparison.OrdinalIgnoreCase))
{
Trace.WriteLine("Applying Jellyfin Configuration");
await ApplyConfigurationAsync(packageUrl, progress);
}
// Step 5: Resign package if needed
// Step 6: Resign package if needed
if (certificateResult.RequiresResign)
{
Trace.WriteLine("Resigning package with new certificate");
progress?.Invoke(Constants.LocalizationKeys.PackageAndSign.Localized());
var resignResults = await ResignPackageAsync(
packageUrl,
@@ -272,13 +287,14 @@ namespace Jellyfin2Samsung.Services
if (resignResults.ExitCode != 0 || resignResults.Output.Contains(Constants.TizenErrorCodes.ResignFailed))
{
Trace.WriteLine($"Resign output: {resignResults.Output}");
progress?.Invoke(Constants.LocalizationKeys.InstallationFailed.Localized());
_appSettings.TryOverwrite = false;
return InstallResult.FailureResult($"Package resigning failed: {resignResults.Output}");
}
}
// Step 6: Install package and handle results
// Step 7: Install package and handle results
progress?.Invoke(Constants.LocalizationKeys.InstallingPackage.Localized());
return await HandleInstallationResultAsync(
@@ -319,6 +335,7 @@ namespace Jellyfin2Samsung.Services
bool canDelete = await GetTvDiagnoseAsync(tvIpAddress);
var (alreadyInstalled, appId) = await CheckForInstalledApp(tvIpAddress, packageUrl);
Trace.WriteLine($"Diagnose canDelete: {canDelete}, alreadyInstalled: {alreadyInstalled}, appId: {appId}");
if (!canDelete && alreadyInstalled)
{
@@ -331,7 +348,11 @@ namespace Jellyfin2Samsung.Services
if (_appSettings.DeletePreviousInstall)
{
progress?.Invoke(Constants.LocalizationKeys.DeleteExistingVersion.Localized());
await UninstallPackageAsync(tvIpAddress, appId!);
var uninstallResult = await UninstallPackageAsync(tvIpAddress, appId!);
Trace.WriteLine($"Uninstall output: {uninstallResult.Output}");
if (uninstallResult.Output.Contains(Constants.TizenErrorCodes.NotInstalled))
return InstallResult.SuccessResult();
var (stillInstalled, _) = await CheckForInstalledApp(tvIpAddress, packageUrl);
if (stillInstalled)
@@ -362,8 +383,7 @@ namespace Jellyfin2Samsung.Services
if (string.IsNullOrEmpty(tvDuid))
return null;
string tizenOs = await FetchTizenOsAsync(tvIpAddress);
string sdkToolPath = await FetchSdkPathAsync(tvIpAddress);
var (tizenOs, sdkToolPath) = await FetchCapabilitiesAsync(tvIpAddress);
if (string.IsNullOrEmpty(tizenOs))
tizenOs = Constants.Defaults.TizenOsVersion;
@@ -382,6 +402,7 @@ namespace Jellyfin2Samsung.Services
#region Certificate Handling
private async Task<CertificateResult> HandleCertificateAsync(
string tvIpAddress,
DeviceInfo deviceInfo,
string packageUrl,
ProgressCallback? progress,
@@ -483,7 +504,7 @@ namespace Jellyfin2Samsung.Services
? Constants.Defaults.HomeDeveloperPath
: deviceInfo.SdkToolPath;
await AllowPermitInstall(tvIpAddress: deviceInfo.Duid, deviceProfilePath, targetPath);
await AllowPermitInstall(tvIpAddress, deviceProfilePath, targetPath);
}
return new CertificateResult
@@ -502,11 +523,13 @@ namespace Jellyfin2Samsung.Services
private async Task ApplyConfigurationAsync(string packageUrl, ProgressCallback? progress)
{
// Only apply configuration if JellyfinIP is set and this is a Jellyfin package
if (string.IsNullOrEmpty(_appSettings.JellyfinIP))
return;
if (!packageUrl.Contains(Constants.AppIdentifiers.JellyfinAppName, StringComparison.OrdinalIgnoreCase))
var name = Path.GetFileName(packageUrl);
var isJellyfinPackage = name.Contains("jellyfin", StringComparison.OrdinalIgnoreCase);
if (!isJellyfinPackage)
return;
// Apply server settings via JS injection
@@ -534,11 +557,13 @@ namespace Jellyfin2Samsung.Services
if (_appSettings.TryOverwrite)
{
Trace.WriteLine("Installation failed, insufficient space! retrying with remove previous version");
_appSettings.TryOverwrite = false;
return await InstallPackageAsync(packageUrl, tvIpAddress, cancellationToken, progress, onSamsungLoginStarted);
}
_appSettings.TryOverwrite = false;
Trace.WriteLine("Installation failed, insufficient space!");
return InstallResult.FailureResult($"Installation failed: {Constants.LocalizationKeys.InsufficientSpace.Localized()}");
}
@@ -552,6 +577,7 @@ namespace Jellyfin2Samsung.Services
{
_appSettings.TryOverwrite = false;
_appSettings.ForceSamsungLogin = true;
_appSettings.DeletePreviousInstall = true;
return await InstallPackageAsync(packageUrl, tvIpAddress, cancellationToken, progress, onSamsungLoginStarted);
}
@@ -631,18 +657,17 @@ namespace Jellyfin2Samsung.Services
return output.Output.Split('\n', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? string.Empty;
}
private async Task<string> FetchTizenOsAsync(string tvIpAddress)
private async Task<(string tizenOs, string sdkToolPath)> FetchCapabilitiesAsync(string tvIpAddress)
{
var output = await _processHelper.RunCommandAsync(TizenSdbPath!, $"capability {tvIpAddress}");
var match = RegexPatterns.TizenCapability.PlatformVersion.Match(output.Output);
return match.Success ? match.Groups[1].Value.Trim() : string.Empty;
}
private async Task<string> FetchSdkPathAsync(string tvIpAddress)
{
var output = await _processHelper.RunCommandAsync(TizenSdbPath!, $"capability {tvIpAddress}");
var match = RegexPatterns.TizenCapability.SdkToolPath.Match(output.Output);
return match.Success ? match.Groups[1].Value.Trim() : Constants.Defaults.SdkToolPath;
var versionMatch = RegexPatterns.TizenCapability.PlatformVersion.Match(output.Output);
string tizenOs = versionMatch.Success ? versionMatch.Groups[1].Value.Trim() : string.Empty;
var pathMatch = RegexPatterns.TizenCapability.SdkToolPath.Match(output.Output);
string sdkToolPath = pathMatch.Success ? pathMatch.Groups[1].Value.Trim() : Constants.Defaults.SdkToolPath;
return (tizenOs, sdkToolPath);
}
private async Task<string> GetTvDuidAsync(string tvIpAddress)
@@ -660,24 +685,42 @@ namespace Jellyfin2Samsung.Services
private async Task<(bool isInstalled, string? appId)> CheckForInstalledApp(string tvIpAddress, string packageUrl)
{
var output = await _processHelper.RunCommandAsync(TizenSdbPath!, $"apps {tvIpAddress}");
var result = await _processHelper.RunCommandAsync(TizenSdbPath!, $"apps {tvIpAddress}");
var output = result?.Output ?? string.Empty;
// Read what the WGT *claims* its app id is (best effort fallback for "no listing" cases)
var wgtAppId = await FileHelper.ReadWgtApplicationId(packageUrl);
// Case 3: no listing -> assume installed, return WGT app id as best-effort
if (string.IsNullOrWhiteSpace(output) ||
output.Contains("Could not retrieve app list", StringComparison.OrdinalIgnoreCase) ||
output.Contains("Remote closed channel", StringComparison.OrdinalIgnoreCase))
{
return (true, wgtAppId);
}
// Case 1/2: listing returned -> parse TV output
var baseSearch = Path.GetFileNameWithoutExtension(packageUrl).Split('-')[0];
var blockRegex = RegexPatterns.TizenApp.CreateAppBlockByTitleRegex(baseSearch);
var blockMatch = blockRegex.Match(output.Output);
var blockMatch = blockRegex.Match(output);
// Case 2: listing returned but not present
if (!blockMatch.Success)
return (false, null);
// Case 1: listing returned and present -> TV's id is the uninstall/overwrite truth
var block = blockMatch.Value;
var appIdMatch = RegexPatterns.TizenApp.AppTizenId.Match(block);
string tvAppId = appIdMatch.Groups[1].Value.Trim();
string? packageAppId = await FileHelper.ReadWgtPackageId(packageUrl);
var tvAppId = appIdMatch.Success ? appIdMatch.Groups[1].Value.Trim() : null;
if (tvAppId == $"{packageAppId}.{Constants.AppIdentifiers.JellyfinAppName}")
return (true, tvAppId);
// If we matched by title but ID isn't matching Config ID (jellyfin-secondary)
Debug.WriteLine($"TV APP ID: {tvAppId} - CONFIG APP ID: {wgtAppId}");
Trace.WriteLine($"TV APP ID: {tvAppId} - CONFIG APP ID: {wgtAppId}");
if (tvAppId != wgtAppId)
return (false, null);
return (false, null);
// If we matched by title but couldn't parse ID, fall back to WGT ID
return (true, !string.IsNullOrWhiteSpace(tvAppId) ? tvAppId : wgtAppId);
}
private async Task<string> GetInstalledAppId(string tvIpAddress, string appTitle)

View File

@@ -0,0 +1,101 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using Jellyfin2Samsung.Views;
using System;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Services
{
/// <summary>
/// Service for showing update-related dialogs.
/// </summary>
public class UpdateDialogService : IUpdateDialogService
{
private readonly IDialogService _dialogService;
private readonly ILocalizationService _localizationService;
private UpdateDialog? _currentDialog;
public UpdateDialogService(IDialogService dialogService, ILocalizationService localizationService)
{
_dialogService = dialogService;
_localizationService = localizationService;
}
private Window? GetMainWindow()
{
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
return desktop.MainWindow;
return null;
}
private string L(string key) => _localizationService.GetString(key);
/// <inheritdoc />
public async Task<UpdateDialogChoice> ShowUpdateAvailableDialogAsync(UpdateCheckResult updateInfo)
{
var mainWindow = GetMainWindow();
if (mainWindow == null)
return UpdateDialogChoice.Cancel;
// Ensure we're on the UI thread
if (!Dispatcher.UIThread.CheckAccess())
{
return await Dispatcher.UIThread.InvokeAsync(async () =>
await ShowUpdateAvailableDialogAsync(updateInfo));
}
_currentDialog = new UpdateDialog();
_currentDialog.Initialize(updateInfo);
var result = await _currentDialog.ShowDialogAsync(mainWindow);
_currentDialog = null;
return result;
}
/// <inheritdoc />
public async Task<bool> ShowDownloadProgressAsync(IProgress<int> progress)
{
// This is handled within the UpdateDialog itself via the IsDownloading property
// The progress is reported through the dialog's UpdateProgress method
await Task.CompletedTask;
return true;
}
/// <inheritdoc />
public async Task ShowApplyingUpdateMessageAsync()
{
var mainWindow = GetMainWindow();
if (mainWindow == null)
return;
if (!Dispatcher.UIThread.CheckAccess())
{
await Dispatcher.UIThread.InvokeAsync(async () =>
await ShowApplyingUpdateMessageAsync());
return;
}
await _dialogService.ShowMessageAsync(
L("UpdateApplying"),
L("UpdateApplyingMessage"));
}
/// <inheritdoc />
public async Task ShowUpdateErrorAsync(string errorMessage)
{
if (!Dispatcher.UIThread.CheckAccess())
{
await Dispatcher.UIThread.InvokeAsync(async () =>
await ShowUpdateErrorAsync(errorMessage));
return;
}
await _dialogService.ShowErrorAsync($"{L("UpdateError")}: {errorMessage}");
}
}
}

View File

@@ -0,0 +1,484 @@
using Jellyfin2Samsung.Helpers;
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Jellyfin2Samsung.Services
{
/// <summary>
/// Service for checking and applying application updates via GitHub.
/// Uses the Atom feed endpoint to avoid API rate limiting.
/// </summary>
public class UpdaterService : IUpdaterService
{
private readonly HttpClient _httpClient;
private const string RepoOwner = "Jellyfin2Samsung";
private const string RepoName = "Samsung-Jellyfin-Installer";
private const string AtomFeedUrl = $"https://github.com/{RepoOwner}/{RepoName}/releases.atom";
private const string ReleasesApiUrl = $"https://api.github.com/repos/{RepoOwner}/{RepoName}/releases/latest";
public string ReleasesPageUrl => $"https://github.com/{RepoOwner}/{RepoName}/releases";
public string CurrentVersion => AppSettings.Default.AppVersion;
public UpdaterService(HttpClient httpClient)
{
_httpClient = httpClient;
}
/// <inheritdoc />
public async Task<UpdateCheckResult> CheckForUpdateAsync(CancellationToken cancellationToken = default)
{
try
{
// First try Atom feed (no rate limit)
var atomResult = await CheckViaAtomFeedAsync(cancellationToken);
if (atomResult.IsSuccess && atomResult.IsUpdateAvailable)
{
// Get download URL from API (only if update available)
await EnrichWithDownloadUrlAsync(atomResult, cancellationToken);
}
return atomResult;
}
catch (Exception ex)
{
Trace.WriteLine($"Update check failed: {ex}");
return UpdateCheckResult.Failed($"Failed to check for updates: {ex.Message}", CurrentVersion);
}
}
private async Task<UpdateCheckResult> CheckViaAtomFeedAsync(CancellationToken cancellationToken)
{
try
{
using var request = new HttpRequestMessage(HttpMethod.Get, AtomFeedUrl);
request.Headers.Accept.ParseAdd("application/atom+xml");
using var response = await _httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var atomXml = await response.Content.ReadAsStringAsync(cancellationToken);
var latestEntry = ParseAtomFeed(atomXml);
if (latestEntry == null)
{
return UpdateCheckResult.NoUpdateAvailable(CurrentVersion);
}
var latestVersion = latestEntry.TagName;
var isUpdateAvailable = IsVersionGreater(latestVersion, CurrentVersion);
return new UpdateCheckResult
{
IsUpdateAvailable = isUpdateAvailable,
CurrentVersion = CurrentVersion,
LatestVersion = latestVersion,
ReleaseTitle = latestEntry.Title,
ReleaseNotes = HtmlUtils.StripHtml(HtmlUtils.RemoveMarkdownTable(latestEntry.Content)),
ReleasesPageUrl = latestEntry.Link,
PublishedAt = latestEntry.Updated
};
}
catch (Exception ex)
{
Trace.WriteLine($"Atom feed check failed: {ex}");
return UpdateCheckResult.Failed($"Failed to parse release feed: {ex.Message}", CurrentVersion);
}
}
private GitHubAtomEntry? ParseAtomFeed(string atomXml)
{
try
{
var doc = XDocument.Parse(atomXml);
XNamespace atom = "http://www.w3.org/2005/Atom";
// Get all entries and filter out beta/pre-releases
var entry = doc.Descendants(atom + "entry").FirstOrDefault(e =>
{
var title = e.Element(atom + "title")?.Value ?? string.Empty;
// Filter out entries with beta
return !title.Contains("beta", StringComparison.OrdinalIgnoreCase);
});
if (entry == null)
return null;
return new GitHubAtomEntry
{
Id = entry.Element(atom + "id")?.Value ?? string.Empty,
Title = entry.Element(atom + "title")?.Value ?? string.Empty,
Updated = DateTime.TryParse(entry.Element(atom + "updated")?.Value, out var updated) ? updated : null,
Link = entry.Element(atom + "link")?.Attribute("href")?.Value ?? string.Empty,
Content = entry.Element(atom + "content")?.Value ?? string.Empty,
AuthorName = entry.Element(atom + "author")?.Element(atom + "name")?.Value ?? string.Empty
};
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to parse Atom feed: {ex}");
return null;
}
}
private async Task EnrichWithDownloadUrlAsync(UpdateCheckResult result, CancellationToken cancellationToken)
{
try
{
using var request = new HttpRequestMessage(HttpMethod.Get, ReleasesApiUrl);
using var response = await _httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
// API rate limited or error - user can still use manual download
Trace.WriteLine($"GitHub API returned {response.StatusCode}, download URL unavailable");
return;
}
var json = await response.Content.ReadAsStringAsync(cancellationToken);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
if (!root.TryGetProperty("assets", out var assets))
return;
var platformSuffix = GetPlatformSuffix();
foreach (var asset in assets.EnumerateArray())
{
if (!asset.TryGetProperty("name", out var nameElement))
continue;
var name = nameElement.GetString() ?? string.Empty;
// Match platform-specific archive
if (name.Contains(platformSuffix, StringComparison.OrdinalIgnoreCase) &&
(name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) ||
name.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase)))
{
if (asset.TryGetProperty("browser_download_url", out var urlElement))
{
result.DownloadUrl = urlElement.GetString();
return;
}
}
}
// Fallback: try to find any zip file
foreach (var asset in assets.EnumerateArray())
{
if (!asset.TryGetProperty("name", out var nameElement))
continue;
var name = nameElement.GetString() ?? string.Empty;
if (name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
{
if (asset.TryGetProperty("browser_download_url", out var urlElement))
{
result.DownloadUrl = urlElement.GetString();
return;
}
}
}
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to get download URL: {ex}");
// Not critical - user can still download manually
}
}
private static string GetPlatformSuffix()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return "win";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return "linux";
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return "osx";
return "win"; // Default fallback
}
/// <inheritdoc />
public async Task<string> DownloadUpdateAsync(
string downloadUrl,
IProgress<int>? progress = null,
CancellationToken cancellationToken = default)
{
var tempDir = Path.Combine(Path.GetTempPath(), "Jellyfin2Samsung_Update");
Directory.CreateDirectory(tempDir);
var fileName = Path.GetFileName(new Uri(downloadUrl).LocalPath);
var downloadPath = Path.Combine(tempDir, fileName);
// Clean up old downloads
if (File.Exists(downloadPath))
File.Delete(downloadPath);
using var response = await _httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
var downloadedBytes = 0L;
await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);
await using var fileStream = new FileStream(downloadPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
var buffer = new byte[8192];
int bytesRead;
while ((bytesRead = await contentStream.ReadAsync(buffer, cancellationToken)) > 0)
{
await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
downloadedBytes += bytesRead;
if (totalBytes > 0)
{
var percentage = (int)((downloadedBytes * 100) / totalBytes);
progress?.Report(percentage);
}
}
progress?.Report(100);
return downloadPath;
}
/// <inheritdoc />
public async Task<bool> ApplyUpdateAsync(string downloadedFilePath, CancellationToken cancellationToken = default)
{
try
{
var appDir = AppContext.BaseDirectory;
var updateDir = Path.Combine(Path.GetTempPath(), "Jellyfin2Samsung_Update", "extracted");
var backupDir = Path.Combine(Path.GetTempPath(), "Jellyfin2Samsung_Update", "backup");
// Clean extraction directory
if (Directory.Exists(updateDir))
Directory.Delete(updateDir, true);
Directory.CreateDirectory(updateDir);
// Extract update
if (downloadedFilePath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
{
ZipFile.ExtractToDirectory(downloadedFilePath, updateDir);
}
else
{
throw new NotSupportedException("Only ZIP archives are supported for automatic updates.");
}
// Find the actual application directory (might be in a subfolder)
var extractedAppDir = FindApplicationDirectory(updateDir);
if (extractedAppDir == null)
{
throw new InvalidOperationException("Could not find application files in the update package.");
}
// Create update script
var scriptPath = CreateUpdateScript(extractedAppDir, appDir, backupDir);
// Launch the update script and exit
LaunchUpdateScript(scriptPath);
return true;
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to apply update: {ex}");
throw;
}
}
private string? FindApplicationDirectory(string extractedDir)
{
// Check if the main executable is directly in the extracted directory
var exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "Jellyfin2Samsung.exe"
: "Jellyfin2Samsung";
if (File.Exists(Path.Combine(extractedDir, exeName)))
return extractedDir;
// Check subdirectories
foreach (var subDir in Directory.GetDirectories(extractedDir))
{
if (File.Exists(Path.Combine(subDir, exeName)))
return subDir;
// Check one level deeper
foreach (var subSubDir in Directory.GetDirectories(subDir))
{
if (File.Exists(Path.Combine(subSubDir, exeName)))
return subSubDir;
}
}
return null;
}
private string CreateUpdateScript(string sourceDir, string targetDir, string backupDir)
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var scriptExtension = isWindows ? ".bat" : ".sh";
var scriptPath = Path.Combine(Path.GetTempPath(), $"jellyfin2samsung_update{scriptExtension}");
var exeName = isWindows ? "Jellyfin2Samsung.exe" : "Jellyfin2Samsung";
var processId = Environment.ProcessId;
string scriptContent;
if (isWindows)
{
scriptContent = $@"@echo off
chcp 65001 > nul
echo Waiting for application to close...
:waitloop
tasklist /FI ""PID eq {processId}"" 2>NUL | find /I ""{processId}"" >NUL
if not errorlevel 1 (
timeout /t 1 /nobreak > nul
goto waitloop
)
echo Creating backup...
if exist ""{backupDir}"" rmdir /s /q ""{backupDir}""
mkdir ""{backupDir}""
xcopy ""{targetDir}\*"" ""{backupDir}\"" /E /H /Y /Q
echo Installing update...
xcopy ""{sourceDir}\*"" ""{targetDir}\"" /E /H /Y /Q
echo Starting application...
start """" ""{Path.Combine(targetDir, exeName)}""
echo Cleaning up...
timeout /t 2 /nobreak > nul
rmdir /s /q ""{Path.Combine(Path.GetTempPath(), "Jellyfin2Samsung_Update")}""
del ""%~f0""
";
}
else
{
scriptContent = $@"#!/bin/bash
echo ""Waiting for application to close...""
while kill -0 {processId} 2>/dev/null; do
sleep 1
done
echo ""Creating backup...""
rm -rf ""{backupDir}""
mkdir -p ""{backupDir}""
cp -r ""{targetDir}/""* ""{backupDir}/""
echo ""Installing update...""
cp -rf ""{sourceDir}/""* ""{targetDir}/""
chmod +x ""{Path.Combine(targetDir, exeName)}""
echo ""Starting application...""
nohup ""{Path.Combine(targetDir, exeName)}"" &
echo ""Cleaning up...""
sleep 2
rm -rf ""{Path.Combine(Path.GetTempPath(), "Jellyfin2Samsung_Update")}""
rm -- ""$0""
";
}
File.WriteAllText(scriptPath, scriptContent);
if (!isWindows)
{
// Make script executable on Unix
Process.Start("chmod", $"+x \"{scriptPath}\"")?.WaitForExit();
}
return scriptPath;
}
private void LaunchUpdateScript(string scriptPath)
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var startInfo = new ProcessStartInfo
{
UseShellExecute = true,
CreateNoWindow = !isWindows, // Show window on Windows for user feedback
WindowStyle = isWindows ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden
};
if (isWindows)
{
startInfo.FileName = "cmd.exe";
startInfo.Arguments = $"/c \"{scriptPath}\"";
}
else
{
startInfo.FileName = "/bin/bash";
startInfo.Arguments = scriptPath;
}
Process.Start(startInfo);
}
/// <inheritdoc />
public void OpenReleasesPage()
{
try
{
var psi = new ProcessStartInfo
{
FileName = ReleasesPageUrl,
UseShellExecute = true
};
Process.Start(psi);
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to open releases page: {ex}");
}
}
private static bool IsVersionGreater(string latestVersion, string currentVersion)
{
// Clean version strings
var latestClean = CleanVersionString(latestVersion);
var currentClean = CleanVersionString(currentVersion);
if (Version.TryParse(latestClean, out var latest) &&
Version.TryParse(currentClean, out var current))
{
return latest > current;
}
// Fallback to string comparison
return string.Compare(latestClean, currentClean, StringComparison.OrdinalIgnoreCase) > 0;
}
private static string CleanVersionString(string version)
{
if (string.IsNullOrEmpty(version))
return "0.0.0";
// Remove 'v' prefix
var cleaned = version.TrimStart('v', 'V');
// Remove suffixes like -beta, -alpha, -rc
var dashIndex = cleaned.IndexOf('-');
if (dashIndex > 0)
cleaned = cleaned.Substring(0, dashIndex);
return cleaned;
}
}
}

View File

@@ -1,12 +1,18 @@
using CommunityToolkit.Mvvm.Input;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Jellyfin2Samsung.Helpers;
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.ViewModels
@@ -16,21 +22,53 @@ namespace Jellyfin2Samsung.ViewModels
public ObservableCollection<BuildVersion> JellyfinVersions { get; } = new();
public ObservableCollection<BuildVersion> CommunityApps { get; } = new();
public ObservableCollection<ProviderOption> ProviderOptions { get; } = new();
[ObservableProperty]
private ProviderOption? selectedProviderOption;
private static readonly HttpClient _http = new();
private readonly Dictionary<string, Bitmap?> _bitmapCache = new(StringComparer.OrdinalIgnoreCase);
private bool _isLoading;
private int _rebuildVersion;
public BuildInfoViewModel()
{
CommunityApps.CollectionChanged += (_, __) =>
{
if (_isLoading) return;
QueueRebuild();
};
JellyfinVersions.CollectionChanged += (_, __) =>
{
if (_isLoading) return;
QueueRebuild();
};
_ = LoadAsync();
}
private void QueueRebuild()
{
// Start a rebuild, but only the latest one is allowed to apply results
var version = Interlocked.Increment(ref _rebuildVersion);
_ = RebuildProviderOptionsAsync(version);
}
public async Task LoadAsync()
{
try
{
using var client = new HttpClient();
_isLoading = true;
var jellyfinMd = await client.GetStringAsync(AppSettings.Default.ReleaseInfo);
var communityMd = await client.GetStringAsync(AppSettings.Default.CommunityInfo);
var jellyfinMd = await _http.GetStringAsync(AppSettings.Default.ReleaseInfo);
var communityMd = await _http.GetStringAsync(AppSettings.Default.CommunityInfo);
JellyfinVersions.Clear();
CommunityApps.Clear();
// Parse Jellyfin table
ParseVersionsTable(jellyfinMd, JellyfinVersions);
JellyfinVersions.Add(new BuildVersion
@@ -39,7 +77,12 @@ namespace Jellyfin2Samsung.ViewModels
Description = "Moonfin is optimized for the viewing experience on Samsung Smart TVs."
});
// Static Jellyfin entries
JellyfinVersions.Add(new BuildVersion
{
FileName = "Litefin",
Description = "Litefin is designed to provide a premium media browsing and playback experience, even on legacy hardware."
});
JellyfinVersions.Add(new BuildVersion
{
FileName = "Legacy",
@@ -55,19 +98,144 @@ namespace Jellyfin2Samsung.ViewModels
JellyfinVersions.Add(new BuildVersion
{
FileName = "AVPlay 10.10.z - SmartHub",
Description = "Includes AVPlay video player patches for better Samsung TV compatibility for10.10.z SmartHub variant"
Description = "Includes AVPlay video player patches for better Samsung TV compatibility for 10.10.z SmartHub variant"
});
// Parse community apps
ParseApplicationsTable(communityMd, CommunityApps);
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to load build info: {ex}");
}
finally
{
_isLoading = false;
}
// Do a single authoritative rebuild after load finishes
var version = Interlocked.Increment(ref _rebuildVersion);
await RebuildProviderOptionsAsync(version);
if (SelectedProviderOption is null && ProviderOptions.Count > 0)
SelectedProviderOption = ProviderOptions[0];
}
// Remove markdown formatting like **bold**, emoji, etc.
private async Task RebuildProviderOptionsAsync(int version)
{
// If a newer rebuild was queued, abandon this one
if (version != Volatile.Read(ref _rebuildVersion))
return;
// Map ONLY the apps that truly need unique thumbnails
var communityPreviewUrls = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Moonlight", Constants.PreviewImages.Moonlight },
{ "Fireplace", Constants.PreviewImages.Fireplace },
{ "TVApp", Constants.PreviewImages.TVApp },
{ "Twitch", Constants.PreviewImages.Twitch },
{ "Club Info Board", Constants.PreviewImages.ClubInfoBoard },
{ "Doom", Constants.PreviewImages.Doom },
{ "TransportTycoonDeluxe", Constants.PreviewImages.TTD },
};
// Exceptions inside JellyfinVersions that should NOT use Jellyfin image
var jellyfinOverrides = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Moonfin", Constants.PreviewImages.Moonfin },
{ "Litefin", Constants.PreviewImages.Litefin },
};
var jellyfinBitmap = await LoadBitmapAsync(Constants.PreviewImages.Jellyfin);
// Build locally (dont mutate ObservableCollection from background thread)
var built = new List<ProviderOption>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
void AddIfNew(string name, Bitmap? bmp)
{
name = (name ?? string.Empty).Trim();
if (name.Length == 0) return;
if (!seen.Add(name)) return;
built.Add(new ProviderOption
{
DisplayName = name,
PreviewImage = bmp
});
}
// 1) Jellyfin top entry
AddIfNew("Jellyfin", jellyfinBitmap);
// 2) Community apps
foreach (var app in CommunityApps)
{
var name = (app.FileName ?? string.Empty).Trim();
if (name.Length == 0) continue;
var url = communityPreviewUrls.FirstOrDefault(kvp =>
name.Contains(kvp.Key, StringComparison.OrdinalIgnoreCase)).Value;
var bmp = url is not null ? await LoadBitmapAsync(url) : null;
AddIfNew(name, bmp);
}
// 3) Jellyfin builds (default Jellyfin image, override forks like Moonfin)
foreach (var build in JellyfinVersions)
{
var name = (build.FileName ?? string.Empty).Trim();
if (name.Length == 0) continue;
Bitmap? bmp = jellyfinBitmap;
var overrideUrl = jellyfinOverrides.FirstOrDefault(kvp =>
name.Contains(kvp.Key, StringComparison.OrdinalIgnoreCase)).Value;
if (overrideUrl is not null)
bmp = await LoadBitmapAsync(overrideUrl);
AddIfNew(name, bmp);
}
// If a newer rebuild was queued while we were downloading images, abandon this one
if (version != Volatile.Read(ref _rebuildVersion))
return;
await Dispatcher.UIThread.InvokeAsync(() =>
{
ProviderOptions.Clear();
foreach (var opt in built)
ProviderOptions.Add(opt);
if (SelectedProviderOption is null && ProviderOptions.Count > 0)
SelectedProviderOption = ProviderOptions[0];
});
}
private async Task<Bitmap?> LoadBitmapAsync(string url)
{
if (_bitmapCache.TryGetValue(url, out var cached))
return cached;
try
{
var bytes = await _http.GetByteArrayAsync(url);
await using var ms = new MemoryStream(bytes);
var bmp = new Bitmap(ms);
_bitmapCache[url] = bmp;
return bmp;
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to load image '{url}': {ex.Message}");
_bitmapCache[url] = null;
return null;
}
}
// -------- your existing parsing methods (unchanged) --------
private static string CleanText(string input)
{
var text = RegexPatterns.BuildInfo.MarkdownBold.Replace(input, "$1");
@@ -78,22 +246,19 @@ namespace Jellyfin2Samsung.ViewModels
private void ParseVersionsTable(string md, ObservableCollection<BuildVersion> target)
{
var match = RegexPatterns.BuildInfo.VersionsTable.Match(md);
if (!match.Success) return;
var table = match.Groups["table"].Value;
var rows = RegexPatterns.BuildInfo.TableRow2Columns.Matches(table);
bool headerSkipped = false;
foreach (Match row in rows)
foreach (System.Text.RegularExpressions.Match row in rows)
{
var col1 = row.Groups[1].Value.Trim();
var col2 = row.Groups[2].Value.Trim();
if (!headerSkipped &&
col1.Equals("File name", StringComparison.OrdinalIgnoreCase))
if (!headerSkipped && col1.Equals("File name", StringComparison.OrdinalIgnoreCase))
{
headerSkipped = true;
continue;
@@ -113,22 +278,20 @@ namespace Jellyfin2Samsung.ViewModels
private void ParseApplicationsTable(string md, ObservableCollection<BuildVersion> target)
{
var match = RegexPatterns.BuildInfo.ApplicationsTable.Match(md);
if (!match.Success) return;
var table = match.Groups["table"].Value;
var rows = RegexPatterns.BuildInfo.TableRow3Columns.Matches(table);
bool headerSkipped = false;
foreach (Match row in rows)
foreach (System.Text.RegularExpressions.Match row in rows)
{
var col1 = row.Groups[1].Value.Trim();
var col2 = row.Groups[2].Value.Trim();
var col3 = row.Groups[3].Value.Trim();
if (!headerSkipped &&
col1.Contains("Application", StringComparison.OrdinalIgnoreCase))
if (!headerSkipped && col1.Contains("Application", StringComparison.OrdinalIgnoreCase))
{
headerSkipped = true;
continue;
@@ -153,4 +316,4 @@ namespace Jellyfin2Samsung.ViewModels
public event Action? OnRequestClose;
}
}
}

View File

@@ -83,8 +83,6 @@ namespace Jellyfin2Samsung.ViewModels
[ObservableProperty]
private bool streamValidated = false;
[ObservableProperty]
private string streamValidationStatus = string.Empty;
[ObservableProperty]
private bool enableBackdrops;
@@ -139,6 +137,9 @@ namespace Jellyfin2Samsung.ViewModels
[ObservableProperty]
private bool canOpenDebugWindow;
[ObservableProperty]
private bool showMdnsWarning = false;
[ObservableProperty]
private string selectedServerInputMode = "IP : Port";
@@ -182,10 +183,20 @@ namespace Jellyfin2Samsung.ViewModels
[ObservableProperty]
private bool darkMode;
[ObservableProperty]
private string gitHubToken = string.Empty;
[ObservableProperty]
private bool showGitHubToken = false;
[ObservableProperty]
private NetworkInterfaceOption? selectedNetworkInterface;
public ObservableCollection<LanguageOption> AvailableLanguages { get; }
public ObservableCollection<ExistingCertificates> AvailableCertificates { get; } = new();
public ObservableCollection<JellyfinUser> AvailableJellyfinUsers { get; } = new();
public ObservableCollection<JellyfinUser> SelectedJellyfinUsers { get; }
public ObservableCollection<NetworkInterfaceOption> NetworkInterfaces { get; } = new();
// ========== End Main Settings Properties ==========
public ObservableCollection<string> AvailableThemes { get; } = new()
@@ -289,6 +300,7 @@ namespace Jellyfin2Samsung.ViewModels
public string LblValidateCss => _localizationService.GetString("lblValidateCss");
public string LblCssValidationStatus => _localizationService.GetString("lblCssValidationStatus");
public string LblClearCss => _localizationService.GetString("lblClearCss");
public string LblMdnsWarning => _localizationService.GetString("lblMdnsWarning");
// Main Settings Tab labels
@@ -305,6 +317,9 @@ namespace Jellyfin2Samsung.ViewModels
public string LblRTL => _localizationService.GetString("lblRTL");
public string LblKeepWGTFile => _localizationService.GetString("lblKeepWGTFile");
public string LblSettingsHeader => _localizationService.GetString("lblSettings");
public string LblGitHubToken => _localizationService.GetString("lblGitHubToken");
public string LblGitHubTokenHint => _localizationService.GetString("lblGitHubTokenHint");
public char GitHubTokenPasswordChar => ShowGitHubToken ? '\0' : '*';
public bool CanLogin => ServerValidated &&
!string.IsNullOrWhiteSpace(JellyfinUsername) &&
@@ -346,7 +361,7 @@ namespace Jellyfin2Samsung.ViewModels
InitializeAsyncSettings();
InitializeMainSettings();
UpdateServerIpStatus();
_ = LoadLocalIpAsync();
_ = LoadNetworkInterfacesAsync();
_ = InitializeCertificatesAsync();
}
@@ -419,6 +434,7 @@ namespace Jellyfin2Samsung.ViewModels
OnPropertyChanged(nameof(LblCssHint));
OnPropertyChanged(nameof(LblValidateCss));
OnPropertyChanged(nameof(LblCssValidationStatus));
OnPropertyChanged(nameof(LblMdnsWarning));
// Main Settings Tab labels
OnPropertyChanged(nameof(LblTabMainSettings));
OnPropertyChanged(nameof(LblMainSettings));
@@ -433,6 +449,8 @@ namespace Jellyfin2Samsung.ViewModels
OnPropertyChanged(nameof(LblRTL));
OnPropertyChanged(nameof(LblKeepWGTFile));
OnPropertyChanged(nameof(LblSettingsHeader));
OnPropertyChanged(nameof(LblGitHubToken));
OnPropertyChanged(nameof(LblGitHubTokenHint));
}
partial void OnAudioLanguagePreferenceChanged(string? value)
@@ -521,6 +539,8 @@ namespace Jellyfin2Samsung.ViewModels
OnPropertyChanged(nameof(JellyfinBasePath));
AppSettings.Default.Save();
CheckForMdnsHostname(uri.Host);
// Auto-validate the server connection
_ = AutoValidateServerAsync();
}
@@ -667,6 +687,7 @@ namespace Jellyfin2Samsung.ViewModels
AppSettings.Default.JellyfinUserId = "";
AppSettings.Default.JellyfinServerId = "";
AppSettings.Default.JellyfinServerLocalAddress = "";
AppSettings.Default.JellyfinServerName = "";
AppSettings.Default.IsJellyfinAdmin = false;
AppSettings.Default.Save();
@@ -690,28 +711,6 @@ namespace Jellyfin2Samsung.ViewModels
await LoadJellyfinUsersAsync();
}
[RelayCommand]
private async Task TestConnectionAsync()
{
StreamValidated = false;
StreamValidationStatus = "Validating...";
var testUrl = UrlHelper.CombineUrl(LocalYoutubeServer, "/health");
var isReachable = await _jellyfinApiClient.TestServerConnectionAsync(testUrl);
if (isReachable)
{
StreamValidated = true;
StreamValidationStatus = "✓ Connected";
}
else
{
StreamValidated = false;
StreamValidationStatus = "✕ Unreachable";
}
}
[RelayCommand]
private async Task TestServerAsync()
{
@@ -762,6 +761,12 @@ namespace Jellyfin2Samsung.ViewModels
Trace.WriteLine($"[ServerID] Stored server LocalAddress: {serverInfo.LocalAddress}");
}
if (!string.IsNullOrEmpty(serverInfo.ServerName))
{
AppSettings.Default.JellyfinServerName = serverInfo.ServerName;
Trace.WriteLine($"[ServerID] Stored server name: {serverInfo.ServerName}");
}
AppSettings.Default.Save();
}
}
@@ -1212,6 +1217,7 @@ namespace Jellyfin2Samsung.ViewModels
SelectedJellyfinProtocol = uri.Scheme;
JellyfinServerIp = uri.Host;
SelectedJellyfinPort = uri.Port.ToString();
CheckForMdnsHostname(uri.Host);
}
else
{
@@ -1300,9 +1306,21 @@ namespace Jellyfin2Samsung.ViewModels
Trace.WriteLine($"Updated Jellyfin IP: {AppSettings.Default.JellyfinIP}");
AppSettings.Default.Save();
UpdateServerIpStatus();
CheckForMdnsHostname(JellyfinServerIp);
}
}
/// <summary>
/// Checks if the given hostname is an mDNS (.local) address and shows a warning.
/// Samsung TVs (Tizen) cannot reliably resolve mDNS hostnames, which causes
/// the server to appear as "undefined" on the TV after network disruptions.
/// </summary>
private void CheckForMdnsHostname(string? hostname)
{
ShowMdnsWarning = !string.IsNullOrEmpty(hostname) &&
hostname.EndsWith(".local", StringComparison.OrdinalIgnoreCase);
}
private void UpdateServerIpStatus()
{
ServerIpSet = !string.IsNullOrEmpty(AppSettings.Default.JellyfinIP) ||
@@ -1336,27 +1354,43 @@ namespace Jellyfin2Samsung.ViewModels
OpenAfterInstall = AppSettings.Default.OpenAfterInstall;
KeepWGTFile = AppSettings.Default.KeepWGTFile;
DarkMode = AppSettings.Default.DarkMode;
GitHubToken = AppSettings.Default.GitHubToken ?? string.Empty;
}
private async Task LoadLocalIpAsync()
private async Task LoadNetworkInterfacesAsync()
{
try
{
var ip = await _networkService.GetPrimaryOutboundIPAddressAsync();
var interfaces = await _networkService.GetNetworkInterfaceOptionsAsync();
await Dispatcher.UIThread.InvokeAsync(() =>
{
LocalIP = ip ?? string.Empty;
AppSettings.Default.LocalIp = ip;
AppSettings.Default.Save();
NetworkInterfaces.Clear();
foreach (var ni in interfaces)
NetworkInterfaces.Add(ni);
// Restore previous selection: match by name first (stable across DHCP changes),
// fall back to IP match, then default to first interface
var savedName = AppSettings.Default.SavedNetworkInterfaceName;
var savedIp = AppSettings.Default.LocalIp;
SelectedNetworkInterface =
(!string.IsNullOrEmpty(savedName)
? NetworkInterfaces.FirstOrDefault(i => i.Name == savedName)
: null)
?? (!string.IsNullOrEmpty(savedIp)
? NetworkInterfaces.FirstOrDefault(i => i.IpAddress == savedIp)
: null)
?? NetworkInterfaces.FirstOrDefault();
});
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to get local IP: {ex}");
Trace.WriteLine($"Failed to load network interfaces: {ex}");
}
}
private async Task InitializeCertificatesAsync()
{
var certificates = _certificateHelper.GetAvailableCertificates(AppSettings.CertificatePath);
@@ -1403,6 +1437,18 @@ namespace Jellyfin2Samsung.ViewModels
}
}
partial void OnSelectedNetworkInterfaceChanged(NetworkInterfaceOption? value)
{
if (value == null)
return;
LocalIP = value.IpAddress;
AppSettings.Default.LocalIp = value.IpAddress;
AppSettings.Default.SavedNetworkInterfaceName = value.Name;
AppSettings.Default.Save();
}
// Property changed handlers for Main Settings
partial void OnSelectedLanguageChanged(LanguageOption? value)
{
@@ -1453,6 +1499,12 @@ namespace Jellyfin2Samsung.ViewModels
AppSettings.Default.Save();
}
partial void OnDeletePreviousInstallChanged(bool value)
{
AppSettings.Default.DeletePreviousInstall = value;
AppSettings.Default.Save();
}
partial void OnRtlReadingChanged(bool value)
{
AppSettings.Default.RTLReading = value;
@@ -1476,6 +1528,17 @@ namespace Jellyfin2Samsung.ViewModels
_themeService.SetTheme(value);
}
partial void OnGitHubTokenChanged(string value)
{
AppSettings.Default.GitHubToken = value;
AppSettings.Default.Save();
}
partial void OnShowGitHubTokenChanged(bool value)
{
OnPropertyChanged(nameof(GitHubTokenPasswordChar));
}
// ========== End Main Settings Methods ==========
public void Dispose()

View File

@@ -28,6 +28,8 @@ namespace Jellyfin2Samsung.ViewModels
private readonly INetworkService _networkService;
private readonly ILocalizationService _localizationService;
private readonly IThemeService _themeService;
private readonly IUpdaterService _updaterService;
private readonly IUpdateDialogService _updateDialogService;
private readonly FileHelper _fileHelper;
private readonly DeviceHelper _deviceHelper;
private readonly TizenApiClient _tizenApiClient;
@@ -35,6 +37,7 @@ namespace Jellyfin2Samsung.ViewModels
private readonly JellyfinConfigViewModel _settingsViewModel;
private readonly AddLatestRelease _addLatestRelease;
private CancellationTokenSource? _samsungLoginCts;
private CancellationTokenSource? _initializationCts;
[ObservableProperty]
private ObservableCollection<GitHubRelease> releases = new ObservableCollection<GitHubRelease>();
@@ -94,6 +97,8 @@ namespace Jellyfin2Samsung.ViewModels
INetworkService networkService,
ILocalizationService localizationService,
IThemeService themeService,
IUpdaterService updaterService,
IUpdateDialogService updateDialogService,
HttpClient httpClient,
DeviceHelper deviceHelper,
TizenApiClient tizenApiClient,
@@ -110,6 +115,8 @@ namespace Jellyfin2Samsung.ViewModels
_packageHelper = packageHelper;
_localizationService = localizationService;
_themeService = themeService;
_updaterService = updaterService;
_updateDialogService = updateDialogService;
_fileHelper = fileHelper;
_settingsViewModel = settingsViewModel;
@@ -163,10 +170,14 @@ namespace Jellyfin2Samsung.ViewModels
AvailableAssets = value != null
? new ObservableCollection<Asset>(value.Assets)
: new ObservableCollection<Asset>();
SelectedAsset = AvailableAssets.FirstOrDefault();
SelectedAsset =
AvailableAssets.FirstOrDefault(a => a.IsDefault)
?? AvailableAssets.FirstOrDefault();
RefreshCanExecuteChanged();
}
partial void OnSelectedAssetChanged(Asset? value)
{
RefreshCanExecuteChanged();
@@ -214,8 +225,17 @@ namespace Jellyfin2Samsung.ViewModels
public async Task InitializeAsync()
{
// Create a new CTS for initialization that can be cancelled when update dialog shows
_initializationCts?.Cancel();
_initializationCts?.Dispose();
_initializationCts = new CancellationTokenSource();
var token = _initializationCts.Token;
try
{
// Check for updates first (non-blocking, runs in background)
_ = CheckForUpdatesAsync();
SetStatus("CheckingTizenSdb");
string tizenSdb = await _tizenInstaller.EnsureTizenSdbAvailable();
@@ -228,11 +248,18 @@ namespace Jellyfin2Samsung.ViewModels
ProcessHelper.KillSdbServers();
await LoadReleasesAsync();
await LoadReleasesAsync(token);
token.ThrowIfCancellationRequested();
SetStatus("ScanningNetwork");
await LoadDevicesAsync();
await LoadDevicesAsync(token);
CustomWgtPath = AppSettings.Default.CustomWgtPath ?? "";
}
catch (OperationCanceledException)
{
// Initialization was cancelled (likely due to update dialog)
Trace.WriteLine("Initialization cancelled");
}
catch (Exception ex)
{
SetStatus("InitializationFailed");
@@ -240,6 +267,153 @@ namespace Jellyfin2Samsung.ViewModels
}
}
private async Task CheckForUpdatesAsync()
{
// Skip if update check is disabled
if (!AppSettings.Default.CheckForUpdatesOnStartup)
return;
try
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(Constants.Updater.UpdateCheckTimeoutSeconds));
var updateResult = await _updaterService.CheckForUpdateAsync(cts.Token);
if (!updateResult.IsSuccess || !updateResult.IsUpdateAvailable)
return;
// Skip if user previously skipped this version
if (updateResult.LatestVersion == AppSettings.Default.SkippedUpdateVersion)
return;
// Cancel initialization tasks (network scan, release loading) before showing dialog
// This prevents the UI from freezing while background tasks continue
_initializationCts?.Cancel();
// Show update dialog on UI thread
await Dispatcher.UIThread.InvokeAsync(async () =>
{
var choice = await _updateDialogService.ShowUpdateAvailableDialogAsync(updateResult);
switch (choice)
{
case UpdateDialogChoice.Manual:
_updaterService.OpenReleasesPage();
// Resume initialization after user sees the releases page
_ = ResumeInitializationAsync();
break;
case UpdateDialogChoice.Automatic:
await PerformAutomaticUpdateAsync(updateResult);
// No resume needed - app will restart
break;
case UpdateDialogChoice.Skip:
AppSettings.Default.SkippedUpdateVersion = updateResult.LatestVersion;
AppSettings.Default.Save();
// Resume initialization after user skips
_ = ResumeInitializationAsync();
break;
case UpdateDialogChoice.Cancel:
default:
// Resume initialization after user cancels
_ = ResumeInitializationAsync();
break;
}
});
// Update last check time
AppSettings.Default.LastUpdateCheck = DateTime.UtcNow;
AppSettings.Default.Save();
}
catch (OperationCanceledException)
{
// Timeout - silently ignore
Trace.WriteLine("Update check timed out");
}
catch (Exception ex)
{
// Don't show errors for update check failures - it's not critical
Trace.WriteLine($"Update check failed: {ex}");
}
}
private async Task PerformAutomaticUpdateAsync(Models.UpdateCheckResult updateResult)
{
if (string.IsNullOrEmpty(updateResult.DownloadUrl))
{
await _updateDialogService.ShowUpdateErrorAsync(L("UpdateError"));
return;
}
try
{
// Download update
var progress = new Progress<int>(p =>
{
Dispatcher.UIThread.Post(() =>
{
StatusBar = $"{L("UpdateDownloading")} {p}%";
});
});
StatusBar = L("UpdateDownloading");
var downloadedPath = await _updaterService.DownloadUpdateAsync(updateResult.DownloadUrl, progress);
// Apply update
await _updateDialogService.ShowApplyingUpdateMessageAsync();
var success = await _updaterService.ApplyUpdateAsync(downloadedPath);
if (success)
{
// Exit application - update script will restart it
if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.Shutdown();
}
}
}
catch (Exception ex)
{
Trace.WriteLine($"Automatic update failed: {ex}");
await _updateDialogService.ShowUpdateErrorAsync(ex.Message);
}
}
private async Task ResumeInitializationAsync()
{
// Create a new CTS for the resumed initialization
_initializationCts?.Dispose();
_initializationCts = new CancellationTokenSource();
var token = _initializationCts.Token;
try
{
// Only load releases and devices if they haven't been loaded yet
if (!Releases.Any())
{
await LoadReleasesAsync(token);
token.ThrowIfCancellationRequested();
}
if (!AvailableDevices.Any(d => d.IpAddress != L("lblOther")))
{
SetStatus("ScanningNetwork");
await LoadDevicesAsync(token);
}
CustomWgtPath = AppSettings.Default.CustomWgtPath ?? "";
}
catch (OperationCanceledException)
{
Trace.WriteLine("Resumed initialization cancelled");
}
catch (Exception ex)
{
Trace.WriteLine($"Resumed initialization failed: {ex}");
}
}
[RelayCommand(CanExecute = nameof(CanRefresh))]
private async Task RefreshAsync()
{
@@ -287,24 +461,34 @@ namespace Jellyfin2Samsung.ViewModels
if (customPaths?.Length > 0)
{
await _packageHelper.InstallCustomPackagesAsync(
customPaths,
SelectedDevice,
_samsungLoginCts.Token,
progress => Dispatcher.UIThread.Post(() => StatusBar = progress),
onSamsungLoginStarted: OnSamsungLoginStarted);
try
{
await _packageHelper.InstallCustomPackagesAsync(
customPaths,
SelectedDevice,
_samsungLoginCts.Token,
progress => Dispatcher.UIThread.Post(() => StatusBar = progress),
onSamsungLoginStarted: OnSamsungLoginStarted);
}
finally
{
IsSamsungLoginActive = false;
_samsungLoginCts.Dispose();
_samsungLoginCts = null;
}
foreach (var customPath in customPaths)
if (!AppSettings.Default.KeepWGTFile)
_packageHelper.CleanupDownloadedPackage(customPath);
AppSettings.Default.CustomWgtPath = null;
AppSettings.Default.Save();
CustomWgtPath = string.Empty;
}
else
{
if(SelectedRelease == null || SelectedDevice == null)
return;
string? downloadPath = await _packageHelper.DownloadReleaseAsync(
SelectedRelease,
SelectedAsset,
@@ -426,40 +610,34 @@ namespace Jellyfin2Samsung.ViewModels
}
private async Task LoadReleasesAsync()
private async Task LoadReleasesAsync(CancellationToken cancellationToken = default)
{
IsLoading = true;
try
{
var list = new List<GitHubRelease>();
async Task fetch(string url, string name)
async Task fetch(string url, string prefix, string name, int take = 1)
{
var release = await _addLatestRelease.GetLatestReleaseAsync(url, name);
if (release != null)
list.Add(release);
cancellationToken.ThrowIfCancellationRequested();
var release = await _addLatestRelease.GetReleasesAsync(url, prefix, name, take);
if (release.Count > 0)
list.AddRange(release);
}
await fetch(AppSettings.Default.ReleasesUrl, Constants.AppIdentifiers.JellyfinAppName);
await fetch(AppSettings.Default.MoonfinRelease, "Moonfin");
await fetch(AppSettings.Default.JellyfinAvRelease, "Jellyfin - AVPlay");
await fetch(AppSettings.Default.JellyfinAvRelease, "Jellyfin - AVPlay - 10.10z SmartHub");
await fetch(AppSettings.Default.JellyfinLegacy, "Jellyfin - Legacy");
await fetch(AppSettings.Default.CommunityRelease, "Tizen Community");
await fetch(AppSettings.Default.ReleasesUrl, "Jellyfin - ", string.Empty, 5);
await fetch(AppSettings.Default.MoonfinRelease, string.Empty, "Moonfin", 1);
await fetch(AppSettings.Default.LiteFinRelease, string.Empty, "Litefin", 1);
await fetch(AppSettings.Default.JellyfinAvRelease, string.Empty, "Jellyfin - AVPlay", 1);
await fetch(AppSettings.Default.JellyfinAvRelease, string.Empty, "Jellyfin - AVPlay - 10.10z SmartHub", 1);
await fetch(AppSettings.Default.JellyfinLegacy, string.Empty, "Jellyfin - Legacy", 1);
await fetch(AppSettings.Default.CommunityRelease, string.Empty, "Tizen Community", 1);
cancellationToken.ThrowIfCancellationRequested();
Releases.Clear();
foreach (var r in list)
Releases.Add(r);
Releases.Add(new GitHubRelease
{
Name = Constants.AppIdentifiers.CustomWgtFile,
TagName = string.Empty,
PublishedAt = string.Empty,
Url = string.Empty,
Assets = new List<Asset>()
});
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
@@ -468,11 +646,22 @@ namespace Jellyfin2Samsung.ViewModels
}
finally
{
// Always add the custom WGT option, regardless of GitHub failures
if (!Releases.Any(r => r.Name == Constants.AppIdentifiers.CustomWgtFile))
{
Releases.Add(new GitHubRelease
{
Name = Constants.AppIdentifiers.CustomWgtFile,
TagName = string.Empty,
PublishedAt = string.Empty,
Url = string.Empty,
Assets = new List<Asset>()
});
}
IsLoading = false;
}
}
private async Task LoadDevicesAsync(CancellationToken cancellationToken = default, bool virtualScan = false)
{
IsLoadingDevices = true;
@@ -527,6 +716,10 @@ namespace Jellyfin2Samsung.ViewModels
}
}
}
catch (OperationCanceledException)
{
throw; // Re-throw to be handled by caller
}
catch (Exception ex)
{
await _dialogService.ShowErrorAsync($"Failed to load devices: {ex}");
@@ -608,9 +801,17 @@ namespace Jellyfin2Samsung.ViewModels
public void Dispose()
{
DisposeSamsungCts();
DisposeInitializationCts();
_localizationService.LanguageChanged -= OnLanguageChanged;
_themeService.ThemeChanged -= OnThemeChanged;
}
private void DisposeInitializationCts()
{
_initializationCts?.Cancel();
_initializationCts?.Dispose();
_initializationCts = null;
}
}
}
}

View File

@@ -0,0 +1,112 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using System;
namespace Jellyfin2Samsung.ViewModels
{
public partial class UpdateDialogViewModel : ViewModelBase
{
private readonly ILocalizationService _localizationService;
[ObservableProperty]
private string currentVersion = string.Empty;
[ObservableProperty]
private string latestVersion = string.Empty;
[ObservableProperty]
private string releaseTitle = string.Empty;
[ObservableProperty]
private string releaseNotes = string.Empty;
[ObservableProperty]
private DateTime? publishedAt;
[ObservableProperty]
private bool hasDownloadUrl;
[ObservableProperty]
private bool isDownloading;
[ObservableProperty]
private int downloadProgress;
[ObservableProperty]
private string downloadStatus = string.Empty;
/// <summary>
/// The user's choice after interacting with the dialog.
/// </summary>
public UpdateDialogChoice? DialogResult { get; private set; }
/// <summary>
/// Event raised when the dialog should be closed.
/// </summary>
public event EventHandler? RequestClose;
// Localized strings
public string L(string key) => _localizationService.GetString(key);
public string DialogTitle => L("UpdateAvailable");
public string CurrentVersionLabel => L("UpdateCurrentVersion");
public string LatestVersionLabel => L("UpdateLatestVersion");
public string ReleaseNotesLabel => L("UpdateReleaseNotes");
public string ManualButtonText => L("UpdateManual");
public string AutomaticButtonText => L("UpdateAutomatic");
public string SkipButtonText => L("UpdateSkip");
public string DownloadingText => L("UpdateDownloading");
public UpdateDialogViewModel(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
public void Initialize(UpdateCheckResult updateInfo)
{
CurrentVersion = updateInfo.CurrentVersion;
LatestVersion = updateInfo.LatestVersion;
ReleaseTitle = updateInfo.ReleaseTitle;
ReleaseNotes = updateInfo.ReleaseNotes;
PublishedAt = updateInfo.PublishedAt;
HasDownloadUrl = !string.IsNullOrEmpty(updateInfo.DownloadUrl);
}
[RelayCommand]
private void SelectManual()
{
DialogResult = UpdateDialogChoice.Manual;
RequestClose?.Invoke(this, EventArgs.Empty);
}
[RelayCommand(CanExecute = nameof(CanSelectAutomatic))]
private void SelectAutomatic()
{
DialogResult = UpdateDialogChoice.Automatic;
RequestClose?.Invoke(this, EventArgs.Empty);
}
[RelayCommand]
private void SelectSkip()
{
DialogResult = UpdateDialogChoice.Skip;
RequestClose?.Invoke(this, EventArgs.Empty);
}
[RelayCommand]
private void Cancel()
{
DialogResult = UpdateDialogChoice.Cancel;
RequestClose?.Invoke(this, EventArgs.Empty);
}
private bool CanSelectAutomatic() => HasDownloadUrl && !IsDownloading;
public void UpdateDownloadProgress(int progress, string status)
{
DownloadProgress = progress;
DownloadStatus = status;
}
}
}

View File

@@ -4,9 +4,9 @@
xmlns:viewModels="clr-namespace:Jellyfin2Samsung.ViewModels"
x:DataType="viewModels:BuildInfoViewModel"
SizeToContent="WidthAndHeight"
MinWidth="650"
MaxWidth="900"
MinHeight="500"
MinWidth="900"
MaxWidth="1200"
MinHeight="520"
WindowStartupLocation="CenterOwner"
Title="Jellyfin Tizen Versions">
@@ -15,112 +15,175 @@
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Window.Styles>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
Padding="16"
CornerRadius="8"
Margin="12"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="1">
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
Padding="16"
CornerRadius="8"
Margin="12"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect BlurRadius="15"
OffsetX="2"
OffsetY="2"
Color="#00000020"
Opacity="0.15"/>
OffsetX="2"
OffsetY="2"
Color="#00000020"
Opacity="0.15"/>
</Border.Effect>
<StackPanel Spacing="20">
<StackPanel Spacing="16">
<!-- Header -->
<Border Background="#2C3E50"
Padding="14,10"
CornerRadius="6">
Padding="14,10"
CornerRadius="6">
<TextBlock Text="Jellyfin Tizen Build Versions"
Foreground="White"
FontWeight="SemiBold"
FontSize="18"
HorizontalAlignment="Center"/>
Foreground="White"
FontWeight="SemiBold"
FontSize="18"
HorizontalAlignment="Center"/>
</Border>
<!-- Two-column layout -->
<Grid ColumnDefinitions="*,340"
ColumnSpacing="18">
<Border Background="#34495E"
Padding="10"
CornerRadius="6">
<TextBlock Text="Official Jellyfin Builds"
Foreground="White"
FontWeight="Medium"
FontSize="15"/>
</Border>
<!-- ================= LEFT COLUMN ================= -->
<StackPanel Grid.Column="0" Spacing="16">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="4"
Height="200">
<DataGrid ItemsSource="{Binding JellyfinVersions}"
AutoGenerateColumns="False"
HeadersVisibility="Column"
GridLinesVisibility="All"
IsReadOnly="True"
ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn Header="File name" Width="180"
Binding="{Binding FileName}" />
<DataGridTemplateColumn Header="Description" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"
TextWrapping="Wrap"
Padding="4"
VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Border>
<Border Background="#34495E"
Padding="10"
CornerRadius="6">
<TextBlock Text="Jellyfin Builds"
Foreground="White"
FontWeight="Medium"
FontSize="15"/>
</Border>
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="4"
Height="220">
<DataGrid ItemsSource="{Binding JellyfinVersions}"
AutoGenerateColumns="False"
HeadersVisibility="Column"
GridLinesVisibility="All"
IsReadOnly="True"
ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn Header="File name" Width="180"
Binding="{Binding FileName}" />
<DataGridTemplateColumn Header="Description" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"
TextWrapping="Wrap"
Padding="4"
VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Border>
<Border Background="#34495E"
Padding="10"
CornerRadius="6"
Margin="0,6,0,0">
<TextBlock Text="Community Applications"
Foreground="White"
FontWeight="Medium"
FontSize="15"/>
</Border>
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="4"
Height="280">
<DataGrid ItemsSource="{Binding CommunityApps}"
AutoGenerateColumns="False"
HeadersVisibility="Column"
GridLinesVisibility="All"
RowHeight="32"
IsReadOnly="True"
ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn Header="Application" Width="180"
Binding="{Binding FileName}" />
<DataGridTextColumn Header="Description" Width="*"
Binding="{Binding Description}" />
</DataGrid.Columns>
</DataGrid>
</Border>
</StackPanel>
<!-- ================= RIGHT COLUMN ================= -->
<!-- CompileBindings off so you can iterate on option types freely -->
<StackPanel Grid.Column="1"
Spacing="10"
x:CompileBindings="False">
<Border Background="#34495E"
Padding="10"
CornerRadius="6">
<TextBlock Text="App Preview"
Foreground="White"
FontWeight="Medium"
FontSize="15"/>
</Border>
<!-- Dropdown: TEXT ONLY (no preview image here) -->
<ComboBox ItemsSource="{Binding ProviderOptions}"
SelectedItem="{Binding SelectedProviderOption, Mode=TwoWay}"
MinWidth="320"
HorizontalAlignment="Stretch"
DisplayMemberBinding="{Binding DisplayName}"/>
<!-- Preview card: image shows ONLY here -->
<Border Height="320"
CornerRadius="12"
ClipToBounds="True"
Background="#22000000">
<Grid>
<!-- fallback text behind -->
<StackPanel VerticalAlignment="Center"
HorizontalAlignment="Center"
Spacing="6"
Margin="12">
<TextBlock Text="No preview available"
FontWeight="SemiBold"
FontSize="14"
HorizontalAlignment="Center"/>
<TextBlock Text="This app has no thumbnail."
Opacity="0.7"
FontSize="12"
HorizontalAlignment="Center"/>
</StackPanel>
<!-- image on top (if null, it simply won't render) -->
<Image Source="{Binding SelectedProviderOption.PreviewImage}"
Stretch="Uniform"
StretchDirection="DownOnly"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Border>
<Border Background="#34495E"
Padding="10"
CornerRadius="6"
Margin="0,10,0,0">
<TextBlock Text="Community Applications"
Foreground="White"
FontWeight="Medium"
FontSize="15"/>
</Border>
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="1"
CornerRadius="4"
Height="260">
<DataGrid ItemsSource="{Binding CommunityApps}"
AutoGenerateColumns="False"
HeadersVisibility="Column"
GridLinesVisibility="All"
RowHeight="32"
IsReadOnly="True"
ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn Header="Application" Width="180"
Binding="{Binding FileName}" />
<DataGridTextColumn Header="Description" Width="*"
Binding="{Binding Description}" />
</DataGrid.Columns>
</DataGrid>
</Border>
</StackPanel>
</Grid>
<!-- Footer -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0,10,0,0">
HorizontalAlignment="Right"
Margin="0,6,0,0">
<Button Content="Close"
Width="100"
Command="{Binding CloseCommand}"/>
Width="100"
Command="{Binding CloseCommand}"/>
</StackPanel>
</StackPanel>
</Border>
</Window>
</Window>

View File

@@ -133,11 +133,50 @@
Text="{Binding LblLocalIP}"
Classes="label"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding LocalIP, Mode=TwoWay}"
Classes="subtitle"
VerticalAlignment="Center"/>
<ComboBox Grid.Column="1"
Classes="clean"
ItemsSource="{Binding NetworkInterfaces}"
SelectedItem="{Binding SelectedNetworkInterface, Mode=TwoWay}"
DisplayMemberBinding="{Binding DisplayText}"
HorizontalAlignment="Stretch"/>
</Grid>
<!-- GitHub Token (PAT) -->
<Grid ColumnDefinitions="180,*" ColumnSpacing="12">
<TextBlock Grid.Column="0"
Text="{Binding LblGitHubToken}"
Classes="label"
VerticalAlignment="Center"/>
<Grid Grid.Column="1" ColumnDefinitions="*,Auto" ColumnSpacing="8">
<TextBox Grid.Column="0"
Text="{Binding GitHubToken}"
PasswordChar="{Binding GitHubTokenPasswordChar}"
Watermark="ghp_xxxxxxxxxxxxxxxxxxxx"
Classes="clean"
HorizontalAlignment="Stretch"/>
<ToggleButton Grid.Column="1"
IsChecked="{Binding ShowGitHubToken}"
VerticalAlignment="Center"
Width="36"
Height="36"
CornerRadius="4"
Padding="0"
ToolTip.Tip="Show/Hide token">
<Panel>
<fa:SymbolIcon Symbol="View" Width="16" Height="16" IsVisible="{Binding !ShowGitHubToken}"/>
<fa:SymbolIcon Symbol="Cancel" Width="16" Height="16" IsVisible="{Binding ShowGitHubToken}"/>
</Panel>
</ToggleButton>
</Grid>
</Grid>
<TextBlock Text="{Binding LblGitHubTokenHint}"
FontSize="11"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"
FontStyle="Italic"
TextWrapping="Wrap"
Margin="180,0,0,0"/>
</StackPanel>
<Border Height="1" Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" Margin="0,4"/>
@@ -369,6 +408,18 @@
</StackPanel>
</Grid>
<!-- mDNS (.local) Warning -->
<Border IsVisible="{Binding ShowMdnsWarning}"
Background="#FFF3CD"
CornerRadius="4"
Padding="10,8"
Margin="0,4,0,0">
<TextBlock Text="{Binding LblMdnsWarning}"
Foreground="#856404"
FontSize="12"
TextWrapping="Wrap"/>
</Border>
</StackPanel>
<Border Height="1" Background="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" Margin="0,4"/>
@@ -503,7 +554,7 @@
</Button>
<ToggleSwitch Grid.Column="1"
IsChecked="{Binding EnableDevLogs, Mode=TwoWay}"
IsEnabled="{Binding IsAuthenticated}"/>
IsEnabled="{Binding ServerIpSet}"/>
</Grid>
</Grid>
@@ -515,7 +566,7 @@
VerticalAlignment="Center"/>
<ToggleSwitch Grid.Column="1"
IsChecked="{Binding UseServerScripts}"
IsEnabled="{Binding IsAuthenticated}"/>
IsEnabled="{Binding ServerIpSet}"/>
</Grid>
<!-- Fix YouTube 153 -->
@@ -525,41 +576,12 @@
Classes="label"
VerticalAlignment="Center"/>
<ToggleSwitch Grid.Column="1"
IsEnabled="{Binding IsAuthenticated}"
IsEnabled="{Binding ServerIpSet}"
IsChecked="{Binding PatchYoutubePlugin}"/>
</Grid>
<!-- TextBox shown only when PatchYoutubePlugin == true -->
<Grid ColumnDefinitions="180,*" ColumnSpacing="12">
<TextBlock Grid.Column="0"
Text="{Binding LblYtDlpServer}"
Classes="label"
VerticalAlignment="Center"
IsVisible="{Binding PatchYoutubePlugin}"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
<TextBox
Text="{Binding LocalYoutubeServer}"
Watermark="http://192.168.1.123:8123"
IsVisible="{Binding PatchYoutubePlugin}" />
<Button
Classes="secondary"
Content="{Binding LblValidateStream}"
Command="{Binding TestConnectionCommand}"
IsVisible="{Binding PatchYoutubePlugin}"
Padding="8,4"
FontSize="12"/>
<TextBlock
Text="{Binding StreamValidationStatus}"
VerticalAlignment="Center"
IsVisible="{Binding PatchYoutubePlugin}"
Margin="6,0,0,0"/>
</StackPanel>
</Grid>
<!-- Spacer -->
<Border Height="24"/>
</StackPanel>
</StackPanel>
@@ -779,7 +801,7 @@
<StackPanel Spacing="8" HorizontalAlignment="Center">
<TextBlock
Text="{Binding SelectedJellyTheme.Name}"
Text="{Binding SelectedJellyTheme.Name, FallbackValue=''}"
FontSize="14"
FontWeight="SemiBold"
HorizontalAlignment="Center"

View File

@@ -0,0 +1,211 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Jellyfin2Samsung.ViewModels"
mc:Ignorable="d"
d:DesignWidth="480"
d:DesignHeight="420"
x:Class="Jellyfin2Samsung.Views.UpdateDialog"
x:DataType="viewModels:UpdateDialogViewModel"
Width="480"
MinHeight="360"
SizeToContent="Height"
Title="{Binding DialogTitle}"
WindowStartupLocation="CenterOwner"
CanResize="False"
CornerRadius="12">
<Window.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentTheme.xaml"/>
</Window.Styles>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
Padding="24"
CornerRadius="12"
Margin="10"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect BlurRadius="20"
OffsetX="2"
OffsetY="2"
Color="#00000020"
Opacity="0.2"/>
</Border.Effect>
<StackPanel Spacing="16">
<!-- Header with Icon -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="12">
<PathIcon Data="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
Width="28"
Height="28"
Foreground="#22c55e"/>
<TextBlock Text="{Binding DialogTitle}"
FontSize="20"
FontWeight="Bold"
Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}"
VerticalAlignment="Center"/>
</StackPanel>
<!-- Version Info -->
<Border Background="{DynamicResource CardBackgroundFillColorSecondaryBrush}"
CornerRadius="8"
Padding="16">
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" RowSpacing="8" ColumnSpacing="12">
<!-- Current Version -->
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{Binding CurrentVersionLabel}"
FontWeight="SemiBold"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"
VerticalAlignment="Center"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding CurrentVersion}"
Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}"
VerticalAlignment="Center"/>
<!-- Latest Version -->
<TextBlock Grid.Row="1" Grid.Column="0"
Text="{Binding LatestVersionLabel}"
FontWeight="SemiBold"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"
VerticalAlignment="Center"/>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Spacing="8">
<TextBlock Text="{Binding LatestVersion}"
Foreground="#22c55e"
FontWeight="Bold"
VerticalAlignment="Center"/>
<Border Background="#22c55e"
CornerRadius="4"
Padding="6,2">
<TextBlock Text="NEW"
FontSize="10"
FontWeight="Bold"
Foreground="White"/>
</Border>
</StackPanel>
<!-- Release Title -->
<TextBlock Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Text="{Binding ReleaseTitle}"
FontSize="13"
FontStyle="Italic"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"
TextTrimming="CharacterEllipsis"
IsVisible="{Binding ReleaseTitle, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</Grid>
</Border>
<!-- Release Notes -->
<StackPanel Spacing="8"
IsVisible="{Binding ReleaseNotes, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Text="{Binding ReleaseNotesLabel}"
FontWeight="SemiBold"
FontSize="13"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"/>
<ScrollViewer MaxHeight="120"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<TextBlock Text="{Binding ReleaseNotes}"
TextWrapping="Wrap"
FontSize="12"
Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}"
LineHeight="18"/>
</ScrollViewer>
</StackPanel>
<!-- Download Progress (shown during download) -->
<StackPanel Spacing="8" IsVisible="{Binding IsDownloading}">
<TextBlock Text="{Binding DownloadStatus}"
FontSize="13"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"
HorizontalAlignment="Center"/>
<ProgressBar Value="{Binding DownloadProgress}"
Minimum="0"
Maximum="100"
Height="8"
CornerRadius="4"
Foreground="#2563eb"/>
<TextBlock Text="{Binding DownloadProgress, StringFormat={}{0}%}"
FontSize="12"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"
HorizontalAlignment="Center"/>
</StackPanel>
<!-- Buttons -->
<StackPanel Spacing="12" IsVisible="{Binding !IsDownloading}">
<!-- Primary Actions -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="12">
<!-- Manual Button -->
<Button MinWidth="140"
Height="40"
Background="#6366f1"
Foreground="White"
CornerRadius="8"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding SelectManualCommand}">
<StackPanel Orientation="Horizontal" Spacing="8">
<PathIcon Data="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"
Width="16"
Height="16"
Foreground="White"/>
<TextBlock Text="{Binding ManualButtonText}"
VerticalAlignment="Center"/>
</StackPanel>
</Button>
<!-- Automatic Button -->
<Button MinWidth="140"
Height="40"
Background="#22c55e"
Foreground="White"
CornerRadius="8"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding SelectAutomaticCommand}"
IsEnabled="{Binding HasDownloadUrl}">
<StackPanel Orientation="Horizontal" Spacing="8">
<PathIcon Data="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"
Width="16"
Height="16"
Foreground="White"/>
<TextBlock Text="{Binding AutomaticButtonText}"
VerticalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<!-- Skip Button -->
<Button HorizontalAlignment="Center"
MinWidth="100"
Height="32"
Background="Transparent"
Foreground="{DynamicResource SystemControlForegroundBaseMediumBrush}"
BorderThickness="0"
Command="{Binding SelectSkipCommand}">
<TextBlock Text="{Binding SkipButtonText}"
FontSize="12"
TextDecorations="Underline"/>
</Button>
</StackPanel>
<!-- Tooltip for disabled automatic button -->
<TextBlock Text="(Automatic download unavailable - GitHub API rate limited)"
FontSize="11"
FontStyle="Italic"
Foreground="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
HorizontalAlignment="Center"
IsVisible="{Binding !HasDownloadUrl}"/>
</StackPanel>
</Border>
</Window>

View File

@@ -0,0 +1,51 @@
using Avalonia.Controls;
using Avalonia.Styling;
using Jellyfin2Samsung.Helpers;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using Jellyfin2Samsung.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Views;
public partial class UpdateDialog : Window
{
public UpdateDialogViewModel ViewModel { get; }
public UpdateDialog()
{
InitializeComponent();
var localizationService = App.Services.GetRequiredService<ILocalizationService>();
ViewModel = new UpdateDialogViewModel(localizationService);
DataContext = ViewModel;
// Apply theme
RequestedThemeVariant = AppSettings.Default.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light;
// Handle close request from ViewModel
ViewModel.RequestClose += (_, _) => Close();
}
public void Initialize(UpdateCheckResult updateInfo)
{
ViewModel.Initialize(updateInfo);
}
public async Task<UpdateDialogChoice> ShowDialogAsync(Window parent)
{
await ShowDialog(parent);
return ViewModel.DialogResult ?? UpdateDialogChoice.Cancel;
}
public void UpdateProgress(int progress, string status)
{
ViewModel.UpdateDownloadProgress(progress, status);
}
public void SetDownloading(bool isDownloading)
{
ViewModel.IsDownloading = isDownloading;
}
}

View File

@@ -18,9 +18,9 @@
<br/>
It handles device detection, certificates, and installation so you dont have to fight with Tizen Studio or manual sideloading.
<br/><br/>
🌐 Available in: Danish, Dutch, English, French, German, Turkish
🌐 Available in: Danish, Dutch, English, French, German, Portuguese, Turkish
<br/>
🇩🇰 🇳🇱 🇬🇧 🇫🇷 🇩🇪 🇹🇷
🇩🇰 🇳🇱 🇬🇧 🇫🇷 🇩🇪 🇵🇹 🇹🇷
</p>
---
@@ -31,8 +31,8 @@
| Channel | Version | Notes |
|------------|---------------------------------------------------------------------|------------------------------|
| **Stable** | [v2.0.0.0](https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases/tag/v2.0.0.0) | Recommended for most users |
| **Beta** | [N/A](#) | Includes new features |
| **Stable** | [v2.2.0.8](https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases/tag/v2.2.0.8) | Recommended for most users |
| **Beta** | [v2.2.0.9-beta](https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases/tag/v2.2.0.9-beta) | Includes new features |
<!-- versions:end -->
@@ -56,6 +56,8 @@ Thats it. No manual certificate handling required in most cases.
🎥 Full walkthrough:
https://www.youtube.com/watch?v=_8mSV5pW-ic
**NixOS:** Clone the [`Samsung2Jellyfin` branch](https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer.git) and run `nix-shell` — the shell environment will automatically build and launch the tool.
---
## 📚 Documentation
@@ -88,11 +90,24 @@ https://github.com/PatrickSt1991/tizen-community-packages
## 🛠️ Support & Contributing
- Bug reports & feature requests: [Issues](../../issues)
- Ideas & questions: [Discussions](../../discussions)
- Chat: https://discord.gg/7mga3zh8Cv
Contributions of all kinds are welcome — whether its bug reports, feature requests, code, documentation, or translations.
Contributions are welcome — issues, PRs, translations, or documentation.
- Bug reports & feature requests: [Issues](../../issues)
- Ideas, feedback & questions: [Discussions](../../discussions)
- Community chat: [Discord](https://discord.gg/7mga3zh8Cv)
## 🌍 Translations
Want to help translate **Jellyfin2Samsung**? Community translations are always appreciated.
You can contribute here:
- [Transifex](https://app.transifex.com/madebypatrick/jellyfin2samsung)
- [Crowdin](https://crowdin.com/project/jellyfin2samsung)
You can help by translating missing strings, improving existing translations, or reviewing your language.
Translation updates are synced back into this repository automatically.
---
@@ -115,6 +130,8 @@ This project is made possible by the people who contribute their time, knowledge
Special thanks to:
- **jeppevinkel** — for providing the Jellyfin Tizen `.wgt` builds
https://github.com/jeppevinkel/jellyfin-tizen-builds
- **Hungry__Alpaca** — for the Moonfin client and related work
https://github.com/Moonfin-Client/Tizen
- **@RadicalMuffinMan** — for the Moonfin client and related work
https://github.com/Moonfin-Client/Smart-TV
- **@MoazSalem** — for the Litefin client and related work
https://github.com/MoazSalem/litefin/

3
crowdin.yml Normal file
View File

@@ -0,0 +1,3 @@
files:
- source: /Jellyfin2Samsung-CrossOS/Assets/Localization/en.json
translation: /Jellyfin2Samsung-CrossOS/Assets/Localization/%two_letters_code%.json

83
shell.nix Normal file
View File

@@ -0,0 +1,83 @@
{ pkgs ? import <nixpkgs> {} }:
let
krb5WithUnversionedLib = pkgs.runCommand "krb5-unversioned-so" {} ''
mkdir -p $out/lib
ln -s ${pkgs.krb5}/lib/libgssapi_krb5.so.2 $out/lib/libgssapi_krb5.so
'';
# FHS environment that provides /lib, /lib64, /usr/lib etc.
# so dynamically linked binaries (TizenSdb, .NET native bits) just work.
fhs = pkgs.buildFHSEnv {
name = "jellyfin2samsung-fhs";
targetPkgs = p: with p; [
# Build and Run
dotnet-sdk_8
patchelf
file
stdenv.cc.cc.lib
openssl
icu
zlib
libgcc.lib
krb5
krb5WithUnversionedLib
# Fonts and Rendering
fontconfig
freetype
libGL
dejavu_fonts
freefont_ttf
# X11 tools
xorg.libX11
xorg.libICE
xorg.libSM
xorg.libXext
xorg.libXcursor
xorg.libXi
xorg.libXrandr
xorg.libXrender
xorg.libXinerama
xorg.libXcomposite
xorg.libXdamage
xorg.libXfixes
xorg.libXtst
# Some other packages that may be needed if not installed system wide
nmap
iproute2
curl
wget
xdg-utils
];
runScript = pkgs.writeShellScript "jellyfin2samsung-entry" ''
export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_NOLOGO=1
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release \
-r linux-x64 \
--self-contained true \
-p:PublishSingleFile=false \
-p:PublishTrimmed=false
Jellyfin2Samsung-CrossOS/bin/Release/net8.0/linux-x64/publish/Jellyfin2Samsung
'';
};
in
pkgs.mkShell {
name = "jellyfin2samsung";
buildInputs = [
fhs
];
shellHook = ''
jellyfin2samsung-fhs
'';
}

6
transifex.yml Normal file
View File

@@ -0,0 +1,6 @@
filters:
- filter_type: file
file_format: KEYVALUEJSON
source_file: Assets/Localization/en.json
source_language: en
translation_files_expression: 'Assets/Localization/<lang>.json'