i18n(mobile): polish UI strings → English
Tłumaczenie wszystkich user-facing stringów PL→EN (bug-report 2026-05-31 "dalej wszystko po polsku"). Alerty, przyciski, placeholdery, labelki w 12 ekranach/komponentach: BugReportFAB, AppLock(Screen/Settings/PinEntry), applock biometric prompts, doodstream error msgs, MovieDetail, PlaybackQuality, Player, SceneDetail, ScenesFilter, SiteScenes. Komentarze w kodzie zostają PL. Zmiany były WIP drugiego okna (uncommitted); wjechały do bundla 0.2.1 przy buildzie (były w working tree) — apka zainstalowana już ma EN. Ten commit utrwala je w gicie żeby nie zginęły. Czysto stringi, zero zmian logiki. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
55503136e6
commit
b942565a6e
12 changed files with 80 additions and 80 deletions
|
|
@ -87,11 +87,11 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
|
||||
const submit = useCallback(async () => {
|
||||
if (!client) {
|
||||
Alert.alert('Bug report', 'Brak połączenia z backendem.');
|
||||
Alert.alert('Bug report', 'No connection to the backend.');
|
||||
return;
|
||||
}
|
||||
if (!message.trim()) {
|
||||
Alert.alert('Bug report', 'Wpisz krótki opis.');
|
||||
Alert.alert('Bug report', 'Enter a short description.');
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
|
|
@ -136,9 +136,9 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
setOpen(false);
|
||||
setMessage('');
|
||||
setScreenshot(null);
|
||||
Alert.alert('Bug report', 'Wysłano. Dzięki!');
|
||||
Alert.alert('Bug report', 'Sent. Thanks!');
|
||||
} catch (e) {
|
||||
Alert.alert('Bug report', `Nie udało się wysłać: ${(e as Error).message}`);
|
||||
Alert.alert('Bug report', `Failed to send: ${(e as Error).message}`);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
>
|
||||
<Pressable style={styles.backdropTap} onPress={() => setOpen(false)} />
|
||||
<View style={styles.sheet}>
|
||||
<Text style={styles.title}>Zgłoś buga</Text>
|
||||
<Text style={styles.title}>Report a bug</Text>
|
||||
|
||||
{screenshot ? (
|
||||
<View style={styles.preview}>
|
||||
|
|
@ -174,7 +174,7 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
resizeMode="contain"
|
||||
/>
|
||||
<View style={styles.toggleRow}>
|
||||
<Text style={styles.toggleLabel}>Dołącz screenshot</Text>
|
||||
<Text style={styles.toggleLabel}>Attach screenshot</Text>
|
||||
<Switch
|
||||
value={includeScreenshot}
|
||||
onValueChange={setIncludeScreenshot}
|
||||
|
|
@ -188,7 +188,7 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
</View>
|
||||
) : (
|
||||
<Text style={styles.noScreenshot}>
|
||||
Screenshot niedostępny (capture fail). Wyślę sam tekst.
|
||||
Screenshot unavailable (capture failed). Sending text only.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
style={styles.input}
|
||||
value={message}
|
||||
onChangeText={setMessage}
|
||||
placeholder="Co jest nie tak? Tytuł sceny / co próbujesz zrobić / co zobaczyłeś"
|
||||
placeholder="What's wrong? Scene title / what you're trying to do / what you saw"
|
||||
placeholderTextColor={theme.mutedDim}
|
||||
multiline
|
||||
autoFocus
|
||||
|
|
@ -208,7 +208,7 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
onPress={() => setOpen(false)}
|
||||
disabled={submitting}
|
||||
>
|
||||
<Text style={styles.btnText}>Anuluj</Text>
|
||||
<Text style={styles.btnText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.btn, styles.btnSend]}
|
||||
|
|
@ -218,7 +218,7 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
{submitting ? (
|
||||
<ActivityIndicator color={theme.fg} />
|
||||
) : (
|
||||
<Text style={styles.btnText}>Wyślij</Text>
|
||||
<Text style={styles.btnText}>Send</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export async function setTimeoutSeconds(s: number): Promise<void> {
|
|||
}
|
||||
|
||||
export async function setPin(pin: string): Promise<void> {
|
||||
if (!/^\d{4,8}$/.test(pin)) throw new Error('PIN musi mieć 4-8 cyfr');
|
||||
if (!/^\d{4,8}$/.test(pin)) throw new Error('PIN must be 4-8 digits');
|
||||
await SecureStore.setItemAsync(PIN_KEY, pin);
|
||||
}
|
||||
|
||||
|
|
@ -66,8 +66,8 @@ export async function biometricAvailable(): Promise<boolean> {
|
|||
|
||||
export async function authenticateBiometric(): Promise<boolean> {
|
||||
const res = await LocalAuthentication.authenticateAsync({
|
||||
promptMessage: 'Odblokuj Goon',
|
||||
cancelLabel: 'Użyj PIN',
|
||||
promptMessage: 'Unlock Goon',
|
||||
cancelLabel: 'Use PIN',
|
||||
disableDeviceFallback: true,
|
||||
});
|
||||
return res.success;
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export async function resolveDoodStream(
|
|||
// 30x → resolve Location, kontynuuj. Inaczej traktuj jako final.
|
||||
if (r.status >= 300 && r.status < 400) {
|
||||
const loc = r.headers.get('location');
|
||||
if (!loc) return { error: `redirect ${r.status} bez Location` };
|
||||
if (!loc) return { error: `redirect ${r.status} without Location` };
|
||||
try {
|
||||
currentUrl = new URL(loc, currentUrl).toString();
|
||||
} catch {
|
||||
|
|
@ -147,7 +147,7 @@ async function _continueAfterFetch(
|
|||
// Tu odsyłamy do WebView fallback. Sam `turnstile` w HTML nie wystarcza —
|
||||
// pełna strona playera ZAWIERA opcjonalny turnstile container.
|
||||
if (html.length < 2000 || /challenge-platform/i.test(html)) {
|
||||
return { error: 'captcha_gate (mobile IP też zablokowane?)' };
|
||||
return { error: 'captcha_gate (mobile IP also blocked?)' };
|
||||
}
|
||||
return { error: 'no_pass_md5_in_html' };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,9 +99,9 @@ export function AppLockScreen({ onUnlock, onLogout }: Props) {
|
|||
<View style={styles.lockBadge}>
|
||||
<Text style={styles.lockGlyph}>g</Text>
|
||||
</View>
|
||||
<Text style={styles.title}>Goon zablokowany</Text>
|
||||
<Text style={styles.title}>Goon locked</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{bioEnabled && bioAvailable ? 'Użyj odcisku lub PIN-u' : 'Wpisz PIN'}
|
||||
{bioEnabled && bioAvailable ? 'Use fingerprint or PIN' : 'Enter PIN'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ export function AppLockScreen({ onUnlock, onLogout }: Props) {
|
|||
|
||||
{onLogout ? (
|
||||
<Pressable onPress={onLogout} hitSlop={12} style={styles.logout}>
|
||||
<Text style={styles.logoutText}>Wyloguj się</Text>
|
||||
<Text style={styles.logoutText}>Log out</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import { theme } from '../theme';
|
|||
import { PinEntry } from './PinEntry';
|
||||
|
||||
const TIMEOUT_OPTIONS: { label: string; seconds: number }[] = [
|
||||
{ label: 'Natychmiast', seconds: 0 },
|
||||
{ label: '30 sek', seconds: 30 },
|
||||
{ label: 'Immediately', seconds: 0 },
|
||||
{ label: '30 sec', seconds: 30 },
|
||||
{ label: '1 min', seconds: 60 },
|
||||
{ label: '5 min', seconds: 300 },
|
||||
{ label: '15 min', seconds: 900 },
|
||||
|
|
@ -63,7 +63,7 @@ export function AppLockSettingsScreen() {
|
|||
if (stage === 'enter-current') {
|
||||
return (
|
||||
<PinEntry
|
||||
title="Wpisz aktualny PIN"
|
||||
title="Enter current PIN"
|
||||
error={errorText}
|
||||
onCancel={() => {
|
||||
setStage('menu');
|
||||
|
|
@ -72,7 +72,7 @@ export function AppLockSettingsScreen() {
|
|||
onSubmit={async (candidate) => {
|
||||
const ok = await verifyPin(candidate);
|
||||
if (!ok) {
|
||||
setErrorText('Nieprawidłowy PIN');
|
||||
setErrorText('Incorrect PIN');
|
||||
return;
|
||||
}
|
||||
setErrorText(null);
|
||||
|
|
@ -85,7 +85,7 @@ export function AppLockSettingsScreen() {
|
|||
if (stage === 'set-new') {
|
||||
return (
|
||||
<PinEntry
|
||||
title="Wpisz nowy PIN (4-8 cyfr)"
|
||||
title="Enter new PIN (4-8 digits)"
|
||||
error={errorText}
|
||||
onCancel={() => {
|
||||
setStage('menu');
|
||||
|
|
@ -94,7 +94,7 @@ export function AppLockSettingsScreen() {
|
|||
}}
|
||||
onSubmit={async (candidate) => {
|
||||
if (candidate.length < 4) {
|
||||
setErrorText('Min. 4 cyfry');
|
||||
setErrorText('Min. 4 digits');
|
||||
return;
|
||||
}
|
||||
setNewPin(candidate);
|
||||
|
|
@ -108,7 +108,7 @@ export function AppLockSettingsScreen() {
|
|||
if (stage === 'confirm-new') {
|
||||
return (
|
||||
<PinEntry
|
||||
title="Potwierdź PIN"
|
||||
title="Confirm PIN"
|
||||
error={errorText}
|
||||
onCancel={() => {
|
||||
setStage('menu');
|
||||
|
|
@ -117,7 +117,7 @@ export function AppLockSettingsScreen() {
|
|||
}}
|
||||
onSubmit={async (candidate) => {
|
||||
if (candidate !== newPin) {
|
||||
setErrorText('PIN-y nie pasują');
|
||||
setErrorText('PINs do not match');
|
||||
return;
|
||||
}
|
||||
await setPin(newPin);
|
||||
|
|
@ -126,7 +126,7 @@ export function AppLockSettingsScreen() {
|
|||
setErrorText(null);
|
||||
setStage('menu');
|
||||
await refresh();
|
||||
Alert.alert('Gotowe', 'PIN zapisany. Blokada włączona.');
|
||||
Alert.alert('Done', 'PIN saved. App lock enabled.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
@ -135,7 +135,7 @@ export function AppLockSettingsScreen() {
|
|||
if (stage === 'disable-confirm') {
|
||||
return (
|
||||
<PinEntry
|
||||
title="Wpisz PIN aby wyłączyć blokadę"
|
||||
title="Enter PIN to disable lock"
|
||||
error={errorText}
|
||||
onCancel={() => {
|
||||
setStage('menu');
|
||||
|
|
@ -144,7 +144,7 @@ export function AppLockSettingsScreen() {
|
|||
onSubmit={async (candidate) => {
|
||||
const ok = await verifyPin(candidate);
|
||||
if (!ok) {
|
||||
setErrorText('Nieprawidłowy PIN');
|
||||
setErrorText('Incorrect PIN');
|
||||
return;
|
||||
}
|
||||
await setEnabled(false);
|
||||
|
|
@ -161,13 +161,13 @@ export function AppLockSettingsScreen() {
|
|||
return (
|
||||
<ScrollView style={styles.root} contentContainerStyle={{ paddingBottom: 40 }}>
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Blokada aplikacji</Text>
|
||||
<Text style={styles.sectionTitle}>App lock</Text>
|
||||
|
||||
<View style={styles.row}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.label}>Włączona</Text>
|
||||
<Text style={styles.label}>Enabled</Text>
|
||||
<Text style={styles.hint}>
|
||||
{settings.enabled ? 'Wymaga PIN przy starcie i po przerwie' : 'Wyłączona'}
|
||||
{settings.enabled ? 'Requires PIN at startup and after a break' : 'Disabled'}
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
|
|
@ -194,7 +194,7 @@ export function AppLockSettingsScreen() {
|
|||
setErrorText(null);
|
||||
}}
|
||||
>
|
||||
<Text style={styles.actionText}>Zmień PIN</Text>
|
||||
<Text style={styles.actionText}>Change PIN</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
|
|
@ -202,14 +202,14 @@ export function AppLockSettingsScreen() {
|
|||
{settings.enabled ? (
|
||||
<>
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Biometria</Text>
|
||||
<Text style={styles.sectionTitle}>Biometrics</Text>
|
||||
<View style={styles.row}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.label}>Odcisk / Face Unlock</Text>
|
||||
<Text style={styles.label}>Fingerprint / Face Unlock</Text>
|
||||
<Text style={styles.hint}>
|
||||
{bioAvailable
|
||||
? 'Szybsze odblokowywanie, PIN jako fallback'
|
||||
: 'Brak biometrii w urządzeniu'}
|
||||
? 'Faster unlocking, PIN as fallback'
|
||||
: 'No biometrics on this device'}
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
|
|
@ -226,8 +226,8 @@ export function AppLockSettingsScreen() {
|
|||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Czas do zablokowania</Text>
|
||||
<Text style={styles.hint}>Po wyjściu z aplikacji ile czekać przed lockiem</Text>
|
||||
<Text style={styles.sectionTitle}>Time until lock</Text>
|
||||
<Text style={styles.hint}>How long to wait after leaving the app before locking</Text>
|
||||
<View style={styles.chipRow}>
|
||||
{TIMEOUT_OPTIONS.map((opt) => {
|
||||
const active = settings.timeoutSeconds === opt.seconds;
|
||||
|
|
@ -253,16 +253,16 @@ export function AppLockSettingsScreen() {
|
|||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.hint}>
|
||||
Aplikacja jest również ukryta na liście ostatnich aplikacji i blokuje zrzuty ekranu.
|
||||
The app is also hidden in the recent apps list and blocks screenshots.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>O aplikacji</Text>
|
||||
<Text style={styles.sectionTitle}>About</Text>
|
||||
<View style={styles.row}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.label}>Wersja</Text>
|
||||
<Text style={styles.hint}>Bieżący JS bundle (OTA-updated)</Text>
|
||||
<Text style={styles.label}>Version</Text>
|
||||
<Text style={styles.hint}>Current JS bundle (OTA-updated)</Text>
|
||||
</View>
|
||||
<Text style={styles.versionValue}>{APP_VERSION}</Text>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ function WatchChip({
|
|||
if (parts.length > 1) {
|
||||
Alert.alert(
|
||||
title,
|
||||
'Film składa się z kilku części. Wybierz którą zacząć.',
|
||||
'This movie has several parts. Choose which one to start.',
|
||||
[
|
||||
...parts.map((p) => ({
|
||||
text: ((p.raw as any).part_label as string) ?? p.quality ?? 'Part',
|
||||
|
|
@ -216,7 +216,7 @@ function WatchChip({
|
|||
});
|
||||
},
|
||||
})),
|
||||
{ text: 'Anuluj', style: 'cancel' as const },
|
||||
{ text: 'Cancel', style: 'cancel' as const },
|
||||
],
|
||||
);
|
||||
return;
|
||||
|
|
@ -246,36 +246,36 @@ function WatchChip({
|
|||
const onLongPress = () => {
|
||||
Alert.alert(
|
||||
pb.origin,
|
||||
'Co zrobić z tym linkiem?',
|
||||
'What do you want to do with this link?',
|
||||
[
|
||||
{
|
||||
text: 'Otwórz w przeglądarce (diagnostyka)',
|
||||
text: 'Open in browser (diagnostics)',
|
||||
onPress: async () => {
|
||||
try {
|
||||
const url = pb.page_url || pb.embed_url;
|
||||
if (url) {
|
||||
await Linking.openURL(url);
|
||||
} else {
|
||||
Alert.alert('Brak URL', 'Ten playback nie ma page_url do otworzenia.');
|
||||
Alert.alert('No URL', 'This playback has no page_url to open.');
|
||||
}
|
||||
} catch (e) {
|
||||
Alert.alert('Nie udało się otworzyć', e instanceof Error ? e.message : String(e));
|
||||
Alert.alert('Could not open', e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Oznacz jako nieprawidłowy',
|
||||
text: 'Mark as invalid',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await client.markMoviePlaybackDead(movieId, pb.id);
|
||||
queryClient.invalidateQueries({ queryKey: ['movie', movieId] });
|
||||
} catch (e) {
|
||||
Alert.alert('Nie udało się', e instanceof Error ? e.message : String(e));
|
||||
Alert.alert('Failed', e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
},
|
||||
},
|
||||
{ text: 'Anuluj', style: 'cancel' },
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
],
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,14 +57,14 @@ export function PinEntry({ title, error, onSubmit, onCancel }: Props) {
|
|||
|
||||
<View style={styles.actions}>
|
||||
<Pressable style={styles.ghostBtn} onPress={onCancel}>
|
||||
<Text style={styles.ghostText}>Anuluj</Text>
|
||||
<Text style={styles.ghostText}>Cancel</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={[styles.primaryBtn, pin.length < 4 && styles.primaryBtnDisabled]}
|
||||
disabled={pin.length < 4}
|
||||
onPress={() => onSubmit(pin)}
|
||||
>
|
||||
<Text style={styles.primaryText}>Dalej</Text>
|
||||
<Text style={styles.primaryText}>Next</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export function PlaybackQualityModal({
|
|||
>
|
||||
<Pressable style={styles.backdrop} onPress={onCancel}>
|
||||
<Pressable style={styles.sheet} onPress={(e) => e.stopPropagation()}>
|
||||
<Text style={styles.title}>Wybierz jakość</Text>
|
||||
<Text style={styles.title}>Select quality</Text>
|
||||
{sorted.map((link, i) => {
|
||||
const px = qualityToInt(link.quality);
|
||||
return (
|
||||
|
|
@ -70,7 +70,7 @@ export function PlaybackQualityModal({
|
|||
);
|
||||
})}
|
||||
<Pressable style={styles.cancel} onPress={onCancel}>
|
||||
<Text style={styles.cancelText}>Anuluj</Text>
|
||||
<Text style={styles.cancelText}>Cancel</Text>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
|
|
|
|||
|
|
@ -606,7 +606,7 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
<View style={styles.overlay}>
|
||||
<Text style={styles.errorTitle}>Playback failed</Text>
|
||||
<Text style={styles.errorBody}>
|
||||
{playerError?.message ?? 'Stream nie odpalił się.'}
|
||||
{playerError?.message ?? 'The stream did not start.'}
|
||||
</Text>
|
||||
<Pressable style={styles.btn} onPress={() => nav.goBack()}>
|
||||
<Text style={styles.btnText}>Back</Text>
|
||||
|
|
@ -1129,7 +1129,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
|
|||
return (
|
||||
<View style={[styles.root, styles.overlay]}>
|
||||
<ActivityIndicator color={theme.fg} size="large" />
|
||||
<Text style={styles.overlayText}>Resolwuję bezpośredni link...</Text>
|
||||
<Text style={styles.overlayText}>Resolving direct link...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -1153,7 +1153,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
|
|||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: theme.fg, fontWeight: '600' }}>Otwórz w WebView</Text>
|
||||
<Text style={{ color: theme.fg, fontWeight: '600' }}>Open in WebView</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -1197,7 +1197,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) {
|
|||
})
|
||||
}
|
||||
>
|
||||
<Text style={styles.extractText}>▶ Otwórz w native playerze</Text>
|
||||
<Text style={styles.extractText}>▶ Open in native player</Text>
|
||||
<Text style={styles.extractSub}>{extractedUrl.slice(0, 80)}</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@ function PlaybackButton({
|
|||
const embedLinks = unique.filter((l) => !l.stream_url && !!l.embed_url);
|
||||
|
||||
if (directLinks.length === 0 && embedLinks.length === 0) {
|
||||
Alert.alert('No stream', 'porn-app nie zwrócił żadnego stream URL — fallback do strony.');
|
||||
Alert.alert('No stream', 'porn-app did not return any stream URL — falling back to the page.');
|
||||
await openUrl(source.page_url);
|
||||
return;
|
||||
}
|
||||
|
|
@ -567,8 +567,8 @@ function PlaybackButton({
|
|||
// lub tube page 404/410). Refresh sceny żeby ten button zniknął z listy.
|
||||
if (e instanceof ApiError && e.status === 410) {
|
||||
Alert.alert(
|
||||
'Link martwy',
|
||||
'Tube usunął ten film. Oznaczyliśmy źródło, więcej go nie zobaczysz.',
|
||||
'Dead link',
|
||||
'The tube removed this video. We marked the source, you won\'t see it again.',
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ['scene', sceneId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['scenes'] });
|
||||
|
|
@ -579,8 +579,8 @@ function PlaybackButton({
|
|||
// następne kliknięcie pewnie zadziała.
|
||||
if (e instanceof ApiError && e.status === 503) {
|
||||
Alert.alert(
|
||||
'Spróbuj ponownie',
|
||||
'Chwilowy problem z hosterem. Kliknij Play ponownie za moment.',
|
||||
'Try again',
|
||||
'Temporary problem with the host. Tap Play again in a moment.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -638,25 +638,25 @@ function PlaybackButton({
|
|||
const onLongPress = () => {
|
||||
Alert.alert(
|
||||
label,
|
||||
'Co zrobić z tym linkiem?',
|
||||
'What do you want to do with this link?',
|
||||
[
|
||||
{
|
||||
text: 'Otwórz w przeglądarce (diagnostyka)',
|
||||
text: 'Open in browser (diagnostics)',
|
||||
onPress: async () => {
|
||||
try {
|
||||
const url = source.page_url || source.embed_url || source.stream_url;
|
||||
if (url) {
|
||||
await Linking.openURL(url);
|
||||
} else {
|
||||
Alert.alert('Brak URL', 'Ten playback nie ma page_url do otworzenia.');
|
||||
Alert.alert('No URL', 'This playback has no page_url to open.');
|
||||
}
|
||||
} catch (e) {
|
||||
Alert.alert('Nie udało się otworzyć', e instanceof Error ? e.message : String(e));
|
||||
Alert.alert('Could not open', e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Oznacz jako nieprawidłowy',
|
||||
text: 'Mark as invalid',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
|
|
@ -664,11 +664,11 @@ function PlaybackButton({
|
|||
queryClient.invalidateQueries({ queryKey: ['scene', sceneId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['scenes'] });
|
||||
} catch (e) {
|
||||
Alert.alert('Nie udało się', e instanceof Error ? e.message : String(e));
|
||||
Alert.alert('Failed', e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
},
|
||||
},
|
||||
{ text: 'Anuluj', style: 'cancel' },
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
],
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ export function ScenesFilterModal({
|
|||
style={styles.search}
|
||||
value={draft.origin}
|
||||
onChangeText={(v) => setDraft({ ...draft, origin: v })}
|
||||
placeholder="np. hqporner, porntrex, xnxx — puste = wszystkie"
|
||||
placeholder="e.g. hqporner, porntrex, xnxx — empty = all"
|
||||
placeholderTextColor={theme.muted}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
|
|
|
|||
|
|
@ -82,12 +82,12 @@ export function SiteScenesScreen() {
|
|||
onPress={() => setFilterOpen(true)}
|
||||
>
|
||||
<Text style={styles.filterBtnText}>
|
||||
Tagi{selectedTags.length > 0 ? ` ${selectedTags.length}` : ''}
|
||||
Tags{selectedTags.length > 0 ? ` ${selectedTags.length}` : ''}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{selectedTags.length > 0 ? (
|
||||
<Pressable style={styles.clearBtn} onPress={() => setSelectedTags([])}>
|
||||
<Text style={styles.clearBtnText}>Wyczyść</Text>
|
||||
<Text style={styles.clearBtnText}>Clear</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
|
|
@ -181,14 +181,14 @@ function TagPickerModal({
|
|||
<View style={modalStyles.backdrop}>
|
||||
<View style={modalStyles.sheet}>
|
||||
<View style={modalStyles.header}>
|
||||
<Text style={modalStyles.title}>Filtruj po tagach</Text>
|
||||
<Text style={modalStyles.title}>Filter by tags</Text>
|
||||
<Pressable onPress={onClose}>
|
||||
<Text style={modalStyles.close}>✕</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<TextInput
|
||||
style={modalStyles.search}
|
||||
placeholder="Szukaj tagu…"
|
||||
placeholder="Search tags…"
|
||||
placeholderTextColor={theme.muted}
|
||||
value={q}
|
||||
onChangeText={setQ}
|
||||
|
|
@ -213,7 +213,7 @@ function TagPickerModal({
|
|||
);
|
||||
})}
|
||||
{tags.length === 0 ? (
|
||||
<Text style={modalStyles.muted}>Brak wyników</Text>
|
||||
<Text style={modalStyles.muted}>No results</Text>
|
||||
) : null}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
|
@ -226,7 +226,7 @@ function TagPickerModal({
|
|||
<Text
|
||||
style={[modalStyles.footerBtnText, selected.length === 0 && { opacity: 0.4 }]}
|
||||
>
|
||||
Wyczyść
|
||||
Clear
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
|
|
@ -234,7 +234,7 @@ function TagPickerModal({
|
|||
onPress={() => onApply(selected)}
|
||||
>
|
||||
<Text style={modalStyles.footerBtnTextPrimary}>
|
||||
Zastosuj{selected.length > 0 ? ` (${selected.length})` : ''}
|
||||
Apply{selected.length > 0 ? ` (${selected.length})` : ''}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue