mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 06:06:18 -08:00
* feat(portals): find the same movie in your other playlists A movie that exists in several imported Xtream playlists now shows a "Sources N" chip on its detail page and in the player. Switching playlist mid-film keeps the timecode, a preferred source can be pinned per movie, and a failed stream offers the alternatives instead of a dead end. The governing rule is that a guess is never presented as a fact. Every metadata value carries where it came from — `api` (the provider said so), `parsed` (inferred from the title) or `probe` (we contacted the stream). Facts render as plain tags, guesses are prefixed `~` in a warning colour, and an unknown value renders no tag at all plus a "check" affordance. Ranking and failover read through `factualOnly()`, so a filename claiming 4K is structurally unable to outrank a source that was actually reached. A probe that could not complete reports "unknown", never "unavailable". Scope is deliberately narrow: Xtream to Xtream, movies only, Electron only. Stalker never reaches the `content` table and M3U is a JSON blob whose search forces live content; both are additive later, since the candidate type already carries all three portal kinds. In the PWA every entry point is gated off and the chip renders nothing. Auto-failover is opt-in and off by default. Each source is tried at most once per session, so it terminates structurally, and the switch is never silent — the toast names the new playlist, offers an undo, and warns that the dub may differ only when both sides state an audio track as fact. Notable details: - Playlist names are routinely the pasted URL, credentials included. They are never rendered raw; a short host-only label is derived instead. - Quality is derived from pixel width, not height: a 2.39:1 1080p master is 1920x800, and bucketing that by height would publish "720p" as a fact. - Switching is a single `inlinePlayback.set()` so the player and engine survive and re-seek; the carried position is read before the 15s persistence throttle so it does not rewind. - Sources from one playlist collapse into a group, since the same film often appears there several times under different stream ids. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): stop stale source resolutions from committing Addresses three defects Greptile found in the multi-source review. **Concurrent switches committed out of order.** Selecting a second source before the first resolution returned let the slower request overwrite the newer selection and repoint Undo at itself. `switchTo` now takes a sequence number and drops its result if a newer switch already committed. **Stale switches crossed movie sessions.** Navigating to another film while a resolution was in flight let the continuation activate the old film's source inside the new controller — and restart it from that session's zero resume position. The controller is now snapshotted per operation and the movie session is revalidated after every await. `check()` had the same hazard across its two awaits and is guarded the same way. **Short titles skipped discovery entirely.** The trigram tokenizer cannot index tokens under three characters, so "Up", "It" or "Us" produced an empty MATCH expression and the query was discarded before SQLite was consulted — the chip could never appear for those films. Discovery now falls back to a bounded scan when FTS structurally cannot serve the title; the existing two-tier normalized confirmation still rejects loose hits like "Upgrade". Each fix carries a regression test; all three were mutation-checked by removing the guard and confirming exactly those tests fail. The previous test asserting that short titles return nothing encoded the bug and has been replaced. The host spec passed 400 lines, so its fixtures moved to a shared module and the race suite into its own file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): make the pin decide playback and keep failover going Second round of Greptile review findings. **A pin had no behavioural effect.** Loading a stored pin only decorated the row: Play still started the route's playlist and failover ranking ignored `isPinned`, so "make this the main source" survived a restart as an icon and nothing else. The primary action now starts from the pinned source when one is set, and the pin outranks everything else in failover ranking. **Failover stopped at the first unresolvable candidate.** An expired account or a failing `get_vod_info` on the top-ranked source ended the attempt, and since production calls `failover()` only once — on the original playback failure — a healthy lower-ranked source was never reached. It now continues through untried candidates. `switchTo` reports why it stopped so the loop can tell "could not resolve, try the next one" from "something newer owns the screen"; without that distinction a superseded switch would have spun forever, because only the former marks the candidate tried. **Identity ignored enrichment.** The key was `playlistId:contentId:title`, so when `get_vod_info` added a TMDB id and release year to an unchanged title the host saw no change, never reloaded, and kept yearless discovery and title-only pin keys — a `tmdb:`-keyed pin could never be found. The key now covers every field that affects matching. **A server refusing HEAD read as unavailable.** Some stream hosts answer 405 or 501 to HEAD yet serve the media over GET. The probe now retries once with the ranged GET the main process already supported, instead of caching a working source as failed and penalising it during failover. Greptile also flagged a missing token check after the resolve await in `switchTo`; that guard landed in4db3a2fdand sits on the line directly below. Answered on the thread rather than changed. The host service passed 400 lines again, so the pin, probe, switch-notice and current-row concerns moved into focused modules beside it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(portals): record the behaviour the review rounds changed The architecture doc and CLAUDE.md described the feature as first written, not as it now behaves: pins were documented as a stored preference without saying they decide playback, failover was described as stopping at the first unresolvable candidate, the probe as HEAD-only, and discovery as pure FTS with no mention that short titles cannot be tokenized at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): invalidate the session while the movie identity is empty The staleness guard added in4db3a2fdbumped the session only inside `load()`, which leaves a window the guard does not cover: route navigation empties the movie identity first, and `load()` for the replacement runs only once a title is knowable again. A resolution completing in that interval still carried a session number that matched, so it passed the check and started the previous movie's source over the page the user was navigating to. The binding effect now bumps the session as soon as the identity goes null, so anything already in flight is invalidated at the moment the old movie stops being the one on screen rather than when the next one finishes loading. `lastMovieKey` is deliberately left alone: returning to the same movie should not re-run discovery, and the controller's state is still correct — only the in-flight operations needed invalidating. Regression test added and mutation-checked: removing the bump fails exactly that test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): stop the source list from losing the real alternatives Six review findings, all in how multi-source decides what to show and what it is playing. Discovery: the current playlist is now excluded in SQL rather than after the fact, so its own duplicate rows can no longer spend the whole row budget before a single alternative is read. The short-title scan matches the token as a word instead of a substring and orders by title length in a wider window, so "Titanic" and "The Italian Job" cannot push the real "It" out of it. Session: metadata enrichment re-runs discovery for the film already on screen. That is a refresh, not a new session — a second identity key (playlistId:contentId) now separates the two, so the source the user switched to keeps playing and stays named, the tried set stays burned, the position survives and a switch in flight still commits. Resume: the multi-source controller no longer records the engine's pre-seek timeupdate at ~0. The playback service's one-shot latch now reports whether the position can be believed, and until it can, the requested start time stands in — so a switch during the initial seek does not restart the film. UI: the in-player sources picker gets the same auto-failover setting and match kind as the detail page's, instead of always rendering the default and dropping the toggle. The caption counts distinct playlists, not stream variants, since the popover groups a portal's copies under that portal. Session mechanics and the pin toggle move into their own modules to keep the host service inside the line budget. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): keep the playing row when the refined year rejects it Follow-on from keeping the session across a rediscovery. The rerun can legitimately drop the row that is playing: enrichment supplies the release year, and the year gate then rejects a copy the yearless search had admitted — "Dune" 1984 while the user is watching the 2021 film. Off the list is right; it is not the same film. Off the screen is not. It is what is streaming, so it stays as a row and keeps the playing badge, rather than letting the caption name a playlist that is not sending any bytes. Also covers the new session key directly in the identity spec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): stop a pin write from landing on the next movie Two findings from the review of the previous round. A pin write is an IPC round-trip, and the user can navigate during it. The continuation then applied one film's answer to another film's controller — and because unpinning returns "nothing pinned", it would clear the pin the new movie had just loaded and its Play action would quietly stop starting from the preferred source. It now commits only while the same film is still on screen, like every other async path here. The short-title scan drops its row limit. FTS keeps its window because it ranks by relevance, so what it keeps is what matters; a scan cannot rank, so a window there silently decides which valid sources the user is allowed to see. It also bought nothing: the GLOB cannot use an index, so SQLite reads every row either way and the limit only truncated the answer. What bounds the scan is its predicate — reaching it means the whole title is one or two characters. The switch-notice type moves to the module that builds it, which also removes a circular type import between the two. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): keep external playback, the pin and the resume point honest Five findings from the round-5 review. An external player launched for an alternative carries that playlist's ids, so the page disowned its own session: the primary button never became Stop, stopping found nothing to stop, and another click opened a second player. Multi-source now tells playback which source is actually active, and the matcher accepts either that or the route's own stream. Stop also has to beat the pin. The primary action consults the pin first — that is what makes a pin decide where playback starts — but while a session is running the same button reads Stop, and consulting the pin there made the control do the opposite of its label. A pinned source started from the Resume button resolved at zero, because nothing reports a live position until the first timeupdate. The controller is now seeded from the persisted position, one-way: a live value always wins, since the stored one lags it and applying it would rewind. A pin whose write failed was still shown as pinned, promising a preference that reopening the movie would not have. Portal failures in this path logged raw errors. An Xtream error message carries the stream URL, and that URL is built out of the username and password, so they now go through the redacting logger. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): make every alias of a pin agree, and stop losing rows Four findings from the latest review pass. A pin lookup accepts several aliases of the same movie, but a write only touched the most-trusted one — so after enrichment the title alias still pointed at whatever was pinned before, and a reopen that read it (because TMDB had not landed yet, or its request failed) started the source the user had just replaced. Writes now go to every alias. That alias set was also missing one. Enrichment supplies the year as well as the id, so a pin set before either existed is stored yearless; the candidate list skipped that form entirely and orphaned the row. Discovery could lose whole playlists: one playlist listing a film in dozens of categories produces identically ranked rows that fill the window before another playlist is read. The collapse now happens in SQL, before the limit, rather than in TypeScript afterwards where the missing rows are already gone. And an abandoned source pick finishing late cleared the spinner from the row the user was actually waiting on. Removes `isExhausted()` from the host service — no caller outside its own tests, where the assertion above it already proved the same thing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): probe like playback, and stop the pin answering for a remake Five findings from the latest review pass. Writing a pin to every alias — last round's fix for stale aliases — was wrong in the other direction: `title:{base}:` is shared by every remake, so a known-year decision stored there answers for a different film. Pin Dune (2021), open Dune (1984) before its year arrives, and it would start the 2021 source. A write now clears every alias and stores only the canonical key, which retires the stale ones without making any of them ambiguous. The probe checked a bare URL while playback sends the playlist's User-Agent, Referer and Origin. A panel that requires them answers 401/403, so a stream that plays perfectly was reported dead and penalised in failover ranking. The switch toast interpolated the raw playlist name. Users routinely name a playlist after the URL they pasted, so that line could put credentials over the video; the notice now carries the same safe label the rows use. External players have no timeupdate, so their polled position IS the live one. Feeding it through the seed — which stops at the first value — froze the resume point where playback started, and a switch an hour in rewound to the beginning. And auto-failover concluded "nowhere to go" when a stream failed before discovery answered, stranding the user on the error screen. Moves `switchTo` into the session module, which is where the rest of the switch mechanics already live, and splits the route spec along the same rendering/behaviour seam the other suites use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): re-check the movie after waiting, and follow the alternative Three findings, two of them regressions from the previous round. Awaiting a pending discovery before failover let the user navigate during that wait: the continuation then ran against whatever controller was current and could answer one film's playback failure by starting another film's alternative. Both waits — failover and pinned Play — now re-check that the same movie still owns the screen. Pinned Play also needed the wait it did not have. Pressing Play while the pin lookup was still out concluded "nothing is pinned" and started the route's own source, making a persisted preference depend on worker latency. And the position bridge still accepted only the route's ids, so an external player running an alternative had every progress update discarded: the resume point stayed where playback began and a switch an hour in rewound the lot. The session matcher and the bridge now share one ownership predicate, since a page that shows Stop for a session whose progress it throws away is the bug in two halves. The test for the external case previously set the position signal directly, which bypassed the very filter that was broken; it now drives the bridge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): stop a remake matching, and let a pin survive its own playlist Three findings from the latest review pass. `normalizeTitleKeys` strips bracketed segments as tag noise, so "Dune (1984)" normalizes to exactly "dune" — an EXACT match for the 2021 film, ranked above every fuzzy one, with the year never consulted because that tier skipped the gate. Auto-failover could switch the user to the other film entirely. The year is now read out of brackets too, and a stated disagreement rejects the row on either tier. Playback positions are keyed by (playlist, stream), so watching through a pinned alternative stores progress under ITS ids while the page loads the route copy's row. Starting the pin therefore resumed from a position belonging to a different copy — usually zero. It now loads its own. And a pin can point at another copy of the film inside the playlist being viewed, which discovery excludes wholesale: the pinned row was absent from the list, so nothing showed as pinned and Play ignored the preference. The pin is now read before discovery, which keeps that one row. Moves the pin-shaped decisions into the pin module, where the persistence helpers already live. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): keep a pinned play, a same-playlist copy and Check honest Five findings from the latest review pass. Reading a pinned source's own position is a database round-trip, and the user can navigate across it — the continuation then handed one film's source id to whichever movie now owned the screen. Guarded, like every other await here. Allowing a pinned copy to live in the current playlist made "is this the route's own source?" a two-part question, and the ownership check still asked only about the playlist: an external session for that copy was disowned, so Stop vanished and its progress was dropped. The yearless title alias is shared by every remake, so clearing every alias before a write could delete a different film's pin. Writes and unpins now touch only keys that name one film — plus the ambiguous row this session actually read, which is the one the user is looking at and the one whose absence would make an unpin come back. Restart left the seeded position in the controller, so a failure before the first timeupdate resolved the next source back at it. And the alternative rows on the playback-error screen had a Check button wired to nothing at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): stop a rediscovery restoring the pin it started with A same-movie rediscovery read the pin, then held that snapshot across its source lookup and applied it afterwards. A pin made while the lookup was out was therefore overwritten by the older value: the row and the primary Play action named a source the database no longer held. The snapshot is now applied as soon as it is read, so a later write simply wins on ordering rather than needing to be detected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): write the pin before retiring it, and keep the badge honest Three findings from the latest review pass. Repinning cleared the old rows and then wrote the new one, so a write that failed after the clear left nothing persisted while the row still showed the old pin. The order is reversed: the new key is stored first and the stale ones retired only once it landed. Lookups are most-trusted-first, so a leftover alias never outranks what was just written. Starting a source from the picker, or letting a pin decide the primary Play, never recorded the movie as recently viewed — unlike every other way of playing it. And closing an alternative's player and pressing Play started the route stream while the controller still marked the alternative active, so the picker and caption named a source that was not running. Moves the discovery pass into the session module beside the switch and failover mechanics, and splits the pin spec along the persistence/playback seam, both to stay inside the file-size rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): stop claiming playback, a cached answer and a resolution Three findings, all of them the same rule: never state as fact something the app has not established. The "Playing from …" caption appeared as soon as discovery marked a source active — before Play was pressed, and again after the player was closed. It now requires a player that is actually running. Probe answers were cached by URL alone, but the request now carries the playlist's headers. Two playlists sharing a stream URL could therefore be told the other's answer, marking a source dead without ever asking it. And any width below 900 was labelled 480p, published with `api` provenance: a 640x360 stream stated 480p as a fact, and a 720x576 PAL source likewise. Widths below HD only resolve with the height — 720 is NTSC 480p or PAL 576p — so an unrecognised shape now carries no quality tag at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): match the sub-HD formats, and drop the caption on failure Two follow-ups to the previous round, both the same rule again. The 800-wide band still answered from the width alone, so 800x600 and 800x450 were labelled 480p — published with `api` provenance, so read as a measurement. Sub-HD formats are now matched against known shapes with the same 5% tolerance the height path uses, and anything unrecognised carries no tag at all. And "Playing from ..." survived a playback failure: the inline host stays mounted while the diagnostic is on screen, so the page named a source for a stream it had just reported it could not play. The caption now clears on failure and returns when the engine produces time again. Splits the route playback spec along the "what it does" / "what it claims" seam and lifts the repeated active-source stub into one helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): let the height veto a width match, and hold the failure state Two follow-ups to the previous round, both in code it introduced. A width that matched exactly one sub-HD format ignored the height entirely, so 640x480 came back as 360p — a measurement the numbers contradict. The height now vetoes, but only in the direction that can be wrong: cropping removes lines, so a SHORTER frame is a letterboxed master of that format and the width still names it, while a taller one is a different shape and gets no tag. That keeps the reason width is preferred in the first place. And picking a source off the error screen cleared the failure state before the switch resolved, so an alternative that could not be resolved left the diagnostic on screen while the caption went back to claiming playback. The flag now clears only once a switch actually starts something. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): release the resume latch when the target cannot be reached Carrying a position into a shorter cut of the same film — two hours into a 90-minute source — leaves the engine unable to ever report that time, so the one-shot latch never released: every position save was suppressed for the rest of the session, and the impossible start time kept being reported to multi-source for the next switch. The latch now also opens when a known duration puts the requested point out of reach, while a reachable one still waits as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): say "Playing" only while something is playing `isActive` means "the source a switch or Play would use". Discovery sets it the moment the page opens and it survives closing the player, so it could not back the two claims the UI made in the present tense: the "Playing from" caption and the source row's Playing badge. Both appeared on a page where nothing had started, and came back after the player was closed. `playbackLive` is now that statement, and both read it. Inline it needs a timeupdate — `inlinePlayback()` is only the REQUEST to play, non-null while the engine is still opening the stream and still non-null after it fails — and external it needs the session past `launching`. A row that is merely selected reads "Current" (new key, filled for all 19 locales). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): start a never-watched pinned source from the beginning Positions are keyed by (playlist, stream). When the pin points at a copy the user has never opened, the lookup returns nothing and the controller was left holding the ROUTE copy's position — so Play dropped them 42 minutes into an unstarted film, and the first save wrote that timecode back under the pinned source's key, making it permanent. The spec asserted the old behaviour, so it is flipped rather than extended; a second case covers the host that supplies no lookup at all, where "never watched" was never established and the position must be left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): describe the copy the primary button will actually play Two gaps found by review. A pin makes the primary button play a copy the page never loaded a position for — positions are keyed by (playlist, stream). The label, timecode and Restart affordance still came from the route copy's row, so the button could read "Resume 42:18" and start an unwatched copy at zero, or read "Play" and jump into the middle of one already watched. `createPrimaryActionPosition` lets the pinned copy's row govern, including when that row is absent: never watched is an answer, not a fallback to someone else's progress. A manual source switch also mounts a DIFFERENT stream in the same host while marking the new source active at once, so the previous stream's timeupdate was still vouching for it — the caption and the badge claimed the new source while it was still opening. That path now clears the latch like Play and Restart do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): keep the route's own resume point, and honour a closed pin Two more from review, both variations on "selected is not playing". `vodPlaybackPosition` followed whichever copy last reported — so after an alternative played, Resume and its label described that copy's row while starting the route's stream, jumping it to a timecode nobody reached in it. It now splits: `vodPlaybackPosition` stays the last position seen (the progress bar and the switch handoff want the stream on screen), and `routePlaybackPosition` holds the route copy's own row for everything that acts on the route's stream. `pinnedSourceAwaitingPlay` skipped the pin whenever its row was active, but `isActive` means selected — the pinned row stays selected after its player is closed, so the next Play went to the route copy and ignored the stored preference until the page was reopened. It now takes `playbackLive` too. The host service crossed the 400-line cap on the way, so the four derived alternative counts moved into `vod-multi-source-counts.ts`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): keep the primary button honest across navigation and pins Four follow-ups from review, all consequences of splitting the position signals. - Route reuse (the Similar rail) cleared only `vodPlaybackPosition`, so the button kept the previous movie's Resume label — and start point — until the new lookup landed. Both signals and the playback latch now reset together. - The primary button's fall-through past an unresolvable pin reached the service directly, skipping the bookkeeping a route start needs: the controller kept the alternative's timecode and the old stream's timeupdate still vouched for the new one. It now goes through the route's own wrappers, and Resume seeds the controller with the ROUTE copy's position. - `alternativePlaylistCount` counted the playlist being watched whenever it held a second copy, so "also found in 2 other playlists" could mean one. - The pinned copy's stored row went stale the moment the user watched it; its live position now wins while it is the one playing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): do not spend a source's failover turn on mere selection `setActiveSource` marked the source tried, but discovery calls it the moment the page opens and a pin or the picker can call it before anything plays. So opening a movie burned the route copy's turn: if a pinned alternative then failed, failover skipped a healthy untouched source — and with only one alternative, reported the options exhausted. Selection and attempt are now separate. `setActiveSource` selects; `markPlaying` also spends the turn, and only the three places that really start playback call it. `runFailover` additionally retires whatever is on screen before picking, so the failing source is spent however it got there — relying on the start paths alone would leave one hole per path, and the cost of missing it is a ping-pong between two sources. One existing spec asserted the old behaviour (a route copy burned by a switch it never played); it now plays first, so it still covers what it meant to — that the tried set survives a rediscovery. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(portals): carry VOD source pins through playlist backup The new pins table was invisible to backup: exporting a playlist and re-importing it on a new machine silently dropped every "main source" choice, with nothing in the archive to say the choice had ever been made. Pins now ride along under the playlist they point AT — carrying them anywhere else would restore a preference for a portal the archive never contained. `matchKey` names the film rather than the portal, so it survives untouched and only the playlist id is remapped to the imported copy. `sourcePins` is the one optional collection in the Xtream user state: archives written before multi-source existed simply do not have it, so its absence is age rather than damage. Only a wrong type is rejected, and pins without a usable match key or content id are dropped, since writing one would occupy the unique key of a film it does not describe. Adds `DB_LIST_VOD_SOURCE_PINS` through the usual six seams (operation, worker case, event, preload, bridge contract, service). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(portals): follow the normalized restore state's new collection `normalizeXtreamPendingRestoreState` now always emits `sourcePins`, like every other collection it canonicalizes, so three specs that assert the exact normalized shape had to follow. Adds coverage for the sanitizing itself: a pin without a usable match key or content id is dropped, and a non-string `updatedAt` is discarded rather than carried. Caught by CI, not locally — the earlier full run served `playlist-shared-ui` from the Nx cache, so it reported green on a stale result. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(portals): lift the VOD route's orchestration out of the component The details route had grown to 864 lines — the repository's hard maximum is 400, and while the file predates the rule, a baselined exemption is not a budget to spend. Three component-provided services now hold what the component was accumulating: `VodDetailsMultiSourceUiService` (the playback-evidence latch, the caption, the primary button's position, source actions and the failover toast), `VodDetailsSimilarService` (the rail and its cross-portal lookup), and `VodDetailsDownloadsService`. The component keeps its public API, so the template and the existing specs are untouched. 864 -> 566 lines. The downloads move also fixes a latent bug: `downloadVod` and `playFromLocal` read `route.snapshot.params`, which is stale once the router reuses this component for detail-to-detail navigation (the Similar rail) — so a download started from a film reached that way fetched the previous one. They now read the same route-params signal everything else uses, with a regression test. Also from review: pins are applied on the FRESH-import path too. A new playlist has no content when the archive is read, so its user state is parked and replayed after the import — the merge path I wired first never ran there, and every pin was dropped. A failed pin write now propagates instead of being ignored: the backup entry is reported failed, and the parked state is kept so a transient failure can be retried rather than silently losing the preference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): read array-shaped codecs, bound short-title scans, honour alias clears Three from review. `info.video`/`info.audio` are declared — and sent by the mock server and many panels — as string arrays, but the resolver only read the ffprobe object shape. Every array response therefore lost the provider's codec, so those source rows showed no codec fact and the "dub may differ" warning could never fire. `readStreamInfo` now accepts both, and states nothing when the provider stated nothing. The FTS-empty fallback scan matched only the FIRST token, which is fine for a one-word short title but not for "I Am": every catalog row containing the word "i" came back for TypeScript to throw away — a full scan of a large catalog on the single database worker, just to open a detail page. Every token must now appear. `writePin` reported success when the canonical write landed but retiring the old alias failed. Lookups read aliases before the canonical key, so reopening the movie before enrichment would start the source the user just replaced, with the icon promising otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): write a pin and retire its aliases in one transaction Split across two calls, a half-failure had no honest outcome. Reporting success left a surviving alias to win the next lookup and start the source the user had just replaced; reporting failure — which the previous round changed it to — left the canonical row durable while the UI showed a pin that was no longer the stored one. Review was right both times, which is the tell that the two-step shape was the problem. `setVodSourcePin` now takes the keys to retire and does both inside one `db.transaction()`, with the synchronous `.run()` form the better-sqlite3 driver requires there (issue #1137's lesson). `retireKeys` rides through the worker op, the IPC contract, the preload bridge and the service, so there is one call and one outcome. Also corrects the architecture doc: the scan path is reached whenever no token clears the trigram minimum, not only when the whole title is one or two characters — the claim the previous commit's code change had already falsified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): tell a superseded pinned play from an unusable pin Double-clicking Play while a pinned source resolves put both handlers into `playPinnedSource()`. The second supersedes the first, so the first returned `false` — which the route read as "no usable pin" and answered by starting the route source over the playback the second click had just begun. `playPinnedSource` now reports `played` / `superseded` / `unavailable`, and only `unavailable` falls through. This is the same distinction `runFailover` already draws between "keep going" and "stop, something newer owns the screen"; the pinned path simply never had it. The host crossed the 400-line cap again on the way, so the pinned-play errand (wait out an in-flight discovery, re-check the session, start the source) moved into the pin module beside `playPinned`, and the pin-toggle commit went with it. 388 lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): restart honours the pin, and a switch replaces the player Three from review, all in the pinned-playback seam. A pinned copy watched through resolved to its stored seconds, so the button read Play — the label uses the in-progress rule — and then started near the end. Both now go through one `isResumablePosition`, so the label and the start point cannot disagree. Restart sat beside a Resume that honours a foreign pin, but called `playVod` and started the ROUTE copy — silently switching the user's playlist. It now restarts whatever the primary button acts on, falling back to the route source only when there is no usable pin. Switching sources left a running external player alone. With MPV or VLC and instance reuse off the backend spawns a second detached process, so both sources kept playing and Stop owned only the newer one. Also merges master, and puts the five host specs on a shared harness — they each carried the same 31-line TestBed, which is what pushed two of them over the file-size cap as cases were added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): find short Unicode titles, and stop two pickers racing Five from review. Greptile's P1: a short non-ASCII title was undiscoverable. SQLite's `LOWER()` and GLOB classes are ASCII-only, so "он" never matched a stored "Он" and the source simply never appeared. ASCII tokens keep the word-boundary GLOB; a non-ASCII token falls back to a substring test against both the folded and the as-typed form, which the normalized confirmation afterwards makes safe. A probe now retries the ranged GET for 400 and 403, not just 405/501 — those are what a WAF returns for an unexpected HEAD on a URL it serves happily over GET, and calling that source dead also ranked it below worse ones. Three races, all the same shape as ones fixed earlier in this branch: - a pinned play awaiting its resume lookup did not notice a source picked across it, and finished last, replacing the user's choice; - two overlapping switches both saw the same external session, both awaited its close, and both launched — two detached players again; - the primary button showed the ROUTE copy's Resume while the pinned copy's row was still loading, so a click started somewhere else entirely. Also puts the races spec on the shared host harness, which is what keeps it inside the file-size rule now that it carries two more cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): close the player we launched, not the one we now own Three follow-ups, two of them to last round's own fixes. The external-session close was defeated in exactly the case it was written for: `switchToSource` marks the DESTINATION active before handing playback over, so by the time the service ran, the process still playing no longer looked like ours and was left running beside its replacement. The service now remembers the ids it launched with, independently of what is active. The ASCII/Unicode branch was decided from the NORMALIZED token, which folds diacritics — "Ça" arrived as "ca", looked like plain ASCII, and took the GLOB path while the stored title still read "Ça". Decided from the raw token now. Backup restore upserted archived pins but never removed the playlist's existing ones, so a present-but-empty collection left stale preferences alive — unlike the playback positions cleared beside it. An absent collection (an older archive) still means "no opinion" and is left alone. Four files crossed the size cap on the way; the split ones now share `title-sources.spec-data.ts` and `playlist-backup.xtream-fixtures.ts`, and the external-session ownership moved to its own module. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): absent is not empty, and every start claims the generation Four more from review, three of them defects in last round's fixes. The restore normalizer materialized `sourcePins: []` for archives that never had the field, so "absent means no opinion" became "this archive says there are no pins" and a merge cleared the user's. Absent now stays absent. My test for that behaviour had passed for the wrong reason — it stubbed an empty pin list, so the clear was skipped whether or not the guard worked. `startGeneration` was claimed only by the switch path, so a plain Play, Resume or Restart could be overtaken by a switch still awaiting its close. Every start claims it now. Raw and normalized tokens were paired by position, which breaks when normalization drops a whole word: "FR: Ça" normalizes to "ca" and got handed the raw token "FR:", sending it down the ASCII branch it cannot match from. They are paired by normalized form instead. And the ambiguous yearless alias (`title:dune:`) is no longer written or retired beside a precise key — it may hold another remake's pre-enrichment pin. It stays available when it is the only key there is, since refusing to pin at all would be worse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): a failed close must not leave the page claiming a dead source When `closeSession()` rejected, `startResolvedPlayback` rejected with it and never launched — while `switchToSource` had already marked the destination active and reported the switch as successful. The page then named a source that nothing was playing. The close failure is logged and the replacement starts anyway. A close that rejects usually means the session was already gone, and a possibly-lingering process is the lesser of the two evils: the alternative is a UI that lies about what is on screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(portals): split the external-playback handoff out of the service Both the service and its spec crossed the 400-line cap with the close-failure handling, so the handoff — deciding which process is ours, closing it, and surviving a close that rejects — now lives in `vod-details-external-session.ts` with its own spec file. Two tests had to start awaiting: replacing a running external player is a round-trip, and the handoff now yields once even when there is nothing to close, so the new playback is mounted a microtask later than before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style(portals): format the extracted external-session module Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): fold diacritics in the title index Cross-playlist matching compares normalized titles ("Amélie" -> "amelie") against an index built from the raw title, and the trigram tokenizer does not fold diacritics by default. Every accented title was therefore invisible to the FTS path: two identical `Amélie` entries produced no candidates at all. That is the broadest of the Unicode gaps review found, and it predates the short-title work. The tokenizer is fixed at CREATE time, so existing databases recreate and rebuild the index once behind a migration marker. `remove_diacritics` needs SQLite 3.45+, so support is probed on a temp table first: an older runtime keeps its working index untouched and the migration is not recorded as done, leaving a later version free to upgrade it. Case folding for non-ASCII remains impossible in stock SQLite — "ОН" cannot find "Он" by any available predicate — and is documented as the known limit rather than patched around again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): clear a playlist's pins by playlist, not by key list Restoring over a playlist reused the keyed clear, which caps its input at MAX_KEYS_PER_LOOKUP to bound an IN clause. A playlist with more than eight pinned movies therefore kept the surplus while the call still reported success, and the restore then wrote the archive's pins on top — leaving the union of two states, which is neither the one the user asked for. Clearing is now a dedicated delete-by-playlist operation with no key list to truncate, and it refuses a blank playlist id rather than deleting everything. A failure fails the entry instead of being swallowed: `listForPlaylist` returns `[]` on error and `clear` returns `false`, so ignoring the result made a failed read indistinguishable from "there was nothing to clear". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): keep a pin readable under every identity of its film Two defects in the pin/position subsystem, both reported in review. A pin was stored under the movie's most-trusted key alone and its other keys retired. But a movie's identity GROWS: the film keyed `tmdb:438631` today was `title:dune:2021` before enrichment, and reopening it cold asks for the poorer key first. The preference was therefore ignored until enrichment landed — and permanently when enrichment is off or never answers. The decision is now written under every key in `write` (never the yearless form, which every remake shares), one upsert per key plus the leftover retirement in the same transaction. `setVodSourcePin` also reports failure for a pin with no usable key instead of claiming a write it never made. The primary button asked whether the pinned copy's position had loaded by testing presence rather than identity, so re-pinning left it wearing the previous copy's timecode until the new lookup returned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(portals): record the key-addressing limit a pin write cannot close Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): fold non-ASCII case in the scan, and read years as tags I was wrong about SQLite twice over, and both errors cost matches. GLOB character classes are NOT ASCII-only. `patternCompare` reads them as UTF-8 code points, so `'Он' GLOB '*[Оо][Нн]*'` is true — only `LOWER()` is ASCII-only. The scan tier now folds the case in JavaScript, where Unicode case mapping is real, and hands SQLite one class per character. A short Cyrillic or Greek title stored in a different case is found instead of being silently absent from the Sources chip. The builder returns `null`, leaving the substring tests as the whole answer, for a token holding a GLOB metacharacter (GLOB has no escape character) or a case mapping that changes length. The FTS tier is untouched and still cannot fold — that needs a stored normalized-title column. The movie's own year came from `extractYear`, which reads a year from anywhere in the title. That is right where a year is a search hint, wrong where it is an identity: `2001: A Space Odyssey` was treated as a 2001 film, so every genuine 1968 copy failed the year gate and the movie had no alternatives at all — and its pin key moved the moment enrichment supplied the real year. `releaseTagYear` accepts only bracketed and trailing forms; the repo's own TRAILING_YEAR_PATTERN already documented this exact hazard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): cover a letter spelled two ways in lower case Greek Σ lowercases to σ, but a word-final sigma is written ς and is equally a lowercase of it, so a class built only from the character in hand knew one spelling of two. Each class now also carries the uppercase form's own lowercase, which reaches the other one. One-way on purpose: σ → Σ → σ never arrives at ς. Left so because ς is only correct at the end of a word, which is exactly where the request's last character sits — the pair that occurs in real titles is covered, and closing the other direction needs a fold table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): let an exact title keep a number that is part of its name Both match tiers weighed the same year, taken from a trailing four-digit tail or a bracketed tag. On the exact tier that rejects the very copy it was meant to confirm: reaching it means both titles are the SAME string, so the trailing digits belong to both, and comparing them against a release year out of metadata makes "Blade Runner 2049" disagree with its own stated 2017 — the genuine alternative disappears at the moment enrichment lands, which is when the user has most reason to expect it. The exact tier now reads the bracketed form only. Brackets are never part of a name, so "Dune (1984)" is still rejected against 2021. The base tier is unchanged: it has just stripped a trailing year, and that year is the only thing separating "Dune 1984" from "Dune 2021". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(database): verify the title index folds, rather than trust the marker `createTables` declares content_title_fts with the plain trigram tokenizer, and the diacritics migration declares it again with folding. Two sources of truth for one tokenizer: if the table ever went missing after the marker was written, `CREATE TABLE IF NOT EXISTS` would restore the unfolded form and the migration would skip it on the marker alone. The upgrade now reads the live table's own DDL from sqlite_master and rebuilds unless it really folds. A degraded index is invisible from the outside — discovery just stops finding "Pokémon" for "pokemon" — so the record has to be checked against the thing it describes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): give the route's own row the facts the page already has Two provenance defects found in review. The current-source row is never resolved — nothing needs to fetch a URL for the stream already playing — so it carried no provider metadata at all, while every alternative got its facts from the resolve preceding playback. `audioDiffersFactually` requires a fact on BOTH sides, so the "dub may differ" warning was structurally unreachable on the commonest switch there is: route to alternative. It could only ever fire between two alternatives that had both been resolved. The row now carries what `get_vod_info` already told the page, via a `providerVodMetadataOf` mapper shared with the resolver so the two cannot describe one movie differently. Quality bucketed every width from 900 to 1199 as 576p, so a 960x540 stream — an ordinary 540p encode — was published as "576p" with `api` provenance: a measurement its own pixels contradict, from the one field that is supposed to mean the provider said so. That range holds two standard formats, so it is matched now rather than bucketed, exactly as the sub-HD sizes already were. A width matching no known format yields no tag and a check chip, which is the honest answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): let the height veto a width-derived quality, and refresh route facts Both of these are gaps I saw and chose not to close last round; a reviewer was right that neither survives its own reasoning. The shape check only ran below 1200, so the HD ranges kept publishing wrong-but-confident labels: 1440x1080 is anamorphic 1080 and 1600x900 is 900p, and both were "720p" with `api` provenance — the provenance that means the provider said so. Ranges are fine up there, the standard widths really are far apart, but only once a known height can veto the answer. Same rule the matched formats already used: a shorter frame is a letterboxed master, a taller one is a different shape and gets no tag. And the route row picked up provider facts only when discovery reran. On a sparse panel `get_vod_info` can answer with no year and no TMDB id, so the movie key is unchanged, nothing reruns, and the row keeps stating nothing — leaving `audioDiffersFactually` one-sided and the dub warning unreachable on exactly the switch it exists for. It now takes those facts on without rediscovering, merged onto the existing row so a probe result already sitting there survives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): a codec is not a dub, and two waits needed a switch guard Three findings from review. The "dub may differ" warning compared audio CODECS. AAC and AC3 routinely carry the same dub, and two AC3 tracks can carry different ones, so it fired on every identical-language re-encode and stayed silent on the dub changes it exists for — wrong in both directions, which is worse than absent, because a warning people learn to ignore is not a warning. Worse, the previous commit made it reach the common route-to-alternative switch for the first time, so the false claim was about to get louder. It now reads a new `audioLanguage`, taken from the track's language tag and never from the codec. `audio` stays as a display fact. Few panels tag a language, so the warning is usually silent — the same answer the rest of this feature gives when it does not know. `failover()` validated only the session across its wait for a discovery in flight. The session moves when the FILM does, so a source the user picked — or the route stream they restarted — during that wait was then treated as the thing that failed and switched away from. It claims and rechecks a switch generation, as the pinned path already did. And the scan's ASCII branch could not find "Ça" from a folded "ca", while the non-ASCII branch found "Ca" from "Ça" — so whether two playlists could see each other depended on which one was open. Each ASCII letter now carries its accented forms, derived by decomposition rather than tabulated, so it cannot drift from the normalizer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(portals): pin what the declared audio shape can and cannot say The array shape the mock server and many panels send carries a codec and no language, so the dub warning is silent for every source arriving that way. Asserted rather than assumed, alongside the ffprobe shapes that do carry one — otherwise a later reader sees an unused field and wires the codec back into the warning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): a failed pin read must not export as "no pins" Three findings, all in code from this session. Backup called the lenient `listForPlaylist`, which turns a failed read into `[]`. Since `e0ebbeaf` made restore treat `sourcePins` as authoritative — clearing the playlist's pins before applying it — an export whose read failed produced a file that looks complete and wipes every pin on restore. Losing them is bad; losing them through the one feature meant to protect them is worse. Backup now uses a strict listing that throws, so the export fails instead. The diacritic map stopped at U+024F, which is tidy and leaves Vietnamese out: `ố` is U+1ED1, the normalizer folds it to `o`, and the scan filtered those rows out before confirmation. Latin Extended Additional is included now; the filter decides what belongs, so the range only has to be wide. And two panels spelling one language differently (`eng` vs `en`, or `en-US`) raised a dub warning between identical tracks. Tags are canonicalized before comparison — 639-2 collapses to 639-1, both German forms meet at `de`, regions drop, and `und` becomes nothing. Anything that survives longer than three characters is not a language code, so the comparison is declined rather than guessed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(portals): stop two backup paths from deleting pins they never read Two data-loss paths, both P1, both mine. A web export wrote `sourcePins: []`. Pins are Electron-only, so out there we cannot read them — which is not the same as knowing there are none, and restore treats the collection as authoritative. A backup made in the browser was therefore an instruction to delete every pin the moment it was imported on the desktop. There are three answers here, not two: pins exist, there are none, and "could not look". The last omits the field, exactly as an archive written before pins existed does. The same rule now covers Electron with the bridge method missing. I had written a test asserting the unreachable-store case resolves to an empty list "because a backup made there is complete". That reasoning was wrong: empty was true of what the runtime could see, never of the playlist. Restore also cleared the playlist's pins and then wrote the archive's one by one. A write failing partway left the previous pins already gone and only a prefix applied — a state belonging to neither, reported as a failure the user could not undo. `DB_REPLACE_VOD_SOURCE_PINS` does the clear and every write in one transaction, so the playlist ends up as the archive describes it or exactly as it was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1388 lines
83 KiB
JSON
1388 lines
83 KiB
JSON
{
|
||
"CANCEL": "Annuler",
|
||
"CLEAR": "Effacer",
|
||
"CLOSE": "Fermer",
|
||
"BACK": "Retour",
|
||
"SHOW_MORE": "Plus",
|
||
"SHOW_LESS": "Moins",
|
||
"PLAYLISTS": "Listes de lecture",
|
||
"HOME": {
|
||
"TITLE": "IPTVnator",
|
||
"SUBTITLE": "Choisissez une liste de lecture, ou ajoutez en une nouvelle via un transfert de fichier ou via une URL",
|
||
"TABS": {
|
||
"RECENTLY_ADDED": "Récemment ajoutés",
|
||
"FILE_UPLOAD": "Ajouter un fichier",
|
||
"URL_UPLOAD": "Ajouter une URL",
|
||
"TEXT_IMPORT": "Importer sous forme de texte",
|
||
"XTREME_IMPORT": "Ajouter Xtreme Code",
|
||
"STALKER_PORTAL_IMPORT": "Ajouter un portail Stalker"
|
||
},
|
||
"PLAYLISTS": {
|
||
"NO_PLAYLISTS": "Aucune liste de lecture n'a été ajoutée",
|
||
"ADD_FIRST": "Veuillez ajouter votre première liste de lecture depuis un autre onglet",
|
||
"WELCOME_TITLE": "Bienvenue sur IPTVnator",
|
||
"WELCOME_DESCRIPTION": "Commencez votre voyage de streaming en ajoutant votre première liste de lecture. Connectez-vous à votre service IPTV préféré et profitez de la télévision en direct, des films et des séries.",
|
||
"ADD_YOUR_FIRST": "Ajouter votre première liste",
|
||
"FEATURE_M3U": "Fichiers M3U/M3U8",
|
||
"FEATURE_XTREAM": "Xtream Codes",
|
||
"FEATURE_STALKER": "Portail Stalker",
|
||
"FEATURE_M3U_LABEL": "Add an M3U file or URL",
|
||
"FEATURE_XTREAM_LABEL": "Connect via Xtream Codes",
|
||
"FEATURE_STALKER_LABEL": "Connect to a Stalker portal",
|
||
"VALUE_PROPS_LABEL": "What you can do",
|
||
"VALUE_PROP_LIVE": "Live TV with EPG",
|
||
"VALUE_PROP_VOD": "Movies & Series",
|
||
"VALUE_PROP_MULTISOURCE": "Multi-source library",
|
||
"VALUE_PROP_OFFLINE": "Offline downloads",
|
||
"DASHBOARD_WELCOME_TAGLINE": "Your IPTV library, in one place.",
|
||
"DASHBOARD_WELCOME_DESCRIPTION": "Connect M3U files, Xtream Codes, or Stalker portals — then enjoy live TV, movies and series on every screen.",
|
||
"FEATURE_LIVE_TITLE": "Live TV",
|
||
"FEATURE_LIVE_DESC": "Stream live channels with full schedule.",
|
||
"FEATURE_VOD_TITLE": "Movies & Series",
|
||
"FEATURE_VOD_DESC": "On-demand library with covers and details.",
|
||
"FEATURE_EPG_TITLE": "EPG & Catch-up",
|
||
"FEATURE_EPG_DESC": "Program guide and time-shifted playback.",
|
||
"FEATURE_OFFLINE_TITLE": "Offline downloads",
|
||
"FEATURE_OFFLINE_DESC": "Save movies and episodes for later.",
|
||
"SOURCES_WELCOME_TITLE": "Add your first playlist",
|
||
"SOURCES_WELCOME_BODY": "Pick a source type below to get started.",
|
||
"SOURCE_M3U_NEEDS": "Local file or URL pointing to an M3U/M3U8 playlist.",
|
||
"SOURCE_M3U_ADD": "Add M3U",
|
||
"SOURCE_XTREAM_NEEDS": "Username, password and server URL from your provider.",
|
||
"SOURCE_XTREAM_ADD": "Add Xtream",
|
||
"SOURCE_STALKER_NEEDS": "Portal URL and MAC address (Ministra-compatible).",
|
||
"SOURCE_STALKER_ADD": "Add Stalker",
|
||
"CONTENT_LIVE": "Live",
|
||
"CONTENT_VOD": "Movies",
|
||
"CONTENT_SERIES": "Series",
|
||
"CONTENT_EPG": "EPG",
|
||
"NO_RESULTS_TITLE": "Aucune liste trouvée",
|
||
"NO_RESULTS_DESCRIPTION": "Aucune liste ne correspond à votre recherche. Essayez d'ajuster vos termes de recherche ou effacez le filtre pour voir toutes les listes.",
|
||
"ADDED_VIA_URL": "Ajouté par URL. URL :",
|
||
"ADDED_VIA_FILE": "Ajouté par transfert de fichier",
|
||
"XTREAM_PLAYLIST": "Liste Xtream",
|
||
"STALKER_PORTAL": "Portail Stalker",
|
||
"CHANNELS": "Chaînes",
|
||
"ADDED": "Ajouté",
|
||
"SHOW_DETAILS": "Afficher les détails de la liste de lecture",
|
||
"REMOVE": "Retirer la liste de lecture",
|
||
"SEARCH_PLAYLISTS": "Rechercher des listes par titre",
|
||
"INFO_DIALOG": {
|
||
"PLAYLIST_DETAILS": "Détails de la liste de lecture",
|
||
"TITLE": "Titre",
|
||
"FROM_URL": "Ajouté par URL",
|
||
"IMPORT_DATE": "Date d'importation",
|
||
"CHANNELS": "Nombre de chaînes",
|
||
"CLOSE": "Fermer",
|
||
"SAVE": "Enregistrer",
|
||
"USER_AGENT": "Agent utilisateur",
|
||
"ORIGINAL_FILENAME": "Nom du fichier d'origine",
|
||
"FILE_PATH": "Chemin du fichier",
|
||
"UPDATE_FAILED": "Échec de la dernière mise à jour de liste de lecture",
|
||
"AUTO_UPDATE": "Mise à jour automatique",
|
||
"AUTO_UPDATE_DESCRIPTION": "Si la fonction de mise à jour automatique est activée, la liste de lecture sera mise à jour automatiquement à chaque fois que l'app est lancée.",
|
||
"CUSTOM_USER_AGENT": "Certains fournisseurs d'IPTV requièrent un agent utilisateur spécifique afin de lire leur liste de lecture.",
|
||
"EXPORT_PLAYLIST": "Exporter la liste de lecture en m3u",
|
||
"SERVER_URL": "URL du serveur",
|
||
"USERNAME": "Nom d'utilisateur",
|
||
"PASSWORD": "Mot de passe",
|
||
"MAC_ADDRESS": "Adresse Mac",
|
||
"PLAYLIST_EXPORT_SUCCESS": "La liste de lecture a été exportée avec succès.",
|
||
"PLAYLIST_EXPORT_ERROR": "Erreur : Échec de l'exportation de la liste de lecture.",
|
||
"COPY_URL": "Copier l'URL dans le presse-papiers",
|
||
"URL_COPIED": "URL copiée dans le presse-papiers"
|
||
},
|
||
"REMOVE_DIALOG": {
|
||
"TITLE": "Retirer la liste de lecture",
|
||
"MESSAGE": "Êtes-vous certain de vouloir complètement retirer cette liste de lecture ?",
|
||
"SUCCESS": "La liste de lecture a été supprimée avec succès",
|
||
"IN_PROGRESS": "Suppression de la liste de lecture…",
|
||
"DELETING_FAVORITES": "Suppression des favoris…",
|
||
"DELETING_RECENT": "Suppression des éléments récemment consultés…",
|
||
"DELETING_PROGRESS": "Suppression de la progression de lecture…",
|
||
"DELETING_DOWNLOADS": "Suppression des téléchargements…",
|
||
"DELETING_CONTENT": "Suppression du contenu en cache…",
|
||
"DELETING_CATEGORIES": "Suppression des catégories…",
|
||
"DELETING_PLAYLIST": "Suppression de l'enregistrement de la liste de lecture…"
|
||
},
|
||
"UPDATED": "Mis à jour",
|
||
"REFRESH": "Mettre à jour la liste de lecture",
|
||
"REFRESH_XTREAM": "Actualiser la liste Xtream depuis le serveur",
|
||
"REFRESH_XTREAM_DIALOG": {
|
||
"TITLE": "Actualiser la liste Xtream",
|
||
"MESSAGE": "Cela supprimera le cache Xtream local et réimportera les catégories et les flux depuis le serveur distant. Les favoris, les éléments récemment consultés, la visibilité des catégories masquées et la progression de lecture seront restaurés après une réimportation réussie si le contenu correspondant existe toujours. Si le serveur est inaccessible, la restauration restera en attente jusqu'à une prochaine actualisation réussie. Continuer ?",
|
||
"STARTED": "Actualisation démarrée. Navigation vers la liste de lecture...",
|
||
"ERROR": "Échec de l'actualisation de la liste de lecture. Veuillez réessayer.",
|
||
"IN_PROGRESS": "Actualisation de la liste de lecture…",
|
||
"COLLECTING_DATA": "Collecte des favoris et des éléments récemment consultés…",
|
||
"DELETING_CONTENT": "Suppression du contenu en cache…",
|
||
"DELETING_CATEGORIES": "Suppression des catégories en cache…"
|
||
},
|
||
"GLOBAL_FAVORITES": "Favoris globaux",
|
||
"GLOBAL_FAVORITES_DESCRIPTION": "Liste de lecture générée automatiquement avec les favoris agrégés de toutes les listes",
|
||
"MY_PLAYLISTS": "Mes listes de lecture",
|
||
"MY_PLAYLISTS_SUBTITLE": "toutes les listes disponibles",
|
||
"PLAYLIST_UPDATE_SUCCESS": "Succès ! La liste de lecture a été mise à jour avec succès.",
|
||
"PLAYLIST_UPDATE_ERROR": "Erreur lors de la mise à jour des détails de la liste.",
|
||
"PLAYLIST_UPDATE_FILE_NOT_FOUND": "Échec de l'actualisation de la liste de lecture. Le fichier local n'est plus disponible. Vérifiez le chemin du fichier ou réimportez la liste de lecture.",
|
||
"PLAYLIST_UPDATE_FILE_ACCESS_ERROR": "Échec de l'actualisation de la liste de lecture. L'application ne peut plus accéder au fichier local.",
|
||
"AUTO_REFRESH_UPDATE_SUCCESS": "Succès ! Les listes de lecture ont été mises à jour avec succès",
|
||
"AUTO_REFRESH_UPDATE_PARTIAL": "Actualisation automatique incomplète : {{updated}} listes de lecture sur {{total}} mises à jour, {{failed}} en échec.",
|
||
"AUTO_REFRESH_UPDATE_PARTIAL_WITH_SKIPPED": "Actualisation automatique incomplète : {{updated}} listes de lecture sur {{total}} mises à jour, {{failed}} en échec, {{skipped}} ignorées (aucune URL ni chemin de fichier).",
|
||
"AUTO_REFRESH_UPDATE_FAILED": "Échec de l'actualisation automatique. Aucune des {{total}} listes de lecture n'a pu être mise à jour.",
|
||
"AUTO_REFRESH_UPDATE_SKIPPED": "Actualisation automatique des listes de lecture : {{updated}} sur {{total}} mises à jour, {{skipped}} ignorées (aucune URL ni chemin de fichier).",
|
||
"AUTO_REFRESH_ENABLED": "Actualisation automatique activée"
|
||
},
|
||
"ADD_PLAYLIST": {
|
||
"PICK_METHOD": "Choisissez comment vous souhaitez vous connecter. Vous pourrez renommer la liste plus tard.",
|
||
"METHOD_URL_LABEL": "URL M3U",
|
||
"METHOD_URL_SUB": "Collez un lien vers un fichier .m3u ou .m3u8 distant",
|
||
"METHOD_FILE_LABEL": "Fichier M3U",
|
||
"METHOD_FILE_SUB": "Téléversez une liste de lecture depuis votre ordinateur",
|
||
"METHOD_XTREAM_LABEL": "Identifiants Xtream",
|
||
"METHOD_XTREAM_SUB": "Connectez-vous avec hôte, nom d'utilisateur et mot de passe",
|
||
"METHOD_STALKER_LABEL": "Portail Stalker",
|
||
"METHOD_STALKER_SUB": "Adresse MAC et URL du portail",
|
||
"METHOD_TEXT_LABEL": "Texte m3u brut",
|
||
"METHOD_TEXT_SUB": "Pour les listes ponctuelles ou quand vous n'avez que du texte"
|
||
},
|
||
"FILE_UPLOAD": {
|
||
"DRAG_DROP": "Glissez un fichier ici",
|
||
"OR": "ou",
|
||
"CHOOSE_PLAYLIST": "Choisir une liste de lecture",
|
||
"BROWSE": "Parcourir les fichiers",
|
||
"REJECTED": "Le fichier a été rejeté, format de fichier non pris en charge {{filename}}.",
|
||
"OPEN_FAILED": "Impossible d’ouvrir \"{{filename}}\"."
|
||
},
|
||
"URL_UPLOAD": {
|
||
"PLAYLIST_URL": "URL de la liste de lecture (m3u, m3u8)",
|
||
"ADD_PLAYLIST": "Ajouter la liste de lecture",
|
||
"CORS_NOTE": "Remarque : Pour éviter les problèmes CORS, l'application utilise un service proxy CORS public. Si vous souhaitez télécharger une liste avec des données sensibles, il est plus sûr de l'importer depuis un fichier.",
|
||
"ERROR_FETCH_FAILED": "Échec de la récupération de la liste de lecture. Veuillez vérifier l'URL et réessayer.",
|
||
"ERROR_403": "Accès refusé (403) : le serveur a refusé la requête. L'URL peut nécessiter une authentification ou la liste de lecture est restreinte.",
|
||
"ERROR_404": "Liste de lecture introuvable (404) : l'URL ne pointe pas vers une liste de lecture valide. Veuillez vérifier l'URL.",
|
||
"ERROR_401": "Non autorisé (401) : une authentification est requise pour accéder à cette liste de lecture.",
|
||
"ERROR_INVALID_TLS": "Certificate for this playlist host is invalid.",
|
||
"ERROR_TLS_HOST_UNKNOWN": "Could not determine the playlist host. Please retry manually.",
|
||
"TRUST_TLS_HOST": "Trust host",
|
||
"TRUST_TLS_HOST_TITLE": "Trust invalid certificate?",
|
||
"TRUST_TLS_HOST_WARNING": "Only continue if you trust this playlist host. IPTVnator will allow invalid TLS certificates for this host, but other hosts still require valid certificates."
|
||
},
|
||
"TEXT_IMPORT": {
|
||
"LABEL": "Insérer la liste m3u(8) sous forme de texte",
|
||
"BUTTON_LABEL": "Importer"
|
||
},
|
||
"XTREAM_PLAYLIST": {
|
||
"TITLE": "Titre de la liste",
|
||
"SERVER_URL": "URL du serveur",
|
||
"USERNAME": "Nom d'utilisateur",
|
||
"PASSWORD": "Mot de passe",
|
||
"ADD": "Ajouter",
|
||
"TEST_CREDENTIALS": "Tester les identifiants",
|
||
"URL_VALIDATION_ERROR": "Doit être une URL valide avec un protocole (par exemple http://example.com ou https://example.com:4324)"
|
||
},
|
||
"STALKER_PORTAL": {
|
||
"TITLE": "Titre",
|
||
"MAC_ADDRESS": "Adresse Mac",
|
||
"SERIAL_NUMBER": "Numéro de série (optionnel)",
|
||
"SERIAL_NUMBER_HINT": "À utiliser si déjà enregistré auprès du fournisseur",
|
||
"DEVICE_ID_1": "ID d'appareil 1 (optionnel)",
|
||
"DEVICE_ID_2": "ID d'appareil 2 (optionnel)",
|
||
"DEVICE_ID_HINT": "64 caractères hexadécimaux — à utiliser si le fournisseur exige un ID d'appareil spécifique",
|
||
"SIGNATURE_1": "Signature 1 (optionnelle)",
|
||
"SIGNATURE_2": "Signature 2 (optionnelle)",
|
||
"SIGNATURE_HINT": "64 caractères hexadécimaux — à utiliser si le fournisseur exige des signatures de vérification d'appareil",
|
||
"SERVER_URL": "URL du serveur",
|
||
"URL_VALIDATION_ERROR": "Doit être une URL valide avec un protocole (par exemple http://example.com/c ou https://example.com/stalker_portal/c)",
|
||
"ADD": "Ajouter",
|
||
"VALIDATING": "Validation du portail…"
|
||
},
|
||
"FILTER_BY_NAME": "Filtrer par nom",
|
||
"FILTER_AND_SORT": "Filtrer et trier",
|
||
"SORT_PLAYLISTS": "Trier les listes",
|
||
"SORT_OPTIONS": {
|
||
"NAME_ASC": "Nom (A-Z)",
|
||
"NAME_DESC": "Nom (Z-A)",
|
||
"NEWEST": "Date d'ajout (Plus récent)",
|
||
"OLDEST": "Date d'ajout (Plus ancien)",
|
||
"CUSTOM_ORDER": "Ordre personnalisé (Drag & Drop)"
|
||
},
|
||
"PLAYLIST_TYPES": {
|
||
"M3U": "M3U (local, URL, texte)",
|
||
"XTREAM": "Xtream",
|
||
"STALKER": "Stalker"
|
||
},
|
||
"IMPORTED_AS_TEXT": "Importé sous forme de texte",
|
||
"PARSING_ERROR": "Erreur : échec de l'analyse, liste de lecture non valide :"
|
||
},
|
||
"SETTINGS": {
|
||
"TITLE": "Paramètres",
|
||
"GENERAL": "Général",
|
||
"GENERAL_SUBTITLE": "Modifier la configuration de l'application",
|
||
"PLAYBACK": "Lecture",
|
||
"EPG_SOURCES": "Sources EPG",
|
||
"EPG_SOURCES_DESCRIPTION": "Ajoutez une ou plusieurs URL comme sources EPG",
|
||
"PLAYLIST_EPG_SOURCES_AUTO_SELECTED": "{{enabled}} of {{detected}} detected playlist EPG sources were auto-selected for import.",
|
||
"EPG_EMPTY_TITLE": "No EPG sources yet",
|
||
"EPG_EMPTY_HINT": "Add a URL to fetch program data for your channels.",
|
||
"PREFER_UPLOADED_EPG_TITLE": "Préférer vos sources EPG pour les playlists Xtream",
|
||
"PREFER_UPLOADED_EPG_HINT": "Par défaut, les playlists Xtream utilisent le guide des programmes de votre fournisseur, et vos sources EPG ne comblent que les chaînes manquantes. Activez cette option pour utiliser d'abord votre EPG.",
|
||
"DATA": "Données",
|
||
"BACKUP": "Backup",
|
||
"BACKUP_DESCRIPTION": "Export your library as a snapshot, or restore from a previous backup.",
|
||
"RESET": "Reset",
|
||
"RESET_DESCRIPTION": "Permanently delete content stored in this app.",
|
||
"ABOUT": "À propos",
|
||
"NAV_GENERAL": "Général",
|
||
"NAV_PLAYBACK": "Lecture",
|
||
"NAV_EPG": "EPG",
|
||
"NAV_REMOTE": "Télécommande",
|
||
"NAV_DATA": "Données",
|
||
"NAV_BACKUP": "Backup",
|
||
"NAV_RESET": "Reset",
|
||
"NAV_ABOUT": "À propos",
|
||
"NAV_TMDB": "Métadonnées",
|
||
"TMDB": "Métadonnées (TMDB)",
|
||
"TMDB_DESCRIPTION": "Enrichir les détails des films et séries avec les données de The Movie Database",
|
||
"TMDB_ENABLE": "Activer les métadonnées TMDB",
|
||
"TMDB_ENABLE_DESCRIPTION": "Améliore les synopsis, la distribution, les notes et les visuels dans les vues détaillées. Lorsque cette option est activée, les titres des films et séries sont envoyés à TMDB.",
|
||
"TMDB_API_KEY": "Clé API TMDB (optionnelle)",
|
||
"TMDB_API_KEY_DESCRIPTION": "Utiliser votre propre clé API TMDB au lieu de celle intégrée",
|
||
"TMDB_GET_KEY_HINT": "Obtenez une clé API gratuite sur themoviedb.org",
|
||
"TMDB_TEST_BUTTON": "Vérifier la clé",
|
||
"TMDB_TEST_SUCCESS": "La clé fonctionne — TMDB a répondu avec succès",
|
||
"TMDB_TEST_FAILED": "Échec de la vérification — TMDB a rejeté la clé ou est injoignable",
|
||
"TMDB_CACHE": "Cache des métadonnées",
|
||
"TMDB_CACHE_DESCRIPTION": "Les métadonnées téléchargées sont stockées localement pour que les pages de détails s'ouvrent instantanément. Après le vidage, l'application récupérera des données fraîches.",
|
||
"TMDB_CACHE_SIZE": "{{entries}} entrées · {{size}}",
|
||
"TMDB_CACHE_CLEAR": "Vider le cache",
|
||
"TMDB_CACHE_ERROR": "Impossible de lire le cache — réessayez",
|
||
"TMDB_CREDITS": "Métadonnées",
|
||
"TMDB_ATTRIBUTION": "Ce produit utilise l'API TMDB mais n'est ni approuvé ni certifié par TMDB.",
|
||
"APP_PREFERENCES": "Préférences de l'application",
|
||
"HERO_EYEBROW": "Paramètres de l'espace de travail",
|
||
"HERO_TITLE": "Façonnez l'expérience IPTVnator",
|
||
"HERO_DESCRIPTION": "Réglez la lecture, les guides, l'accès distant et la gestion des données depuis un seul endroit, sans perdre le style actuel de l'espace de travail.",
|
||
"SUBTITLE": "Modifiez la configuration de l'application",
|
||
"DESCRIPTION": "Changez les options de la liste ci-dessous et enregistrez vos modifications.",
|
||
"EPG_URL_LABEL": "URL du EPG (*.xml ou *.xml.gz)",
|
||
"EPG_URL_PLACEHOLDER": "Saisir une URL",
|
||
"REFRESH_EPG": "Mise à jour de l'EPG",
|
||
"REFRESH_ALL_EPG": "Tout actualiser",
|
||
"REMOVE_EPG_SOURCE": "Supprimer la source EPG",
|
||
"EPG_STATUS_FRESH": "À jour",
|
||
"EPG_STATUS_STALE": "À actualiser",
|
||
"EPG_STATUS_ERROR": "Échec",
|
||
"VIDEO_PLAYER_LABEL": "Lecteur vidéo",
|
||
"PLAYER_HTML5": "Lecteur vidéo HTML5",
|
||
"PLAYER_VIDEOJS": "Lecteur Video.js",
|
||
"PLAYER_ARTPLAYER": "ArtPlayer",
|
||
"PLAYER_EMBEDDED_MPV": "MPV intégré (expérimental)",
|
||
"PLAYER_MPV": "Lecteur MPV",
|
||
"PLAYER_VLC": "VLC",
|
||
"VIDEO_PLAYER_PLACEHOLDER": "Choisir une option",
|
||
"VERSION": "Version",
|
||
"SAVE_CHANGES": "Enregistrer les modifications",
|
||
"BACK_TO_HOME": "Retourner à l'accueil",
|
||
"NEW_VERSION_AVAILABLE": "Une nouvelle version est disponible",
|
||
"LATEST_VERSION": "Vous utilisez la dernière version",
|
||
"STREAM_FORMAT": "Format du flux",
|
||
"OPEN_EXTERNAL_PLAYER_ON_DOUBLE_CLICK": "Lancer MPV/VLC par double-clic",
|
||
"OPEN_EXTERNAL_PLAYER_ON_DOUBLE_CLICK_DESCRIPTION": "Exiger un double-clic sur les lignes de chaînes avant de lancer la lecture avec MPV ou VLC.",
|
||
"LANGUAGE": "Langue",
|
||
"SETTINGS_SAVED": "La configuration a été enregistrée avec succès.",
|
||
"SETTINGS_SAVE_FAILED": "Les paramètres n'ont pas pu être enregistrés. Vos modifications seront perdues au redémarrage de l'application.",
|
||
"SETTINGS_LOAD_FAILED": "Les paramètres enregistrés n'ont pas pu être chargés — les valeurs par défaut sont affichées. Vérifiez qu'aucune autre copie de l'application n'est ouverte.",
|
||
"THEME": "Thème",
|
||
"SHOW_CAPTIONS": "Afficher les sous-titres",
|
||
"SHOW_DASHBOARD": "Afficher le tableau de bord",
|
||
"SHOW_DASHBOARD_DESCRIPTION": "Garde le tableau de bord disponible dans le rail de l'espace de travail. Lorsqu'il est désactivé, Sources devient la première page de l'espace de travail.",
|
||
"ADD_EPG_SOURCE": "Ajouter une source EPG",
|
||
"CLEAR_EPG_DATA": "Effacer les données EPG",
|
||
"CLEAR_EPG_DIALOG": {
|
||
"TITLE": "Effacer les données EPG",
|
||
"MESSAGE": "Êtes-vous sûr de vouloir supprimer toutes les données EPG (Guide électronique des programmes) de la base de données ? Cela supprimera tous les programmes mis en cache."
|
||
},
|
||
"EPG_DATA_CLEARED": "Les données EPG ont été effacées",
|
||
"EPG_DATA_CLEAR_FAILED": "Échec de l'effacement des données EPG. Veuillez réessayer.",
|
||
"CLEARING_EPG_DATA": "Effacement…",
|
||
"EPG_URL_ERROR": "La valeur n'est pas une URL valide",
|
||
"VIDEO_PLAYER_DESCRIPTION": "Sélectionnez le lecteur vidéo par défaut",
|
||
"WEB_PLAYER_SHARED_CONTROLS": "Commandes unifiées pour les lecteurs web (expérimental)",
|
||
"WEB_PLAYER_SHARED_CONTROLS_DESCRIPTION": "Utilise les commandes partagées d’IPTVnator dans HTML5, Video.js et ArtPlayer.",
|
||
"PLAYER_AMBIENT_MODE": "Mode ambiant",
|
||
"PLAYER_AMBIENT_MODE_DESCRIPTION": "Remplit l'espace autour du lecteur avec une affiche floue et assombrie au lieu de barres noires simples.",
|
||
"PLAYER_UP_NEXT_RAIL": "Rail d'épisodes « À suivre »",
|
||
"PLAYER_UP_NEXT_RAIL_DESCRIPTION": "Sur les fenêtres larges, ancre le lecteur de séries à gauche et affiche les épisodes à venir à côté.",
|
||
"STREAM_FORMAT_DESCRIPTION": "Sélectionnez le format de flux par défaut (xtream)",
|
||
"LANGUAGE_DESCRIPTION": "Sélectionnez la langue de l'application",
|
||
"THEME_DESCRIPTION": "Sélectionnez le thème visuel de l'application",
|
||
"COVER_SIZE": "Taille des affiches",
|
||
"COVER_SIZE_DESCRIPTION": "Ajustez la taille des cartes affiches dans les grilles et les rails. Les grandes tailles sont plus faciles à voir sur les téléviseurs ; les petites tailles affichent plus de contenu par ligne sur les grands écrans.",
|
||
"COVER_SIZE_SMALL": "Petite",
|
||
"COVER_SIZE_MEDIUM": "Moyenne",
|
||
"COVER_SIZE_LARGE": "Grande",
|
||
"EPG_VIEW_MODE": "Vue du guide",
|
||
"EPG_VIEW_MODE_DESCRIPTION": "Choisissez comment le guide des programmes sous le lecteur est affiché : une frise chronologique horizontale ou une liste verticale d'une seule journée.",
|
||
"EPG_VIEW_MODE_TIMELINE": "Chronologie",
|
||
"EPG_VIEW_MODE_LIST": "Liste",
|
||
"STARTUP_BEHAVIOR": "Vue de démarrage",
|
||
"STARTUP_BEHAVIOR_DESCRIPTION": "Choisissez si IPTVnator ouvre la première vue disponible de l'espace de travail ou restaure votre dernière vue de section. Première vue signifie Tableau de bord lorsqu'il est activé, sinon Sources.",
|
||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Première vue disponible",
|
||
"STARTUP_BEHAVIOR_RESTORE_LAST_VIEW": "Restaurer la dernière vue",
|
||
"SHOW_CAPTIONS_DESCRIPTION": "Afficher ou masquer les sous-titres par défaut",
|
||
"SHOW_EXTERNAL_PLAYBACK_BAR": "Afficher le pied de page de lecture en cours",
|
||
"SHOW_EXTERNAL_PLAYBACK_BAR_DESCRIPTION": "Garde la barre d'état du lecteur externe visible en bas de l'espace de travail sur ordinateur.",
|
||
"STRIP_COUNTRY_PREFIX": "Retirer les préfixes de pays des noms de chaînes",
|
||
"STRIP_COUNTRY_PREFIX_DESCRIPTION": "Supprime les préfixes de pays ou de groupe comme \"US | \" ou \"UK - \" des noms des chaînes en direct dans toute l'application.",
|
||
"VERSION_DESCRIPTION": "Version actuelle de l'application",
|
||
"ABOUT_SUBTITLE": "Version, liens du projet et façons de soutenir IPTVnator.",
|
||
"EMBEDDED_MPV_NOTICE_TITLE": "Runtime MPV intégré",
|
||
"EMBEDDED_MPV_NOTICE_DESCRIPTION": "Les versions desktop peuvent inclure les bibliothèques libmpv et FFmpeg empaquetées pour le lecteur MPV intégré expérimental. Les utilisateurs n'ont pas besoin d'installer l'application MPV séparément ; les lecteurs externes MPV et VLC restent disponibles comme lecteurs de secours.",
|
||
"COMMUNITY": "Projet et communauté",
|
||
"COMMUNITY_DESCRIPTION": "Mettez une étoile au dépôt, lisez le blog ou rejoignez le groupe Telegram pour les actualités et l'aide.",
|
||
"LINK_BLOG": "Blog",
|
||
"LINK_GITHUB_DESCRIPTION": "Mettez une étoile au dépôt, ouvrez un ticket ou contribuez",
|
||
"LINK_BLOG_DESCRIPTION": "Notes de version, astuces et actualités du projet",
|
||
"LINK_TELEGRAM_DESCRIPTION": "Annonces, communauté et support",
|
||
"SUPPORT_TITLE": "Soutenir le projet",
|
||
"SUPPORT_DESCRIPTION": "IPTVnator est libre et open source, développé en toute transparence. S'il fait partie de votre quotidien, une petite contribution aide à maintenir le projet en vie et à façonner la suite — merci.",
|
||
"SUPPORT_SPONSORS_HINT": "Récurrent ou ponctuel",
|
||
"SUPPORT_KOFI_HINT": "Offrez-moi un café",
|
||
"EPG_NOTE": "Actuellement, l'EPG n'est disponible que dans la version Electron d'IPTVnator.",
|
||
"EPG_NOTE_URL_TEXT": "Vérifiez-le ici",
|
||
"IMPORT_EXPORT_DATA": "Importer/exporter des listes de lecture",
|
||
"IMPORT_EXPORT_DATA_DESCRIPTION": "Exportez ou importez des listes au format JSON utilisé dans l'application.",
|
||
"IMPORT_DATA": "Importer",
|
||
"EXPORT_DATA": "Exporter",
|
||
"EXPORTING_DATA": "Exportation…",
|
||
"REMOVE_ALL": "Delete all playlists and playlist data",
|
||
"REMOVE_ALL_BUTTON": "Delete all",
|
||
"REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.",
|
||
"REMOVE_DIALOG": {
|
||
"TITLE": "Delete all playlists?",
|
||
"MESSAGE": "Delete all {{count}} playlists from the app. This also removes stored playlist data on this device.",
|
||
"SUMMARY_TITLE": "Playlist sources",
|
||
"CONSEQUENCES_TITLE": "This also removes",
|
||
"CONSEQUENCE_FAVORITES": "Favorites saved for these playlists",
|
||
"CONSEQUENCE_RECENTLY_VIEWED": "Recently viewed history",
|
||
"CONSEQUENCE_PLAYBACK": "Saved playback positions",
|
||
"CONSEQUENCE_DOWNLOADS": "Download history entries linked to those playlists",
|
||
"CONSEQUENCE_XTREAM_CACHE": "Imported Xtream categories and locally cached content",
|
||
"BACKUP_HINT": "Use Export first if you want a restorable backup before deleting.",
|
||
"CONFIRM": "Delete everything"
|
||
},
|
||
"IMPORT_ERROR": "Erreur d'importation, veuillez essayer avec un autre fichier.",
|
||
"PLAYLISTS_REMOVED": "All playlists and playlist data were removed.",
|
||
"MPV_PLAYER_PATH_LABEL": "Chemin mpv",
|
||
"MPV_PLAYER_PATH_DESCRIPTION": "Définissez le chemin vers MPV. Sur macOS, vous pouvez utiliser le bundle de l’app MPV, comme /Applications/mpv.app, ou le chemin de l’exécutable.",
|
||
"MPV_COMPATIBLE_PLAYER_TIP": "IINA peut être lancé comme lecteur compatible MPV sur macOS, mais utilisez son chemin exécutable, comme /Applications/IINA.app/Contents/MacOS/iina-cli ou /Applications/IINA.app/Contents/MacOS/IINA. Les contrôles IPTVnator, le suivi de position et la réutilisation d’instance ne sont garantis qu’avec MPV IPC.",
|
||
"MPV_PLAYER_PATH": "Chemin du lecteur mpv",
|
||
"MPV_PLAYER_ARGUMENTS_LABEL": "mpv command-line arguments",
|
||
"MPV_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to mpv when IPTVnator starts a new external player process.",
|
||
"MPV_PLAYER_ARGUMENTS": "mpv arguments",
|
||
"MPV_REUSE_INSTANCE_LABEL": "Réutiliser l'instance MPV",
|
||
"MPV_REUSE_INSTANCE_DESCRIPTION": "Lorsqu'elle est activée, la même fenêtre MPV sera réutilisée pour lire différents flux. Lorsqu'elle est désactivée, chaque flux s'ouvre dans une nouvelle fenêtre MPV.",
|
||
"RECORDING_FOLDER_LABEL": "Dossier d’enregistrement",
|
||
"RECORDING_FOLDER_DESCRIPTION": "Dossier des enregistrements de flux en direct depuis MPV intégré. Laissez vide pour utiliser le dossier Téléchargements du système.",
|
||
"RECORDING_FOLDER": "Dossier d’enregistrement",
|
||
"RECORDING_FOLDER_SELECT": "Choisir le dossier d’enregistrement",
|
||
"VLC_PLAYER_PATH_LABEL": "Chemin VLC",
|
||
"VLC_PLAYER_PATH_DESCRIPTION": "Définissez le chemin vers VLC. Sur macOS, vous pouvez utiliser le bundle de l’app VLC, comme /Applications/VLC.app, ou le chemin de l’exécutable.",
|
||
"VLC_PLAYER_PATH": "Chemin du lecteur VLC",
|
||
"VLC_PLAYER_ARGUMENTS_LABEL": "VLC command-line arguments",
|
||
"VLC_PLAYER_ARGUMENTS_DESCRIPTION": "Optional arguments passed to VLC when IPTVnator starts a new external player process.",
|
||
"VLC_PLAYER_ARGUMENTS": "VLC arguments",
|
||
"EXTERNAL_PLAYER_ARGUMENTS_HINT": "Use one argument per line. These values are applied before stream-specific IPTVnator options.",
|
||
"VLC_REUSE_INSTANCE_LABEL": "Réutiliser l'instance VLC",
|
||
"VLC_REUSE_INSTANCE_DESCRIPTION": "Lorsqu'elle est activée, la même fenêtre VLC sera réutilisée pour lire différents flux. Lorsqu'elle est désactivée, chaque flux s'ouvre dans une nouvelle fenêtre VLC.",
|
||
"REMOTE_CONTROL": "Télécommande",
|
||
"REMOTE_CONTROL_DESCRIPTION": "Activer la télécommande de l'application.",
|
||
"REMOTE_CONTROL_PORT": "Port de télécommande",
|
||
"REMOTE_CONTROL_PORT_DESCRIPTION": "Port utilisé pour la télécommande de l'application. Le port 8765 est utilisé par défaut.",
|
||
"REMOTE_CONTROL_URL": "URL de la télécommande",
|
||
"REMOTE_CONTROL_URL_DESCRIPTION": "Accédez à la télécommande depuis un autre appareil sur le même réseau.",
|
||
"SHOW_QR_CODE": "Afficher le QR code",
|
||
"STATUS_ENABLED": "Activé",
|
||
"STATUS_DISABLED": "Désactivé",
|
||
"SOURCE_SINGULAR": "source",
|
||
"SOURCE_PLURAL": "sources",
|
||
"PORT_RANGE_LABEL": "Port (1-65535)",
|
||
"PORT_REQUIRED": "Le port est requis",
|
||
"PORT_MIN": "Le port doit être au moins 1",
|
||
"PORT_MAX": "Le port doit être au plus 65535",
|
||
"PORT_PATTERN": "Seuls les chiffres sont autorisés",
|
||
"REMOVE_ALL_IN_PROGRESS": "Deleting playlist data…",
|
||
"REMOVE_ALL_PROGRESS": "Deleting playlist data… {{current}} / {{total}}",
|
||
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again.",
|
||
"NAV_DASHBOARD": "Dashboard",
|
||
"DASHBOARD": "Dashboard",
|
||
"DASHBOARD_SUBTITLE": "Choose which dashboard rails are shown in the workspace.",
|
||
"DASHBOARD_HERO": "Hero banner",
|
||
"DASHBOARD_HERO_DESCRIPTION": "Show the large top banner for your most recent item.",
|
||
"DASHBOARD_RAIL_CONTINUE": "Continue Watching",
|
||
"DASHBOARD_RAIL_CONTINUE_DESCRIPTION": "Show recently watched movies and series as cover cards.",
|
||
"DASHBOARD_RAIL_LIVE_FAVORITES": "Live now on your favorites",
|
||
"DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION": "Show favorite live channels with current EPG progress.",
|
||
"DASHBOARD_RAIL_RECENT_LIVE": "Recently watched live TV",
|
||
"DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION": "Show live channels you opened recently with current EPG progress.",
|
||
"DASHBOARD_RAIL_FAVORITE_VOD": "Favorite movies & series",
|
||
"DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION": "Show favorited movies and series as cover cards.",
|
||
"DASHBOARD_RAIL_SOURCES": "Recently used sources",
|
||
"DASHBOARD_RAIL_SOURCES_DESCRIPTION": "Show shortcuts to your most recently used playlists and portals.",
|
||
"DASHBOARD_RAIL_TMDB_TRENDING": "Tendances de la semaine (TMDB)",
|
||
"DASHBOARD_RAIL_TMDB_TRENDING_DESCRIPTION": "Afficher les films et séries en tendance hebdomadaire de TMDB, mis en correspondance avec vos bibliothèques. Nécessite l'activation des métadonnées TMDB.",
|
||
"DASHBOARD_RAIL_XTREAM_ADDED": "Recently added on Xtream",
|
||
"DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION": "Show the newest Xtream movies, series, and channels.",
|
||
"APP_UPDATE_TITLE": "Application update",
|
||
"APP_UPDATE_DESCRIPTION": "Check GitHub Releases for desktop app updates.",
|
||
"APP_UPDATE_IDLE": "Ready to check for updates",
|
||
"APP_UPDATE_CHECKING": "Checking for updates…",
|
||
"APP_UPDATE_AVAILABLE": "Version {{version}} is available",
|
||
"APP_UPDATE_NOT_AVAILABLE": "You are using the latest version",
|
||
"APP_UPDATE_DOWNLOADING": "Downloading version {{version}}…",
|
||
"APP_UPDATE_DOWNLOADED": "Version {{version}} is ready to install",
|
||
"APP_UPDATE_MANUAL_AVAILABLE": "Version {{version}} is available for manual installation",
|
||
"APP_UPDATE_ERROR": "Update check failed",
|
||
"APP_UPDATE_CHECK": "Check again",
|
||
"APP_UPDATE_DOWNLOAD": "Update",
|
||
"APP_UPDATE_INSTALL": "Restart application",
|
||
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
|
||
"APP_UPDATE_RELEASE_NOTES": "What's new",
|
||
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV : moteur frame-copy (expérimental)",
|
||
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "La vidéo est rendue sur le canevas de l'application, les menus et boîtes de dialogue s'affichent donc au-dessus.",
|
||
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Prend effet après le redémarrage de l'application.",
|
||
"VOD_AUTO_FAILOVER": "Changer de source automatiquement en cas d'erreur de lecture",
|
||
"VOD_AUTO_FAILOVER_DESCRIPTION": "Lorsqu'un film ne parvient pas à démarrer et que le même film existe dans une autre liste de lecture, bascule automatiquement vers celle-ci en conservant la position de lecture. Désactivé par défaut ; le changement est toujours annoncé, car une autre source peut proposer un doublage différent."
|
||
},
|
||
"THEMES": {
|
||
"DARK_THEME": "Sombre",
|
||
"LIGHT_THEME": "Clair",
|
||
"SYSTEM_THEME": "Système"
|
||
},
|
||
"AUDIO_PLAYER": {
|
||
"PLAY": "Lecture",
|
||
"PAUSE": "Pause",
|
||
"PREVIOUS_STATION": "Station précédente",
|
||
"NEXT_STATION": "Station suivante",
|
||
"MUTE": "Muet",
|
||
"UNMUTE": "Activer le son",
|
||
"VOLUME": "Volume"
|
||
},
|
||
"EMBEDDED_MPV": {
|
||
"PLAYER": {
|
||
"BACK": "Retour",
|
||
"RETRY": "Réessayer",
|
||
"STALLED": "Ce flux met plus de temps que prévu à démarrer.",
|
||
"PLAYBACK_FAILED": "Échec de la lecture avec MPV intégré.",
|
||
"CHECKING_SUPPORT": "Vérification de la prise en charge de MPV intégré...",
|
||
"NOT_AVAILABLE": "MPV intégré n'est pas disponible dans cet environnement.",
|
||
"LOADING_STREAM": "Chargement du flux…",
|
||
"PLAY": "Lecture",
|
||
"PAUSE": "Pause",
|
||
"PREVIOUS_EPISODE": "Épisode précédent",
|
||
"NEXT_EPISODE": "Épisode suivant",
|
||
"BACK_10_SECONDS": "Reculer de 10 secondes",
|
||
"FORWARD_10_SECONDS": "Avancer de 10 secondes",
|
||
"LIVE_STREAM": "Diffusion en direct",
|
||
"PLAYBACK_POSITION": "Position de lecture",
|
||
"MUTE": "Muet",
|
||
"UNMUTE": "Activer le son",
|
||
"MUTED": "Son coupé",
|
||
"VOLUME": "Volume",
|
||
"VOLUME_LABEL": "Volume {{percent}} %",
|
||
"AUDIO_TRACKS": "Pistes audio",
|
||
"SUBTITLES": "Sous-titres",
|
||
"SUBTITLES_OFF": "Désactivés",
|
||
"TRACK_DEFAULT": "Par défaut",
|
||
"AUDIO_TRACK_FALLBACK": "Audio {{index}}",
|
||
"SUBTITLE_TRACK_FALLBACK": "Sous-titre {{index}}",
|
||
"PLAYBACK_SPEED": "Vitesse de lecture",
|
||
"SPEED_TOOLTIP": "Vitesse : {{speed}}",
|
||
"ASPECT_RATIO": "Format d'image",
|
||
"ASPECT_TOOLTIP": "Format : {{aspect}}",
|
||
"ASPECT_DEFAULT": "Par défaut",
|
||
"START_RECORDING": "Démarrer l'enregistrement",
|
||
"STOP_RECORDING": "Arrêter l'enregistrement",
|
||
"RECORDING": "Enregistrement",
|
||
"RECORDING_SAVED": "Enregistrement sauvegardé",
|
||
"RECORDING_FAILED": "Échec de l'enregistrement",
|
||
"RECORDING_FAILED_TO_START": "L'enregistrement n'a pas pu démarrer.",
|
||
"RECORDING_FAILED_TO_STOP": "L'enregistrement n'a pas pu s'arrêter.",
|
||
"SAVED_TO": "Enregistré dans {{path}}",
|
||
"ENTER_PICTURE_IN_PICTURE": "Activer le mode image dans l’image",
|
||
"EXIT_PICTURE_IN_PICTURE": "Quitter le mode image dans l’image",
|
||
"ENTER_FULLSCREEN": "Passer en plein écran",
|
||
"EXIT_FULLSCREEN": "Quitter le plein écran"
|
||
}
|
||
},
|
||
"PLAYBACK_DIAGNOSTICS": {
|
||
"UNSUPPORTED_CONTAINER": {
|
||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||
"DESCRIPTION": "The browser player may not decode this container inline. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA for broader codec support."
|
||
},
|
||
"UNSUPPORTED_CODEC": {
|
||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||
"DESCRIPTION": "The stream advertises codecs that the browser player may not decode. Copy the stream URL and open it in an external player such as MPV, VLC, or IINA."
|
||
},
|
||
"MEDIA_DECODE_ERROR": {
|
||
"TITLE": "The browser player could not decode this stream",
|
||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||
},
|
||
"NETWORK_ERROR": {
|
||
"TITLE": "The stream could not be loaded",
|
||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||
},
|
||
"BROWSER_ACCESS_ERROR": {
|
||
"TITLE": "The browser player could not access this stream",
|
||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.",
|
||
"PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player."
|
||
},
|
||
"DRM_OR_ENCRYPTION": {
|
||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||
},
|
||
"UNKNOWN_PLAYBACK_ERROR": {
|
||
"TITLE": "The browser player reported a playback error",
|
||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||
},
|
||
"NATIVE_FALLBACK_TITLE": "Open it in a native player instead",
|
||
"INLINE_FAILURE_TITLE": "Copy the URL or try again",
|
||
"ACTION_OPEN_MPV": "Open in MPV",
|
||
"ACTION_OPEN_VLC": "Open in VLC",
|
||
"ACTION_OPEN_MPV_HINT": "configured player",
|
||
"ACTION_OPEN_VLC_HINT": "configured player",
|
||
"ACTION_COPY_URL": "Copy URL",
|
||
"ACTION_RETRY": "Retry",
|
||
"DETAILS_SUMMARY": "Technical details",
|
||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||
"DETAIL_CODE": "Diagnostic code",
|
||
"DETAIL_PLAYER": "Player",
|
||
"DETAIL_SOURCE": "Source",
|
||
"DETAIL_CONTAINER": "Container",
|
||
"DETAIL_MIME_TYPE": "MIME type",
|
||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||
"DETAIL_ERROR_DETAILS": "Error details"
|
||
},
|
||
"LAYOUT": {
|
||
"HIDE_CHANNELS_LIST": "Masquer la liste des chaînes",
|
||
"SHOW_CHANNELS_LIST": "Afficher la liste des chaînes",
|
||
"TOGGLE_SIDEBAR_TOOLTIP": "Basculer la liste des chaînes (⌘/Ctrl+B)"
|
||
},
|
||
"CHANNELS": {
|
||
"UPLOAD_OR_SELECT_OTHER_PLAYLIST": "Transférer ou choisir une autre liste de lecture",
|
||
"ALL_CHANNELS": "Toutes les chaînes",
|
||
"GROUPS": "Groupes",
|
||
"MANAGE_GROUPS": "Gérer les groupes",
|
||
"MANAGE_GROUPS_HINT": "Utilisez la gestion des groupes pour réafficher les groupes.",
|
||
"NO_VISIBLE_GROUPS": "Aucun groupe visible",
|
||
"SEARCH_GROUPS": "Rechercher des groupes",
|
||
"UNGROUPED": "Non-groupé",
|
||
"UNNAMED_CHANNEL": "Chaîne sans nom",
|
||
"FAVORITES": "Favoris",
|
||
"NO_FAVORITES": "Aucun favoris",
|
||
"USE_STAR_TO_FAVORITE": "Veuillez utiliser l'icône 'étoile' pour ajouter une chaîne aux favoris.",
|
||
"REMOVE_FAVORITE": "Retirer des favoris",
|
||
"ADD_FAVORITE": "Ajouter aux favoris",
|
||
"SHOW_DETAILS": "Afficher les détails de la chaîne",
|
||
"MAP_EPG": "Associer une chaîne EPG",
|
||
"FAVORITES_UPDATED": "Les favoris ont été mis à jour !",
|
||
"SELECT_CHANNEL_PLAYBACK": "Veuillez sélectionner une chaîne pour démarrer la lecture",
|
||
"SORT_BY": "Trier par",
|
||
"SORT_BY_DATE": "Trier par date d'ajout (Plus récent)",
|
||
"SORT_BY_RATING": "Trier par note",
|
||
"SORT_BY_NAME": "Trier par nom (A-Z)",
|
||
"LOAD_MORE": "Charger plus",
|
||
"REMAINING": "restantes",
|
||
"LOADING": "Chargement des chaînes…",
|
||
"LOADING_MORE": "Chargement…",
|
||
"NO_SEARCH_RESULTS": "Aucune chaîne trouvée",
|
||
"NO_CHANNELS_FOUND": "Aucune chaîne trouvée",
|
||
"NO_GROUPS_FOUND": "Aucun groupe trouvé",
|
||
"TRY_DIFFERENT_SEARCH": "Essayez un autre terme de recherche",
|
||
"DETAILS_DIALOG": {
|
||
"TITLE": "Détails de la chaîne",
|
||
"DESCRIPTION": "Métadonnées en lecture seule extraites du fichier de la liste de lecture.",
|
||
"SUMMARY": "Résumé",
|
||
"TVG": "Métadonnées TVG",
|
||
"CATCHUP": "Catchup",
|
||
"HTTP": "En-têtes HTTP",
|
||
"NAME": "Nom",
|
||
"CHANNEL_ID": "ID de la chaîne",
|
||
"STREAM_URL": "URL du flux",
|
||
"GROUP": "Groupe",
|
||
"RADIO": "Radio",
|
||
"CATCHUP_AVAILABLE": "Catchup disponible",
|
||
"WINDOW": "Fenêtre d'archivage",
|
||
"PLAYBACK_SUPPORTED": "Lecture prise en charge",
|
||
"EPG_PARAMS": "Paramètres EPG",
|
||
"TIMESHIFT": "Timeshift",
|
||
"TVG_ID": "TVG ID",
|
||
"TVG_NAME": "Nom TVG",
|
||
"TVG_URL": "URL TVG",
|
||
"TVG_LOGO": "Logo TVG",
|
||
"TVG_REC": "TVG rec",
|
||
"CATCHUP_TYPE": "Type de Catchup",
|
||
"CATCHUP_SOURCE": "Source du Catchup",
|
||
"CATCHUP_DAYS": "Jours de Catchup",
|
||
"HTTP_ORIGIN": "Origin",
|
||
"HTTP_REFERRER": "Referrer",
|
||
"HTTP_USER_AGENT": "User-Agent",
|
||
"EMPTY": "Non fourni",
|
||
"NOT_AVAILABLE": "Non disponible",
|
||
"DAYS_ONE": "1 jour",
|
||
"DAYS_OTHER": "{{count}} jours"
|
||
},
|
||
"GROUP_MANAGEMENT": {
|
||
"TITLE": "Gérer les groupes",
|
||
"SELECTED": "Sélectionnés",
|
||
"SELECT_ALL": "Tout sélectionner",
|
||
"DESELECT_ALL": "Tout désélectionner",
|
||
"SEARCH_PLACEHOLDER": "Rechercher des groupes…",
|
||
"NO_RESULTS": "Aucun groupe correspondant trouvé",
|
||
"SAVE": "Enregistrer"
|
||
}
|
||
},
|
||
"TOP_MENU": {
|
||
"OPEN_MULTI_EPG": "Ouvrir la vue Multi-EPG"
|
||
},
|
||
"EPG": {
|
||
"NEXT_DAY": "Jour suivant",
|
||
"PREVIOUS_DAY": "Jour précédent",
|
||
"ZOOM_IN": "Zoom avant",
|
||
"ZOOM_OUT": "Zoom arrière",
|
||
"FILTER_CHANNELS": "Filtrer les chaînes",
|
||
"CLOSE_FILTER": "Fermer le filtre",
|
||
"SEARCH_PROGRAMS": "Rechercher des programmes",
|
||
"CLOSE_PROGRAM_SEARCH": "Fermer la recherche de programmes",
|
||
"LIVE_STREAM": "Diffusion en direct",
|
||
"ARCHIVE_PLAYBACK_UNAVAILABLE_TITLE": "Lecture des archives indisponible",
|
||
"ARCHIVE_PLAYBACK_UNAVAILABLE_DESCRIPTION": "Ce fournisseur expose l'historique des programmes pour la chaîne, mais n'expose pas la lecture archive/catch-up.",
|
||
"EPG_NOT_AVAILABLE_CHANNEL_DESCRIPTION": "Veuillez ajouter/modifier l'URL de la liste EPG dans les réglages de l'application.",
|
||
"FETCH_EPG": "Récupération des données EPG...",
|
||
"ERROR": "L'EPG n'a pas pu être chargé.",
|
||
"DOWNLOAD_SUCCESS": "L'EPG a été téléchargé avec succès.",
|
||
"NO_PROGRAM_INFO": "Aucune information sur le programme disponible",
|
||
"PROGRAM_DIALOG": {
|
||
"PROGRAM_DETAILS": "Détails du programme TV",
|
||
"TITLE": "Titre",
|
||
"CATEGORY": "Catégorie",
|
||
"PARENTAL_RATING_SYSTEM": "Système de classification du contenu télévisé",
|
||
"DESCRIPTION": "Description",
|
||
"LANGUAGE": "Langue",
|
||
"SHOW_PROGRAM_DETAILS": "Afficher les détails de ce programme",
|
||
"EPISODE": "Épisode",
|
||
"DURATION": "Durée"
|
||
},
|
||
"FETCH_SUCCESS": "L'EPG a été récupéré avec succès",
|
||
"IMPORT_PROGRESS": "Importation EPG",
|
||
"CHANNELS": "chaînes",
|
||
"PROGRAMS": "programmes",
|
||
"LOADING": "Chargement",
|
||
"QUEUED": "En file d'attente",
|
||
"TOTAL_CHANNELS": "Total des chaînes",
|
||
"TOTAL_PROGRAMS": "Total des programmes",
|
||
"UP_TO_DATE": "L'EPG est à jour, aucune synchronisation nécessaire",
|
||
"RETRY": "Réessayer",
|
||
"ALLOW_PRIVATE_SOURCE": "Allow source",
|
||
"ALLOW_PRIVATE_SOURCE_TITLE": "Allow private-network EPG source?",
|
||
"ALLOW_PRIVATE_SOURCE_WARNING": "Only allow this if you trust the EPG source. IPTVnator will let this exact EPG URL connect to private or local network addresses.",
|
||
"TRUST_TLS_HOST": "Trust host",
|
||
"TRUST_TLS_HOST_TITLE": "Trust invalid certificate?",
|
||
"TRUST_TLS_HOST_WARNING": "Only continue if you trust this host. IPTVnator will allow invalid TLS certificates for this host, but other hosts still require valid certificates.",
|
||
"CURRENT_PROGRAM": "Current program",
|
||
"ARCHIVE_PLAYBACK": "Lecture archive",
|
||
"COLLAPSE_PANEL": "Collapse EPG panel",
|
||
"EXPAND_PANEL": "Expand EPG panel",
|
||
"TIMELINE": {
|
||
"TODAY": "Aujourd'hui",
|
||
"NOW": "Maintenant",
|
||
"JUMP_NOW": "Aller à maintenant",
|
||
"RETURN_TO_LIVE": "Retour au direct",
|
||
"ON_NOW": "En ce moment",
|
||
"MINUTES_LEFT": "min restantes",
|
||
"FROM_ARCHIVE": "Depuis les archives",
|
||
"WATCH": "Regarder",
|
||
"WATCH_LIVE": "Regarder en direct",
|
||
"WATCH_FROM_START": "Regarder depuis le début",
|
||
"ARCHIVE_UNAVAILABLE_NOTE": "Lecture indisponible — ce fournisseur n'expose pas d'archive.",
|
||
"CATCHUP_FAILED": "Impossible de démarrer la lecture archive pour ce programme",
|
||
"SCHEDULE_ONLY_NOTICE": "Programme uniquement. Ce fournisseur expose l'historique des programmes mais pas le catch-up — les programmes passés ne peuvent pas être lus.",
|
||
"EMPTY_DAY_TITLE": "Aucun programme pour ce jour",
|
||
"EMPTY_DAY_DESCRIPTION": "Le programme du jour sélectionné n'est pas encore arrivé. D'autres jours ont des données.",
|
||
"JUMP_TODAY": "Aller à aujourd'hui",
|
||
"JUMP_NEAREST": "Jour le plus proche avec programme",
|
||
"CHANNEL_UNMAPPED_TITLE": "Aucun programme pour cette chaîne",
|
||
"CHANNEL_UNMAPPED_DESCRIPTION": "Cette chaîne n'est pas associée à la source EPG (aucun tvg-id trouvé). Les autres chaînes ne sont pas affectées.",
|
||
"PROVIDER_NO_EPG_TITLE": "Le fournisseur ne fournit pas de guide",
|
||
"PROVIDER_NO_EPG_DESCRIPTION": "Cette source n'expose de guide TV pour aucune chaîne. Si vous avez besoin d'un programme, demandez à votre fournisseur.",
|
||
"M3U_SETUP_TITLE": "Connecter un guide des programmes",
|
||
"M3U_SETUP_DESCRIPTION": "Les listes M3U nécessitent une source EPG externe (XMLTV). Ajoutez un lien url-tvg et le programme apparaîtra ici.",
|
||
"OPEN_EPG_SETTINGS": "Ouvrir les paramètres EPG",
|
||
"ERROR_TITLE": "Impossible de charger le guide",
|
||
"ERROR_DESCRIPTION": "La source EPG n'a pas répondu. Vérifiez votre connexion ou actualisez la source.",
|
||
"RETRY": "Réessayer",
|
||
"MINUTES_SHORT": "min",
|
||
"ZOOM": "Zoom",
|
||
"ZOOM_DAY": "Vue de la journée",
|
||
"ZOOM_HOURS": "Par heure",
|
||
"ZOOM_DETAIL": "Détaillé",
|
||
"GROUP_SHORT": "progr. courts",
|
||
"GROUP_EXPAND": "afficher ›"
|
||
}
|
||
},
|
||
"EPG_MAPPING_DIALOG": {
|
||
"TITLE": "Associer une chaîne EPG",
|
||
"SUBTITLE": "Sélectionner manuellement une chaîne EPG pour « {{channelName}} »",
|
||
"SEARCH_PLACEHOLDER": "Rechercher des chaînes EPG...",
|
||
"CURRENT_MAPPING": "Association actuelle",
|
||
"NO_MAPPING": "Aucune association EPG définie",
|
||
"SAVE": "Enregistrer l'association",
|
||
"REMOVE": "Supprimer l'association",
|
||
"CLOSE": "Fermer",
|
||
"NO_RESULTS": "Aucune chaîne EPG trouvée",
|
||
"SEARCH_HINT": "Saisissez au moins {{min}} caractères pour rechercher",
|
||
"SAVED": "Association EPG enregistrée",
|
||
"REMOVED": "Association EPG supprimée",
|
||
"SAVE_FAILED": "Impossible de mettre à jour l'association EPG"
|
||
},
|
||
"LANGUAGES": {
|
||
"ARABIC": "العربية",
|
||
"MOROCCAN_ARABIC": "العربية (اللهجة المغربية)",
|
||
"ENGLISH": "English",
|
||
"KOREAN": "한국어",
|
||
"RUSSIAN": "Русский",
|
||
"GERMAN": "Deutsch",
|
||
"SPANISH": "Español",
|
||
"CHINESE": "简体中文",
|
||
"TRADITIONAL_CHINESE": "繁體中文",
|
||
"FRENCH": "Français",
|
||
"ITALIAN": "Italiano",
|
||
"TURKISH": "Türkçe",
|
||
"JAPANESE": "日本語",
|
||
"DUTCH": "Nederlands",
|
||
"BELARUSIAN": "Беларуская",
|
||
"POLISH": "Polski",
|
||
"PORTUGUESE": "Português",
|
||
"GREEK": "Ελληνικά",
|
||
"HUNGARIAN": "Magyar"
|
||
},
|
||
"YES": "Oui",
|
||
"NO": "Non",
|
||
"MENU": {
|
||
"OPEN": "Ouvrir le menu",
|
||
"SUPPORT_ARIA": "Soutenir IPTVnator sur GitHub",
|
||
"SETTINGS_ARIA": "Ouvrir les paramètres",
|
||
"BUG_REPORT": "Signaler un bug",
|
||
"WHAT_IS_NEW": "Nouveautés",
|
||
"ABOUT": "À propos",
|
||
"ABOUT_ARIA": "À propos de l'application",
|
||
"SUPPORT_PROJECT": "Soutenir le projet"
|
||
},
|
||
"ABOUT": {
|
||
"TITLE": "À propos d'IPTVnator",
|
||
"DESCRIPTION": "Application lecteur IPTV multiplateforme avec de nombreuses fonctionnalités, comme la prise en charge des listes m3u et m3u8, les favoris, le guide TV, les archives TV/rattrapage et plus encore.",
|
||
"VERSION": "Version",
|
||
"GITHUB_TOOLTIP": "Dépôt IPTVnator sur GitHub",
|
||
"BUY_ME_A_COFFEE_TOOLTIP": "Offrez-moi un café",
|
||
"TWITTER_TOOLTIP": "Partager sur Twitter"
|
||
},
|
||
"UPDATE_AVAILABLE": "Mise à jour disponible",
|
||
"REFRESH": "Actualiser",
|
||
"XTREAM": {
|
||
"PLAY": "Lire",
|
||
"OPENING_PLAYER": "Ouverture du lecteur…",
|
||
"NAME": "Nom",
|
||
"RELEASE_DATE": "Date de sortie",
|
||
"GENRE": "Genre",
|
||
"COUNTRY": "Pays",
|
||
"ACTORS": "Acteurs",
|
||
"DIRECTOR": "Réalisateur",
|
||
"SIMILAR": "Similaires",
|
||
"CREW_JOB_DIRECTOR": "Réalisateur",
|
||
"CREW_JOB_CREATOR": "Créateur",
|
||
"ACTOR_FILMOGRAPHY": "Filmographie",
|
||
"ACTOR_FILTER_ALL": "Tout",
|
||
"ACTOR_SCOPE_THIS_PORTAL": "Ce portail",
|
||
"ACTOR_SCOPE_ALL_PORTALS": "Tous les portails",
|
||
"ACTOR_NO_RESULTS": "Aucun résultat pour ce filtre",
|
||
"ACTOR_IN_LIBRARY": "Dans votre bibliothèque",
|
||
"ACTOR_SEARCH_IN_PORTAL": "Rechercher dans le portail",
|
||
"ACTOR_BORN": "Naissance :",
|
||
"ACTOR_DIED": "Décédé",
|
||
"SERIES_STATUS_RETURNING": "En cours",
|
||
"SERIES_STATUS_PLANNED": "Planifiée",
|
||
"SERIES_STATUS_IN_PRODUCTION": "En production",
|
||
"SERIES_STATUS_ENDED": "Terminée",
|
||
"SERIES_STATUS_CANCELED": "Annulée",
|
||
"SERIES_STATUS_PILOT": "Pilote",
|
||
"DURATION": "Durée",
|
||
"CAST": "Distribution",
|
||
"EPISODE_RUN_TIME": "Durée de l'épisode",
|
||
"RATING": "Note",
|
||
"IMDB_RATING": "Note IMDB",
|
||
"KINOPOISK_RATING": "Note Kinopoisk",
|
||
"DESCRIPTION": "Description",
|
||
"YEAR": "Année",
|
||
"AGE": "Âge",
|
||
"YOUTUBE_TRAILER": "Bande-annonce YouTube",
|
||
"EPISODE": "Épisode",
|
||
"RESUME": "Reprendre",
|
||
"RESTART": "Recommencer",
|
||
"CONTINUE_WATCHING": "Reprendre la lecture",
|
||
"PLAY_FIRST_EPISODE": "Lire le premier épisode",
|
||
"PLAY_EPISODE": "Lire l'épisode {{episode}}",
|
||
"RESUME_EPISODE": "Reprendre l'épisode",
|
||
"PLAY_NEXT_EPISODE": "Lire l'épisode suivant",
|
||
"SERIES_WATCHED": "Série vue",
|
||
"WATCHED": "Vu",
|
||
"IN_PROGRESS": "En cours",
|
||
"MARK_WATCHED": "Marquer comme vu",
|
||
"MARK_UNWATCHED": "Marquer comme non vu",
|
||
"EPISODES_WATCHED": "{{count}} sur {{total}} épisodes vus",
|
||
"DETAIL_FALLBACK": {
|
||
"STATUS": "Métadonnées du portail indisponibles",
|
||
"NOTE": "Les métadonnées étendues n'ont pas été fournies par ce portail.",
|
||
"STREAM_ID": "ID du flux",
|
||
"CATEGORY": "Catégorie",
|
||
"CONTAINER": "Format",
|
||
"SOURCE_TITLE": "Titre source"
|
||
},
|
||
"ACCOUNT_INFO": {
|
||
"TITLE": "Informations du compte",
|
||
"USER_INFO": "Informations utilisateur",
|
||
"STATUS": "Statut",
|
||
"USERNAME": "Nom d'utilisateur",
|
||
"ACTIVE_CONNECTIONS": "Connexions actives",
|
||
"CREATED": "Créé le",
|
||
"EXPIRES": "Expire le",
|
||
"TRIAL_ACCOUNT": "Compte d'essai",
|
||
"YES": "Oui",
|
||
"NO": "Non",
|
||
"ALLOWED_FORMATS": "Formats autorisés",
|
||
"SERVER_INFO": "Informations du serveur",
|
||
"LIVE_TV": "TV en direct",
|
||
"MOVIES": "Films",
|
||
"TV_SERIES": "Séries TV",
|
||
"URL": "URL",
|
||
"PROTOCOL": "Protocole",
|
||
"TIMEZONE": "Fuseau horaire",
|
||
"SERVER_TIME": "Heure du serveur",
|
||
"PORTS": "Ports",
|
||
"HTTP_PORT": "HTTP",
|
||
"HTTPS_PORT": "HTTPS",
|
||
"RTMP_PORT": "RTMP"
|
||
},
|
||
"CATEGORY_MANAGEMENT": {
|
||
"TITLE": "Gérer les catégories",
|
||
"LOADING": "Chargement des catégories…",
|
||
"SELECTED": "Sélectionnées",
|
||
"SELECT_ALL": "Tout sélectionner",
|
||
"DESELECT_ALL": "Tout désélectionner",
|
||
"SEARCH_PLACEHOLDER": "Rechercher des catégories…",
|
||
"NO_RESULTS": "Aucune catégorie correspondante trouvée",
|
||
"NO_CATEGORIES": "Aucune catégorie disponible",
|
||
"SAVE": "Enregistrer"
|
||
}
|
||
},
|
||
"PORTALS": {
|
||
"ALL_CATEGORIES": "Toutes les catégories",
|
||
"MY_FAVORITES": "Mes favoris",
|
||
"ADD_TO_FAVORITES": "Ajouter aux favoris",
|
||
"REMOVE_FROM_FAVORITES": "Retirer des favoris",
|
||
"REMOVED_FROM_FAVORITES": "Retiré des favoris",
|
||
"ADDED_TO_FAVORITES": "Ajouté aux favoris",
|
||
"FAVORITES": "Favoris",
|
||
"RECENTLY_VIEWED": "Récemment consultés",
|
||
"THIS_PLAYLIST": "Cette liste de lecture",
|
||
"ALL_PLAYLISTS": "Toutes les listes de lecture",
|
||
"LIVE_TV": "TV en direct",
|
||
"MOVIES": "Films",
|
||
"SERIES": "Séries",
|
||
"SEARCH": "Rechercher",
|
||
"NO_FAVORITES": "Aucun favori pour le moment",
|
||
"NO_RECENT_ITEMS": "Aucun élément récemment consulté",
|
||
"REMOVE_FROM_RECENT": "Retirer des éléments récemment consultés",
|
||
"BROWSE_ALL_MOVIES": "Parcourir tous les films",
|
||
"BROWSE_ALL_SERIES": "Parcourir toutes les séries",
|
||
"BROWSE_ALL_LIVE": "Parcourir toute la TV en direct",
|
||
"SIDEBAR": {
|
||
"MAIN": "Principal",
|
||
"MOVIES": "Films",
|
||
"LIVE_TV": "TV en direct",
|
||
"SERIES": "Séries",
|
||
"LIBRARY": "Bibliothèque",
|
||
"SEARCH": "Rechercher",
|
||
"RECENT": "Récent",
|
||
"RECENTLY_ADDED": "Récemment ajoutés",
|
||
"FAVORITES": "Favoris"
|
||
},
|
||
"ERROR_VIEW": {
|
||
"IMPORT_CANCELLED": {
|
||
"TITLE": "Importation annulée",
|
||
"DESCRIPTION": "L'importation de la liste de lecture a été annulée. Réessayez quand vous voudrez poursuivre le chargement de cette source."
|
||
},
|
||
"ACCOUNT_EXPIRED": {
|
||
"TITLE": "Compte expiré",
|
||
"DESCRIPTION": "Ce compte a expiré. Veuillez vérifier les identifiants dans les paramètres de la liste ou la supprimer."
|
||
},
|
||
"ACCOUNT_INACTIVE": {
|
||
"TITLE": "Compte inactif",
|
||
"DESCRIPTION": "Ce compte est inactif. Veuillez vérifier les identifiants dans les paramètres de la liste de lecture ou la supprimer."
|
||
},
|
||
"UNAUTHORIZED": {
|
||
"TITLE": "Non autorisé",
|
||
"DESCRIPTION": "Veuillez vérifier les identifiants dans les paramètres de la liste ou la supprimer."
|
||
},
|
||
"NOT_FOUND": {
|
||
"TITLE": "Portail non trouvé",
|
||
"DESCRIPTION": "Veuillez vérifier les identifiants et l'URL du portail dans les paramètres de la liste ou la supprimer."
|
||
},
|
||
"PORTAL_UNAVAILABLE": {
|
||
"TITLE": "Portail indisponible",
|
||
"DESCRIPTION": "Le portail n'a pas pu être joint. Vérifiez l'URL du serveur ou réessayez quand la source sera de nouveau disponible."
|
||
},
|
||
"EMPTY_CATEGORY": {
|
||
"TITLE": "Catégorie vide",
|
||
"DESCRIPTION": "Cette catégorie n'a pas de contenu, veuillez sélectionner une autre catégorie ou modifier le type de contenu"
|
||
},
|
||
"UNKNOWN_ERROR": {
|
||
"TITLE": "Une erreur s'est produite",
|
||
"DESCRIPTION": "Erreur inconnue, veuillez réessayer plus tard"
|
||
},
|
||
"NO_CATEGORY_SELECTED": {
|
||
"TITLE": "Aucune catégorie sélectionnée",
|
||
"DESCRIPTION": "Veuillez sélectionner une catégorie pour voir le contenu"
|
||
},
|
||
"PLAYLIST_SETTINGS": "Paramètres de la liste",
|
||
"HOME": "Accueil",
|
||
"DELETE": "Supprimer"
|
||
},
|
||
"EMPTY_LIST_VIEW": {
|
||
"TITLE": "Aucun résultat trouvé",
|
||
"NO_SEARCH_RESULTS": "Rien trouvé, essayez de modifier votre recherche"
|
||
},
|
||
"SEARCH_VIEW": {
|
||
"INITIAL_TITLE": "Rechercher du contenu",
|
||
"INITIAL_DESCRIPTION": "Saisissez au moins 3 caractères pour effectuer une recherche dans la TV en direct, les films et les séries.",
|
||
"RESULTS_FOUND": "{{count}} résultats trouvés",
|
||
"NO_RESULTS_FOR": "Aucun résultat trouvé pour \"{{term}}\"",
|
||
"GROUP_BY_PLAYLIST": "Grouper les résultats par liste de lecture",
|
||
"VISIBLE_CATEGORIES_ONLY": "Catégories visibles uniquement",
|
||
"GLOBAL_SEARCH_SOURCE_NOTE": "Global search includes results from Xtream and M3U playlists.",
|
||
"GLOBAL_INITIAL_DESCRIPTION": "Enter at least 2 characters to search across Xtream and M3U playlists.",
|
||
"LOADING_MORE_RESULTS": "Loading more results..."
|
||
},
|
||
"STREAM_URL_COPIED": "URL du flux copiée dans le presse-papiers",
|
||
"COPY_STREAM_URL": "Copier l'URL du flux",
|
||
"NOW_PLAYING": "En cours de lecture",
|
||
"UP_NEXT": "À suivre",
|
||
"CLOSE_PLAYER": "Fermer le lecteur",
|
||
"ABOUT": "À propos",
|
||
"SEASONS_AND_EPISODES": "Saisons et épisodes",
|
||
"SEASON_TAB": "Saison {{number}}",
|
||
"BACK_TO_PLAYING_EPISODE": "Retour à l'épisode en cours",
|
||
"GRID_VIEW": "Vue en grille",
|
||
"LIST_VIEW": "Vue en liste",
|
||
"NO_EPISODES_AVAILABLE": "Aucun épisode disponible",
|
||
"NO_EPISODES_AVAILABLE_MESSAGE": "Cette série est répertoriée par votre fournisseur, mais aucune saison ni épisode n'est disponible à la lecture.",
|
||
"SEASON_EMPTY": "Cette saison est vide",
|
||
"SEASON_EMPTY_MESSAGE": "Cette saison est répertoriée par votre fournisseur, mais aucun épisode lisible n'est disponible.",
|
||
"EPISODE_INFO": "Détails de l'épisode",
|
||
"SELECT_CATEGORY": "Sélectionner une catégorie",
|
||
"PAGE": "Page",
|
||
"CONTENT_NOT_AVAILABLE": "Ce contenu n'est pas disponible sur ce serveur",
|
||
"PLAYBACK_ERROR": "Échec de l'obtention de l'URL de lecture",
|
||
"LOADING_SEASONS": "Chargement des saisons…",
|
||
"LOADING_ALL_CHANNELS": "Chargement de la liste complète des chaînes…",
|
||
"REFRESH_CHANNEL_LIST": "Actualiser la liste des chaînes",
|
||
"NO_CHANNELS_IN_CATEGORY": "Aucune chaîne dans cette catégorie",
|
||
"ITEM": "élément",
|
||
"ITEMS": "éléments",
|
||
"NO_EPISODES": "Aucun épisode disponible",
|
||
"ALL_RADIO": "Toutes les radios",
|
||
"MULTI_SOURCE": {
|
||
"SOURCES": "Sources",
|
||
"SOURCE": "Source",
|
||
"SOURCES_FOR": "Sources pour « {{title}} »",
|
||
"OPEN_SOURCES": "Afficher les autres sources",
|
||
"MATCHED_BY_TMDB": "correspondance trouvée via TMDB",
|
||
"MATCHED_BY_TITLE": "correspondance trouvée d'après le titre",
|
||
"MATCH_BY_TITLE": "par titre",
|
||
"PLAYING": "En lecture",
|
||
"CURRENT": "Actuelle",
|
||
"PLAY_FROM_SOURCE": "Lire depuis cette source",
|
||
"PIN_SOURCE": "Définir comme source principale",
|
||
"UNPIN_SOURCE": "Ne plus utiliser comme source principale",
|
||
"CHECK": "vérifier",
|
||
"CHECK_TOOLTIP": "Vérifier la disponibilité",
|
||
"PROBE_OK": "disponible",
|
||
"PROBE_FAIL": "indisponible",
|
||
"PROBE_CHECKING": "vérification",
|
||
"AUTO_FAILOVER": "Bascule automatique en cas d'erreur",
|
||
"PLAYING_FROM": "Lecture depuis :",
|
||
"SWITCH_TITLE": "Source indisponible — bascule vers {{playlist}}",
|
||
"SWITCH_DETAIL": "reprise à {{timecode}}",
|
||
"SWITCH_AUDIO_WARNING": "le doublage peut être différent",
|
||
"SWITCHED_TO": "Basculé vers {{playlist}}",
|
||
"UNDO": "Annuler",
|
||
"CHOOSE_OTHER": "Choisir une autre source",
|
||
"TRY_ANOTHER_SOURCE": "Essayer une autre source",
|
||
"ERROR_HAS_ALTERNATIVES": "{{playlist}} ne diffuse pas ce flux. Ce film est présent dans vos autres listes de lecture :",
|
||
"RESUME_KEPT": "la position de lecture sera conservée · {{timecode}}",
|
||
"ALL_SOURCES_TRIED": "Toutes les sources ont été essayées.",
|
||
"ALSO_FOUND_IN_ONE": "— également disponible dans une autre liste de lecture",
|
||
"ALSO_FOUND_IN_OTHER": "— également disponible dans {{count}} autres listes de lecture",
|
||
"CHECK_ALL": "tout vérifier",
|
||
"FILTER_PLACEHOLDER": "Filtrer par liste de lecture",
|
||
"NO_MATCHES": "Aucune liste de lecture ne correspond à ce filtre",
|
||
"COPIES": "{{count}} copies dans cette liste de lecture",
|
||
"MORE_SOURCES": "+{{count}} dans la liste des sources"
|
||
}
|
||
},
|
||
"INFORMATION": "Information",
|
||
"DOWNLOADS": {
|
||
"TITLE": "Téléchargements",
|
||
"DOWNLOAD": "Télécharger",
|
||
"PLAY_LOCAL": "Lire local",
|
||
"EMPTY": "Aucun téléchargement",
|
||
"EMPTY_HINT": "Téléchargez des films et épisodes pour les regarder hors ligne",
|
||
"NO_PLAYLISTS_TITLE": "Add a playlist to start downloading",
|
||
"NO_PLAYLISTS_BODY": "Once you add a playlist, you can save movies and episodes here for offline playback.",
|
||
"NO_ITEMS_BODY": "Use the download button on any movie or episode to save it for offline playback.",
|
||
"NO_FILTER_MATCH": "No downloads match the current filter",
|
||
"NO_FILTER_MATCH_HINT": "Try a different search term",
|
||
"NOT_AVAILABLE": "Téléchargements non disponibles",
|
||
"DESKTOP_ONLY": "Cette fonctionnalité n'est disponible que dans l'application de bureau",
|
||
"NO_FOLDER": "Aucun dossier de téléchargement sélectionné",
|
||
"CHANGE_FOLDER": "Changer de dossier",
|
||
"CLEAR_COMPLETED": "Effacer terminés",
|
||
"CLEAR_COMPLETED_DIALOG": {
|
||
"TITLE": "Effacer les téléchargements terminés",
|
||
"MESSAGE": "Cela retire les entrées terminées, échouées et annulées de la vue actuelle des téléchargements. Cela ne supprime aucun fichier déjà téléchargé sur le disque."
|
||
},
|
||
"SOURCE_PLAYLIST_MISSING": "Ce téléchargement ne peut plus être ouvert dans la bibliothèque car sa liste de lecture source a été supprimée.",
|
||
"FILE_NOT_FOUND": "Ce fichier téléchargé n'est plus disponible sur le disque.",
|
||
"FILE_ACTION_ERROR": "L'action sur le fichier n'a pas pu être effectuée.",
|
||
"ACTION_FAILED": "Échec de l'action",
|
||
"PAUSE": "Mettre en pause",
|
||
"RESUME": "Reprendre",
|
||
"CANCEL": "Annuler",
|
||
"RETRY": "Réessayer",
|
||
"REMOVE": "Supprimer",
|
||
"PLAY": "Lire",
|
||
"REVEAL": "Afficher dans le dossier",
|
||
"COPY_URL": "Copier l'URL de téléchargement",
|
||
"URL_COPIED": "URL de téléchargement copiée dans le presse-papiers",
|
||
"URL_COPY_FAILED": "Échec de la copie de l'URL dans le presse-papiers",
|
||
"STATUS": {
|
||
"QUEUED": "En attente",
|
||
"DOWNLOADING": "En cours",
|
||
"COMPLETED": "Terminé",
|
||
"FAILED": "Échec",
|
||
"CANCELED": "Annulé",
|
||
"PAUSED": "En pause"
|
||
}
|
||
},
|
||
"REMOTE_CONTROL": {
|
||
"TITLE": "Télécommande",
|
||
"SUBTITLE": "Contrôlez votre lecteur IPTV à distance",
|
||
"UP_CHANNEL": "Chaîne précédente",
|
||
"DOWN_CHANNEL": "Chaîne suivante",
|
||
"HEADER": "Télécommande IPTVNator",
|
||
"FOOTER": "Télécommande IPTVNator"
|
||
},
|
||
"WORKSPACE": {
|
||
"FAVORITES_ONLY": "Favoris uniquement",
|
||
"SORT_CUSTOM": "Ordre personnalisé",
|
||
"SORT_DATE_DESC": "Date d'ajout (plus récents en premier)",
|
||
"SORT_DATE_ASC": "Date d'ajout (plus anciens en premier)",
|
||
"SORT_NAME_ASC": "Nom A-Z",
|
||
"SORT_NAME_DESC": "Nom Z-A",
|
||
"SORT_TOP_RATED": "Top rated",
|
||
"SORT_LOWEST_RATED": "Lowest rated first",
|
||
"REFINE_ACTION": "Refine",
|
||
"REFINE_SORT_SECTION": "Sort by",
|
||
"REFINE_RATING_SECTION": "Minimum rating",
|
||
"REFINE_CLEAR_RATING": "Clear {{value}}.0 and higher rating filter",
|
||
"FILTER_RATING": "Filter by rating",
|
||
"FILTER_RATING_ANY": "Any rating",
|
||
"FILTER_RATING_MIN": "{{value}}.0 and higher",
|
||
"SORT_LABEL": "Trier : ",
|
||
"SORT_SERVER": "Ordre du serveur",
|
||
"CATEGORY_SORT_ARIA": "Trier les catégories",
|
||
"DASHBOARD": {
|
||
"CONTINUE_WATCHING": "Reprendre la lecture",
|
||
"LIVE_FAVORITES": "En direct sur vos favoris",
|
||
"LIVE_RECENT": "Reprendre la TV en direct",
|
||
"REMAINING_SECONDS": "Il reste {{seconds}} s",
|
||
"REMAINING_MINUTES": "Il reste {{minutes}} min",
|
||
"REMAINING_HOURS": "Il reste {{hours}} h",
|
||
"REMAINING_HOURS_MINUTES": "Il reste {{hours}} h {{minutes}} min",
|
||
"PERCENT_WATCHED": "{{value}}% regardé",
|
||
"UNTITLED_SOURCE": "Source sans titre",
|
||
"GLOBAL_FAVORITES": "Favoris globaux",
|
||
"REMOVE_FROM_HISTORY": "Retirer de l'historique",
|
||
"RECENT_SOURCES": "Sources récemment utilisées",
|
||
"MANAGE_ALL": "Tout gérer",
|
||
"SEE_ALL": "Voir tout",
|
||
"SEE_ALL_COUNT": "Voir les {{count}}",
|
||
"RECENTLY_WATCHED": "Récemment regardés",
|
||
"XTREAM_RECENTLY_ADDED": "Récemment ajoutés sur Xtream",
|
||
"TMDB_TRENDING": "Tendances de la semaine",
|
||
"NO_HISTORY": "Aucun élément trouvé pour ce filtre.",
|
||
"NO_HISTORY_HINT": "Votre historique de visionnage apparaîtra ici une fois que vous aurez commencé à lire du contenu depuis vos listes de lecture.",
|
||
"XTREAM": "Xtream",
|
||
"STALKER": "Stalker",
|
||
"M3U": "M3U",
|
||
"STALKER_PORTAL": "Portail Stalker",
|
||
"PROVIDER": "Fournisseur",
|
||
"TYPE_LIVE": "Direct",
|
||
"TYPE_MOVIE": "Film",
|
||
"TYPE_SERIES": "Série",
|
||
"SEASON_EPISODE_BADGE": "S{{season}}·E{{episode}}",
|
||
"NOT_YET_SYNCED": "Pas encore synchronisé",
|
||
"RECENTLY_WATCHED_LIVE_TV": "Recently watched live TV",
|
||
"FAVORITE_MOVIES_AND_SERIES": "Favorite movies & series"
|
||
},
|
||
"SHELL": {
|
||
"BRAND_ALT": "IPTVnator",
|
||
"RAIL_DASHBOARD": "Tableau de bord",
|
||
"OPEN_DASHBOARD": "Ouvrir le tableau de bord",
|
||
"RAIL_SOURCES": "Sources",
|
||
"OPEN_SOURCES": "Ouvrir les sources",
|
||
"RAIL_GLOBAL_FAVORITES": "Favoris",
|
||
"RAIL_GLOBAL_RECENT": "Récemment consultés",
|
||
"RAIL_GLOBAL_SEARCH": "Global search",
|
||
"RAIL_SETTINGS": "Paramètres",
|
||
"OPEN_SETTINGS": "Ouvrir les paramètres",
|
||
"RAIL_LIBRARY": "Bibliothèque",
|
||
"RAIL_MOVIES": "Films",
|
||
"RAIL_LIVE": "TV en direct",
|
||
"RAIL_SERIES": "Séries",
|
||
"RAIL_RECENTLY_ADDED": "Récemment ajoutés",
|
||
"RAIL_SEARCH": "Recherche avancée",
|
||
"RAIL_RECENT": "Récemment consultés",
|
||
"RAIL_FAVORITES": "Favoris",
|
||
"RAIL_DOWNLOADS": "Téléchargements",
|
||
"EXTERNAL_PLAYBACK_CLOSE": "Close player",
|
||
"RAIL_ALL_CHANNELS": "Toutes les chaînes",
|
||
"RAIL_GROUPS": "Groupes",
|
||
"RAIL_CONTEXT_ACTIONS": "Actions de section",
|
||
"SEARCH_LABEL": "Rechercher",
|
||
"SEARCH_SOURCES_PLACEHOLDER": "Rechercher des sources (toutes les listes)…",
|
||
"SEARCH_GLOBAL_PLACEHOLDER": "Search all playlists...",
|
||
"SEARCH_PLAYLIST_PLACEHOLDER": "Rechercher dans cette liste de lecture…",
|
||
"SEARCH_SECTION_PLACEHOLDER": "Rechercher dans cette section…",
|
||
"FILTER_SECTION_PLACEHOLDER": "Filtrer cette section…",
|
||
"SEARCH_STATUS_LOADED_ONLY": "Chaînes chargées uniquement",
|
||
"COMMAND_PALETTE_SHORTCUT": "Palette de commandes (Ctrl/Cmd+K)",
|
||
"OPEN_COMMAND_PALETTE": "Ouvrir la palette de commandes",
|
||
"ADD_PLAYLIST_ALL": "Ajouter une liste de lecture",
|
||
"ADD_PLAYLIST": "Ajouter une liste de lecture",
|
||
"OPEN_DOWNLOADS": "Ouvrir les téléchargements",
|
||
"OPEN_CONTEXT_ACTIONS": "Ouvrir les actions de section",
|
||
"NO_SECTION_ACTIONS": "Aucune action pour cette section",
|
||
"PLAYLIST_INFO": "Infos de la liste de lecture",
|
||
"ACCOUNT_INFO": "Infos du compte",
|
||
"REFRESH_PLAYLIST": "Actualiser la liste de lecture",
|
||
"CLEAR_COMPLETED_DOWNLOADS_ALL": "Effacer les téléchargements terminés (toutes les listes)",
|
||
"CLEAR_COMPLETED_DOWNLOADS_ALL_ARIA": "Effacer les téléchargements terminés pour toutes les listes de lecture",
|
||
"CLEAR_COMPLETED_DOWNLOADS_THIS_PLAYLIST": "Effacer les téléchargements terminés (cette liste)",
|
||
"CLEAR_COMPLETED_DOWNLOADS_THIS_PLAYLIST_ARIA": "Effacer les téléchargements terminés pour cette liste de lecture",
|
||
"CLEAR_RECENTLY_VIEWED_SECTION": "Effacer les éléments récemment consultés",
|
||
"CLEAR_RECENTLY_VIEWED_SECTION_ARIA": "Effacer les éléments récemment consultés pour cette section",
|
||
"CLEAR_RECENTLY_VIEWED_TYPE": "Effacer les {{type}} récemment consultés",
|
||
"CLEAR_RECENTLY_VIEWED_DIALOG_TITLE": "Effacer les {{type}} récemment consultés ?",
|
||
"CLEAR_RECENTLY_VIEWED_DIALOG_MESSAGE_PLAYLIST": "Tous les éléments {{type}} de cette liste de lecture seront retirés de votre historique récemment consulté. Cette action est irréversible.",
|
||
"CLEAR_RECENTLY_VIEWED_DIALOG_MESSAGE_ALL": "Tous les éléments {{type}} de toutes les listes de lecture seront retirés de votre historique récemment consulté. Cette action est irréversible.",
|
||
"CLEAR_FAVORITES_TYPE": "Effacer les favoris {{type}}",
|
||
"CLEAR_FAVORITES_DIALOG_TITLE": "Retirer tous les favoris {{type}} ?",
|
||
"CLEAR_FAVORITES_DIALOG_MESSAGE_PLAYLIST": "Tous les favoris {{type}} de cette liste de lecture seront retirés. Cette action est irréversible.",
|
||
"CLEAR_FAVORITES_DIALOG_MESSAGE_ALL": "Tous les favoris {{type}} de toutes les listes de lecture seront retirés. Cette action est irréversible.",
|
||
"XTREAM_CODE": "Xtream Code",
|
||
"STALKER_PORTAL": "Portail Stalker",
|
||
"CHANNELS_COUNT": "{{count}} chaînes",
|
||
"NO_SOURCES_AVAILABLE": "Aucune source disponible",
|
||
"ONE_SOURCE_AVAILABLE": "1 source disponible",
|
||
"SOURCES_AVAILABLE": "{{count}} sources disponibles",
|
||
"GO_TO_DASHBOARD": "Back to dashboard",
|
||
"GO_TO_SOURCES": "Go to Sources",
|
||
"DROP_OVERLAY_TITLE": "Drop to add playlist",
|
||
"DROP_OVERLAY_BODY": "Release an .m3u or .m3u8 file anywhere in the workspace to import it.",
|
||
"DROP_OVERLAY_REJECTED_BODY": "Please drop an .m3u or .m3u8 file. Press Esc to dismiss.",
|
||
"DROP_OVERLAY_REJECTED_UNSUPPORTED": "Unsupported file type",
|
||
"DROP_OVERLAY_REJECTED_EMPTY": "That file is empty",
|
||
"DROP_OVERLAY_REJECTED_READ_ERROR": "Could not read that file",
|
||
"DROP_IMPORT_SUCCESS": "Importing \"{{title}}\"…",
|
||
"XTREAM_IMPORT_TITLE": "Synchronisation de la liste de lecture",
|
||
"XTREAM_IMPORT_REMOTE_BADGE": "Source distante",
|
||
"XTREAM_IMPORT_LOCAL_BADGE": "Bibliothèque locale",
|
||
"XTREAM_IMPORT_DETAIL_REMOTE": "En attente de la réponse du fournisseur.",
|
||
"XTREAM_IMPORT_DETAIL_LOCAL": "Préparation d'une bascule plus rapide entre listes de lecture pour la prochaine visite.",
|
||
"XTREAM_IMPORT_PREPARING": "Préparation de la bibliothèque locale…",
|
||
"XTREAM_IMPORT_LOADING": "Récupération des données de la liste depuis la source…",
|
||
"XTREAM_IMPORT_SAVING": "Enregistrement des données de la liste localement…",
|
||
"XTREAM_IMPORT_RESTORING_FAVORITES": "Restauration des favoris depuis la bibliothèque locale…",
|
||
"XTREAM_IMPORT_RESTORING_RECENT": "Restauration des éléments récemment consultés depuis la bibliothèque locale…",
|
||
"XTREAM_IMPORT_PROGRESS": "{{type}} importé : {{current}} / {{total}}",
|
||
"XTREAM_IMPORT_CANCEL": "Arrêter la synchronisation",
|
||
"XTREAM_IMPORT_CANCELLING": "Arrêt de la synchronisation…",
|
||
"COMMANDS": {
|
||
"GLOBAL_SEARCH_LABEL": "Search all playlists",
|
||
"GLOBAL_SEARCH_DESCRIPTION": "Open global search",
|
||
"PLAYLIST_SEARCH_LABEL": "Recherche avancée dans cette liste de lecture",
|
||
"PLAYLIST_SEARCH_DESCRIPTION": "Ouvrir la recherche avancée de la liste de lecture",
|
||
"OPEN_GLOBAL_FAVORITES_LABEL": "Ouvrir les favoris globaux",
|
||
"OPEN_GLOBAL_FAVORITES_DESCRIPTION": "Naviguer vers les favoris agrégés",
|
||
"OPEN_DOWNLOADS_LABEL": "Ouvrir les téléchargements",
|
||
"OPEN_DOWNLOADS_DESCRIPTION": "Naviguer vers la vue des téléchargements",
|
||
"OPEN_GLOBAL_RECENT_LABEL": "Ouvrir les éléments récemment consultés",
|
||
"OPEN_GLOBAL_RECENT_DESCRIPTION": "Ouvrir la page globale des éléments récemment consultés",
|
||
"OPEN_SETTINGS_LABEL": "Ouvrir les paramètres",
|
||
"OPEN_SETTINGS_DESCRIPTION": "Naviguer vers les paramètres de l'application",
|
||
"OPEN_SOURCES_LABEL": "Gérer les sources",
|
||
"OPEN_SOURCES_DESCRIPTION": "Naviguer vers la gestion des sources",
|
||
"OPEN_DASHBOARD_LABEL": "Ouvrir le tableau de bord",
|
||
"OPEN_DASHBOARD_DESCRIPTION": "Naviguer vers le tableau de bord",
|
||
"ADD_PLAYLIST_LABEL": "Ajouter une nouvelle source",
|
||
"ADD_PLAYLIST_DESCRIPTION": "Ouvrir la fenêtre d'ajout de liste de lecture",
|
||
"ADD_PLAYLIST_M3U_LABEL": "Add M3U playlist",
|
||
"ADD_PLAYLIST_M3U_DESCRIPTION": "Open add dialog on the M3U file or URL tab",
|
||
"ADD_PLAYLIST_XTREAM_LABEL": "Add Xtream Codes playlist",
|
||
"ADD_PLAYLIST_XTREAM_DESCRIPTION": "Open add dialog on the Xtream Codes tab",
|
||
"ADD_PLAYLIST_STALKER_LABEL": "Add Stalker portal",
|
||
"ADD_PLAYLIST_STALKER_DESCRIPTION": "Open add dialog on the Stalker Portal tab",
|
||
"REFRESH_PLAYLIST_LABEL": "Actualiser la liste de lecture",
|
||
"REFRESH_PLAYLIST_DESCRIPTION": "Resynchroniser les données depuis la source distante",
|
||
"PLAYLIST_INFO_LABEL": "Détails de la liste de lecture",
|
||
"PLAYLIST_INFO_DESCRIPTION": "Afficher les informations de la liste de lecture en cours",
|
||
"ACCOUNT_INFO_LABEL": "Infos du compte",
|
||
"ACCOUNT_INFO_DESCRIPTION": "Afficher les détails du compte du serveur Xtream",
|
||
"OPEN_VIEW_DESCRIPTION": "Naviguer vers {{view}}",
|
||
"CLEAR_CURRENT_VIEW_DESCRIPTION": "Effacer tous les éléments {{type}} visibles dans cette vue",
|
||
"OPEN_MULTI_EPG_DESCRIPTION": "Ouvrir la vue EPG multi-chaînes",
|
||
"GO_TO_VOD_LABEL": "Films",
|
||
"GO_TO_VOD_DESCRIPTION": "Aller à la section films",
|
||
"GO_TO_LIVE_LABEL": "TV en direct",
|
||
"GO_TO_LIVE_DESCRIPTION": "Aller à la section TV en direct",
|
||
"GO_TO_SERIES_LABEL": "Séries",
|
||
"GO_TO_SERIES_DESCRIPTION": "Aller à la section séries",
|
||
"SWITCH_PLAYER_LABEL": "Basculer le lecteur sur {{name}}",
|
||
"SWITCH_PLAYER_DESCRIPTION": "Définir {{name}} comme lecteur vidéo actif",
|
||
"SWITCH_PLAYER_FEEDBACK": "Lecteur défini sur {{name}}"
|
||
},
|
||
"XTREAM_REFRESH_TITLE": "Actualisation de la playlist",
|
||
"XTREAM_REFRESH_COLLECTING_DATA": "Préservation des données de votre bibliothèque...",
|
||
"XTREAM_REFRESH_DELETING_CONTENT": "Suppression des flux en cache...",
|
||
"XTREAM_REFRESH_DELETING_CATEGORIES": "Suppression des catégories en cache...",
|
||
"XTREAM_REFRESH_DETAIL_LOCAL": "Les favoris, l’historique de lecture, les catégories masquées et la progression sont préparés pour être restaurés après la synchronisation.",
|
||
"XTREAM_REFRESH_PROGRESS": "Enregistrements locaux traités : {{current}} / {{total}}",
|
||
"RAIL_RADIO": "Radio"
|
||
},
|
||
"SOURCES": {
|
||
"TITLE": "Sources",
|
||
"SUBTITLE": "Filtrer et trier les listes de lecture",
|
||
"TYPE": "Type",
|
||
"ALL": "Toutes",
|
||
"M3U_PLAYLISTS": "Listes de lecture M3U",
|
||
"XTREAM_PLAYLISTS": "Listes de lecture Xtream",
|
||
"STALKER_PLAYLISTS": "Listes de lecture Stalker",
|
||
"ALL_PLAYLISTS": "Toutes les listes de lecture",
|
||
"PLAYLIST_COUNT_ONE": "1 liste de lecture",
|
||
"PLAYLIST_COUNT_OTHER": "{{count}} listes de lecture"
|
||
},
|
||
"CONTEXT": {
|
||
"CATEGORIES": "Catégories",
|
||
"MOVIE_CATEGORIES": "Catégories de films",
|
||
"SERIES_CATEGORIES": "Catégories de séries",
|
||
"LIVE_CATEGORIES": "Catégories en direct",
|
||
"XTREAM_SYNCING_MOVIES": "Synchronisation des catégories de films…",
|
||
"XTREAM_SYNCING_SERIES": "Synchronisation des catégories de séries…",
|
||
"XTREAM_SYNCING_LIVE": "Synchronisation des catégories en direct…",
|
||
"XTREAM_SOURCE": "Source Xtream",
|
||
"STALKER_PORTAL": "Portail Stalker",
|
||
"MANAGE_CATEGORIES": "Gérer les catégories",
|
||
"LOAD_CATEGORIES_ERROR": "Impossible de charger les catégories",
|
||
"LOAD_CATEGORIES_ERROR_HINT": "Veuillez réessayer ou vérifier les paramètres du portail.",
|
||
"FILTER_BY_TYPE": "Filtrer par type",
|
||
"ITEM_COUNT_ONE": "1 élément",
|
||
"ITEM_COUNT_OTHER": "{{count}} éléments",
|
||
"SEARCH": "Rechercher",
|
||
"SEARCH_CATEGORIES": "Rechercher des catégories",
|
||
"RADIO_CATEGORIES": "Catégories radio"
|
||
},
|
||
"COMMAND_PALETTE": {
|
||
"TITLE": "Palette de commandes",
|
||
"SUBTITLE": "Actions globales et contextuelles",
|
||
"SEARCH_PLACEHOLDER": "Saisir pour filtrer les commandes",
|
||
"SEARCH_LABEL": "Filtrer les commandes",
|
||
"EMPTY": "Aucune commande trouvée",
|
||
"GROUP_GLOBAL": "Actions globales",
|
||
"GROUP_PLAYLIST": "Cette liste de lecture",
|
||
"GROUP_VIEW": "Vue actuelle",
|
||
"GROUP_SECTION": "Cette section",
|
||
"GROUP_RECENT": "Récemment utilisées"
|
||
},
|
||
"SHORTCUTS": {
|
||
"TITLE": "Keyboard shortcuts",
|
||
"SUBTITLE": "Workspace, navigation, playback, and dialog shortcuts available in IPTVnator.",
|
||
"OPEN_TOOLTIP": "Keyboard shortcuts (?)",
|
||
"OPEN_ARIA": "Open keyboard shortcuts",
|
||
"PLATFORM": {
|
||
"MAC": "macOS layout",
|
||
"OTHER": "Windows/Linux layout"
|
||
},
|
||
"GROUPS": {
|
||
"GLOBAL": "Global",
|
||
"NAVIGATION": "Navigation",
|
||
"PLAYBACK": "Playback",
|
||
"DIALOGS": "Dialogs and lists"
|
||
},
|
||
"ITEMS": {
|
||
"OPEN_KEYBOARD_SHORTCUTS": "Open this shortcuts list",
|
||
"OPEN_COMMAND_PALETTE": "Open command palette",
|
||
"OPEN_GLOBAL_SEARCH": "Open global search",
|
||
"OPEN_RECENTLY_VIEWED": "Open recently viewed",
|
||
"SUBMIT_SHELL_SEARCH": "Submit workspace search",
|
||
"TOGGLE_SIDEBAR": "Toggle sidebar",
|
||
"M3U_CHANNEL_NUMBER": "Select M3U channel by number",
|
||
"PLAY_PAUSE": "Play or pause embedded MPV playback",
|
||
"TOGGLE_FULLSCREEN": "Toggle embedded MPV fullscreen",
|
||
"SEEK": "Seek embedded MPV playback by 5 seconds",
|
||
"ADJUST_VOLUME": "Adjust volume by 5%",
|
||
"MUTE_AUDIO": "Mute audio",
|
||
"CLOSE_PLAYER_POPOVERS": "Close embedded MPV popovers",
|
||
"COMMAND_PALETTE_NAVIGATION": "Move command palette selection",
|
||
"COMMAND_PALETTE_RUN": "Run selected command palette item",
|
||
"CLOSE_DIALOGS": "Close dialog or dismiss overlay",
|
||
"DOWNLOADS_OPEN_ITEM": "Open focused download item in the library"
|
||
}
|
||
},
|
||
"GLOBAL_FAVORITES": {
|
||
"TITLE": "Favoris globaux",
|
||
"SUBTITLE": "TV en direct de toutes les listes de lecture",
|
||
"SEARCH_PLACEHOLDER": "Rechercher dans les favoris…",
|
||
"NO_RESULTS": "Aucun résultat",
|
||
"NO_RESULTS_HINT": "Essayez un autre terme de recherche",
|
||
"EMPTY_TITLE": "Aucun favori TV en direct pour le moment",
|
||
"EMPTY_HINT": "Mettez en favori des chaînes en direct depuis vos listes M3U, Xtream ou Stalker pour les voir ici.",
|
||
"EMPTY_PLAYER": "Sélectionnez une chaîne pour commencer à regarder",
|
||
"NO_PLAYLISTS_TITLE": "No favorites to show",
|
||
"NO_PLAYLISTS_BODY": "Add a playlist to start collecting favorites from live channels, movies, and series.",
|
||
"NO_ITEMS_TITLE": "No favorites yet",
|
||
"NO_ITEMS_BODY": "Tap the star icon on any channel, movie, or series to save it here.",
|
||
"UNKNOWN_CHANNEL": "Inconnue"
|
||
},
|
||
"GLOBAL_RECENT": {
|
||
"NO_PLAYLISTS_TITLE": "Nothing watched yet",
|
||
"NO_PLAYLISTS_BODY": "Add a playlist and your watching history will appear here.",
|
||
"NO_ITEMS_TITLE": "Nothing watched yet",
|
||
"NO_ITEMS_BODY": "Start watching anything from your library and it will show up here."
|
||
}
|
||
}
|
||
}
|