nixamp 0.27.2 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +39 -16
  2. package/backtoschool/dist/assets/ar-DK3LI2hP.js +1 -0
  3. package/backtoschool/dist/assets/cs-C9cckHFh.js +1 -0
  4. package/backtoschool/dist/assets/da-BpNIZAZo.js +1 -0
  5. package/backtoschool/dist/assets/de-C1DX_kDk.js +1 -0
  6. package/backtoschool/dist/assets/es-bymdOEnB.js +1 -0
  7. package/backtoschool/dist/assets/fi-p1DAJXf3.js +1 -0
  8. package/backtoschool/dist/assets/fr-w1qH9bbD.js +1 -0
  9. package/backtoschool/dist/assets/hi-DnZJakpY.js +1 -0
  10. package/backtoschool/dist/assets/hu-N7uSyLAk.js +1 -0
  11. package/backtoschool/dist/assets/id-CX3MVU3E.js +1 -0
  12. package/backtoschool/dist/assets/index-4LdMSur6.js +93 -0
  13. package/backtoschool/dist/assets/{index-OE2xWZUH.css → index-D06NfY1L.css} +1 -1
  14. package/backtoschool/dist/assets/it-CQ0H28FI.js +1 -0
  15. package/backtoschool/dist/assets/nl-DJqCLHMq.js +1 -0
  16. package/backtoschool/dist/assets/ru-Rk2yagke.js +1 -0
  17. package/backtoschool/dist/assets/sv-1r9eOl4-.js +1 -0
  18. package/backtoschool/dist/assets/uk-CrRsLNHV.js +1 -0
  19. package/backtoschool/dist/assets/vi-B5kxrjP7.js +1 -0
  20. package/backtoschool/dist/assets/zh-BB_S8QpG.js +1 -0
  21. package/backtoschool/dist/index.html +50 -38
  22. package/dist/admin.js +5 -4
  23. package/dist/event-writer.d.ts +39 -0
  24. package/dist/event-writer.js +139 -0
  25. package/dist/i18n.d.ts +26 -0
  26. package/dist/i18n.js +81 -0
  27. package/dist/live-api.d.ts +2 -0
  28. package/dist/live-api.js +37 -2
  29. package/dist/live-interpreter.d.ts +3 -1
  30. package/dist/live-interpreter.js +109 -83
  31. package/dist/live-recovery.d.ts +6 -0
  32. package/dist/live-recovery.js +11 -0
  33. package/dist/locales/ar.d.ts +201 -0
  34. package/dist/locales/ar.js +200 -0
  35. package/dist/locales/cs.d.ts +201 -0
  36. package/dist/locales/cs.js +200 -0
  37. package/dist/locales/da.d.ts +201 -0
  38. package/dist/locales/da.js +200 -0
  39. package/dist/locales/de.d.ts +201 -0
  40. package/dist/locales/de.js +200 -0
  41. package/dist/locales/en.d.ts +201 -0
  42. package/dist/locales/en.js +201 -0
  43. package/dist/locales/es.d.ts +201 -0
  44. package/dist/locales/es.js +200 -0
  45. package/dist/locales/fi.d.ts +201 -0
  46. package/dist/locales/fi.js +200 -0
  47. package/dist/locales/fr.d.ts +201 -0
  48. package/dist/locales/fr.js +200 -0
  49. package/dist/locales/hi.d.ts +201 -0
  50. package/dist/locales/hi.js +200 -0
  51. package/dist/locales/hu.d.ts +201 -0
  52. package/dist/locales/hu.js +200 -0
  53. package/dist/locales/id.d.ts +201 -0
  54. package/dist/locales/id.js +200 -0
  55. package/dist/locales/it.d.ts +201 -0
  56. package/dist/locales/it.js +200 -0
  57. package/dist/locales/nl.d.ts +201 -0
  58. package/dist/locales/nl.js +200 -0
  59. package/dist/locales/ru.d.ts +201 -0
  60. package/dist/locales/ru.js +200 -0
  61. package/dist/locales/sv.d.ts +201 -0
  62. package/dist/locales/sv.js +200 -0
  63. package/dist/locales/uk.d.ts +201 -0
  64. package/dist/locales/uk.js +200 -0
  65. package/dist/locales/vi.d.ts +201 -0
  66. package/dist/locales/vi.js +200 -0
  67. package/dist/locales/zh.d.ts +201 -0
  68. package/dist/locales/zh.js +200 -0
  69. package/dist/mail-sender.d.ts +9 -0
  70. package/dist/mail-sender.js +11 -0
  71. package/dist/main.js +26 -12
  72. package/dist/notify.d.ts +3 -3
  73. package/dist/notify.js +8 -5
  74. package/dist/password-reset.d.ts +2 -3
  75. package/dist/password-reset.js +4 -2
  76. package/dist/server.d.ts +2 -0
  77. package/dist/server.js +13 -1
  78. package/dist/shared-translation.d.ts +1 -0
  79. package/dist/shared-translation.js +32 -23
  80. package/dist/ui-aliases.d.ts +4 -0
  81. package/dist/ui-aliases.js +41 -0
  82. package/dist/ui-languages.d.ts +62 -0
  83. package/dist/ui-languages.js +32 -0
  84. package/package.json +1 -1
  85. package/src/admin.ts +5 -4
  86. package/src/event-writer.ts +126 -0
  87. package/src/i18n.ts +74 -0
  88. package/src/live-api.ts +26 -2
  89. package/src/live-interpreter.ts +87 -68
  90. package/src/live-recovery.ts +10 -0
  91. package/src/locales/ar.ts +201 -0
  92. package/src/locales/cs.ts +201 -0
  93. package/src/locales/da.ts +201 -0
  94. package/src/locales/de.ts +201 -0
  95. package/src/locales/en.ts +201 -0
  96. package/src/locales/es.ts +201 -0
  97. package/src/locales/fi.ts +201 -0
  98. package/src/locales/fr.ts +201 -0
  99. package/src/locales/hi.ts +201 -0
  100. package/src/locales/hu.ts +201 -0
  101. package/src/locales/id.ts +201 -0
  102. package/src/locales/it.ts +201 -0
  103. package/src/locales/nl.ts +201 -0
  104. package/src/locales/ru.ts +201 -0
  105. package/src/locales/sv.ts +201 -0
  106. package/src/locales/uk.ts +201 -0
  107. package/src/locales/vi.ts +201 -0
  108. package/src/locales/zh.ts +201 -0
  109. package/src/mail-sender.ts +12 -0
  110. package/src/main.ts +25 -12
  111. package/src/notify.ts +10 -7
  112. package/src/password-reset.ts +5 -5
  113. package/src/server.ts +14 -1
  114. package/src/shared-translation.ts +27 -19
  115. package/src/ui-aliases.ts +42 -0
  116. package/src/ui-languages.ts +31 -0
  117. package/web/dist/assets/ar-DK3LI2hP.js +1 -0
  118. package/web/dist/assets/background.worker-Hz7oaQ8X.js +547 -0
  119. package/web/dist/assets/cs-C9cckHFh.js +1 -0
  120. package/web/dist/assets/da-BpNIZAZo.js +1 -0
  121. package/web/dist/assets/de-C1DX_kDk.js +1 -0
  122. package/web/dist/assets/es-bymdOEnB.js +1 -0
  123. package/web/dist/assets/fi-p1DAJXf3.js +1 -0
  124. package/web/dist/assets/fr-w1qH9bbD.js +1 -0
  125. package/web/dist/assets/hi-DnZJakpY.js +1 -0
  126. package/web/dist/assets/{hls-3VKVEQE3-CAL4qBEq.js → hls-3VKVEQE3-DURNoQ5W.js} +1 -1
  127. package/web/dist/assets/hu-N7uSyLAk.js +1 -0
  128. package/web/dist/assets/id-CX3MVU3E.js +1 -0
  129. package/web/dist/assets/index-BXJlt2r6.js +3 -0
  130. package/web/dist/assets/index-Brm9OAsr.css +1 -0
  131. package/web/dist/assets/it-CQ0H28FI.js +1 -0
  132. package/web/dist/assets/{mpegts-H9oC2fAK.js → mpegts-BdyqUrVC.js} +1 -1
  133. package/web/dist/assets/{mpegts-LO6RVLD6-Bkd9Sqr6.js → mpegts-LO6RVLD6-yGogIzG9.js} +1 -1
  134. package/web/dist/assets/nl-DJqCLHMq.js +1 -0
  135. package/web/dist/assets/ru-Rk2yagke.js +1 -0
  136. package/web/dist/assets/sv-1r9eOl4-.js +1 -0
  137. package/web/dist/assets/uk-CrRsLNHV.js +1 -0
  138. package/web/dist/assets/vi-B5kxrjP7.js +1 -0
  139. package/web/dist/assets/zh-BB_S8QpG.js +1 -0
  140. package/web/dist/index.html +132 -131
  141. package/web/dist/sw.js +24 -7
  142. package/backtoschool/dist/assets/index-pQ_uH4bZ.js +0 -93
  143. package/web/dist/assets/background.worker-DH8ag5Q4.js +0 -547
  144. package/web/dist/assets/index-CLeEWlms.js +0 -3
  145. package/web/dist/assets/index-D34dTY18.css +0 -1
