mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-14 00:30:56 +03:00
Compare commits
10 Commits
dependabot
...
twitch-emo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
148297d558 | ||
|
|
7167c362d2 | ||
|
|
8ac0ac2976 | ||
|
|
3a20e03490 | ||
|
|
7124792721 | ||
|
|
b32396164d | ||
|
|
3d633f9e47 | ||
|
|
cfa0a62587 | ||
|
|
1d53f6b1b6 | ||
|
|
398b2c0e1c |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
if: contains(github.ref, '/tags/v')
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: create release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
draft: true
|
||||
prerelease: false
|
||||
- name: download build artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: youtubedl-material
|
||||
path: ${{runner.temp}}/youtubedl-material
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -68,4 +68,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
8
.github/workflows/docker-pr.yml
vendored
8
.github/workflows/docker-pr.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: Set hash
|
||||
id: vars
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
@@ -24,11 +24,11 @@ jobs:
|
||||
json: '{"type": "docker", "tag": "nightly", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
|
||||
dir: 'backend/'
|
||||
- name: setup platform emulator
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: setup multi-arch docker build
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: build & push images
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
6
.github/workflows/docker-release.yml
vendored
6
.github/workflows/docker-release.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set hash
|
||||
id: vars
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
type=raw,value=latest
|
||||
|
||||
- name: setup platform emulator
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: setup multi-arch docker build
|
||||
uses: docker/setup-buildx-action@v2
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: build & push images
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set hash
|
||||
id: vars
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
dir: 'backend/'
|
||||
|
||||
- name: setup platform emulator
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: setup multi-arch docker build
|
||||
uses: docker/setup-buildx-action@v2
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: build & push images
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
4
.github/workflows/mocha.yml
vendored
4
.github/workflows/mocha.yml
vendored
@@ -21,9 +21,9 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '${{ matrix.node }}'
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- name: 'Cache node_modules'
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-v${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
@@ -706,7 +706,7 @@ app.use(function(req, res, next) {
|
||||
next();
|
||||
} else if (req.query.apiKey && config_api.getConfigItem('ytdl_use_api_key') && req.query.apiKey === config_api.getConfigItem('ytdl_api_key')) {
|
||||
next();
|
||||
} else if (req.path.includes('/api/stream/') || req.path.includes('/api/thumbnail/') || req.path.includes('/api/rss')) {
|
||||
} else if (req.path.includes('/api/stream/') || req.path.includes('/api/thumbnail/') || req.path.includes('/api/emote/') || req.path.includes('/api/rss')) {
|
||||
next();
|
||||
} else {
|
||||
logger.verbose(`Rejecting request - invalid API use for endpoint: ${req.path}. API key received: ${req.query.apiKey}`);
|
||||
@@ -993,11 +993,11 @@ app.post('/api/updateConcurrentStream', optionalJwt, async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/getFullTwitchChat', optionalJwt, async (req, res) => {
|
||||
var id = req.body.id;
|
||||
var type = req.body.type;
|
||||
var uuid = req.body.uuid;
|
||||
var sub = req.body.sub;
|
||||
var user_uid = null;
|
||||
const id = req.body.id;
|
||||
const type = req.body.type;
|
||||
const uuid = req.body.uuid;
|
||||
const sub = req.body.sub;
|
||||
let user_uid = null;
|
||||
|
||||
if (req.isAuthenticated()) user_uid = req.user.uid;
|
||||
|
||||
@@ -1009,12 +1009,12 @@ app.post('/api/getFullTwitchChat', optionalJwt, async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/downloadTwitchChatByVODID', optionalJwt, async (req, res) => {
|
||||
var id = req.body.id;
|
||||
var type = req.body.type;
|
||||
var vodId = req.body.vodId;
|
||||
var uuid = req.body.uuid;
|
||||
var sub = req.body.sub;
|
||||
var user_uid = null;
|
||||
const id = req.body.id;
|
||||
const type = req.body.type;
|
||||
const vodId = req.body.vodId;
|
||||
const uuid = req.body.uuid;
|
||||
const sub = req.body.sub;
|
||||
let user_uid = null;
|
||||
|
||||
if (req.isAuthenticated()) user_uid = req.user.uid;
|
||||
|
||||
@@ -1025,17 +1025,47 @@ app.post('/api/downloadTwitchChatByVODID', optionalJwt, async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Downloading Twitch chat for ${id}`);
|
||||
const full_chat = await twitch_api.downloadTwitchChatByVODID(vodId, id, type, user_uid, sub);
|
||||
logger.info(`Finished downloading Twitch chat for ${id}`);
|
||||
|
||||
res.send({
|
||||
chat: full_chat
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/getTwitchEmotes', async (req, res) => {
|
||||
const uid = req.body.uid;
|
||||
|
||||
const file = await files_api.getVideo(uid);
|
||||
const channel_name = file['uploader'];
|
||||
|
||||
const emotes_path = path.join('appdata', 'emotes', uid, 'emotes.json')
|
||||
if (!fs.existsSync(emotes_path)) {
|
||||
logger.info(`Downloading Twitch emotes for ${channel_name}`);
|
||||
await twitch_api.downloadTwitchEmotes(channel_name, file.uid);
|
||||
logger.info(`Finished downloading Twitch emotes for ${channel_name}`);
|
||||
}
|
||||
|
||||
const emotes = await twitch_api.getTwitchEmotes(file.uid);
|
||||
|
||||
res.send({
|
||||
emotes: emotes
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/emote/:uid/:id', async (req, res) => {
|
||||
const file_uid = decodeURIComponent(req.params.uid);
|
||||
const emote_id = decodeURIComponent(req.params.id);
|
||||
const emote_path = path.join('appdata', 'emotes', file_uid, emote_id);
|
||||
if (fs.existsSync(emote_path)) path.isAbsolute(emote_path) ? res.sendFile(emote_path) : res.sendFile(path.join(__dirname, emote_path));
|
||||
else res.sendStatus(404);
|
||||
});
|
||||
|
||||
// video sharing
|
||||
app.post('/api/enableSharing', optionalJwt, async (req, res) => {
|
||||
var uid = req.body.uid;
|
||||
var is_playlist = req.body.is_playlist;
|
||||
const uid = req.body.uid;
|
||||
const is_playlist = req.body.is_playlist;
|
||||
let success = false;
|
||||
// multi-user mode
|
||||
if (req.isAuthenticated()) {
|
||||
@@ -1643,6 +1673,8 @@ app.get('/api/stream', optionalJwt, async (req, res) => {
|
||||
}
|
||||
if (!fs.existsSync(file_path)) {
|
||||
logger.error(`File ${file_path} could not be found! UID: ${uid}, ID: ${file_obj && file_obj.id}`);
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
const stat = fs.statSync(file_path);
|
||||
const fileSize = stat.size;
|
||||
|
||||
@@ -208,6 +208,9 @@ 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,6 +110,18 @@ 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'
|
||||
|
||||
@@ -815,6 +815,9 @@ exports.applyFilterLocalDB = (db_path, filter_obj, operation) => {
|
||||
filtered &= record[filter_prop] === undefined || record[filter_prop] === null;
|
||||
} else {
|
||||
if (typeof filter_prop_value === 'object') {
|
||||
if (!record[filter_prop]) {
|
||||
continue;
|
||||
}
|
||||
if ('$regex' in filter_prop_value) {
|
||||
filtered &= (record[filter_prop].search(new RegExp(filter_prop_value['$regex'], filter_prop_value['$options'])) !== -1);
|
||||
} else if ('$ne' in filter_prop_value) {
|
||||
@@ -830,10 +833,14 @@ exports.applyFilterLocalDB = (db_path, filter_obj, operation) => {
|
||||
}
|
||||
} else {
|
||||
// handle case of nested property check
|
||||
if (filter_prop.includes('.'))
|
||||
if (filter_prop.includes('.')) {
|
||||
filtered &= utils.searchObjectByString(record, filter_prop) === filter_prop_value;
|
||||
else
|
||||
} else {
|
||||
if (!record[filter_prop]) {
|
||||
continue;
|
||||
}
|
||||
filtered &= record[filter_prop] === filter_prop_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6074
backend/package-lock.json
generated
6074
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.6.1",
|
||||
"@discordjs/core": "^0.5.2",
|
||||
"@tzahi12345/twitch-emoticons": "^1.0.9",
|
||||
"archiver": "^5.3.1",
|
||||
"async": "^3.2.3",
|
||||
"async-mutex": "^0.4.0",
|
||||
|
||||
@@ -550,27 +550,69 @@ describe('Downloader', function() {
|
||||
const expected_args3 = ['-o', '%(title)s.%(ext)s', '--min-filesize', '1'];
|
||||
assert(JSON.stringify(updated_args3) === JSON.stringify(expected_args3));
|
||||
});
|
||||
describe('Twitch', async function () {
|
||||
const twitch_api = require('../twitch');
|
||||
const example_vod = '1710641401';
|
||||
it('Download VOD chat', async function() {
|
||||
this.timeout(300000);
|
||||
if (!fs.existsSync('TwitchDownloaderCLI')) {
|
||||
try {
|
||||
await exec('sh ../docker-utils/fetch-twitchdownloader.sh');
|
||||
fs.copyFileSync('../docker-utils/TwitchDownloaderCLI', 'TwitchDownloaderCLI');
|
||||
} catch (e) {
|
||||
logger.info('TwitchDownloaderCLI fetch failed, file may exist regardless.');
|
||||
}
|
||||
}
|
||||
const sample_path = path.join('test', 'sample.twitch_chat.json');
|
||||
if (fs.existsSync(sample_path)) fs.unlinkSync(sample_path);
|
||||
await twitch_api.downloadTwitchChatByVODID(example_vod, 'sample', null, null, null, './test');
|
||||
assert(fs.existsSync(sample_path));
|
||||
});
|
||||
|
||||
// cleanup
|
||||
if (fs.existsSync(sample_path)) fs.unlinkSync(sample_path);
|
||||
});
|
||||
describe('Twitch', async function () {
|
||||
const twitch_api = require('../twitch');
|
||||
const example_vod = '1710641401';
|
||||
const example_channel = 'keffals';
|
||||
|
||||
it('Get OAuth Token', async function() {
|
||||
this.timeout(300000);
|
||||
const twitch_client_id = config_api.getConfigItem('ytdl_twitch_client_id');
|
||||
const twitch_client_secret = config_api.getConfigItem('ytdl_twitch_client_secret');
|
||||
if (!twitch_client_id || !twitch_client_secret) {
|
||||
logger.info(`Skipping test 'Get OAuth Token' as Twitch client ID or Twitch client secret is missing.`);
|
||||
assert(true);
|
||||
return;
|
||||
}
|
||||
const token = await twitch_api.getTwitchOAuthToken(twitch_client_id, twitch_client_secret);
|
||||
assert(token);
|
||||
});
|
||||
|
||||
it('Get channel ID', async function() {
|
||||
this.timeout(300000);
|
||||
const twitch_client_id = config_api.getConfigItem('ytdl_twitch_client_id');
|
||||
const twitch_client_secret = config_api.getConfigItem('ytdl_twitch_client_secret');
|
||||
if (!twitch_client_id || !twitch_client_secret) {
|
||||
logger.info(`Skipping test 'Get channel ID' as Twitch client ID or Twitch client secret is missing.`);
|
||||
assert(true);
|
||||
return;
|
||||
}
|
||||
const channel_id = await twitch_api.getChannelID(example_channel);
|
||||
assert(channel_id === '494493142');
|
||||
});
|
||||
|
||||
it('Download VOD chat', async function() {
|
||||
this.timeout(300000);
|
||||
if (!fs.existsSync('TwitchDownloaderCLI')) {
|
||||
try {
|
||||
await exec('sh ../docker-utils/fetch-twitchdownloader.sh');
|
||||
fs.copyFileSync('../docker-utils/TwitchDownloaderCLI', 'TwitchDownloaderCLI');
|
||||
} catch (e) {
|
||||
logger.info('TwitchDownloaderCLI fetch failed, file may exist regardless.');
|
||||
}
|
||||
}
|
||||
const sample_path = path.join('test', 'sample.twitch_chat.json');
|
||||
if (fs.existsSync(sample_path)) fs.unlinkSync(sample_path);
|
||||
await twitch_api.downloadTwitchChatByVODID(example_vod, 'sample', null, null, null, './test');
|
||||
assert(fs.existsSync(sample_path));
|
||||
|
||||
// cleanup
|
||||
if (fs.existsSync(sample_path)) fs.unlinkSync(sample_path);
|
||||
});
|
||||
|
||||
it('Download Twitch emotes', async function() {
|
||||
this.timeout(300000);
|
||||
const twitch_client_id = config_api.getConfigItem('ytdl_twitch_client_id');
|
||||
const twitch_client_secret = config_api.getConfigItem('ytdl_twitch_client_secret');
|
||||
if (!twitch_client_id || !twitch_client_secret) {
|
||||
logger.info(`Skipping test 'Download Twitch emotes' as Twitch client ID or Twitch client secret is missing.`);
|
||||
assert(true);
|
||||
return;
|
||||
}
|
||||
const emotesJSON = await twitch_api.downloadTwitchEmotes(example_channel, 'test_uid');
|
||||
assert(emotesJSON && emotesJSON.length > 0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
const config_api = require('./config');
|
||||
const logger = require('./logger');
|
||||
const utils = require('./utils');
|
||||
|
||||
const moment = require('moment');
|
||||
const fs = require('fs-extra')
|
||||
const axios = require('axios');
|
||||
const { EmoteFetcher } = require('@tzahi12345/twitch-emoticons');
|
||||
const path = require('path');
|
||||
const { promisify } = require('util');
|
||||
const child_process = require('child_process');
|
||||
const commandExistsSync = require('command-exists').sync;
|
||||
|
||||
async function getCommentsForVOD(vodId) {
|
||||
let auth_timeout = null;
|
||||
let cached_oauth = null;
|
||||
|
||||
exports.ensureTwitchAuth = async () => {
|
||||
const TIMEOUT_MARGIN_MS = 60*1000;
|
||||
const twitch_client_id = config_api.getConfigItem('ytdl_twitch_client_id');
|
||||
const twitch_client_secret = config_api.getConfigItem('ytdl_twitch_client_secret');
|
||||
if (cached_oauth && auth_timeout && (Date.now() - TIMEOUT_MARGIN_MS) < auth_timeout) return cached_oauth;
|
||||
|
||||
const {token, expires_in} = await exports.getTwitchOAuthToken(twitch_client_id, twitch_client_secret);
|
||||
cached_oauth = token;
|
||||
auth_timeout = Date.now() + expires_in;
|
||||
return token;
|
||||
}
|
||||
|
||||
exports.getCommentsForVOD = async (vodId) => {
|
||||
const exec = promisify(child_process.exec);
|
||||
|
||||
// Reject invalid params to prevent command injection attack
|
||||
@@ -52,7 +70,7 @@ async function getCommentsForVOD(vodId) {
|
||||
return new_json;
|
||||
}
|
||||
|
||||
async function getTwitchChatByFileID(id, type, user_uid, uuid, sub) {
|
||||
exports.getTwitchChatByFileID = async (id, type, user_uid, uuid, sub) => {
|
||||
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||
const subscriptionsFileFolder = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
let file_path = null;
|
||||
@@ -80,10 +98,10 @@ async function getTwitchChatByFileID(id, type, user_uid, uuid, sub) {
|
||||
return chat_file;
|
||||
}
|
||||
|
||||
async function downloadTwitchChatByVODID(vodId, id, type, user_uid, sub, customFileFolderPath = null) {
|
||||
exports.downloadTwitchChatByVODID = async (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 chat = await getCommentsForVOD(vodId);
|
||||
const chat = await exports.getCommentsForVOD(vodId);
|
||||
|
||||
// save file if needed params are included
|
||||
let file_path = null;
|
||||
@@ -108,6 +126,114 @@ async function downloadTwitchChatByVODID(vodId, id, type, user_uid, sub, customF
|
||||
return chat;
|
||||
}
|
||||
|
||||
exports.getTwitchEmotes = async (file_uid) => {
|
||||
const emotes_path = path.join('appdata', 'emotes', file_uid, 'emotes.json')
|
||||
if (!fs.existsSync(emotes_path)) return null;
|
||||
const emote_objs = fs.readJSONSync(emotes_path);
|
||||
// inject custom url
|
||||
for (const emote_obj of emote_objs) {
|
||||
emote_obj.custom_url = `${utils.getBaseURL()}/api/emote/${file_uid}/${emote_obj.id}.${emote_obj.ext}`
|
||||
}
|
||||
return emote_objs;
|
||||
}
|
||||
|
||||
exports.downloadTwitchEmotes = async (channel_name, file_uid) => {
|
||||
const twitch_client_id = config_api.getConfigItem('ytdl_twitch_client_id');
|
||||
const twitch_client_secret = config_api.getConfigItem('ytdl_twitch_client_secret');
|
||||
|
||||
const channel_id = await exports.getChannelID(channel_name);
|
||||
|
||||
const fetcher = new EmoteFetcher(twitch_client_id, twitch_client_secret);
|
||||
|
||||
try {
|
||||
await Promise.allSettled([
|
||||
fetcher.fetchTwitchEmotes(),
|
||||
fetcher.fetchTwitchEmotes(channel_id),
|
||||
fetcher.fetchBTTVEmotes(),
|
||||
fetcher.fetchBTTVEmotes(channel_id),
|
||||
fetcher.fetchSevenTVEmotes(),
|
||||
fetcher.fetchSevenTVEmotes(channel_id),
|
||||
fetcher.fetchFFZEmotes(),
|
||||
fetcher.fetchFFZEmotes(channel_id)
|
||||
]);
|
||||
|
||||
const emotes_dir = path.join('appdata', 'emotes', file_uid);
|
||||
const emote_json_path = path.join(emotes_dir, `emotes.json`);
|
||||
fs.ensureDirSync(emotes_dir);
|
||||
|
||||
const emote_objs = [];
|
||||
let failed_emote_count = 0;
|
||||
for (const [, emote] of fetcher.emotes) {
|
||||
const emote_obj = emote.toObject();
|
||||
|
||||
const ext = emote.imageType;
|
||||
const emote_image_path = path.join(emotes_dir, `${emote.id}.${ext}`);
|
||||
|
||||
try {
|
||||
const link = emote.toLink();
|
||||
if (!fs.existsSync(emote_image_path)) await utils.fetchFile(link, emote_image_path);
|
||||
emote_obj['ext'] = ext;
|
||||
emote_objs.push(emote_obj);
|
||||
} catch (err) {
|
||||
failed_emote_count++;
|
||||
}
|
||||
}
|
||||
if (failed_emote_count) logger.warn(`${failed_emote_count} emotes failed to download for channel ${channel_name}`);
|
||||
await fs.writeJSON(emote_json_path, emote_objs);
|
||||
return emote_objs;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
exports.getTwitchOAuthToken = async (client_id, client_secret) => {
|
||||
logger.verbose('Generating new Twitch auth token');
|
||||
const url = `https://id.twitch.tv/oauth2/token`;
|
||||
|
||||
try {
|
||||
const response = await axios.post(url, {client_id: client_id, client_secret: client_secret, grant_type: 'client_credentials'});
|
||||
const token = response['data']['access_token'];
|
||||
const expires_in = response['data']['expires_in'];
|
||||
if (token) return {token, expires_in};
|
||||
|
||||
logger.error(`Failed to get token.`);
|
||||
return null;
|
||||
} catch (err) {
|
||||
logger.error(`Failed to get token.`);
|
||||
logger.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
exports.getChannelID = async (channel_name) => {
|
||||
const twitch_client_id = config_api.getConfigItem('ytdl_twitch_client_id');
|
||||
const token = await exports.ensureTwitchAuth();
|
||||
const url = `https://api.twitch.tv/helix/users?login=${channel_name}`;
|
||||
const headers = {
|
||||
'Client-ID': twitch_client_id,
|
||||
'Authorization': 'Bearer ' + token,
|
||||
// Accept: 'application/vnd.twitchtv.v5+json; charset=UTF-8'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, {headers: headers});
|
||||
const data = response.data.data;
|
||||
|
||||
if (data && data.length > 0) {
|
||||
const channelID = data[0].id;
|
||||
return channelID;
|
||||
}
|
||||
|
||||
logger.error(`Failed to get channel ID for user ${channel_name}`);
|
||||
if (data.error) logger.error(data.error);
|
||||
return null; // User not found
|
||||
} catch (err) {
|
||||
logger.error(`Failed to get channel ID for user ${channel_name}`);
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
const convertTimestamp = (timestamp) => moment.duration(timestamp, 'seconds')
|
||||
.toISOString()
|
||||
.replace(/P.*?T(?:(\d+?)H)?(?:(\d+?)M)?(?:(\d+).*?S)?/,
|
||||
@@ -115,9 +241,3 @@ const convertTimestamp = (timestamp) => moment.duration(timestamp, 'seconds')
|
||||
const seg = v => v ? v.padStart(2, '0') : '00';
|
||||
return `${seg(ms[0])}:${seg(ms[1])}:${seg(ms[2])}`;
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
getCommentsForVOD: getCommentsForVOD,
|
||||
getTwitchChatByFileID: getTwitchChatByFileID,
|
||||
downloadTwitchChatByVODID: downloadTwitchChatByVODID
|
||||
}
|
||||
@@ -364,26 +364,29 @@ exports.checkExistsWithTimeout = async (filePath, timeout) => {
|
||||
}
|
||||
|
||||
// helper function to download file using fetch
|
||||
exports.fetchFile = async (url, path, file_label) => {
|
||||
exports.fetchFile = async (url, output_path, file_label = null) => {
|
||||
var len = null;
|
||||
const res = await fetch(url);
|
||||
let bar = null;
|
||||
if (file_label) {
|
||||
len = parseInt(res.headers.get("Content-Length"), 10);
|
||||
|
||||
len = parseInt(res.headers.get("Content-Length"), 10);
|
||||
bar = new ProgressBar(` Downloading ${file_label} [:bar] :percent :etas`, {
|
||||
complete: '=',
|
||||
incomplete: ' ',
|
||||
width: 20,
|
||||
total: len
|
||||
});
|
||||
}
|
||||
|
||||
var bar = new ProgressBar(` Downloading ${file_label} [:bar] :percent :etas`, {
|
||||
complete: '=',
|
||||
incomplete: ' ',
|
||||
width: 20,
|
||||
total: len
|
||||
});
|
||||
const fileStream = fs.createWriteStream(path);
|
||||
const fileStream = fs.createWriteStream(output_path);
|
||||
await new Promise((resolve, reject) => {
|
||||
res.body.pipe(fileStream);
|
||||
res.body.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
res.body.on('data', function (chunk) {
|
||||
bar.tick(chunk.length);
|
||||
if (file_label) bar.tick(chunk.length);
|
||||
});
|
||||
fileStream.on("finish", function() {
|
||||
resolve();
|
||||
|
||||
24147
package-lock.json
generated
24147
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -21,20 +21,21 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "^15.0.1",
|
||||
"@angular/animations": "^15.0.1",
|
||||
"@angular/cdk": "^15.0.0",
|
||||
"@angular/common": "^15.0.1",
|
||||
"@angular/compiler": "^15.0.1",
|
||||
"@angular/core": "^15.0.1",
|
||||
"@angular/forms": "^15.0.1",
|
||||
"@angular/localize": "^15.0.1",
|
||||
"@angular/material": "^15.0.0",
|
||||
"@angular/platform-browser": "^15.0.1",
|
||||
"@angular/platform-browser-dynamic": "^15.0.1",
|
||||
"@angular/router": "^15.0.1",
|
||||
"@angular-devkit/core": "^16.0.3",
|
||||
"@angular/animations": "^16.0.3",
|
||||
"@angular/cdk": "^16.0.2",
|
||||
"@angular/common": "^16.0.3",
|
||||
"@angular/compiler": "^16.0.3",
|
||||
"@angular/core": "^16.0.3",
|
||||
"@angular/forms": "^16.0.3",
|
||||
"@angular/localize": "^16.0.3",
|
||||
"@angular/material": "^16.0.2",
|
||||
"@angular/platform-browser": "^16.0.3",
|
||||
"@angular/platform-browser-dynamic": "^16.0.3",
|
||||
"@angular/router": "^16.0.3",
|
||||
"@fontsource/material-icons": "^4.5.4",
|
||||
"@ngneat/content-loader": "^7.0.0",
|
||||
"@tzahi12345/twitch-emoticons": "^1.0.9",
|
||||
"@videogular/ngx-videogular": "^6.0.0",
|
||||
"core-js": "^2.4.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
@@ -43,20 +44,20 @@
|
||||
"fs-extra": "^10.0.0",
|
||||
"material-icons": "^1.10.8",
|
||||
"nan": "^2.14.1",
|
||||
"ngx-avatars": "^1.4.1",
|
||||
"ngx-avatars": "^1.6.1",
|
||||
"ngx-file-drop": "^15.0.0",
|
||||
"rxjs": "^6.6.3",
|
||||
"rxjs-compat": "^6.6.7",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "~4.8.4",
|
||||
"typescript": "~5.0.4",
|
||||
"xliff-to-json": "^1.0.4",
|
||||
"zone.js": "~0.11.4"
|
||||
"zone.js": "~0.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^15.0.1",
|
||||
"@angular/cli": "^15.0.1",
|
||||
"@angular/compiler-cli": "^15.0.1",
|
||||
"@angular/language-service": "^15.0.1",
|
||||
"@angular-devkit/build-angular": "^16.0.3",
|
||||
"@angular/cli": "^16.0.3",
|
||||
"@angular/compiler-cli": "^16.0.3",
|
||||
"@angular/language-service": "^16.0.3",
|
||||
"@types/core-js": "^2.5.2",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/jasmine": "^4.3.1",
|
||||
|
||||
@@ -380,8 +380,8 @@ export class RecentVideosComponent implements OnInit {
|
||||
|
||||
fileSelectionChanged(event: MatSelectionListChange): void {
|
||||
// TODO: make sure below line is possible (_selected is private)
|
||||
const adding = event.option['_selected'];
|
||||
const value = event.option.value;
|
||||
const adding = event.options[0]['_selected'];
|
||||
const value = event.options[0].value;
|
||||
if (adding) {
|
||||
this.selected_data.push(value.uid);
|
||||
this.selected_data_objs.push(value);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="chat-container" #scrollContainer *ngIf="visible_chat">
|
||||
<div style="width: 250px; text-align: center;"><strong>Twitch Chat</strong></div>
|
||||
<div #chat style="max-width: 250px" *ngFor="let chat of visible_chat; let last = last">
|
||||
{{chat.timestamp_str}} - <strong [style.color]="chat.user_color ? chat.user_color : null">{{chat.name}}</strong>: {{chat.message}}
|
||||
{{chat.timestamp_str}} - <strong [style.color]="chat.user_color ? chat.user_color : null">{{chat.name}}</strong>: <span [innerHTML]="chat.message"></span>
|
||||
{{last ? scrollToBottom() : ''}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, ElementRef, Input, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { DatabaseFile } from 'api-types';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { EmoteFetcher, EmoteObject, EmoteParser } from '@tzahi12345/twitch-emoticons';
|
||||
|
||||
@Component({
|
||||
selector: 'app-twitch-chat',
|
||||
@@ -13,6 +14,7 @@ export class TwitchChatComponent implements OnInit, OnDestroy {
|
||||
visible_chat = null;
|
||||
chat_response_received = false;
|
||||
downloading_chat = false;
|
||||
got_emotes = false;
|
||||
|
||||
current_chat_index = null;
|
||||
|
||||
@@ -21,6 +23,9 @@ export class TwitchChatComponent implements OnInit, OnDestroy {
|
||||
|
||||
scrollContainer = null;
|
||||
|
||||
fetcher: EmoteFetcher;
|
||||
parser: EmoteParser;
|
||||
|
||||
@Input() db_file: DatabaseFile = null;
|
||||
@Input() sub = null;
|
||||
@Input() current_timestamp = null;
|
||||
@@ -32,6 +37,7 @@ export class TwitchChatComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getFullChat();
|
||||
this.getEmotes();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -69,10 +75,12 @@ export class TwitchChatComponent implements OnInit, OnDestroy {
|
||||
const latest_chat_timestamp = this.visible_chat.length ? this.visible_chat[this.visible_chat.length - 1]['timestamp'] : 0;
|
||||
|
||||
for (let i = this.current_chat_index + 1; i < this.full_chat.length; i++) {
|
||||
if (this.full_chat[i]['timestamp'] >= latest_chat_timestamp && this.full_chat[i]['timestamp'] <= this.current_timestamp) {
|
||||
this.visible_chat.push(this.full_chat[i]);
|
||||
const new_chat = this.full_chat[i];
|
||||
if (new_chat['timestamp'] >= latest_chat_timestamp && new_chat['timestamp'] <= this.current_timestamp) {
|
||||
new_chat['message'] = this.got_emotes ? this.parseChat(new_chat['message']) : new_chat['message'];
|
||||
this.visible_chat.push(new_chat);
|
||||
this.current_chat_index = i;
|
||||
} else if (this.full_chat[i]['timestamp'] > this.current_timestamp) {
|
||||
} else if (new_chat['timestamp'] > this.current_timestamp) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -118,6 +126,29 @@ export class TwitchChatComponent implements OnInit, OnDestroy {
|
||||
this.chat_check_interval_obj = setInterval(() => this.addNewChatMessages(), this.CHAT_CHECK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
getEmotes() {
|
||||
this.postsService.getTwitchEmotes(this.db_file['uid']).subscribe(res => {
|
||||
const emotes = res['emotes'];
|
||||
this.processEmotes(emotes);
|
||||
});
|
||||
}
|
||||
|
||||
processEmotes(emotes: EmoteObject[]) {
|
||||
this.fetcher = new EmoteFetcher();
|
||||
this.parser = new EmoteParser(this.fetcher, {
|
||||
// Custom HTML format
|
||||
template: `<img class="emote" alt="{name}" src="{link}">`,
|
||||
// Match without :colons:
|
||||
match: /(\w+)+?/g
|
||||
});
|
||||
this.fetcher.fromObject(emotes);
|
||||
this.got_emotes = true;
|
||||
}
|
||||
|
||||
parseChat(chat_message: string) {
|
||||
return this.parser.parse(chat_message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function binarySearch(arr, key, n) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/observable/throw';
|
||||
import { THEMES_CONFIG } from '../themes';
|
||||
import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { Router, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
@@ -118,9 +118,10 @@ import {
|
||||
import { isoLangs } from './dialogs/user-profile-dialog/locales_list';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatDrawerMode } from '@angular/material/sidenav';
|
||||
import type { EmoteObject } from '@tzahi12345/twitch-emoticons';
|
||||
|
||||
@Injectable()
|
||||
export class PostsService implements CanActivate {
|
||||
export class PostsService {
|
||||
path = '';
|
||||
|
||||
// local settings
|
||||
@@ -407,6 +408,10 @@ export class PostsService implements CanActivate {
|
||||
return this.http.post<DownloadTwitchChatByVODIDResponse>(this.path + 'downloadTwitchChatByVODID', body, this.httpOptions);
|
||||
}
|
||||
|
||||
getTwitchEmotes(uid: string) {
|
||||
return this.http.post<{emotes: EmoteObject[]}>(this.path + 'getTwitchEmotes', {uid: uid}, this.httpOptions);
|
||||
}
|
||||
|
||||
downloadPlaylistFromServer(playlist_id, uuid = null) {
|
||||
const body: DownloadFileRequest = {uuid: uuid, playlist_id: playlist_id};
|
||||
return this.http.post(this.path + 'downloadFileFromServer', body, {responseType: 'blob', params: this.httpOptions.params});
|
||||
|
||||
@@ -252,9 +252,25 @@
|
||||
<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 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>
|
||||
|
||||
@@ -4790,157 +4790,6 @@
|
||||
</context-group>
|
||||
<note priority="1" from="description">Download error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="28da11220a3377ddce3c7948825d33101f142782" datatype="html">
|
||||
<source>Extractor</source>
|
||||
<target state="translated">Extraktor</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="2e076ff9866213d0815961c494aa48b177046b9d" datatype="html">
|
||||
<source>Telegram bot token</source>
|
||||
<target state="translated">Telegram-Bot-Token</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">417</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Telegram bot token</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html">
|
||||
<source>Error</source>
|
||||
<target state="translated">Fehler</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="3640026747176198246" datatype="html">
|
||||
<source>Watch content</source>
|
||||
<target state="translated">Inhalt ansehen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8456659390937171831" datatype="html">
|
||||
<source>Show error</source>
|
||||
<target state="translated">Fehler anzeigen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">56</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1236604279860679031" datatype="html">
|
||||
<source>Restart</source>
|
||||
<target state="translated">Neu starten</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9042260521669277115" datatype="html">
|
||||
<source>Pause</source>
|
||||
<target state="translated">Pause</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7182974689040833178" datatype="html">
|
||||
<source>Resume</source>
|
||||
<target state="translated">Fortsetzen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1d4fa01d25990f60abf21c3a451fa8ba262b7912" datatype="html">
|
||||
<source>Unfavorite</source>
|
||||
<target state="translated">Entfavorisieren</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="b78a98bc54259a29cf6250dbaeab5fe11fae91cf" datatype="html">
|
||||
<source>Favorited</source>
|
||||
<target state="translated">Favorisiert</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="1698114086921246480" datatype="html">
|
||||
<source>Unsubscribe</source>
|
||||
<target state="translated">Deabonnieren</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="b6399391e706e2d7b7b7880eb5630e4e6f49728c" datatype="html">
|
||||
<source>Side</source>
|
||||
<target state="translated">Seite</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">35,37</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Side</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="338b44701a53ce3ef2f36abfb56f89c3edfa9eab" datatype="html">
|
||||
<source>Over</source>
|
||||
<target state="translated">Über</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">32,34</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Over</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="e0a11fbea353b1ce1131161774e4a3e10bcb99b1" datatype="html">
|
||||
<source>Large</source>
|
||||
<target state="translated">Groß</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">44,46</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Large</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="378c072ce05889c9771718d05106e7685fcd3507" datatype="html">
|
||||
<source>Medium</source>
|
||||
<target state="translated">Mittel</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">47,49</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Medium</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9a865c2922f5c01899d06c472dba2e5bd63bcff9" datatype="html">
|
||||
<source>Small</source>
|
||||
<target state="translated">Klein</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">50,52</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Small</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="af30e51aa8b67e1133a341ec28359be05150e65c" datatype="html">
|
||||
<source>No description available.</source>
|
||||
<target state="translated">Keine Beschreibung verfügbar.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/player/player.component.html</context>
|
||||
<context context-type="linenumber">25,27</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">No description</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -3934,113 +3934,6 @@
|
||||
</context-group>
|
||||
<note priority="1" from="description">Discord Webhook URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="338b44701a53ce3ef2f36abfb56f89c3edfa9eab" datatype="html">
|
||||
<source>Over</source>
|
||||
<target state="translated">Sobre</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">32,34</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Over</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html">
|
||||
<source>Error</source>
|
||||
<target state="translated">Error</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="3640026747176198246" datatype="html">
|
||||
<source>Watch content</source>
|
||||
<target state="translated">Ver el contenido</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8456659390937171831" datatype="html">
|
||||
<source>Show error</source>
|
||||
<target state="translated">Mostrar el error</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">56</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1236604279860679031" datatype="html">
|
||||
<source>Restart</source>
|
||||
<target state="translated">Reiniciar</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9042260521669277115" datatype="html">
|
||||
<source>Pause</source>
|
||||
<target state="translated">Pausar</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7182974689040833178" datatype="html">
|
||||
<source>Resume</source>
|
||||
<target state="translated">Resumen</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="b6399391e706e2d7b7b7880eb5630e4e6f49728c" datatype="html">
|
||||
<source>Side</source>
|
||||
<target state="translated">Lado</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">35,37</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Side</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="e0a11fbea353b1ce1131161774e4a3e10bcb99b1" datatype="html">
|
||||
<source>Large</source>
|
||||
<target state="translated">Largo</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">44,46</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Large</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="378c072ce05889c9771718d05106e7685fcd3507" datatype="html">
|
||||
<source>Medium</source>
|
||||
<target state="translated">Medio</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">47,49</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Medium</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9a865c2922f5c01899d06c472dba2e5bd63bcff9" datatype="html">
|
||||
<source>Small</source>
|
||||
<target state="translated">Pequeño</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">50,52</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Small</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="af30e51aa8b67e1133a341ec28359be05150e65c" datatype="html">
|
||||
<source>No description available.</source>
|
||||
<target state="translated">Sin una descripción disponible.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/player/player.component.html</context>
|
||||
<context context-type="linenumber">25,27</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">No description</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -4162,7 +4162,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="28da11220a3377ddce3c7948825d33101f142782" datatype="html">
|
||||
<source>Extractor</source>
|
||||
<target state="translated">Extractor</target>
|
||||
<target state="needs-translation">Extractor</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>
|
||||
@@ -4187,845 +4187,6 @@
|
||||
</context-group>
|
||||
<note priority="1" from="description">Delete selected</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="347407180135731058" datatype="html">
|
||||
<source>Audio</source>
|
||||
<target state="translated">Audio</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="8953483585652369683" datatype="html">
|
||||
<source>Archive successfully imported!</source>
|
||||
<target state="translated">Arsip berhasil diimpor!</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="6549265851868599441" datatype="html">
|
||||
<source>Video</source>
|
||||
<target state="translated">Video</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">Hapus arsip</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">Anda ingin menghapus arsip <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="7022070615528435141" datatype="html">
|
||||
<source>Delete</source>
|
||||
<target state="translated">Hapus</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">160</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2525880134753073592" datatype="html">
|
||||
<source>Successfully deleted archive items!</source>
|
||||
<target state="translated">Berhasil menghapus arsip!</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="8224301330941792118" datatype="html">
|
||||
<source>Failed to delete archive items!</source>
|
||||
<target state="translated">Gagal menghapus arsip!</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="a2f14a73f7a6e94479f67423cc51102da8d6f524" datatype="html">
|
||||
<source>None</source>
|
||||
<target state="translated">Tidak ada</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="56b1a3c93fb95fed1805005c561a5e431d57ffae" datatype="html">
|
||||
<source>Blacklist all files</source>
|
||||
<target state="translated">Blacklist semua file</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">Blacklist file langganan yang sudah dihapus</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="d618f383a0ea2458eeb945a85190d4a002ea394b" datatype="html">
|
||||
<source>Arg</source>
|
||||
<target state="translated">Arg</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="37469c9f3e31d95087fa22b6c9c3bc64adf1692d" datatype="html">
|
||||
<source>Enable RSS Feed</source>
|
||||
<target state="translated">Aktifkan RSS Feed</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">271</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">Berhati-hatilah dalam mengaktifkan ini dengan mode multi-pengguna! Data pengguna dapat terekspos.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">272</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">RSS Feed prefix</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="33a7c6d5ff3515fa237f1fd4e43df8b65373954d" datatype="html">
|
||||
<source>Enable all notifications</source>
|
||||
<target state="translated">Aktifkan semua notifikasi</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">352</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Enable all notifications setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="5827fde8fcafdd55ae80921ad3ad4aa01012e203" datatype="html">
|
||||
<source>Use gotify API</source>
|
||||
<target state="translated">Gunakan API gotify</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">396</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Use gotify API setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="8c6e24eab969d9f63a8a0e9d617aee3b99e28ae6" datatype="html">
|
||||
<source>Play all</source>
|
||||
<target state="translated">Mainkan semua</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">Unduh 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">Tambahkan langganan</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="019d4bd6a5690f0cfa0ecf346a4e6bf7f0d8debb" datatype="html">
|
||||
<source>Remove</source>
|
||||
<target state="translated">Buang</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="8564202903947049539" datatype="html">
|
||||
<source>Play</source>
|
||||
<target state="translated">Putar</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">Unduh lagi</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">Lihat kesalahan</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">Lihat tugas</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="1879058637439215882" datatype="html">
|
||||
<source>Download error</source>
|
||||
<target state="translated">Kesalahan pengunduhan</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="cdf5297d8d080a78e8b10debc5c38b7845a3cbe7" datatype="html">
|
||||
<source>Do not ask for confirmation</source>
|
||||
<target state="translated">Jangan konfirmasi</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="9176960997786930103" datatype="html">
|
||||
<source>Error for: <x id="PH" equiv-text="task['title']"/></source>
|
||||
<target state="translated">Kesalahan untuk: <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="1d4fa01d25990f60abf21c3a451fa8ba262b7912" datatype="html">
|
||||
<source>Unfavorite</source>
|
||||
<target state="translated">Hapus dari favorit</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="11a0771f88158a540a54e0e4ec5d25733d65fc0e" datatype="html">
|
||||
<source>Favorite</source>
|
||||
<target state="translated">Favoritkan</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="c35ef0f03a863d33b04aae6807f140397a50f491" datatype="html">
|
||||
<source>Generate RSS URL</source>
|
||||
<target state="translated">Hasilkan URL 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">273</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Generate RSS URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="e08a77594f3d89311cdf6da5090044270909c194" datatype="html">
|
||||
<source>User</source>
|
||||
<target state="translated">Pengguna</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="8336047719608684263" datatype="html">
|
||||
<source>Unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/></source>
|
||||
<target state="translated">Berhenti berlangganan from <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="1698114086921246480" datatype="html">
|
||||
<source>Unsubscribe</source>
|
||||
<target state="translated">Berhenti berlangganan</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">Anda harus memasukkan waktu!</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="378c072ce05889c9771718d05106e7685fcd3507" datatype="html">
|
||||
<source>Medium</source>
|
||||
<target state="translated">Sedang</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">47,49</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Medium</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9a865c2922f5c01899d06c472dba2e5bd63bcff9" datatype="html">
|
||||
<source>Small</source>
|
||||
<target state="translated">Kecil</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">50,52</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Small</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="8c1bf02206fbc371ff69ab1b7e35a17ba29d169d" datatype="html">
|
||||
<source>Use ntfy API</source>
|
||||
<target state="translated">Gunakan API ntfy</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">386</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Use ntfy API setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="06f503e492d6dbcf59e7b9c412ca86913d718689" datatype="html">
|
||||
<source>ntfy topic URL</source>
|
||||
<target state="translated">URL topik ntfy</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">390</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">ntfy topic URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="55f559d6f666b945479f534b0c182f70cd0a8a69" datatype="html">
|
||||
<source>Gotify server URL</source>
|
||||
<target state="translated">URL server Gotify</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">400</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Gotify server URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="a4ed8eba1e057e67d5c2d87b52230f182b3dae4e" datatype="html">
|
||||
<source>Restart required.</source>
|
||||
<target state="translated">Restart diperlukan.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">446</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Restart required hint</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="4b3972c3e9485218508a95f7a4ce7758e3f09ced" datatype="html">
|
||||
<source>Upload</source>
|
||||
<target state="translated">Unggah</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="5947241266456580665" datatype="html">
|
||||
<source>Download failed</source>
|
||||
<target state="translated">Gagal mengunduh</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">Tugas selesai</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="3533826530554274875" datatype="html">
|
||||
<source>Upload Date</source>
|
||||
<target state="translated">Tanggal pengunggahan</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="c41475a25c9f9d9639db9efa56637603a77528b4" datatype="html">
|
||||
<source>Download archive</source>
|
||||
<target state="translated">Unduh arsip</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="4578192247039196794" datatype="html">
|
||||
<source>Task</source>
|
||||
<target state="translated">Tugas</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="6219551536751479443" datatype="html">
|
||||
<source>Finished downloading</source>
|
||||
<target state="translated">Selesai mengunduh</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="5a105e7bd7e7db6ea211fe950fc9f317379acceb" datatype="html">
|
||||
<source>No notifications available</source>
|
||||
<target state="translated">Tidak ada notifikasi</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">Unduhan selesai</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="5000203534763292992" datatype="html">
|
||||
<source>Download restarted!</source>
|
||||
<target state="translated">Unduh dimulai ulang!</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">Hanya video</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="4665451070906079743" datatype="html">
|
||||
<source>Favorited</source>
|
||||
<target state="translated">Favorit</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="c65dd978b3c7566551c0ebefb234c2d41942b847" datatype="html">
|
||||
<source>Delete files older than</source>
|
||||
<target state="translated">Hapus file yang berusia diatas</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="7b4585a9072f3c1292972c14a3d0e14978fbfc9c" datatype="html">
|
||||
<source>Delete old files:</source>
|
||||
<target state="translated">Hapus file lama:</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="6437411876967154040" datatype="html">
|
||||
<source>Audio only</source>
|
||||
<target state="translated">Hanya audio</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="ea2b65121b93921fe54692025da9b9e3ce779ad5" datatype="html">
|
||||
<source>Task settings - <x id="INTERPOLATION" equiv-text="{{task.title}}"/></source>
|
||||
<target state="translated">Pengaturan tugas - <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="1f2809e6a99d511fdb6eaf041d785fe54d0680cc" datatype="html">
|
||||
<source>File card size</source>
|
||||
<target state="translated">Ukuran kartu file</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">File card size</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6268070779441507380" datatype="html">
|
||||
<source>Download Date</source>
|
||||
<target state="translated">Tanggal pengunduhan</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="2492098975665776610" datatype="html">
|
||||
<source>File Size</source>
|
||||
<target state="translated">Ukuran file</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="8953033926734869941" datatype="html">
|
||||
<source>Name</source>
|
||||
<target state="translated">Nama</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="7410432243549869948" datatype="html">
|
||||
<source>Duration</source>
|
||||
<target state="translated">Durasi</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="5ac5a0e5ffe8e5623b40696f4c2403c17349271f" datatype="html">
|
||||
<source>Sidepanel mode</source>
|
||||
<target state="translated">Model sidepanel</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Sidepanel mode</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="1a9b415816364f554ee411020e65219092655271" datatype="html">
|
||||
<source>Title filter</source>
|
||||
<target state="translated">Filter judul</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="9aa62bf1a535a97a4d752bbc5cf1c31af0f0c1f7" datatype="html">
|
||||
<source>Supports regex</source>
|
||||
<target state="translated">Dukungan regex</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="c2faa86201eab08b5b39b5437f96ab9432e125e7" datatype="html">
|
||||
<source>Item limit</source>
|
||||
<target state="translated">Batasan item</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">Yang di favoritkan</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="784837056777689544" datatype="html">
|
||||
<source>Would you like to unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/>?</source>
|
||||
<target state="translated">Apakah anda ingin berhenti berlangganan dari <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="7cedb649779673568447b994463b2882c4e0436a" datatype="html">
|
||||
<source>Slack Webhook URL</source>
|
||||
<target state="translated">URL Slack Webhook</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">380</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Slack Webhook URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9c562d26e041390ecc3f49dabc51cc50ebba7469" datatype="html">
|
||||
<source>Allowed notification types</source>
|
||||
<target state="translated">Jenis notifikasi yang diizinkan</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">356</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Allowed notification types</note>
|
||||
</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="needs-translation">Apakah Anda ingin menghapus <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">159</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="fd467148a18f0921c10d116d4e0174fe29452be4" datatype="html">
|
||||
<source>See documentation here.</source>
|
||||
<target state="translated">Lihat dokumentasi di sini.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">274</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">RSS feed documentation</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="d57c023a4cf63b2f12c10328c15b636ff18929aa" datatype="html">
|
||||
<source>Best</source>
|
||||
<target state="translated">Terbaik</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="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab" datatype="html">
|
||||
<source>Notifications</source>
|
||||
<target state="translated">Notifikasi</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">343</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">Kesalahan pengunduhan</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">359</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Download error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="38992954440d6afb54aeb58af12ca0123ee5e26e" datatype="html">
|
||||
<source>Use Telegram API</source>
|
||||
<target state="translated">Gunakan API Telegram</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">413</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Use Telegram API setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html">
|
||||
<source>Error</source>
|
||||
<target state="translated">Kesalahan</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="3640026747176198246" datatype="html">
|
||||
<source>Watch content</source>
|
||||
<target state="translated">Tonton konten</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="35cf4cdcedc8ef3f94b6100e0d86836e31dbb908" datatype="html">
|
||||
<source>Force autoplay</source>
|
||||
<target state="translated">Memaksa pemutaran otomatis</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">218</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Force autoplay setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="2361a4f76caaa4574803fbcdca8b0a47c91cc7ed" datatype="html">
|
||||
<source>Task finished</source>
|
||||
<target state="translated">Tugas selesai</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">360</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">URL webhook</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">366</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">webhook URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="3264d82792954815be755b3da01e2625458711dc" datatype="html">
|
||||
<source>Discord Webhook URL</source>
|
||||
<target state="translated">URL Webhook Discord</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">373</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Discord Webhook URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="0cfc9cfe7cd8ea14bc053693b28872da739af02c" datatype="html">
|
||||
<source>See docs here.</source>
|
||||
<target state="translated">Lihat dokumen di sini.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">375</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">382</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">392</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">402</context>
|
||||
</context-group>
|
||||
<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">Discord API setting hint</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="8456659390937171831" datatype="html">
|
||||
<source>Show error</source>
|
||||
<target state="translated">Tampilkan kesalahan</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">56</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="c40370dc182b5e4333828b70f7478bde58bb5cfe" datatype="html">
|
||||
<source>Enable notifications</source>
|
||||
<target state="translated">Aktifkan notifikasi</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">349</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Enable notifications setting</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="c5dc5fbcce45e9b1530e2a5c2baa8ebe722aef4c" datatype="html">
|
||||
<source>Download complete</source>
|
||||
<target state="translated">Unduh selesai</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">358</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Download complete</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="b770c48628d98cb4633d6a17e3f0ba0265376af5" datatype="html">
|
||||
<source>Gotify app token</source>
|
||||
<target state="translated">Token aplikasi Gotify</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">407</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Gotify app token</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="eeb0ba2e4743901d8f5eebd9a3529aa1f236c608" datatype="html">
|
||||
<source>Create bot here.</source>
|
||||
<target state="translated">Buat bot di sini.</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">Telegram bot create link</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="3e420c675b8f3f3702576d52e8bb6e8e1d3feda0" datatype="html">
|
||||
<source>How do I get the chat ID?</source>
|
||||
<target state="translated">Bagaimana cara mendapatkan ID obrolan?</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">Telegram chat ID help</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6785427850041119037" datatype="html">
|
||||
<source>Delete category</source>
|
||||
<target state="translated">Hapus kategori</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">158</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">Berhasil menghapus <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">168</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">Gagal menghapus <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">172</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2e076ff9866213d0815961c494aa48b177046b9d" datatype="html">
|
||||
<source>Telegram bot token</source>
|
||||
<target state="translated">Token bot Telegram</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">417</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Telegram bot token</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="144e1a21ebe8fa238f88d2ac27515ed711cfc9a0" datatype="html">
|
||||
<source>Telegram chat ID</source>
|
||||
<target state="translated">ID obrolan Telegram</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">424</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Telegram chat ID</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -5033,113 +5033,6 @@
|
||||
</context-group>
|
||||
<note priority="1" from="description">Discord Webhook URL</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9042260521669277115" datatype="html">
|
||||
<source>Pause</source>
|
||||
<target state="translated">暂停</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="b6399391e706e2d7b7b7880eb5630e4e6f49728c" datatype="html">
|
||||
<source>Side</source>
|
||||
<target state="translated">侧边栏</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">35,37</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Side</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="9a865c2922f5c01899d06c472dba2e5bd63bcff9" datatype="html">
|
||||
<source>Small</source>
|
||||
<target state="translated">小</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">50,52</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Small</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="af30e51aa8b67e1133a341ec28359be05150e65c" datatype="html">
|
||||
<source>No description available.</source>
|
||||
<target state="translated">没有说明。</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/player/player.component.html</context>
|
||||
<context context-type="linenumber">25,27</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">No description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="338b44701a53ce3ef2f36abfb56f89c3edfa9eab" datatype="html">
|
||||
<source>Over</source>
|
||||
<target state="translated">结束</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">32,34</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Over</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html">
|
||||
<source>Error</source>
|
||||
<target state="translated">错误</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Error</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="3640026747176198246" datatype="html">
|
||||
<source>Watch content</source>
|
||||
<target state="translated">观看内容</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8456659390937171831" datatype="html">
|
||||
<source>Show error</source>
|
||||
<target state="translated">查看错误</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">56</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1236604279860679031" datatype="html">
|
||||
<source>Restart</source>
|
||||
<target state="translated">重新开始</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="e0a11fbea353b1ce1131161774e4a3e10bcb99b1" datatype="html">
|
||||
<source>Large</source>
|
||||
<target state="translated">大</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">44,46</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Large</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="378c072ce05889c9771718d05106e7685fcd3507" datatype="html">
|
||||
<source>Medium</source>
|
||||
<target state="translated">中</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||
<context context-type="linenumber">47,49</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Medium</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="7182974689040833178" datatype="html">
|
||||
<source>Resume</source>
|
||||
<target state="translated">恢复</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
Reference in New Issue
Block a user