mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-07 20:10:03 +03:00
Compare commits
16 Commits
docker-fix
...
twitch-dow
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24d8072eb5 | ||
|
|
c81bf980ca | ||
|
|
a91381720f | ||
|
|
edd4a0928c | ||
|
|
770916492e | ||
|
|
6400b807c2 | ||
|
|
3a7e2d9d0f | ||
|
|
ca5381fe0f | ||
|
|
bd8d91ebe5 | ||
|
|
27f05dbae3 | ||
|
|
c7bf1d0e27 | ||
|
|
57be0a032e | ||
|
|
6fe4b22efc | ||
|
|
af2d583924 | ||
|
|
c61d51be76 | ||
|
|
142d708ee3 |
14
Dockerfile
14
Dockerfile
@@ -2,7 +2,7 @@
|
||||
FROM ubuntu:22.04 AS ffmpeg
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
# Use script due local build compability
|
||||
COPY ffmpeg-fetch.sh .
|
||||
COPY docker-utils/ffmpeg-fetch.sh .
|
||||
RUN chmod +x ffmpeg-fetch.sh
|
||||
RUN sh ./ffmpeg-fetch.sh
|
||||
|
||||
@@ -47,6 +47,15 @@ RUN npm config set strict-ssl false && \
|
||||
npm install --prod && \
|
||||
ls -al
|
||||
|
||||
FROM base as python
|
||||
WORKDIR /app
|
||||
COPY docker-utils/GetTwitchDownloader.py .
|
||||
RUN apt update && \
|
||||
apt install -y --no-install-recommends python3-minimal python-is-python3 python3-pip && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
RUN pip install PyGithub requests
|
||||
RUN python GetTwitchDownloader.py
|
||||
|
||||
# Final image
|
||||
FROM base
|
||||
@@ -55,13 +64,14 @@ RUN npm install -g pm2 && \
|
||||
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 python3-pip atomicparsley build-essential && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
RUN pip install tdh-tcd pycryptodomex
|
||||
RUN pip install pycryptodomex
|
||||
WORKDIR /app
|
||||
# User 1000 already exist from base image
|
||||
COPY --chown=$UID:$GID --from=ffmpeg [ "/usr/local/bin/ffmpeg", "/usr/local/bin/ffmpeg" ]
|
||||
COPY --chown=$UID:$GID --from=ffmpeg [ "/usr/local/bin/ffprobe", "/usr/local/bin/ffprobe" ]
|
||||
COPY --chown=$UID:$GID --from=backend ["/app/","/app/"]
|
||||
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
|
||||
RUN chown $UID:$GID .
|
||||
RUN chmod +x /app/fix-scripts/*.sh
|
||||
# Add some persistence data
|
||||
#VOLUME ["/app/appdata"]
|
||||
|
||||
@@ -2031,7 +2031,7 @@ app.post('/api/changeRolePermissions', optionalJwt, async (req, res) => {
|
||||
// notifications
|
||||
|
||||
app.post('/api/getNotifications', optionalJwt, async (req, res) => {
|
||||
const uuid = req.user.uid;
|
||||
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||
|
||||
const notifications = await db_api.getRecords('notifications', {user_uid: uuid});
|
||||
|
||||
@@ -2040,7 +2040,7 @@ app.post('/api/getNotifications', optionalJwt, async (req, res) => {
|
||||
|
||||
// set notifications to read
|
||||
app.post('/api/setNotificationsToRead', optionalJwt, async (req, res) => {
|
||||
const uuid = req.user.uid;
|
||||
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||
|
||||
const success = await db_api.updateRecords('notifications', {user_uid: uuid}, {read: true});
|
||||
|
||||
@@ -2048,7 +2048,7 @@ app.post('/api/setNotificationsToRead', optionalJwt, async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/deleteNotification', optionalJwt, async (req, res) => {
|
||||
const uid = req.body.uid;
|
||||
const uid = req.isAuthenticated() ? req.user.uid : null;
|
||||
|
||||
const success = await db_api.removeRecord('notifications', {uid: uid});
|
||||
|
||||
@@ -2056,7 +2056,7 @@ app.post('/api/deleteNotification', optionalJwt, async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/deleteAllNotifications', optionalJwt, async (req, res) => {
|
||||
const uuid = req.user.uid;
|
||||
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||
|
||||
const success = await db_api.removeAllRecords('notifications', {user_uid: uuid});
|
||||
|
||||
|
||||
@@ -208,9 +208,6 @@ const DEFAULT_CONFIG = {
|
||||
"API_key": "",
|
||||
"use_youtube_API": false,
|
||||
"youtube_API_key": "",
|
||||
"use_twitch_API": false,
|
||||
"twitch_client_ID": "",
|
||||
"twitch_client_secret": "",
|
||||
"twitch_auto_download_chat": false,
|
||||
"use_sponsorblock_API": false,
|
||||
"generate_NFO_files": false,
|
||||
|
||||
@@ -110,18 +110,6 @@ exports.CONFIG_ITEMS = {
|
||||
'key': 'ytdl_youtube_api_key',
|
||||
'path': 'YoutubeDLMaterial.API.youtube_API_key'
|
||||
},
|
||||
'ytdl_use_twitch_api': {
|
||||
'key': 'ytdl_use_twitch_api',
|
||||
'path': 'YoutubeDLMaterial.API.use_twitch_API'
|
||||
},
|
||||
'ytdl_twitch_client_id': {
|
||||
'key': 'ytdl_twitch_client_id',
|
||||
'path': 'YoutubeDLMaterial.API.twitch_client_ID'
|
||||
},
|
||||
'ytdl_twitch_client_secret': {
|
||||
'key': 'ytdl_twitch_client_secret',
|
||||
'path': 'YoutubeDLMaterial.API.twitch_client_secret'
|
||||
},
|
||||
'ytdl_twitch_auto_download_chat': {
|
||||
'key': 'ytdl_twitch_auto_download_chat',
|
||||
'path': 'YoutubeDLMaterial.API.twitch_auto_download_chat'
|
||||
|
||||
@@ -698,9 +698,15 @@ exports.getRecords = async (table, filter_obj = null, return_count = false, sort
|
||||
|
||||
// Update
|
||||
|
||||
exports.updateRecord = async (table, filter_obj, update_obj) => {
|
||||
exports.updateRecord = async (table, filter_obj, update_obj, nested_mode = false) => {
|
||||
// local db override
|
||||
if (using_local_db) {
|
||||
if (nested_mode) {
|
||||
// if object is nested we need to handle it differently
|
||||
update_obj = utils.convertFlatObjectToNestedObject(update_obj);
|
||||
exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'find').merge(update_obj).write();
|
||||
return true;
|
||||
}
|
||||
exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'find').assign(update_obj).write();
|
||||
return true;
|
||||
}
|
||||
@@ -722,6 +728,18 @@ exports.updateRecords = async (table, filter_obj, update_obj) => {
|
||||
return !!(output['result']['ok']);
|
||||
}
|
||||
|
||||
exports.removePropertyFromRecord = async (table, filter_obj, remove_obj) => {
|
||||
// local db override
|
||||
if (using_local_db) {
|
||||
const props_to_remove = Object.keys(remove_obj);
|
||||
exports.applyFilterLocalDB(local_db.get(table), filter_obj, 'find').unset(props_to_remove).write();
|
||||
return true;
|
||||
}
|
||||
|
||||
const output = await database.collection(table).updateOne(filter_obj, {$unset: remove_obj});
|
||||
return !!(output['result']['ok']);
|
||||
}
|
||||
|
||||
exports.bulkUpdateRecordsByKey = async (table, key_label, update_obj) => {
|
||||
// local db override
|
||||
if (using_local_db) {
|
||||
|
||||
@@ -350,7 +350,7 @@ async function downloadQueuedFile(download_uid) {
|
||||
var file_name = filepath_no_extension.substring(fileFolderPath.length, filepath_no_extension.length);
|
||||
|
||||
if (type === 'video' && url.includes('twitch.tv/videos/') && url.split('twitch.tv/videos/').length > 1
|
||||
&& config_api.getConfigItem('ytdl_use_twitch_api') && config_api.getConfigItem('ytdl_twitch_auto_download_chat')) {
|
||||
&& config_api.getConfigItem('ytdl_twitch_auto_download_chat')) {
|
||||
let vodId = url.split('twitch.tv/videos/')[1];
|
||||
vodId = vodId.split('?')[0];
|
||||
twitch_api.downloadTwitchChatByVODID(vodId, file_name, type, download['user_uid']);
|
||||
|
||||
@@ -10,7 +10,7 @@ fi
|
||||
|
||||
# chown current working directory to current user
|
||||
if [ "$*" = "$CMD" ] && [ "$(id -u)" = "0" ]; then
|
||||
find . \! -user "$UID" -exec chown "$UID:$GID" -R '{}' + || echo "WARNING! Could not change directory ownership. If you manage permissions externally this is fine, otherwise you may experience issues when downloading or deleting videos."
|
||||
find . \! -user "$UID" -exec chown "$UID:$GID" '{}' + || echo "WARNING! Could not change directory ownership. If you manage permissions externally this is fine, otherwise you may experience issues when downloading or deleting videos."
|
||||
exec gosu "$UID:$GID" "$0" "$@"
|
||||
fi
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ exports.setupTasks = async () => {
|
||||
const tasks_keys = Object.keys(TASKS);
|
||||
for (let i = 0; i < tasks_keys.length; i++) {
|
||||
const task_key = tasks_keys[i];
|
||||
const mergedDefaultOptions = Object.assign(defaultOptions['all'], defaultOptions[task_key] || {});
|
||||
const mergedDefaultOptions = Object.assign({}, defaultOptions['all'], defaultOptions[task_key] || {});
|
||||
const task_in_db = await db_api.getRecord('tasks', {key: task_key});
|
||||
if (!task_in_db) {
|
||||
// insert task metadata into table if missing, eventually move title to UI
|
||||
@@ -115,14 +115,16 @@ exports.setupTasks = async () => {
|
||||
data: null,
|
||||
error: null,
|
||||
schedule: null,
|
||||
options: Object.assign(defaultOptions['all'], defaultOptions[task_key] || {})
|
||||
options: Object.assign({}, defaultOptions['all'], defaultOptions[task_key] || {})
|
||||
});
|
||||
} else {
|
||||
// verify all options exist in task
|
||||
for (const key of Object.keys(mergedDefaultOptions)) {
|
||||
const option_key = `options.${key}`;
|
||||
// Remove any potential mangled option keys (#861)
|
||||
await db_api.removePropertyFromRecord('tasks', {key: task_key}, {[option_key]: true});
|
||||
if (!(task_in_db.options && task_in_db.options.hasOwnProperty(key))) {
|
||||
const option_key = `options.${key}`
|
||||
await db_api.updateRecord('tasks', {key: task_key}, {[option_key]: mergedDefaultOptions[key]});
|
||||
await db_api.updateRecord('tasks', {key: task_key}, {[option_key]: mergedDefaultOptions[key]}, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +175,15 @@ describe('Database', async function() {
|
||||
await db_api.removeRecord('test', {test_update: 'test'});
|
||||
});
|
||||
|
||||
it('Remove property from record', async function() {
|
||||
await db_api.insertRecordIntoTable('test', {test_keep: 'test', test_remove: 'test'});
|
||||
await db_api.removePropertyFromRecord('test', {test_keep: 'test'}, {test_remove: true});
|
||||
const updated_record = await db_api.getRecord('test', {test_keep: 'test'});
|
||||
assert(updated_record['test_keep']);
|
||||
assert(!updated_record['test_remove']);
|
||||
await db_api.removeRecord('test', {test_keep: 'test'});
|
||||
});
|
||||
|
||||
it('Remove record', async function() {
|
||||
await db_api.insertRecordIntoTable('test', {test_remove: 'test'});
|
||||
const delete_succeeded = await db_api.removeRecord('test', {test_remove: 'test'});
|
||||
@@ -499,7 +508,7 @@ describe('Downloader', function() {
|
||||
});
|
||||
describe('Twitch', async function () {
|
||||
const twitch_api = require('../twitch');
|
||||
const example_vod = '1493770675';
|
||||
const example_vod = '1710641401';
|
||||
it('Download VOD', async function() {
|
||||
const sample_path = path.join('test', 'sample.twitch_chat.json');
|
||||
if (fs.existsSync(sample_path)) fs.unlinkSync(sample_path);
|
||||
@@ -699,4 +708,24 @@ describe('Utils', async function() {
|
||||
const stripped_obj = utils.stripPropertiesFromObject(test_obj, ['test1', 'test3']);
|
||||
assert(!stripped_obj['test1'] && stripped_obj['test2'] && !stripped_obj['test3'])
|
||||
});
|
||||
|
||||
it('Convert flat object to nested object', async function() {
|
||||
// No modfication
|
||||
const flat_obj0 = {'test1': {'test_sub': true}, 'test2': {test_sub: true}};
|
||||
const nested_obj0 = utils.convertFlatObjectToNestedObject(flat_obj0);
|
||||
assert(nested_obj0['test1'] && nested_obj0['test1']['test_sub']);
|
||||
assert(nested_obj0['test2'] && nested_obj0['test2']['test_sub']);
|
||||
|
||||
// Standard setup
|
||||
const flat_obj1 = {'test1.test_sub': true, 'test2.test_sub': true};
|
||||
const nested_obj1 = utils.convertFlatObjectToNestedObject(flat_obj1);
|
||||
assert(nested_obj1['test1'] && nested_obj1['test1']['test_sub']);
|
||||
assert(nested_obj1['test2'] && nested_obj1['test2']['test_sub']);
|
||||
|
||||
// Nested branches
|
||||
const flat_obj2 = {'test1.test_sub': true, 'test1.test2.test_sub': true};
|
||||
const nested_obj2 = utils.convertFlatObjectToNestedObject(flat_obj2);
|
||||
assert(nested_obj2['test1'] && nested_obj2['test1']['test_sub']);
|
||||
assert(nested_obj2['test1'] && nested_obj2['test1']['test2'] && nested_obj2['test1']['test2']['test_sub']);
|
||||
});
|
||||
});
|
||||
@@ -4,19 +4,28 @@ const logger = require('./logger');
|
||||
const moment = require('moment');
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path');
|
||||
const { promisify } = require('util');
|
||||
const child_process = require('child_process');
|
||||
|
||||
async function getCommentsForVOD(clientID, clientSecret, vodId) {
|
||||
const { promisify } = require('util');
|
||||
const child_process = require('child_process');
|
||||
async function getCommentsForVOD(vodId) {
|
||||
const exec = promisify(child_process.exec);
|
||||
|
||||
// Reject invalid params to prevent command injection attack
|
||||
if (!clientID.match(/^[0-9a-z]+$/) || !clientSecret.match(/^[0-9a-z]+$/) || !vodId.match(/^[0-9a-z]+$/)) {
|
||||
logger.error('Client ID, client secret, and VOD ID must be purely alphanumeric. Twitch chat download failed!');
|
||||
if (!vodId.match(/^[0-9a-z]+$/)) {
|
||||
logger.error('VOD ID must be purely alphanumeric. Twitch chat download failed!');
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await exec(`tcd --video ${vodId} --client-id ${clientID} --client-secret ${clientSecret} --format json -o appdata`, {stdio:[0,1,2]});
|
||||
const is_windows = process.platform === 'win32';
|
||||
const cliExt = is_windows ? '.exe' : ''
|
||||
const cliPath = `TwitchDownloaderCLI${cliExt}`
|
||||
|
||||
if (!fs.existsSync(cliPath)) {
|
||||
logger.error(`${cliPath} does not exist. Twitch chat download failed! Get it here: https://github.com/lay295/TwitchDownloader`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await exec(`TwitchDownloaderCLI chatdownload -u ${vodId} -o appdata/${vodId}.json`, {stdio:[0,1,2]});
|
||||
|
||||
if (result['stderr']) {
|
||||
logger.error(`Failed to download twitch comments for ${vodId}`);
|
||||
@@ -73,9 +82,7 @@ async function getTwitchChatByFileID(id, type, user_uid, uuid, sub) {
|
||||
async function downloadTwitchChatByVODID(vodId, id, type, user_uid, sub, customFileFolderPath = null) {
|
||||
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||
const subscriptionsFileFolder = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
const twitch_client_id = config_api.getConfigItem('ytdl_twitch_client_id');
|
||||
const twitch_client_secret = config_api.getConfigItem('ytdl_twitch_client_secret');
|
||||
const chat = await getCommentsForVOD(twitch_client_id, twitch_client_secret, vodId);
|
||||
const chat = await getCommentsForVOD(vodId);
|
||||
|
||||
// save file if needed params are included
|
||||
let file_path = null;
|
||||
|
||||
@@ -501,6 +501,23 @@ exports.updateLoggerLevel = (new_logger_level) => {
|
||||
logger.transports[2].level = new_logger_level;
|
||||
}
|
||||
|
||||
exports.convertFlatObjectToNestedObject = (obj) => {
|
||||
const result = {};
|
||||
for (const key in obj) {
|
||||
const nestedKeys = key.split('.');
|
||||
let currentObj = result;
|
||||
for (let i = 0; i < nestedKeys.length; i++) {
|
||||
if (i === nestedKeys.length - 1) {
|
||||
currentObj[nestedKeys[i]] = obj[key];
|
||||
} else {
|
||||
currentObj[nestedKeys[i]] = currentObj[nestedKeys[i]] || {};
|
||||
currentObj = currentObj[nestedKeys[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// objects
|
||||
|
||||
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) {
|
||||
|
||||
53
docker-utils/GetTwitchDownloader.py
Normal file
53
docker-utils/GetTwitchDownloader.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import platform
|
||||
import requests
|
||||
import shutil
|
||||
import os
|
||||
import re
|
||||
|
||||
from github import Github
|
||||
|
||||
machine = platform.machine()
|
||||
|
||||
def isARM():
|
||||
return True if machine.startswith('arm') else False
|
||||
|
||||
def getLatestFileInRepo(repo, search_string):
|
||||
# Create an unauthenticated instance of the Github object
|
||||
g = Github(os.environ.get('GH_TOKEN'))
|
||||
|
||||
# Replace with the repository owner and name
|
||||
repo = g.get_repo(repo)
|
||||
|
||||
# Get all releases of the repository
|
||||
releases = repo.get_releases()
|
||||
|
||||
# Loop through the releases in reverse order (from latest to oldest)
|
||||
for release in list(releases):
|
||||
# Get the release assets (files attached to the release)
|
||||
assets = release.get_assets()
|
||||
|
||||
# Loop through the assets
|
||||
for asset in assets:
|
||||
if re.search(search_string, asset.name):
|
||||
print(f'Downloading: {asset.name}')
|
||||
response = requests.get(asset.browser_download_url)
|
||||
with open(asset.name, 'wb') as f:
|
||||
f.write(response.content)
|
||||
print(f'Download complete: {asset.name}. Unzipping...')
|
||||
shutil.unpack_archive(asset.name, './')
|
||||
print(f'Unzipping complete!')
|
||||
os.remove(asset.name)
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
# If no matching release is found, print a message
|
||||
print(f'No release found with {search_string}')
|
||||
|
||||
def getLatestCLIRelease():
|
||||
isArm = isARM()
|
||||
searchString = r'.*CLI.*' + "LinuxArm.zip" if isArm else "Linux-x64.zip"
|
||||
getLatestFileInRepo("lay295/TwitchDownloader", searchString)
|
||||
|
||||
getLatestCLIRelease()
|
||||
@@ -38,7 +38,7 @@
|
||||
</ng-container>
|
||||
<ng-container *ngIf="db_file || playlist[currentIndex]"></ng-container>
|
||||
<button (click)="openFileInfoDialog()" *ngIf="db_file || db_playlist" mat-icon-button><mat-icon>info</mat-icon></button>
|
||||
<button *ngIf="db_file && db_file.url.includes('twitch.tv') && postsService['config']['API']['use_twitch_API']" (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
|
||||
<button *ngIf="db_file && db_file.url.includes('twitch.tv')" (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<app-concurrent-stream *ngIf="db_file && api && postsService.config" (setPlaybackRate)="setPlaybackRate($event)" (togglePlayback)="togglePlayback($event)" (setPlaybackTimestamp)="setPlaybackTimestamp($event)" [playing]="api.state === 'playing'" [uid]="uid" [playback_timestamp]="api.time.current/1000" [server_mode]="!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn"></app-concurrent-stream>
|
||||
|
||||
<mat-drawer #drawer class="example-sidenav" mode="side" position="end" [opened]="db_file && db_file['chat_exists'] && postsService['config']['API']['use_twitch_API']">
|
||||
<mat-drawer #drawer class="example-sidenav" mode="side" position="end" [opened]="db_file && db_file['chat_exists']">
|
||||
<ng-container *ngIf="api_ready && db_file && db_file.url.includes('twitch.tv')">
|
||||
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime" [sub]="subscription"></app-twitch-chat>
|
||||
</ng-container>
|
||||
|
||||
@@ -269,25 +269,9 @@
|
||||
<mat-hint><a target="_blank" href="https://developers.google.com/youtube/v3/getting-started"><ng-container i18n="Youtube API Key setting hint">Generating a key is easy!</ng-container></a></mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12 mt-3">
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['API']['use_twitch_API']"><ng-container i18n="Use Twitch API setting">Use Twitch API</ng-container></mat-checkbox>
|
||||
</div>
|
||||
<div *ngIf="new_config['API']['use_twitch_API']" class="col-12 mt-1">
|
||||
<div class="col-12 mt-1">
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['API']['twitch_auto_download_chat']"><ng-container i18n="Auto download Twitch Chat setting">Auto-download Twitch Chat</ng-container></mat-checkbox>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field class="text-field" color="accent">
|
||||
<mat-label i18n="Twitch Client ID">Twitch Client ID</mat-label>
|
||||
<input [disabled]="!new_config['API']['use_twitch_API']" [(ngModel)]="new_config['API']['twitch_client_ID']" matInput required>
|
||||
<mat-hint><a target="_blank" href="https://dev.twitch.tv/docs/api/"><ng-container i18n="Twitch Client ID setting hint">Generating an ID/secret is easy!</ng-container></a></mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12 mt-2">
|
||||
<mat-form-field class="text-field" color="accent">
|
||||
<mat-label i18n="Twitch Client Secret">Twitch Client Secret</mat-label>
|
||||
<input [disabled]="!new_config['API']['use_twitch_API']" [(ngModel)]="new_config['API']['twitch_client_secret']" matInput required>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12 mt-2">
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['API']['use_sponsorblock_API']" matTooltip="Enables a button to skip ads when viewing supported videos." i18n-matTooltip="SponsorBlock API tooltip"><ng-container i18n="Use SponsorBlock API setting">Use SponsorBlock API</ng-container></mat-checkbox>
|
||||
</div>
|
||||
|
||||
4399
src/assets/i18n/messages.et.xlf
Normal file
4399
src/assets/i18n/messages.et.xlf
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1432,7 +1432,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8" datatype="html">
|
||||
<source>Profile</source>
|
||||
<target>个人资料</target>
|
||||
<target state="translated">资料</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/app.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
@@ -3033,7 +3033,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4e1fdb6039c7c6b7630ed70d6d20eb0c9db7d342" datatype="html">
|
||||
<source>Video only</source>
|
||||
<target state="translated">只视频</target>
|
||||
<target state="translated">仅视频</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.html</context>
|
||||
<context context-type="linenumber">55</context>
|
||||
@@ -4011,8 +4011,8 @@
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="39921032161993566" datatype="html">
|
||||
<source>Playlist created.</source>
|
||||
<target state="translated">已创建播放列表。</target>
|
||||
<source>Successfully created playlist!</source>
|
||||
<target state="translated">成功创建播放列表!</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
|
||||
<context context-type="linenumber">56</context>
|
||||
@@ -4191,6 +4191,830 @@
|
||||
<context context-type="linenumber">299</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="c748ac656af9f13998206ef2c52018dd418b0483" datatype="html">
|
||||
<source>Archives</source>
|
||||
<target state="translated">存档</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/app.component.html</context>
|
||||
<context context-type="linenumber">26</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Archives menu label</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="5ca707824ab93066c7d9b44e1b8bf216725c2c22" datatype="html">
|
||||
<source>Filter</source>
|
||||
<target state="translated">筛选</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Filter</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="45cc8ca94b5a50842a9a8ef804a5ab089a38ae5c" datatype="html">
|
||||
<source>ID</source>
|
||||
<target state="translated">ID</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">47</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">ID</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="28da11220a3377ddce3c7948825d33101f142782" datatype="html">
|
||||
<source>Extractor</source>
|
||||
<target state="translated">提取</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">57</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Extractor</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="c150a30bbbdb175b4d08820196a9acb66b167653" datatype="html">
|
||||
<source>Archives empty</source>
|
||||
<target state="translated">存档为空</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Archives empty</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="51a161ce175abcd44f6c6cbd0e996681bf553ac3" datatype="html">
|
||||
<source>Delete selected</source>
|
||||
<target state="translated">删除所选内容</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">77</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Delete selected</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="c41475a25c9f9d9639db9efa56637603a77528b4" datatype="html">
|
||||
<source>Download archive</source>
|
||||
<target state="translated">下载存档</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Download archive</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="a2f14a73f7a6e94479f67423cc51102da8d6f524" datatype="html">
|
||||
<source>None</source>
|
||||
<target state="translated">无</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
||||
<context context-type="linenumber">36</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">None</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="4b3972c3e9485218508a95f7a4ce7758e3f09ced" datatype="html">
|
||||
<source>Upload</source>
|
||||
<target state="translated">上传</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
||||
<context context-type="linenumber">137</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Upload</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6549265851868599441" datatype="html">
|
||||
<source>Video</source>
|
||||
<target state="translated">视频</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
||||
<context context-type="linenumber">40</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3159807825117518005" datatype="html">
|
||||
<source>Delete archives</source>
|
||||
<target state="translated">删除存档</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
||||
<context context-type="linenumber">152</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8425787787095143143" datatype="html">
|
||||
<source>Would you like to delete <x id="selected archives amount" equiv-text="this.selection.selected.length"/> archive(s)?</source>
|
||||
<target state="translated">是否要删除 <x id="selected archives amount" equiv-text="this.selection.selected.length"/> 存档?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
||||
<context context-type="linenumber">153</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8224301330941792118" datatype="html">
|
||||
<source>Failed to delete archive items!</source>
|
||||
<target state="translated">无法删除存档项目!</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
||||
<context context-type="linenumber">174</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="019d4bd6a5690f0cfa0ecf346a4e6bf7f0d8debb" datatype="html">
|
||||
<source>Remove</source>
|
||||
<target state="translated">移除</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.html</context>
|
||||
<context context-type="linenumber">23</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Remove</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6219551536751479443" datatype="html">
|
||||
<source>Finished downloading</source>
|
||||
<target state="translated">下载完成</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5947241266456580665" datatype="html">
|
||||
<source>Download failed</source>
|
||||
<target state="translated">下载失败</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8443034725057696949" datatype="html">
|
||||
<source>Task finished</source>
|
||||
<target state="translated">任务完成</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8564202903947049539" datatype="html">
|
||||
<source>Play</source>
|
||||
<target state="translated">播放</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8643601595923420698" datatype="html">
|
||||
<source>Retry download</source>
|
||||
<target state="translated">重试下载</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
||||
<context context-type="linenumber">31</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8571838164752006148" datatype="html">
|
||||
<source>View error</source>
|
||||
<target state="translated">查看错误</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
||||
<context context-type="linenumber">32</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5709555629190115111" datatype="html">
|
||||
<source>View task</source>
|
||||
<target state="translated">查看任务</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
||||
<context context-type="linenumber">33</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5a105e7bd7e7db6ea211fe950fc9f317379acceb" datatype="html">
|
||||
<source>No notifications available</source>
|
||||
<target state="translated">没有通知</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.html</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">No notifications available</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6876310993601590130" datatype="html">
|
||||
<source>Download completed</source>
|
||||
<target state="translated">下载完成</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
|
||||
<context context-type="linenumber">23</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1879058637439215882" datatype="html">
|
||||
<source>Download error</source>
|
||||
<target state="translated">下载错误</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4578192247039196794" datatype="html">
|
||||
<source>Task</source>
|
||||
<target state="translated">任务</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
|
||||
<context context-type="linenumber">31</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5000203534763292992" datatype="html">
|
||||
<source>Download restarted!</source>
|
||||
<target state="translated">下载已重新启动!</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7911845622864460134" datatype="html">
|
||||
<source>Video only</source>
|
||||
<target state="translated">仅视频</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
|
||||
<context context-type="linenumber">55</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6437411876967154040" datatype="html">
|
||||
<source>Audio only</source>
|
||||
<target state="translated">仅音频</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
|
||||
<context context-type="linenumber">60</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4665451070906079743" datatype="html">
|
||||
<source>Favorited</source>
|
||||
<target state="translated">收藏</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
|
||||
<context context-type="linenumber">65</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3533826530554274875" datatype="html">
|
||||
<source>Upload Date</source>
|
||||
<target state="translated">上传日期</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6268070779441507380" datatype="html">
|
||||
<source>Download Date</source>
|
||||
<target state="translated">下载日期</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8953033926734869941" datatype="html">
|
||||
<source>Name</source>
|
||||
<target state="translated">名称</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="c65dd978b3c7566551c0ebefb234c2d41942b847" datatype="html">
|
||||
<source>Delete files older than</source>
|
||||
<target state="translated">删除早于</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
||||
<context context-type="linenumber">6</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Delete files older than</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="56b1a3c93fb95fed1805005c561a5e431d57ffae" datatype="html">
|
||||
<source>Blacklist all files</source>
|
||||
<target state="translated">将所有文件列入黑名单</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
||||
<context context-type="linenumber">11</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Blacklist deleted files</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9aa1b4779a515170b297d2c0507e6ff9d2e3e0e0" datatype="html">
|
||||
<source>Blacklist deleted subscription files</source>
|
||||
<target state="translated">黑名单删除的订阅文件</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Blacklist deleted subscription files</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="cdf5297d8d080a78e8b10debc5c38b7845a3cbe7" datatype="html">
|
||||
<source>Do not ask for confirmation</source>
|
||||
<target state="translated">不要求确认</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Do not ask for confirmation</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="7b4585a9072f3c1292972c14a3d0e14978fbfc9c" datatype="html">
|
||||
<source>Delete old files:</source>
|
||||
<target state="translated">删除旧文件:</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">66</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Delete old files</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9176960997786930103" datatype="html">
|
||||
<source>Error for: <x id="PH" equiv-text="task['title']"/></source>
|
||||
<target state="translated">错误: <x id="PH" equiv-text="task['title']"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">174</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5ac5a0e5ffe8e5623b40696f4c2403c17349271f" datatype="html">
|
||||
<source>Sidepanel mode</source>
|
||||
<target state="translated">侧板模式</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/about-dialog/about-dialog.component.html</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Sidepanel mode</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1f2809e6a99d511fdb6eaf041d785fe54d0680cc" datatype="html">
|
||||
<source>File card size</source>
|
||||
<target state="translated">文件卡大小</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/about-dialog/about-dialog.component.html</context>
|
||||
<context context-type="linenumber">54</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">File card size</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="d618f383a0ea2458eeb945a85190d4a002ea394b" datatype="html">
|
||||
<source>Arg</source>
|
||||
<target state="translated">参数</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html</context>
|
||||
<context context-type="linenumber">41</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Arg</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="c35ef0f03a863d33b04aae6807f140397a50f491" datatype="html">
|
||||
<source>Generate RSS URL</source>
|
||||
<target state="translated">生成 RSS 网址</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">306</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Generate RSS URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="c2faa86201eab08b5b39b5437f96ab9432e125e7" datatype="html">
|
||||
<source>Item limit</source>
|
||||
<target state="translated">项目限制</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
||||
<context context-type="linenumber">46</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Item limit</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="b78a98bc54259a29cf6250dbaeab5fe11fae91cf" datatype="html">
|
||||
<source>Favorited</source>
|
||||
<target state="translated">收藏</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
||||
<context context-type="linenumber">51</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Favorited</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="8336047719608684263" datatype="html">
|
||||
<source>Unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/></source>
|
||||
<target state="translated">取消订阅 <x id="subscription name" equiv-text="this.sub['name']"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="784837056777689544" datatype="html">
|
||||
<source>Would you like to unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/>?</source>
|
||||
<target state="translated">是否要取消订阅 <x id="subscription name" equiv-text="this.sub['name']"/>?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
|
||||
<context context-type="linenumber">31</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1698114086921246480" datatype="html">
|
||||
<source>Unsubscribe</source>
|
||||
<target state="translated">取消订阅</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
|
||||
<context context-type="linenumber">32</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1091872159779006651" datatype="html">
|
||||
<source>You must input a time!</source>
|
||||
<target state="translated">你必须输入一个时间!</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/update-task-schedule-dialog/update-task-schedule-dialog.component.ts</context>
|
||||
<context context-type="linenumber">70</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="d57c023a4cf63b2f12c10328c15b636ff18929aa" datatype="html">
|
||||
<source>Best</source>
|
||||
<target state="translated">最佳</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/main/main.component.html</context>
|
||||
<context context-type="linenumber">24,25</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Best</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="35cf4cdcedc8ef3f94b6100e0d86836e31dbb908" datatype="html">
|
||||
<source>Force autoplay</source>
|
||||
<target state="translated">强制自动播放</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">235</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Force autoplay setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="37469c9f3e31d95087fa22b6c9c3bc64adf1692d" datatype="html">
|
||||
<source>Enable RSS Feed</source>
|
||||
<target state="translated">启用 RSS 订阅</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">304</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Enable RSS Feed setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="61d6b5fa4311b1c617b66dad72496f9dd43b07b4" datatype="html">
|
||||
<source>Be careful enabling this with multi-user mode! User data may be exposed.</source>
|
||||
<target state="translated">使用多用户模式启用此功能时要小心!用户数据可能会暴露。</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">305</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">RSS Feed prefix</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="fd467148a18f0921c10d116d4e0174fe29452be4" datatype="html">
|
||||
<source>See documentation here.</source>
|
||||
<target state="translated">请看这里的文档。</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">307</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">RSS feed documentation</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab" datatype="html">
|
||||
<source>Notifications</source>
|
||||
<target state="translated">通知</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">376</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Notifications settings label</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="3ffd9490f3a4c0b24021d25e1dc71fcfe5d39cd6" datatype="html">
|
||||
<source>Download error</source>
|
||||
<target state="translated">下载错误</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">392</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Download error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="c40370dc182b5e4333828b70f7478bde58bb5cfe" datatype="html">
|
||||
<source>Enable notifications</source>
|
||||
<target state="translated">启用通知</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">382</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Enable notifications setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="33a7c6d5ff3515fa237f1fd4e43df8b65373954d" datatype="html">
|
||||
<source>Enable all notifications</source>
|
||||
<target state="translated">启用所有通知</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">385</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Enable all notifications setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9c562d26e041390ecc3f49dabc51cc50ebba7469" datatype="html">
|
||||
<source>Allowed notification types</source>
|
||||
<target state="translated">允许的通知类型</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">389</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Allowed notification types</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="c5dc5fbcce45e9b1530e2a5c2baa8ebe722aef4c" datatype="html">
|
||||
<source>Download complete</source>
|
||||
<target state="translated">下载完成</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">391</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Download complete</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="2361a4f76caaa4574803fbcdca8b0a47c91cc7ed" datatype="html">
|
||||
<source>Task finished</source>
|
||||
<target state="translated">任务完成</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">393</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Task finished</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9e766e11a9de375907aaf566897ecc6dac393ebc" datatype="html">
|
||||
<source>Webhook URL</source>
|
||||
<target state="translated">Webhook 网址</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">399</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">webhook URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="8c1bf02206fbc371ff69ab1b7e35a17ba29d169d" datatype="html">
|
||||
<source>Use ntfy API</source>
|
||||
<target state="translated">使用 ntfy API</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">405</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Use ntfy API setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0cfc9cfe7cd8ea14bc053693b28872da739af02c" datatype="html">
|
||||
<source>See docs here.</source>
|
||||
<target state="translated">请看这里的文档。</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">411</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">421</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">428</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">ntfy API setting hint</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="55f559d6f666b945479f534b0c182f70cd0a8a69" datatype="html">
|
||||
<source>Gotify server URL</source>
|
||||
<target state="translated">Gotify 服务网址</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">419</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Gotify server URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="b770c48628d98cb4633d6a17e3f0ba0265376af5" datatype="html">
|
||||
<source>Gotify app token</source>
|
||||
<target state="translated">Gotify 应用令牌</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">426</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Gotify app token</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="38992954440d6afb54aeb58af12ca0123ee5e26e" datatype="html">
|
||||
<source>Use Telegram API</source>
|
||||
<target state="translated">使用 Telegram API</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">432</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Use Telegram API setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="2e076ff9866213d0815961c494aa48b177046b9d" datatype="html">
|
||||
<source>Telegram bot token</source>
|
||||
<target state="translated">Telegram 机器人令牌</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">436</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Telegram bot token</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="eeb0ba2e4743901d8f5eebd9a3529aa1f236c608" datatype="html">
|
||||
<source>Create bot here.</source>
|
||||
<target state="translated">在此处创建机器人。</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">438</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Telegram bot create link</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="144e1a21ebe8fa238f88d2ac27515ed711cfc9a0" datatype="html">
|
||||
<source>Telegram chat ID</source>
|
||||
<target state="translated">Telegram 聊天 ID</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">443</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Telegram chat ID</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="3e420c675b8f3f3702576d52e8bb6e8e1d3feda0" datatype="html">
|
||||
<source>How do I get the chat ID?</source>
|
||||
<target state="translated">如何获取聊天 ID?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">445</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Telegram chat ID help</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="a4ed8eba1e057e67d5c2d87b52230f182b3dae4e" datatype="html">
|
||||
<source>Restart required.</source>
|
||||
<target state="translated">需要重新启动。</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">465</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Restart required hint</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6785427850041119037" datatype="html">
|
||||
<source>Delete category</source>
|
||||
<target state="translated">删除类别</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">173</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2481374649045841364" datatype="html">
|
||||
<source>Would you like to delete <x id="category name" equiv-text="category['name']"/>?</source>
|
||||
<target state="translated">您要删除 <x id="category name" equiv-text="category['name']"/>?</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">174</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7332320960988475089" datatype="html">
|
||||
<source>Successfully deleted <x id="category name" equiv-text="category['name']"/>!</source>
|
||||
<target state="translated">已成功删除 <x id="category name" equiv-text="category['name']"/>!</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3371159074051387771" datatype="html">
|
||||
<source>Failed to delete <x id="category name" equiv-text="category['name']"/>!</source>
|
||||
<target state="translated">删除 <x id="category name" equiv-text="category['name']"/> 失败!</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">187</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8c6e24eab969d9f63a8a0e9d617aee3b99e28ae6" datatype="html">
|
||||
<source>Play all</source>
|
||||
<target state="translated">全部播放</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/subscription/subscription/subscription.component.html</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Play all</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="674a999dd48d7da565ffdd105602261b8a4761ea" datatype="html">
|
||||
<source>Download zip</source>
|
||||
<target state="translated">下载压缩包 (ZIP)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/subscription/subscription/subscription.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Download zip</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0ed98b4c6ec1db6708a963e8a2699478ac97f55c" datatype="html">
|
||||
<source>Add subscription</source>
|
||||
<target state="translated">添加订阅</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/subscriptions/subscriptions.component.html</context>
|
||||
<context context-type="linenumber">60</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Add subscription</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="8953483585652369683" datatype="html">
|
||||
<source>Archive successfully imported!</source>
|
||||
<target state="translated">存档成功导入!</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="ea2b65121b93921fe54692025da9b9e3ce779ad5" datatype="html">
|
||||
<source>Task settings - <x id="INTERPOLATION" equiv-text="{{task.title}}"/></source>
|
||||
<target state="translated">任务设置 - <x id="INTERPOLATION" equiv-text="{{task.title}}"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Task settings</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="06f503e492d6dbcf59e7b9c412ca86913d718689" datatype="html">
|
||||
<source>ntfy topic URL</source>
|
||||
<target state="translated">ntfy 话题网址</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">409</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">ntfy topic URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="347407180135731058" datatype="html">
|
||||
<source>Audio</source>
|
||||
<target state="translated">音频</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7022070615528435141" datatype="html">
|
||||
<source>Delete</source>
|
||||
<target state="translated">删除</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
||||
<context context-type="linenumber">154</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">175</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2525880134753073592" datatype="html">
|
||||
<source>Successfully deleted archive items!</source>
|
||||
<target state="translated">已成功删除存档项目!</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
||||
<context context-type="linenumber">172</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2492098975665776610" datatype="html">
|
||||
<source>File Size</source>
|
||||
<target state="translated">文件大小</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7410432243549869948" datatype="html">
|
||||
<source>Duration</source>
|
||||
<target state="translated">期间</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="11a0771f88158a540a54e0e4ec5d25733d65fc0e" datatype="html">
|
||||
<source>Favorite</source>
|
||||
<target state="translated">喜欢</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
|
||||
<context context-type="linenumber">26</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Favorite button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1d4fa01d25990f60abf21c3a451fa8ba262b7912" datatype="html">
|
||||
<source>Unfavorite</source>
|
||||
<target state="translated">不喜欢</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Unfavorite button</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1a9b415816364f554ee411020e65219092655271" datatype="html">
|
||||
<source>Title filter</source>
|
||||
<target state="translated">标题过滤</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
||||
<context context-type="linenumber">8</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Title filter</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="e08a77594f3d89311cdf6da5090044270909c194" datatype="html">
|
||||
<source>User</source>
|
||||
<target state="translated">用户</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">User</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9aa62bf1a535a97a4d752bbc5cf1c31af0f0c1f7" datatype="html">
|
||||
<source>Supports regex</source>
|
||||
<target state="translated">支持正则表达式</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
||||
<context context-type="linenumber">10</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Supports regex</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="5827fde8fcafdd55ae80921ad3ad4aa01012e203" datatype="html">
|
||||
<source>Use gotify API</source>
|
||||
<target state="translated">使用 gotify API</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">415</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Use gotify API setting</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
Reference in New Issue
Block a user