@@ -1,3 +1,4 @@
1
+ import { audioError, transientAudioError } from "./live-recovery.ts";
1
2
  import { encodeWav } from "./pcm-wav.ts";
2
3
  import type { SpeakerTranscript, SpeakerTurn } from "./speaker-turns.ts";
3
4
  export interface Caption { channel: string; at: number; until: number; text: string; language?: string; original?: string; sourceLanguage?: string; speaker?: string; voiceProfile?: "lower" | "higher" | "unknown"; }
@@ -58,17 +59,20 @@ export class Interpreter {
58
59
  private controller: AbortController | null = null;
59
60
  private translating: number | null = null;
60
61
  private translationController: AbortController | null = null;
62
+ private recognitionFailures = 0;
63
+ private translationFailures = 0;
61
64
  private translations: { lines: Caption[]; language: string; target: string; until: number }[] = [];
62
65
  constructor(private readonly options: {
63
66
  language: () => string; speakers: () => boolean; voices: () => VoiceChoice[];
64
67
  channel: () => string; lines: (lines: Caption[]) => void;
65
- status: (text: string) => void; failed: () => void;
68
+ status: (text: string) => void; failed: (message: string) => void;
66
69
  fetcher?: typeof fetch;
67
70
  }) {}
68
71
 
69
72
  reset(): void {
70
73
  this.generation++; this.controller?.abort(); this.translationController?.abort();
71
74
  this.pending = null; this.running = null; this.translating = null;
75
+ this.recognitionFailures = this.translationFailures = 0;
72
76
  this.translations = []; this.committedUntil = null; this.tracker.reset();
73
77
  }
74
78
  push(window: AudioWindow): void {
@@ -79,7 +83,7 @@ export class Interpreter {
79
83
  private async json(path: string, init: RequestInit, signal: AbortSignal): Promise<any> {
80
84
  const response = await (this.options.fetcher ?? fetch)(path, { ...init, signal });
81
85
  const body = await response.json();
82
- if (!response.ok) throw new Error(body.error || "Audio translation is unavailable.");
86
+ if (!response.ok) throw audioError(body.error || "Audio translation is unavailable.", response.status);
83
87
  return body;
84
88
  }
85
89
  private async run(generation: number): Promise<void> {
@@ -87,60 +91,68 @@ export class Interpreter {
87
91
  try {
88
92
  while (this.pending && generation === this.generation) {
89
93
  const window = this.pending; this.pending = null;
90
- if (Date.now() - window.until > 12_000) continue;
91
- const controller = new AbortController(); this.controller = controller;
92
- const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]);
93
- const speakers = this.options.speakers(), target = this.options.language();
94
- const heard = await this.json(speakers ? "/api/v1/speech/speakers" : "/api/v1/speech/transcribe?live=1", {
95
- method: "POST", headers: { "content-type": "audio/wav" }, body: new Uint8Array(encodeWav(speakers ? window.samples : window.samples.slice(-80_000))),
96
- }, signal) as SpeakerTranscript & { text?: string };
97
- if (generation !== this.generation) return;
98
- const language = heard.language;
99
- let lines: Caption[] = [];
100
- if (speakers) {
101
- const links = this.tracker.reconcile(heard.turns ?? [], window.at, this.options.voices());
102
- // The watermark follows emitted words, not the newest capture
103
- // interval: an overlapping window can recover audio that arrived
104
- // while recognition/translation was busy. Keep unfinished phrases
105
- // in that overlap so short capture intervals do not split every
106
- // sentence (especially damaging when German changes word order).
107
- const cutoff = this.committedUntil ?? window.freshAt;
108
- const turns = heard.turns ?? [];
109
- for (const [index, turn] of turns.entries()) {
110
- const speaker = links.get(turn.speaker)!;
111
- const words = turn.words.filter(word =>
112
- window.at + (word.start + word.end) * 500 >= cutoff &&
113
- (window.at + word.end * 1000 <= window.until - 100 || /[.!?。!?]$/.test(word.text)));
114
- let phrase: typeof words = [];
115
- const emit = (): void => {
116
- if (!phrase.length) return;
117
- lines.push({ channel: this.options.channel(), at: window.at + phrase[0]!.start * 1000,
118
- until: window.at + phrase.at(-1)!.end * 1000, text: phrase.map(word => word.text).join(" ").trim(),
119
- language, speaker: speaker.id, voiceProfile: turn.profile });
120
- phrase = [];
121
- };
122
- for (const word of words) {
123
- if (phrase.length && word.start - phrase.at(-1)!.end >= 0.45) emit();
124
- phrase.push(word);
125
- if (/[.!?。!?]$/.test(word.text) || phrase.map(word => word.text).join(" ").length >= 400) emit();
94
+ try {
95
+ if (Date.now() - window.until > 12_000) continue;
96
+ const controller = new AbortController(); this.controller = controller;
97
+ const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]);
98
+ const speakers = this.options.speakers(), target = this.options.language();
99
+ const heard = await this.json(speakers ? "/api/v1/speech/speakers" : "/api/v1/speech/transcribe?live=1", {
100
+ method: "POST", headers: { "content-type": "audio/wav" }, body: new Uint8Array(encodeWav(speakers ? window.samples : window.samples.slice(-80_000))),
101
+ }, signal) as SpeakerTranscript & { text?: string };
102
+ if (generation !== this.generation) return;
103
+ this.recognitionFailures = 0;
104
+ const language = heard.language;
105
+ let lines: Caption[] = [];
106
+ if (speakers) {
107
+ const links = this.tracker.reconcile(heard.turns ?? [], window.at, this.options.voices());
108
+ // The watermark follows emitted words, not the newest capture
109
+ // interval: an overlapping window can recover audio that arrived
110
+ // while recognition/translation was busy. Keep unfinished phrases
111
+ // in that overlap so short capture intervals do not split every
112
+ // sentence (especially damaging when German changes word order).
113
+ const cutoff = this.committedUntil ?? window.freshAt;
114
+ const turns = heard.turns ?? [];
115
+ for (const [index, turn] of turns.entries()) {
116
+ const speaker = links.get(turn.speaker)!;
117
+ const words = turn.words.filter(word =>
118
+ window.at + (word.start + word.end) * 500 >= cutoff &&
119
+ (window.at + word.end * 1000 <= window.until - 100 || /[.!?。!?]$/.test(word.text)));
120
+ let phrase: typeof words = [];
121
+ const emit = (): void => {
122
+ if (!phrase.length) return;
123
+ lines.push({ channel: this.options.channel(), at: window.at + phrase[0]!.start * 1000,
124
+ until: window.at + phrase.at(-1)!.end * 1000, text: phrase.map(word => word.text).join(" ").trim(),
125
+ language, speaker: speaker.id, voiceProfile: turn.profile });
126
+ phrase = [];
127
+ };
128
+ for (const word of words) {
129
+ if (phrase.length && word.start - phrase.at(-1)!.end >= 0.45) emit();
130
+ phrase.push(word);
131
+ if (/[.!?。!?]$/.test(word.text) || phrase.map(word => word.text).join(" ").length >= 400) emit();
132
+ }
133
+ if (phrase.length && (index < turns.length - 1 ||
134
+ window.until - (window.at + phrase.at(-1)!.end * 1000) >= 350 ||
135
+ window.until - (window.at + phrase[0]!.start * 1000) >= 3000)) emit();
126
136
  }
127
- if (phrase.length && (index < turns.length - 1 ||
128
- window.until - (window.at + phrase.at(-1)!.end * 1000) >= 350 ||
129
- window.until - (window.at + phrase[0]!.start * 1000) >= 3000)) emit();
137
+ } else if (heard.text) {
138
+ lines = [{ channel: this.options.channel(), at: window.freshAt, until: window.until, text: heard.text, language }];
130
139
  }
131
- } else if (heard.text) {
132
- lines = [{ channel: this.options.channel(), at: window.freshAt, until: window.until, text: heard.text, language }];
140
+ if (!lines.length) continue;
141
+ this.committedUntil = Math.max(this.committedUntil ?? window.freshAt, ...lines.map(line => line.until));
142
+ // Drop expired work instead of turning off an otherwise healthy stream.
143
+ this.translations = this.translations.filter(item => Date.now() - item.until <= 12_000).slice(-11);
144
+ this.translations.push({ lines, language, target, until: window.until });
145
+ // Recognition of the next audio window can proceed while the text
146
+ // model translates this phrase. Voice synthesis is a third stage.
147
+ if (this.translating === null) void this.translate(this.generation);
148
+ } catch (error) {
149
+ if (generation !== this.generation) return;
150
+ if (!transientAudioError(error) || ++this.recognitionFailures >= 3) throw error;
151
+ this.options.status("Speech recognition interrupted · listening for the next phrase…");
133
152
  }
134
- if (!lines.length) continue;
135
- this.committedUntil = Math.max(this.committedUntil ?? window.freshAt, ...lines.map(line => line.until));
136
- if (this.translations.length >= 12) throw new Error("Translation fell behind. Try again after the language model has warmed up.");
137
- this.translations.push({ lines, language, target, until: window.until });
138
- // Recognition of the next audio window can proceed while the text
139
- // model translates this phrase. Voice synthesis is a third stage.
140
- if (this.translating === null) void this.translate(this.generation);
141
153
  }
142
154
  } catch (error) {
143
- if (generation === this.generation) { this.reset(); this.options.failed(); this.options.status(error instanceof Error ? error.message : "Live translation stopped."); }
155
+ if (generation === this.generation) { const message = error instanceof Error ? error.message : "Live translation stopped."; this.reset(); this.options.failed(message); this.options.status(message); }
144
156
  } finally { if (this.running === generation) this.running = null; }
145
157
  }
146
158
 
@@ -149,26 +161,33 @@ export class Interpreter {
149
161
  try {
150
162
  while (this.translations.length && generation === this.generation) {
151
163
  const item = this.translations.shift()!;
152
- let { lines } = item;
153
- const { language, target, until } = item;
154
- if (Date.now() - until > 12_000) throw new Error("Translation fell behind. Try again after the language model has warmed up.");
155
- const controller = new AbortController(); this.translationController = controller;
156
- if (target && language !== target) {
157
- if (!language) throw new Error("The audio language could not be detected. Waiting for clearer speech.");
158
- const translated = await this.json("/api/v1/translate?live=1", {
159
- method: "POST", headers: { "content-type": "application/json" },
160
- body: JSON.stringify({ texts: lines.map(line => line.text), from: language, to: target }),
161
- }, AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)])) as { texts: string[] };
162
- if (translated.texts.length !== lines.length || translated.texts.some(text => !text.trim())) throw new Error("Translation omitted a phrase. Enable translated audio again to retry.");
163
- lines = lines.map((line, i) => ({ ...line, original: line.text, sourceLanguage: language, language: target, text: translated.texts[i]! }));
164
+ try {
165
+ let { lines } = item;
166
+ const { language, target, until } = item;
167
+ if (Date.now() - until > 12_000) continue;
168
+ const controller = new AbortController(); this.translationController = controller;
169
+ if (target && language !== target) {
170
+ if (!language) throw audioError("The audio language could not be detected. Waiting for clearer speech.", 502);
171
+ const translated = await this.json("/api/v1/translate?live=1", {
172
+ method: "POST", headers: { "content-type": "application/json" },
173
+ body: JSON.stringify({ texts: lines.map(line => line.text), from: language, to: target }),
174
+ }, AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)])) as { texts: string[] };
175
+ if (translated.texts.length !== lines.length || translated.texts.some(text => !text.trim())) throw audioError("Translation omitted a phrase. Waiting for the next phrase.", 502);
176
+ lines = lines.map((line, i) => ({ ...line, original: line.text, sourceLanguage: language, language: target, text: translated.texts[i]! }));
177
+ }
178
+ if (generation !== this.generation) return;
179
+ if (Date.now() - until > 12_000) continue;
180
+ this.translationFailures = 0;
181
+ this.options.lines(lines.flatMap(line => splitCaption(line)));
182
+ this.options.status(target ? `${language} → ${target} · live translation` : `Original audio language: ${language || "detecting…"}`);
183
+ } catch (error) {
184
+ if (generation !== this.generation) return;
185
+ if (!transientAudioError(error) || ++this.translationFailures >= 3) throw error;
186
+ this.options.status("Translation interrupted · catching up with live speech…");
164
187
  }
165
- if (generation !== this.generation) return;
166
- if (Date.now() - until > 12_000) throw new Error("Translation fell behind. Try again after the language model has warmed up.");
167
- this.options.lines(lines.flatMap(line => splitCaption(line)));
168
- this.options.status(target ? `${language} → ${target} · live translation` : `Original audio language: ${language || "detecting…"}`);
169
188
  }
170
189
  } catch (error) {
171
- if (generation === this.generation) { this.reset(); this.options.failed(); this.options.status(error instanceof Error ? error.message : "Live translation stopped."); }
190
+ if (generation === this.generation) { const message = error instanceof Error ? error.message : "Live translation stopped."; this.reset(); this.options.failed(message); this.options.status(message); }
172
191
  } finally { if (this.translating === generation) this.translating = null; }
173
192
  }
174
193
  }
@@ -0,0 +1,10 @@
1
+ /** Access, billing and throttling failures are terminal. Only transient service
2
+ * and transport failures may recover, with a bounded consecutive failure count. */
3
+ export function transientAudioError(error: unknown): boolean {
4
+ const status = (error as { status?: number } | null)?.status;
5
+ if (typeof status === "number") return [408, 502, 503, 504].includes(status);
6
+ return error instanceof TypeError || (error instanceof Error && ["AbortError", "TimeoutError"].includes(error.name));
7
+ }
8
+ export function audioError(message: string, status: number): Error & { status: number } {
9
+ return Object.assign(new Error(message), { status });
10
+ }
@@ -0,0 +1,201 @@
1
+ import type { Catalogue } from "../i18n.ts";
2
+ export default {
3
+ "Interface language": "لغة الواجهة",
4
+ "Language": "اللغة",
5
+ "Translation language": "لغة الترجمة",
6
+ "Original (auto-detect)": "الأصل (اكتشاف تلقائي)",
7
+ "Play": "تشغيل",
8
+ "Pause": "إيقاف مؤقت",
9
+ "Stop": "إيقاف",
10
+ "Next": "التالي",
11
+ "Previous": "السابق",
12
+ "Select": "اختيار",
13
+ "Next/Prev": "التالي/السابق",
14
+ "Quit": "خروج",
15
+ "Now Playing": "قيد التشغيل",
16
+ "Spectrum Analyser": "محلل الطيف",
17
+ "Playlist": "قائمة التشغيل",
18
+ "Files": "الملفات",
19
+ "Open files": "فتح ملفات",
20
+ "Open folder": "فتح مجلد",
21
+ "Add to library": "إضافة إلى المكتبة",
22
+ "Replace playlist": "استبدال قائمة التشغيل",
23
+ "Filter": "تصفية",
24
+ "Search": "بحث",
25
+ "Nothing loaded.": "لم يُحمّل أي محتوى.",
26
+ "Empty.": "فارغ.",
27
+ "Loading…": "جارٍ التحميل…",
28
+ "Connecting…": "جارٍ الاتصال…",
29
+ "Connected": "متصل",
30
+ "Not connected": "غير متصل",
31
+ "No source": "لا يوجد مصدر",
32
+ "Playing": "يعمل",
33
+ "Stopped": "متوقف",
34
+ "Volume": "مستوى الصوت",
35
+ "Seek": "موضع التشغيل",
36
+ "Full screen": "ملء الشاشة",
37
+ "Download": "تنزيل",
38
+ "Share": "مشاركة",
39
+ "Copy": "نسخ",
40
+ "Copy link": "نسخ الرابط",
41
+ "Copied": "تم النسخ",
42
+ "Save": "حفظ",
43
+ "Cancel": "إلغاء",
44
+ "Close": "إغلاق",
45
+ "Remove": "إزالة",
46
+ "Delete": "حذف",
47
+ "Edit": "تعديل",
48
+ "Add": "إضافة",
49
+ "Send": "إرسال",
50
+ "Retry": "إعادة المحاولة",
51
+ "Back": "رجوع",
52
+ "Next page": "الصفحة التالية",
53
+ "Previous page": "الصفحة السابقة",
54
+ "Show more": "عرض المزيد",
55
+ "Panels": "اللوحات",
56
+ "Reset layout": "إعادة ضبط التخطيط",
57
+ "Show": "إظهار",
58
+ "Hide": "إخفاء",
59
+ "Collapse": "طي",
60
+ "Expand": "توسيع",
61
+ "Move up": "نقل لأعلى",
62
+ "Move down": "نقل لأسفل",
63
+ "Install": "تثبيت",
64
+ "TV mode": "وضع التلفاز",
65
+ "On": "مفعّل",
66
+ "Off": "معطّل",
67
+ "Account": "الحساب",
68
+ "Sign in": "تسجيل الدخول",
69
+ "Sign out": "تسجيل الخروج",
70
+ "Create account": "إنشاء حساب",
71
+ "Email": "البريد الإلكتروني",
72
+ "Password": "كلمة المرور",
73
+ "Forgot password?": "نسيت كلمة المرور؟",
74
+ "Welcome back": "مرحبًا بعودتك",
75
+ "Profile": "الملف الشخصي",
76
+ "Handle": "اسم المستخدم",
77
+ "Your servers": "خوادمك",
78
+ "Server": "الخادم",
79
+ "Connect": "اتصال",
80
+ "Disconnect": "قطع الاتصال",
81
+ "Directory": "الدليل",
82
+ "Favourites": "المفضلة",
83
+ "Follow": "متابعة",
84
+ "Unfollow": "إلغاء المتابعة",
85
+ "Notifications": "الإشعارات",
86
+ "Phone number": "رقم الهاتف",
87
+ "Save number": "حفظ الرقم",
88
+ "Transcript": "النص المفرغ",
89
+ "Captions": "الترجمة النصية",
90
+ "Translate audio": "ترجمة الصوت",
91
+ "Audio language": "لغة الصوت",
92
+ "Audio options": "خيارات الصوت",
93
+ "Keep background sound": "الاحتفاظ بأصوات الخلفية",
94
+ "Background level": "مستوى صوت الخلفية",
95
+ "Speaker voices": "أصوات المتحدثين",
96
+ "Any voice": "أي صوت",
97
+ "Male voice": "صوت رجالي",
98
+ "Female voice": "صوت نسائي",
99
+ "Original audio.": "الصوت الأصلي.",
100
+ "Buy translated audio": "شراء الصوت المترجم",
101
+ "Audio credit": "رصيد الصوت",
102
+ "Buy pass": "شراء تصريح",
103
+ "Pass": "تصريح",
104
+ "Pay with": "الدفع باستخدام",
105
+ "Check payment": "التحقق من الدفع",
106
+ "Open checkout": "الانتقال للدفع",
107
+ "Free sessions": "جلسات مجانية",
108
+ "Usage rates and expiry": "الأسعار ومدة الصلاحية",
109
+ "Catalogs": "الكتالوجات",
110
+ "Add catalog": "إضافة كتالوج",
111
+ "Catalog name": "اسم الكتالوج",
112
+ "Link": "الرابط",
113
+ "Play link": "تشغيل الرابط",
114
+ "Go live": "بدء بث مباشر",
115
+ "Publish": "نشر",
116
+ "Listen": "استماع",
117
+ "Live now": "مباشر الآن",
118
+ "Upcoming": "قريبًا",
119
+ "Parties": "حفلات المشاهدة",
120
+ "Join party": "الانضمام للحفلة",
121
+ "Invite code": "رمز الدعوة",
122
+ "Invite": "دعوة",
123
+ "Chat": "الدردشة",
124
+ "Chat message": "رسالة دردشة",
125
+ "People": "الأشخاص",
126
+ "Speaker": "متحدث",
127
+ "Speakers": "المتحدثون",
128
+ "Listener": "مستمع",
129
+ "Moderator": "مشرف",
130
+ "Raise hand": "رفع اليد",
131
+ "Hand raises": "الأيدي المرفوعة",
132
+ "Questions": "الأسئلة",
133
+ "Resources": "الموارد",
134
+ "Replay": "إعادة العرض",
135
+ "Schedule": "الجدول",
136
+ "Event": "الفعالية",
137
+ "Host": "المضيف",
138
+ "About": "حول",
139
+ "Stage": "المنصة",
140
+ "Title": "العنوان",
141
+ "Description": "الوصف",
142
+ "Topic": "الموضوع",
143
+ "Public": "عام",
144
+ "Anyone with the link": "أي شخص لديه الرابط",
145
+ "Invited people": "الأشخاص المدعوون",
146
+ "Who can join": "من يمكنه الانضمام",
147
+ "Date and time": "التاريخ والوقت",
148
+ "Expected length": "المدة المتوقعة",
149
+ "Repeat": "تكرار",
150
+ "Does not repeat": "لا يتكرر",
151
+ "Every day": "كل يوم",
152
+ "Every week": "كل أسبوع",
153
+ "Homepage": "الموقع الإلكتروني",
154
+ "Host name": "اسم المضيف",
155
+ "Profile photo URL": "رابط صورة الملف الشخصي",
156
+ "Broadcast link": "رابط البث",
157
+ "Edit event": "تعديل الفعالية",
158
+ "Save changes": "حفظ التغييرات",
159
+ "Create live": "إنشاء بث مباشر",
160
+ "Schedule class": "جدولة درس",
161
+ "Start class": "بدء الدرس",
162
+ "End event": "إنهاء الفعالية",
163
+ "End session": "إنهاء الجلسة",
164
+ "Session ended": "انتهت الجلسة",
165
+ "Event ended": "انتهت الفعالية",
166
+ "Cancelled": "ملغاة",
167
+ "Replay available": "إعادة العرض متاحة",
168
+ "Join": "انضمام",
169
+ "Create invite": "إنشاء دعوة",
170
+ "Invite as": "الدعوة بصفة",
171
+ "Watch now": "شاهد الآن",
172
+ "Start a room": "إنشاء غرفة",
173
+ "Recent replays": "إعادات العرض الأخيرة",
174
+ "Your account": "حسابك",
175
+ "Write with AI": "الكتابة بالذكاء الاصطناعي",
176
+ "Suggested title": "العنوان المقترح",
177
+ "Suggested description": "الوصف المقترح",
178
+ "Suggested topic": "الموضوع المقترح",
179
+ "Use these details": "استخدام هذه التفاصيل",
180
+ "Learn something live.": "تعلّم شيئًا مباشرة.",
181
+ "Screen share": "مشاركة الشاشة",
182
+ "Upload and broadcast": "رفع وبث",
183
+ "Help": "مساعدة",
184
+ "Reload Player": "إعادة تحميل المشغل",
185
+ "Copy Bundled CLI Path": "نسخ مسار أداة سطر الأوامر المضمنة",
186
+ "Developer tools": "أدوات المطور",
187
+ "Undo": "تراجع",
188
+ "Redo": "إعادة",
189
+ "Cut": "قص",
190
+ "Paste": "لصق",
191
+ "Select all": "تحديد الكل",
192
+ "Status": "الحالة",
193
+ "Activity log": "سجل النشاط",
194
+ "Admin": "الإدارة",
195
+ "Playback controls": "عناصر التحكم بالتشغيل",
196
+ "Tracks and folders": "المقاطع والمجلدات",
197
+ "Original-language captions.": "ترجمة نصية باللغة الأصلية.",
198
+ "Skip to playback controls": "الانتقال إلى عناصر التحكم بالتشغيل",
199
+ "Skip to playlist": "الانتقال إلى قائمة التشغيل",
200
+ "Skip to transcript and translated audio": "الانتقال إلى النص المفرغ والصوت المترجم"
201
+ } satisfies Catalogue;
@@ -0,0 +1,201 @@
1
+ import type { Catalogue } from "../i18n.ts";
2
+ export default {
3
+ "Interface language": "Jazyk rozhraní",
4
+ "Language": "Jazyk",
5
+ "Translation language": "Jazyk překladu",
6
+ "Original (auto-detect)": "Původní (automatické rozpoznání)",
7
+ "Play": "Přehrát",
8
+ "Pause": "Pozastavit",
9
+ "Stop": "Zastavit",
10
+ "Next": "Další",
11
+ "Previous": "Předchozí",
12
+ "Select": "Vybrat",
13
+ "Next/Prev": "Další/Předchozí",
14
+ "Quit": "Ukončit",
15
+ "Now Playing": "Právě hraje",
16
+ "Spectrum Analyser": "Spektrální analyzátor",
17
+ "Playlist": "Seznam skladeb",
18
+ "Files": "Soubory",
19
+ "Open files": "Otevřít soubory",
20
+ "Open folder": "Otevřít složku",
21
+ "Add to library": "Přidat do knihovny",
22
+ "Replace playlist": "Nahradit seznam",
23
+ "Filter": "Filtrovat",
24
+ "Search": "Hledat",
25
+ "Nothing loaded.": "Nic není načteno.",
26
+ "Empty.": "Prázdné.",
27
+ "Loading…": "Načítání…",
28
+ "Connecting…": "Připojování…",
29
+ "Connected": "Připojeno",
30
+ "Not connected": "Nepřipojeno",
31
+ "No source": "Žádný zdroj",
32
+ "Playing": "Přehrávání",
33
+ "Stopped": "Zastaveno",
34
+ "Volume": "Hlasitost",
35
+ "Seek": "Pozice přehrávání",
36
+ "Full screen": "Celá obrazovka",
37
+ "Download": "Stáhnout",
38
+ "Share": "Sdílet",
39
+ "Copy": "Kopírovat",
40
+ "Copy link": "Kopírovat odkaz",
41
+ "Copied": "Zkopírováno",
42
+ "Save": "Uložit",
43
+ "Cancel": "Zrušit",
44
+ "Close": "Zavřít",
45
+ "Remove": "Odebrat",
46
+ "Delete": "Smazat",
47
+ "Edit": "Upravit",
48
+ "Add": "Přidat",
49
+ "Send": "Odeslat",
50
+ "Retry": "Zkusit znovu",
51
+ "Back": "Zpět",
52
+ "Next page": "Další stránka",
53
+ "Previous page": "Předchozí stránka",
54
+ "Show more": "Zobrazit více",
55
+ "Panels": "Panely",
56
+ "Reset layout": "Obnovit rozložení",
57
+ "Show": "Zobrazit",
58
+ "Hide": "Skrýt",
59
+ "Collapse": "Sbalit",
60
+ "Expand": "Rozbalit",
61
+ "Move up": "Nahoru",
62
+ "Move down": "Dolů",
63
+ "Install": "Nainstalovat",
64
+ "TV mode": "Režim TV",
65
+ "On": "Zapnuto",
66
+ "Off": "Vypnuto",
67
+ "Account": "Účet",
68
+ "Sign in": "Přihlásit se",
69
+ "Sign out": "Odhlásit se",
70
+ "Create account": "Vytvořit účet",
71
+ "Email": "E-mail",
72
+ "Password": "Heslo",
73
+ "Forgot password?": "Zapomenuté heslo?",
74
+ "Welcome back": "Vítejte zpět",
75
+ "Profile": "Profil",
76
+ "Handle": "Uživatelské jméno",
77
+ "Your servers": "Vaše servery",
78
+ "Server": "Server",
79
+ "Connect": "Připojit",
80
+ "Disconnect": "Odpojit",
81
+ "Directory": "Adresář",
82
+ "Favourites": "Oblíbené",
83
+ "Follow": "Sledovat",
84
+ "Unfollow": "Přestat sledovat",
85
+ "Notifications": "Oznámení",
86
+ "Phone number": "Telefonní číslo",
87
+ "Save number": "Uložit číslo",
88
+ "Transcript": "Přepis",
89
+ "Captions": "Titulky",
90
+ "Translate audio": "Překládat zvuk",
91
+ "Audio language": "Jazyk zvuku",
92
+ "Audio options": "Možnosti zvuku",
93
+ "Keep background sound": "Zachovat zvuk na pozadí",
94
+ "Background level": "Hlasitost pozadí",
95
+ "Speaker voices": "Hlasy mluvčích",
96
+ "Any voice": "Libovolný hlas",
97
+ "Male voice": "Mužský hlas",
98
+ "Female voice": "Ženský hlas",
99
+ "Original audio.": "Původní zvuk.",
100
+ "Buy translated audio": "Koupit překlad zvuku",
101
+ "Audio credit": "Zvukový kredit",
102
+ "Buy pass": "Koupit přístup",
103
+ "Pass": "Přístup",
104
+ "Pay with": "Zaplatit pomocí",
105
+ "Check payment": "Ověřit platbu",
106
+ "Open checkout": "Přejít k platbě",
107
+ "Free sessions": "Bezplatné relace",
108
+ "Usage rates and expiry": "Ceny a platnost",
109
+ "Catalogs": "Katalogy",
110
+ "Add catalog": "Přidat katalog",
111
+ "Catalog name": "Název katalogu",
112
+ "Link": "Odkaz",
113
+ "Play link": "Přehrát odkaz",
114
+ "Go live": "Vysílat živě",
115
+ "Publish": "Zveřejnit",
116
+ "Listen": "Poslouchat",
117
+ "Live now": "Právě živě",
118
+ "Upcoming": "Připravované",
119
+ "Parties": "Společná sledování",
120
+ "Join party": "Připojit se ke sledování",
121
+ "Invite code": "Kód pozvánky",
122
+ "Invite": "Pozvat",
123
+ "Chat": "Chat",
124
+ "Chat message": "Zpráva chatu",
125
+ "People": "Lidé",
126
+ "Speaker": "Mluvčí",
127
+ "Speakers": "Mluvčí",
128
+ "Listener": "Posluchač",
129
+ "Moderator": "Moderátor",
130
+ "Raise hand": "Zvednout ruku",
131
+ "Hand raises": "Zvednuté ruce",
132
+ "Questions": "Otázky",
133
+ "Resources": "Materiály",
134
+ "Replay": "Záznam",
135
+ "Schedule": "Rozvrh",
136
+ "Event": "Událost",
137
+ "Host": "Pořadatel",
138
+ "About": "O události",
139
+ "Stage": "Pódium",
140
+ "Title": "Název",
141
+ "Description": "Popis",
142
+ "Topic": "Téma",
143
+ "Public": "Veřejné",
144
+ "Anyone with the link": "Kdokoli s odkazem",
145
+ "Invited people": "Pozvané osoby",
146
+ "Who can join": "Kdo se může připojit",
147
+ "Date and time": "Datum a čas",
148
+ "Expected length": "Očekávaná délka",
149
+ "Repeat": "Opakovat",
150
+ "Does not repeat": "Neopakuje se",
151
+ "Every day": "Každý den",
152
+ "Every week": "Každý týden",
153
+ "Homepage": "Webové stránky",
154
+ "Host name": "Jméno pořadatele",
155
+ "Profile photo URL": "URL profilové fotky",
156
+ "Broadcast link": "Odkaz na vysílání",
157
+ "Edit event": "Upravit událost",
158
+ "Save changes": "Uložit změny",
159
+ "Create live": "Vytvořit vysílání",
160
+ "Schedule class": "Naplánovat lekci",
161
+ "Start class": "Zahájit lekci",
162
+ "End event": "Ukončit událost",
163
+ "End session": "Ukončit relaci",
164
+ "Session ended": "Relace ukončena",
165
+ "Event ended": "Událost ukončena",
166
+ "Cancelled": "Zrušeno",
167
+ "Replay available": "Záznam je k dispozici",
168
+ "Join": "Připojit se",
169
+ "Create invite": "Vytvořit pozvánku",
170
+ "Invite as": "Pozvat jako",
171
+ "Watch now": "Sledovat nyní",
172
+ "Start a room": "Vytvořit místnost",
173
+ "Recent replays": "Nedávné záznamy",
174
+ "Your account": "Váš účet",
175
+ "Write with AI": "Psát pomocí AI",
176
+ "Suggested title": "Navrhovaný název",
177
+ "Suggested description": "Navrhovaný popis",
178
+ "Suggested topic": "Navrhované téma",
179
+ "Use these details": "Použít tyto údaje",
180
+ "Learn something live.": "Učte se naživo.",
181
+ "Screen share": "Sdílet obrazovku",
182
+ "Upload and broadcast": "Nahrát a vysílat",
183
+ "Help": "Nápověda",
184
+ "Reload Player": "Znovu načíst přehrávač",
185
+ "Copy Bundled CLI Path": "Kopírovat cestu k přibalenému CLI",
186
+ "Developer tools": "Vývojářské nástroje",
187
+ "Undo": "Zpět",
188
+ "Redo": "Znovu",
189
+ "Cut": "Vyjmout",
190
+ "Paste": "Vložit",
191
+ "Select all": "Vybrat vše",
192
+ "Status": "Stav",
193
+ "Activity log": "Protokol aktivit",
194
+ "Admin": "Správa",
195
+ "Playback controls": "Ovládání přehrávání",
196
+ "Tracks and folders": "Skladby a složky",
197
+ "Original-language captions.": "Titulky v původním jazyce.",
198
+ "Skip to playback controls": "Přejít k ovládání přehrávání",
199
+ "Skip to playlist": "Přejít k seznamu skladeb",
200
+ "Skip to transcript and translated audio": "Přejít k přepisu a přeloženému zvuku"
201
+ } satisfies Catalogue;