diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd82285e..a0436b5c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,14 +4,14 @@ on: branches: [ "main" ] workflow_dispatch: inputs: - branch: - description: 'Branch to build from' - required: true - default: 'main' repo: description: 'Repository to build from' required: true default: 'Tonwalter888/YouMod' + branch: + description: 'Branch to build from' + required: true + default: 'main' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -28,8 +28,8 @@ jobs: - name: Checkout YouMod uses: actions/checkout@v7 with: - repository: ${{ inputs.repo }} ref: ${{ inputs.branch }} + repository: ${{ inputs.repo }} path: YouMod - name: Install dependencies diff --git a/.github/workflows/ipa.yml b/.github/workflows/ipa.yml index 41677a15..bd60e4da 100644 --- a/.github/workflows/ipa.yml +++ b/.github/workflows/ipa.yml @@ -54,36 +54,12 @@ on: required: false type: boolean - youquality: - description: "Integrate YouQuality" - default: true - required: false - type: boolean - - youspeed: - description: "Integrate YouSpeed" - default: true - required: false - type: boolean - demc: description: "Integrate DontEatMyContent" default: true required: false type: boolean - youmute: - description: "Integrate YouMute" - default: true - required: false - type: boolean - - youloop: - description: "Integrate YouLoop" - default: true - required: false - type: boolean - ytweaks: description: "Integrate YTweaks" default: true @@ -96,36 +72,12 @@ on: required: false type: boolean - ytholdspeed: - description: "Integrate YTHoldForSpeed" - default: true - required: false - type: boolean - - youchoose: - description: "Integrate YouChooseQuality" - default: true - required: false - type: boolean - - youshare: - description: "Integrate YouShare" - default: true - required: false - type: boolean - gonerino: description: "Integrate Gonerino" default: true required: false type: boolean - getcap: - description: "Integrate YouGetCaption" - default: true - required: false - type: boolean - ytshare: description: "Integrate youtube-native-share" default: true @@ -153,17 +105,19 @@ jobs: - name: Hide Sensitive Inputs uses: levibostian/action-hide-sensitive-inputs@v1 with: - exclude_inputs: bundle_id,display_name,youpip,ytuhd,ryd,abconfig,youquality,youspeed,youmute,youloop,ytweaks,youslider,ytholdspeed,youchoose,youshare,gonerino,getcap,demc,ytshare,volboost + exclude_inputs: bundle_id,display_name,youpip,ytuhd,ryd,abconfig,ytweaks,youslider,gonerino,demc,volboost - name: Install MEGA downloader if: ${{ startsWith(inputs.ipa_url, 'https://mega.nz/') }} run: brew install megatools + - name: Download IPA from Mega share link if: ${{ startsWith(inputs.ipa_url, 'https://mega.nz/') }} env: IPA_URL: ${{ inputs.ipa_url }} run: | megadl --no-progress --path youtube.ipa "$IPA_URL" + - name: Download IPA from direct URL if: ${{ !startsWith(inputs.ipa_url, 'https://mega.nz/') }} env: @@ -186,7 +140,7 @@ jobs: echo "YT_VERSION=$(grep -A 1 'CFBundleVersion' ytextracted/Payload/YouTube.app/Info.plist | grep '' | awk -F'[><]' '{print $3}')" >> $GITHUB_ENV - name: Install Dependencies - run: brew install make ldid swiftlint + run: brew install make ldid - name: Set PATH Environment Variables run: | @@ -249,10 +203,7 @@ jobs: run: sed -i '' 's/od -c "$i" | head/od -c "$i" 2>\/dev\/null | head/g' $THEOS/bin/convert_xml_plist.sh || true - name: Install cyan and tbd - run: | - pipx install https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip - wget --quiet --no-verbose "https://github.com/inoahdev/tbd/releases/download/2.2/tbd-mac" -O /usr/local/bin/tbd - chmod +x /usr/local/bin/tbd + run: pipx install https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip - name: Checkout YouMod uses: actions/checkout@v7 @@ -275,7 +226,7 @@ jobs: - name: Clone YTUHD if: ${{ inputs.ytuhd }} - run: git clone --quiet --depth=1 https://github.com/Tonwalter888/YTUHD.git + run: git clone --quiet --depth=1 --recurse-submodules https://github.com/PoomSmart/YTUHD.git - name: Clone Return-YouTube-Dislikes if: ${{ inputs.ryd }} @@ -285,51 +236,26 @@ jobs: run: git clone --quiet --depth=1 https://github.com/PoomSmart/YouGroupSettings.git - name: Clone YTVideoOverlay - if: ${{ inputs.youpip || inputs.ytuhd || inputs.youquality || inputs.youspeed || inputs.youmute || inputs.youloop || inputs.youshare || inputs.getcap }} + if: ${{ inputs.youpip || inputs.ytuhd }} run: git clone --quiet --depth=1 https://github.com/PoomSmart/YTVideoOverlay.git - name: Clone YTABConfig if: ${{ inputs.abconfig }} run: git clone --quiet --depth=1 https://github.com/PoomSmart/YTABConfig.git - - - name: Clone YouQuality - if: ${{ inputs.youquality }} - run: git clone --quiet --depth=1 https://github.com/PoomSmart/YouQuality.git - - name: Clone YouSpeed - if: ${{ inputs.youspeed }} - run: git clone --quiet --depth=1 https://github.com/PoomSmart/YouSpeed.git + - name: Clone UncappedAVPlayer + run: git clone --quiet --depth=1 https://github.com/PoomSmart/UncappedAVPlayer.git - name: Clone DontEatMyContent if: ${{ inputs.demc }} run: git clone --quiet --depth=1 https://github.com/therealFoxster/DontEatMyContent.git - - name: Clone YouMute - if: ${{ inputs.youmute }} - run: git clone --quiet --depth=1 https://github.com/PoomSmart/YouMute.git - - - name: Clone YouLoop - if: ${{ inputs.youloop }} - run: git clone --quiet --depth=1 https://github.com/bhackel/YouLoop.git - - name: Clone YouSlider if: ${{ inputs.youslider }} run: | git clone --quiet --depth=1 https://github.com/PoomSmart/YouSlider.git wget --no-verbose --quiet https://github.com/Tonwalter888/Tonwalter888.github.io/raw/refs/heads/main/deb/alderis.deb - - name: Clone YTHoldForSpeed - if: ${{ inputs.ytholdspeed }} - run: git clone --quiet --depth=1 https://github.com/joshuaseltzer/YTHoldForSpeed.git - - - name: Clone YouChooseQuality - if: ${{ inputs.youchoose }} - run: git clone --quiet https://github.com/PoomSmart/YouChooseQuality.git - - - name: Clone YouShare - if: ${{ inputs.youshare }} - run: git clone --quiet --depth=1 https://github.com/Tonwalter888/YouShare.git - - name: Clone YTweaks if: ${{ inputs.ytweaks }} run: git clone --quiet --depth=1 https://github.com/fosterbarnes/YTweaks.git @@ -338,10 +264,6 @@ jobs: if: ${{ inputs.gonerino }} run: git clone --quiet --depth=1 https://github.com/castdrian/Gonerino.git - - name: Clone YouGetCaption - if: ${{ inputs.getcap }} - run: git clone --quiet --depth=1 https://github.com/PoomSmart/YouGetCaption.git - - name: Clone youtube-native-share if: ${{ inputs.ytshare }} run: git clone --quiet --depth=1 https://github.com/jkhsjdhjs/youtube-native-share.git @@ -368,7 +290,7 @@ jobs: if: ${{ inputs.ytuhd }} run: | cd YTUHD - make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless + make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless SIDELOAD=1 mv packages/*.deb ${{ github.workspace }}/ytuhd.deb - name: Build Return-YouTube-Dislikes @@ -385,7 +307,7 @@ jobs: mv packages/*.deb ${{ github.workspace }}/ygs.deb - name: Build YTVideoOverlay - if: ${{ inputs.youpip || inputs.ytuhd || inputs.youquality || inputs.youspeed || inputs.youmute || inputs.youloop || inputs.youshare || inputs.getcap }} + if: ${{ inputs.youpip || inputs.ytuhd }} run: | cd YTVideoOverlay make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless @@ -398,19 +320,11 @@ jobs: make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless mv packages/*.deb ${{ github.workspace }}/ytabconfig.deb - - name: Build YouQuality - if: ${{ inputs.youquality }} - run: | - cd YouQuality - make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless - mv packages/*.deb ${{ github.workspace }}/youquality.deb - - - name: Build YouSpeed - if: ${{ inputs.youspeed }} + - name: Build UncappedAVPlayer run: | - cd YouSpeed + cd UncappedAVPlayer make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless - mv packages/*.deb ${{ github.workspace }}/youspeed.deb + mv packages/*.deb ${{ github.workspace }}/uncapavplayer.deb - name: Build DontEatMyContent if: ${{ inputs.demc }} @@ -419,20 +333,6 @@ jobs: make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless mv packages/*.deb ${{ github.workspace }}/demc.deb - - name: Build YouMute - if: ${{ inputs.youmute }} - run: | - cd YouMute - make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless - mv packages/*.deb ${{ github.workspace }}/ym.deb - - - name: Build YouLoop - if: ${{ inputs.youloop }} - run: | - cd YouLoop - make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless - mv packages/*.deb ${{ github.workspace }}/youloop.deb - - name: Build YouSlider if: ${{ inputs.youslider }} run: | @@ -440,28 +340,6 @@ jobs: make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless mv packages/*.deb ${{ github.workspace }}/youslider.deb - - name: Build YTHoldForSpeed - if: ${{ inputs.ytholdspeed }} - run: | - cd YTHoldForSpeed - make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless TARGET=iphone:clang:latest:15.0 ARCHS=arm64 - mv packages/*.deb ${{ github.workspace }}/ytholdspeed.deb - - - name: Build YouChooseQuality - if: ${{ inputs.youchoose }} - run: | - cd YouChooseQuality - git checkout 1585a3691b2ef0b59d42c40c31639fd8b79e2cd4 - make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless - mv packages/*.deb ${{ github.workspace }}/youchoose.deb - - - name: Build YouShare - if: ${{ inputs.youshare }} - run: | - cd YouShare - make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless - mv packages/*.deb ${{ github.workspace }}/youshare.deb - - name: Build YTweaks if: ${{ inputs.ytweaks }} run: | @@ -476,13 +354,6 @@ jobs: make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless mv packages/*.deb ${{ github.workspace }}/gonerino.deb - - name: Build YouGetCaption - if: ${{ inputs.getcap }} - run: | - cd YouGetCaption - make clean package DEBUG=0 FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless - mv packages/*.deb ${{ github.workspace }}/ygc.deb - - name: Build youtube-native-share if: ${{ inputs.ytshare }} run: | diff --git a/Files/Ads.x b/Files/Ads.x index 6b19953e..49697ccd 100644 --- a/Files/Ads.x +++ b/Files/Ads.x @@ -9,15 +9,34 @@ static BOOL isProductList(YTICommand *command) { return NO; } -NSString *getAdString(NSString *description) { +static NSString *getPostString(NSString *description) { + for (NSString *str in @[ + @"poll_post_root.eml", + @"options_post_root.eml", + @"images_post_root_slim.eml", + @"images_post_responsive_root.eml", + @"options_post_responsive_root.eml", + @"post_base_wrapper_slim.eml", + @"text_post_root_slim.eml", + @"text_post_responsive_root.eml", + @"videos_post_root.eml" + ]) + if ([description containsString:str]) return str; + return nil; +} + +static NSString *getAdString(NSString *description) { for (NSString *str in @[ @"brand_promo", + @"brand_video_shelf", + @"brand_video_singleton", @"carousel_footered_layout", @"carousel_headered_layout", @"eml.expandable_metadata", @"feed_ad_metadata", @"full_width_portrait_image_layout", @"full_width_square_image_layout", + @"grid_ads_image_layout", @"landscape_image_wide_button_layout", @"post_shelf", @"product_carousel", @@ -51,7 +70,21 @@ static BOOL isAdRenderer(YTIElementRenderer *elementRenderer, int kind) { static NSMutableArray *filteredArray(NSArray *array) { NSMutableArray *newArray = [array mutableCopy]; NSIndexSet *removeIndexes = [newArray indexesOfObjectsPassingTest:^BOOL(YTIItemSectionRenderer *sectionRenderer, NSUInteger idx, BOOL *stop) { + // Filter shelf renderer items (ads and shorts) if ([sectionRenderer isKindOfClass:%c(YTIShelfRenderer)]) { + NSString *description = [sectionRenderer description]; + if ([description containsString:@"community-tab-chip-posts-section"]) return NO; + // Filter shorts + if (IS_ENABLED(HideShortsShelf)) { + if (IS_ENABLED(KeepShortsSubscript) && [description containsString:@"subscriptions-shorts-shelf-item"]) return NO; + if ([description containsString:@"shorts_video_cell.eml"]) return YES; + if ([description containsString:@"shelf_header.eml"] && [description containsString:@"youtube_shorts_24_cairo"]) return YES; + } + // Filter feed posts + NSString *filtered = getPostString(description); + if (IS_ENABLED(HideFeedPost) && filtered) { + return YES; + } YTIShelfSupportedRenderers *content = ((YTIShelfRenderer *)sectionRenderer).content; YTIHorizontalListRenderer *horizontalListRenderer = content.horizontalListRenderer; NSMutableArray *itemsArray = horizontalListRenderer.itemsArray; @@ -61,8 +94,83 @@ static NSMutableArray *filteredArray(NSArray *contentsArray = sectionRenderer.contentsArray; + + NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; + __block NSUInteger lastCellDividerIndex = NSNotFound; + + [contentsArray enumerateObjectsUsingBlock:^(YTIItemSectionSupportedRenderers *item, NSUInteger idx, BOOL *stop) { + NSString *desc = [item description]; + + if ([desc containsString:@"cell_divider.eml"]) { + lastCellDividerIndex = idx; + } + + if ([desc containsString:@"UNLIMITED"] && [desc containsString:@"SPunlimited"]) { + [indexesToRemove addIndex:idx]; + + if (lastCellDividerIndex != NSNotFound) { + [indexesToRemove addIndex:lastCellDividerIndex]; + lastCellDividerIndex = NSNotFound; + } + } + }]; + + [contentsArray removeObjectsAtIndexes:indexesToRemove]; + } + if ([description containsString:@"community-tab-chip-posts-section"]) return NO; + + // Filter shorts shelf + BOOL isShortsShelf = [description containsString:@"shorts_shelf.eml"]; + BOOL isHistory = [description containsString:@"history-shorts-shelf-item"]; + if (IS_ENABLED(HideShortsShelf) && IS_ENABLED(KeepShortsSubscript)) { + if (isShortsShelf && ![description containsString:@"subscriptions-shorts-shelf-item"] && !isHistory) { + return YES; + } + if ([description containsString:@"video_lockup_overlay"]) return YES; + } else if (IS_ENABLED(HideShortsShelf)) { + if (isShortsShelf && !isHistory) { + return YES; + } + if ([description containsString:@"video_lockup_overlay"]) return YES; + } + + // Filter horizontal shelf + if ([description containsString:@"horizontal_shelf.eml"]) { + if (IS_ENABLED(HidePlayables) && [description containsString:@"FEmini_app_destination"]) return YES; + if (IS_ENABLED(HideHoriShelf) && ![description containsString:@"UCYfdidRxbB8Qhf0Nx7ioOYw"] && ![description containsString:@"FElibrary"] && ![description containsString:@"mini_game_card.eml"] && ![description containsString:@"FEplaylist_aggregation"]) { + return YES; + } + } + + if (IS_ENABLED(HideCommuGuide) && ([description containsString:@"community_guidelines.eml"] || [description containsString:@"channel_guidelines_entry_banner.eml"])) { + return YES; + } + + // Filter feed posts + NSString *filtered = getPostString(description); + if (IS_ENABLED(HideFeedPost) && filtered) { + return YES; + } + + if (IS_ENABLED(HideGenMusicShelf) && [description containsString:@"feed_nudge.eml"]) { + return YES; + } + + if (IS_ENABLED(HideSurveys) && [description containsString:@"in_feed_survey.eml"]) { + return YES; + } + + if (IS_ENABLED(HideCommentsSection) && [description containsString:@"comment-item-section"] && [description containsString:@"comments-entry-point"]) { + return YES; + } + NSMutableArray *contentsArray = sectionRenderer.contentsArray; if (contentsArray.count > 1) { NSIndexSet *removeContentsArrayIndexes = [contentsArray indexesOfObjectsPassingTest:^BOOL(YTIItemSectionSupportedRenderers *sectionSupportedRenderers, NSUInteger idx2, BOOL *stop2) { @@ -73,7 +181,10 @@ static NSMutableArray *filteredArray(NSArray *filteredArray(NSArray *filteredArray(NSArray *filteredArray(NSArray *)reels { [reels removeObjectsAtIndexes:[reels indexesOfObjectsPassingTest:^BOOL(YTReelModel *obj, NSUInteger idx, BOOL *stop) { - return [obj respondsToSelector:@selector(videoType)] ? obj.videoType == 3 : NO; + if ([obj respondsToSelector:@selector(videoType)] && obj.videoType == 3) return YES; + if ([obj isKindOfClass:%c(YTReelNonVideoContentModel)]) return YES; + if ([obj respondsToSelector:@selector(videoType)] && obj.videoType == 10 && IS_ENABLED(RemoveShortsPosts)) return YES; + if ([obj respondsToSelector:@selector(videoType)] && (obj.videoType == 4 || obj.videoType == 7) && IS_ENABLED(RemoveShortsLive)) return YES; + return NO; }]]; %orig; } @@ -164,6 +313,7 @@ static NSMutableArray *filteredArray(NSArray *filteredArray(NSArray *filteredArray(NSArray #import @@ -8,60 +7,16 @@ #import #import #import -#import -#import -#import -#import -#import - -@interface YTDefaultSheetController (YouModDownload) -+ (instancetype)sheetControllerWithParentResponder:(id)parentResponder; -- (void)addAction:(YTActionSheetAction *)action; -- (void)presentFromView:(UIView *)view animated:(BOOL)animated completion:(void (^)(void))completion; -- (void)presentFromViewController:(UIViewController *)vc animated:(BOOL)animated completion:(void (^)(void))completion; -@end - -@interface YTPlayerViewController (YouModDownload) -- (YTPlayerResponse *)contentPlayerResponse; -@end - -@interface YTIPlayerResponse (YouModDownload) -- (id)streamingData; -@end - -@interface YTIFormatStream (YouModDownload) -- (NSString *)mimeType; -- (BOOL)hasContentLength; -- (unsigned long long)contentLength; -- (unsigned long long)approxDurationMs; -@end - -@interface YTIVideoDetails (YouModDownload) -- (NSString *)title; -- (NSString *)author; -- (NSString *)shortDescription; -@end - -static UIImage *YouModIconImage(NSInteger iconType) { - YTIIcon *icon = [%c(YTIIcon) new]; - icon.iconType = iconType; - UIImage *image = [icon iconImageWithColor:[UIColor labelColor]]; - return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; -} @interface YouModMenuItem : NSObject @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *subtitle; @property (nonatomic, strong) UIImage *iconImage; @property (nonatomic, copy) void (^handler)(void); -+ (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle handler:(void (^)(void))handler; + (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle icon:(UIImage *)icon handler:(void (^)(void))handler; @end @implementation YouModMenuItem -+ (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle handler:(void (^)(void))handler { - return [self itemWithTitle:title subtitle:subtitle icon:nil handler:handler]; -} + (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle icon:(UIImage *)icon handler:(void (^)(void))handler { YouModMenuItem *item = [YouModMenuItem new]; item.title = title; @@ -73,36 +28,22 @@ static UIImage *YouModIconImage(NSInteger iconType) { @end @interface YouModMediaFormat : NSObject -@property (nonatomic, strong) id source; +@property (nonatomic, strong) YTIFormatStream *source; @property (nonatomic, copy) NSString *urlString; @property (nonatomic, copy) NSString *qualityLabel; @property (nonatomic, copy) NSString *mimeType; -@property (nonatomic, copy) NSDictionary *httpHeaders; -@property (nonatomic, assign) unsigned long long contentLength; -@property (nonatomic, assign) unsigned long long durationMs; -@property (nonatomic, assign) NSInteger fps; +@property (nonatomic, copy) NSString *idp; +@property (nonatomic, assign) NSInteger contentLength; +@property (nonatomic, assign) NSUInteger durationMs; +@property (nonatomic, assign) int fps; +@property (nonatomic, assign) int itag; +@property (nonatomic, assign) int resolution; @property (nonatomic, assign) BOOL video; -@property (nonatomic, copy) NSString *languageCode; -@property (nonatomic, copy) NSString *languageName; -@property (nonatomic, assign) BOOL drcAudio; @end @implementation YouModMediaFormat @end -@interface YouModAudioOutputFormat : NSObject -@property (nonatomic, copy) NSString *identifier; -@property (nonatomic, copy) NSString *title; -@property (nonatomic, copy) NSString *subtitle; -@property (nonatomic, copy) NSString *fileExtension; -@property (nonatomic, copy) NSArray *ffmpegArguments; -@property (nonatomic, assign) BOOL passthroughWhenCompatible; -@property (nonatomic, assign) BOOL supported; -@end - -@implementation YouModAudioOutputFormat -@end - typedef void (^YouModFileDownloadCompletion)(NSURL *fileURL, NSError *error); typedef void (^YouModMergeCompletion)(BOOL success, NSError *error); typedef void (^YouModRangeDownloadProgress)(unsigned long long completedBytes); @@ -144,8 +85,8 @@ typedef void (^YouModRangeDownloadProgress)(unsigned long long completedBytes); @property (nonatomic, strong) NSURLSessionDownloadTask *task; @property (nonatomic, strong) NSURLSessionDataTask *metadataTask; @property (nonatomic, strong) YouModRangeDownloader *rangeDownloader; -@property (nonatomic, strong) UIAlertController *progressAlert; -@property (nonatomic, strong) UIProgressView *progressView; +@property (nonatomic, strong) AVAssetExportSession *exporter; +@property (nonatomic, strong) YMDownloadProgressView *progressPill; @property (nonatomic, weak) UIViewController *presenter; @property (nonatomic, copy) YouModFileDownloadCompletion fileCompletion; @property (nonatomic, strong) NSURL *destinationURL; @@ -161,18 +102,15 @@ typedef void (^YouModRangeDownloadProgress)(unsigned long long completedBytes); @property (nonatomic, assign) BOOL cancelled; @property (nonatomic, copy) NSString *baseProgressTitle; @property (nonatomic, assign) NSTimeInterval downloadStartTime; +@property (nonatomic, copy) void (^downloadCompletionBlock)(NSURL *localURL, NSString *errorMsg); + (instancetype)sharedCoordinator; -- (void)startVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; -- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; -- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID outputFormat:(YouModAudioOutputFormat *)outputFormat presenter:(UIViewController *)presenter; -- (void)startDirectVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; -- (void)startDirectSingleVideoDownloadWithFormat:(YouModMediaFormat *)format fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; -- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; -- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID outputFormat:(YouModAudioOutputFormat *)outputFormat presenter:(UIViewController *)presenter; +- (void)startVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName presenter:(UIViewController *)presenter videoID:(NSString *)vidID; +- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName presenter:(UIViewController *)presenter videoID:(NSString *)vidID; +- (void)startDirectVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName presenter:(UIViewController *)presenter videoID:(NSString *)vidID; +- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName presenter:(UIViewController *)presenter videoID:(NSString *)vidID; +- (void)trimAudioToHalfLengthAtURL:(NSURL *)inputURL toURL:(NSURL *)outputURL completion:(void (^)(NSError *error))completion; - (void)mergeVideoURL:(NSURL *)videoURL audioURL:(NSURL *)audioURL fileName:(NSString *)fileName outputExtension:(NSString *)outputExtension durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter; - (void)mergeVideoWithAVFoundationVideoURL:(NSURL *)videoURL audioURL:(NSURL *)audioURL outputURL:(NSURL *)outputURL durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter fallbackError:(NSError *)fallbackError; -- (void)trimSingleVideoURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter; -- (void)convertAudioURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL outputFormat:(YouModAudioOutputFormat *)outputFormat durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter; @end static const unsigned long long YouModFastDownloadMinimumBytes = 256ULL * 1024ULL; @@ -199,9 +137,8 @@ static NSString *YouModYouTubeCookiesString(void) { } static NSString *YouModNativeUserAgent(void) { - NSString *version = @"21.18.4"; - NSString *sysVersion = [[UIDevice currentDevice].systemVersion stringByReplacingOccurrencesOfString:@"." withString:@"_"] ?: @"18_7"; - return [NSString stringWithFormat:@"com.google.ios.youtube/%@ (iPhone; CPU iPhone OS %@ like Mac OS X)", version, sysVersion]; + NSString *device = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad ? @"iPad" : @"iPhone"; + return [NSString stringWithFormat:@"com.google.ios.youtube/21.26.4 (%@; CPU OS 18_7 like Mac OS X)", device]; } static void YouModApplyDownloadHeaders(NSMutableURLRequest *request, NSDictionary *headers) { @@ -313,7 +250,7 @@ static void YouModApplyDownloadHeaders(NSMutableURLRequest *request, NSDictionar return; } - unsigned long long chunkSize = self.expectedBytes / YouModFastDownloadConcurrency; + unsigned long long chunkSize = self.expectedBytes / 100ULL; if (chunkSize < 256ULL * 1024ULL) chunkSize = 256ULL * 1024ULL; if (chunkSize > YouModFastDownloadChunkBytes) chunkSize = YouModFastDownloadChunkBytes; @@ -486,22 +423,8 @@ void YouModDownloadSetCurrentPlayer(YTPlayerViewController *player) { YouModCurrentPlayerViewController = player; } -static NSString *YouModStringFromSelector(id object, SEL selector) { - if (!object) return nil; - id value = nil; - if ([object respondsToSelector:selector]) { - value = ((id (*)(id, SEL))objc_msgSend)(object, selector); - } else { - @try { - value = [object valueForKey:NSStringFromSelector(selector)]; - } @catch (__unused NSException *exception) { - value = nil; - } - } - if ([value isKindOfClass:NSString.class]) return value; - if ([value isKindOfClass:NSURL.class]) return [(NSURL *)value absoluteString]; - if ([value respondsToSelector:@selector(stringValue)]) return [value stringValue]; - return [value respondsToSelector:@selector(description)] ? [value description] : nil; +YTPlayerViewController *YouModDownloadGetCurrentPlayer(void) { + return YouModCurrentPlayerViewController; } static id YouModObjectFromSelector(id object, SEL selector) { @@ -516,83 +439,19 @@ static id YouModObjectFromSelector(id object, SEL selector) { } } -static unsigned long long YouModUnsignedLongLongFromSelector(id object, SEL selector) { - if (!object) return 0; - if ([object respondsToSelector:selector]) { - return ((unsigned long long (*)(id, SEL))objc_msgSend)(object, selector); - } - @try { - id value = [object valueForKey:NSStringFromSelector(selector)]; - if ([value respondsToSelector:@selector(unsignedLongLongValue)]) - return [value unsignedLongLongValue]; - } @catch (__unused NSException *exception) { - } - return 0; -} - -static BOOL YouModBoolFromSelector(id object, SEL selector) { - if (!object) return NO; - if ([object respondsToSelector:selector]) { - return ((BOOL (*)(id, SEL))objc_msgSend)(object, selector); - } - @try { - id value = [object valueForKey:NSStringFromSelector(selector)]; - if ([value respondsToSelector:@selector(boolValue)]) - return [value boolValue]; - } @catch (__unused NSException *exception) { - } - return NO; -} - -static NSInteger YouModIntegerFromSelector(id object, SEL selector) { - if (!object) return 0; - if ([object respondsToSelector:selector]) { - return ((NSInteger (*)(id, SEL))objc_msgSend)(object, selector); - } - @try { - id value = [object valueForKey:NSStringFromSelector(selector)]; - if ([value respondsToSelector:@selector(integerValue)]) - return [value integerValue]; - } @catch (__unused NSException *exception) { - } - return 0; +static void YouModSendToast(NSString *message) { + UIView *parent = sbGetNotificationParent(); + [SBSkipNotificationView showInView:parent message:message buttonTitle:nil action:nil duration:3.0]; } -static UIViewController *YouModTopViewController(UIViewController *root) { - if (!root) { - UIWindow *keyWindow = nil; - for (UIWindow *window in UIApplication.sharedApplication.windows) { - if (window.isKeyWindow) { - keyWindow = window; - break; - } - } - root = keyWindow.rootViewController; - } - while (root.presentedViewController) root = root.presentedViewController; - if ([root isKindOfClass:UINavigationController.class]) - return YouModTopViewController(((UINavigationController *)root).topViewController); - if ([root isKindOfClass:UITabBarController.class]) - return YouModTopViewController(((UITabBarController *)root).selectedViewController); - return root; +static void YouModSendSuccess(NSString *message) { + UIView *parent = sbGetNotificationParent(); + [SBSkipNotificationView showSuccessInView:parent message:message duration:3.0]; } -static void YouModSendToast(NSString *message, id responder) { - Class toastClass = NSClassFromString(@"YTToastResponderEvent"); - id event = [toastClass eventWithMessage:message firstResponder:responder ?: YouModTopViewController(nil)]; - if ([event respondsToSelector:@selector(send)]) { - [event send]; - return; - } - - UIViewController *presenter = YouModTopViewController([responder isKindOfClass:UIViewController.class] ? responder : nil); - if (!presenter) return; - UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; - [presenter presentViewController:alert animated:YES completion:^{ - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [alert dismissViewControllerAnimated:YES completion:nil]; - }); - }]; +static void YouModSendError(NSString *message) { + UIView *parent = sbGetNotificationParent(); + [SBSkipNotificationView showErrorInView:parent message:message duration:4.0]; } static NSString *YouModByteCount(unsigned long long bytes) { @@ -602,14 +461,6 @@ static NSString *YouModByteCount(unsigned long long bytes) { return [formatter stringFromByteCount:(long long)bytes]; } -static NSString *YouModGenerateCPN(void) { - static NSString *const alphabet = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - NSMutableString *nonce = [NSMutableString stringWithCapacity:16]; - for (NSUInteger i = 0; i < 16; i++) - [nonce appendFormat:@"%C", [alphabet characterAtIndex:arc4random_uniform((uint32_t)alphabet.length)]]; - return nonce; -} - static NSString *YouModURLStringBypassingThrottle(NSString *urlString) { if (urlString.length == 0) return urlString; NSURLComponents *components = [NSURLComponents componentsWithString:urlString]; @@ -637,10 +488,7 @@ static NSString *YouModURLStringWithCPN(NSString *urlString) { if (urlString.length == 0) return urlString; urlString = YouModURLStringBypassingThrottle(urlString); if ([urlString containsString:@"cpn="]) return urlString; - Class ytDataUtils = NSClassFromString(@"YTDataUtils"); - NSString *cpn = ((id (*)(Class, SEL))objc_msgSend)(ytDataUtils, @selector(generateClientSideNonce)); - if (![cpn isKindOfClass:NSString.class] || cpn.length == 0) - cpn = YouModGenerateCPN(); + NSString *cpn = [%c(YTDataUtils) generateClientSideNonce]; NSString *separator = [urlString containsString:@"?"] ? @"&" : @"?"; return [NSString stringWithFormat:@"%@%@cpn=%@", urlString, separator, cpn]; } @@ -658,60 +506,11 @@ static NSString *YouModSanitizedFileName(NSString *name) { static NSURL *YouModDownloadsDirectoryURL(void) { NSURL *documentsURL = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject; - NSURL *downloadsURL = [documentsURL URLByAppendingPathComponent:@"YouMod Downloads" isDirectory:YES]; + NSURL *downloadsURL = [documentsURL URLByAppendingPathComponent:@"YouMod_Downloads" isDirectory:YES]; [NSFileManager.defaultManager createDirectoryAtURL:downloadsURL withIntermediateDirectories:YES attributes:nil error:nil]; return downloadsURL; } -static NSString *YouModLastDownloadDiagnostic; - -static NSURL *YouModDiagnosticLogURL(void) { - return [YouModDownloadsDirectoryURL() URLByAppendingPathComponent:@"youmod-download-diagnostics.txt"]; -} - -static void YouModRecordDownloadDiagnostic(NSString *context, NSString *details) { - if (context.length == 0 && details.length == 0) return; - - NSDateFormatter *formatter = [NSDateFormatter new]; - formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; - formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss ZZZZZ"; - NSString *timestamp = [formatter stringFromDate:NSDate.date]; - NSString *entry = [NSString stringWithFormat:@"[%@]\n%@\n%@\n\n", timestamp ?: @"", context ?: @"", details ?: @""]; - YouModLastDownloadDiagnostic = entry; - - NSURL *logURL = YouModDiagnosticLogURL(); - NSData *data = [entry dataUsingEncoding:NSUTF8StringEncoding]; - if (![NSFileManager.defaultManager fileExistsAtPath:logURL.path]) - [NSFileManager.defaultManager createFileAtPath:logURL.path contents:nil attributes:nil]; - - NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:logURL.path]; - if (!handle) return; - @try { - [handle seekToEndOfFile]; - [handle writeData:data]; - [handle closeFile]; - } @catch (__unused NSException *exception) { - } -} - -static NSString *YouModDownloadDiagnosticText(void) { - if (YouModLastDownloadDiagnostic.length) return YouModLastDownloadDiagnostic; - NSString *log = [NSString stringWithContentsOfURL:YouModDiagnosticLogURL() encoding:NSUTF8StringEncoding error:nil]; - if (log.length == 0) return nil; - NSUInteger maxLength = 12000; - return log.length > maxLength ? [log substringFromIndex:log.length - maxLength] : log; -} - -static void YouModCopyDownloadDiagnostics(UIViewController *presenter) { - NSString *diagnostic = YouModDownloadDiagnosticText(); - if (diagnostic.length == 0) { - YouModSendToast(@"No download diagnostics yet.", presenter); - return; - } - UIPasteboard.generalPasteboard.string = diagnostic; - YouModSendToast(@"Copied download diagnostics", presenter); -} - static NSURL *YouModUniqueFileURL(NSString *fileName, NSString *extension) { NSString *safeName = YouModSanitizedFileName(fileName); NSURL *directoryURL = YouModDownloadsDirectoryURL(); @@ -729,13 +528,6 @@ static NSURL *YouModTemporaryFileURL(NSString *extension) { return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:name]]; } -static NSInteger YouModResolutionFromQuality(NSString *quality); -static NSInteger YouModFPSFromQuality(NSString *quality); -static NSInteger YouModNormalizedFPS(NSInteger fps); -static NSInteger YouModDisplayHeightForVideoHeight(NSInteger height); -static NSString *YouModQualityLabel(NSInteger height, NSInteger fps, NSString *fallback); -static BOOL YouModFFmpegKitAvailable(void); - static unsigned long long YouModDurationMsForURL(NSURL *url) { AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; if (!CMTIME_IS_NUMERIC(asset.duration) || !CMTIME_IS_VALID(asset.duration)) return 0; @@ -744,10 +536,6 @@ static unsigned long long YouModDurationMsForURL(NSURL *url) { return (unsigned long long)llround(seconds * 1000.0); } -static NSString *YouModDurationSecondsArgument(unsigned long long durationMs) { - return [NSString stringWithFormat:@"%.3f", (double)durationMs / 1000.0]; -} - static BOOL YouModCMTimeIsUsable(CMTime time) { if (!CMTIME_IS_VALID(time) || !CMTIME_IS_NUMERIC(time) || CMTIME_IS_INDEFINITE(time)) return NO; Float64 seconds = CMTimeGetSeconds(time); @@ -778,321 +566,33 @@ static CMTime YouModExportDuration(AVAsset *videoAsset, AVAsset *audioAsset, uns return duration; } -static NSMutableArray *YouModFFmpegKitLoadEntries(void) { - static NSMutableArray *entries = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - entries = [NSMutableArray array]; - }); - return entries; -} - -static void YouModAppendFFmpegKitLoadEntry(NSString *format, ...) { - if (format.length == 0) return; - - va_list arguments; - va_start(arguments, format); - NSString *entry = [[NSString alloc] initWithFormat:format arguments:arguments]; - va_end(arguments); - if (entry.length == 0) return; - - NSMutableArray *entries = YouModFFmpegKitLoadEntries(); - @synchronized(entries) { - [entries addObject:entry]; - if (entries.count > 220) - [entries removeObjectsInRange:NSMakeRange(0, entries.count - 220)]; - } -} - -static NSArray *YouModFFmpegKitSearchDirectories(void) { - NSMutableOrderedSet *directories = [NSMutableOrderedSet orderedSet]; - - // Path to YouMod.bundle/Frameworks inside the main app bundle - NSString *bundlePath = [[NSBundle.mainBundle resourcePath] stringByAppendingPathComponent:@"YouMod.bundle"]; - NSString *frameworksInsideBundle = [bundlePath stringByAppendingPathComponent:@"Frameworks"]; - - // Safety check: only add if the directory actually exists - BOOL isDir = NO; - if ([[NSFileManager defaultManager] fileExistsAtPath:frameworksInsideBundle isDirectory:&isDir] && isDir) { - [directories addObject:frameworksInsideBundle]; - } - - return directories.array; -} - -static void YouModDlopenPath(NSString *path, BOOL requireExistingFile) { - if (path.length == 0) return; - if (requireExistingFile && ![NSFileManager.defaultManager fileExistsAtPath:path]) { - YouModAppendFFmpegKitLoadEntry(@"missing %@", path); - return; - } - - dlerror(); - void *handle = dlopen(path.fileSystemRepresentation, RTLD_NOW | RTLD_GLOBAL); - const char *error = dlerror(); - if (handle) { - YouModAppendFFmpegKitLoadEntry(@"loaded %@", path); - } else { - YouModAppendFFmpegKitLoadEntry(@"failed %@\n dlerror=%@", path, error ? [NSString stringWithUTF8String:error] : @"unknown"); - } -} - -static void YouModDlopenPathIfPresent(NSString *path) { - YouModDlopenPath(path, YES); -} - -static void YouModLoadFrameworkBinary(NSString *directory, NSString *frameworkName, NSString *binaryName) { - if (directory.length == 0 || frameworkName.length == 0 || binaryName.length == 0) return; - YouModDlopenPathIfPresent([[directory stringByAppendingPathComponent:[frameworkName stringByAppendingString:@".framework"]] stringByAppendingPathComponent:binaryName]); - YouModDlopenPathIfPresent([[directory stringByAppendingPathComponent:[frameworkName stringByAppendingString:@".framework"]] stringByAppendingPathComponent:frameworkName]); -} - -static void YouModLoadFFmpegKitIfNeeded(void) { - static BOOL attempted = NO; - if (NSClassFromString(@"FFmpegKit")) return; - if (attempted) return; - attempted = YES; - - YouModAppendFFmpegKitLoadEntry(@"[YouMod] Starting bundled FFmpegKit load..."); - - // Order is important: load dependencies (avutil, etc.) before the main toolkit - NSArray *> *frameworks = @[ - @[@"libavutil", @"libavutil"], - @[@"libswresample", @"libswresample"], - @[@"libswscale", @"libswscale"], - @[@"libavcodec", @"libavcodec"], - @[@"libavformat", @"libavformat"], - @[@"libavfilter", @"libavfilter"], - @[@"libavdevice", @"libavdevice"], - @[@"ffmpegkit", @"ffmpegkit"], - @[@"FFmpegKit", @"FFmpegKit"], - ]; - - NSArray *searchDirs = YouModFFmpegKitSearchDirectories(); - if (searchDirs.count == 0) { - YouModAppendFFmpegKitLoadEntry(@"[YouMod] Error: Bundled Frameworks directory not found."); - return; - } - - // Only iterate through our controlled bundle directory - for (NSString *directory in searchDirs) { - for (NSArray *framework in frameworks) { - // This helper uses dlopen on the direct path within our bundle - YouModLoadFrameworkBinary(directory, framework.firstObject, framework.lastObject); - } - - if (NSClassFromString(@"FFmpegKit")) { - YouModAppendFFmpegKitLoadEntry(@"[YouMod] Success: FFmpegKit loaded from bundle."); - return; - } - } - - YouModAppendFFmpegKitLoadEntry(@"[YouMod] Critical: FFmpegKit could not be found in YouMod.bundle."); -} - -static Class YouModFFmpegKitClass(void) { - Class ffmpegKitClass = NSClassFromString(@"FFmpegKit"); - if (!ffmpegKitClass) { - YouModLoadFFmpegKitIfNeeded(); - ffmpegKitClass = NSClassFromString(@"FFmpegKit"); - } - return ffmpegKitClass; -} - -static BOOL YouModFFmpegKitAvailable(void) { - Class ffmpegKitClass = YouModFFmpegKitClass(); - return ffmpegKitClass && [ffmpegKitClass respondsToSelector:@selector(executeWithArgumentsAsync:withCompleteCallback:withLogCallback:withStatisticsCallback:)]; -} - -static NSString *YouModFFmpegKitDiagnosticText(YouModAudioOutputFormat *outputFormat, YouModMediaFormat *sourceFormat, NSString *videoID) { - YouModLoadFFmpegKitIfNeeded(); - - Class ffmpegKitClass = NSClassFromString(@"FFmpegKit"); - SEL executeSelector = @selector(executeWithArgumentsAsync:withCompleteCallback:withLogCallback:withStatisticsCallback:); - NSMutableArray *lines = [NSMutableArray array]; - NSBundle *mainBundle = NSBundle.mainBundle; - NSString *resourcePath = mainBundle.resourcePath ?: @""; - NSString *privateFrameworksPath = mainBundle.privateFrameworksPath ?: @""; - NSString *executablePath = mainBundle.executablePath ?: @""; - NSString *bundlePath = [resourcePath stringByAppendingPathComponent:@"YouMod.bundle"]; - NSString *packageFrameworkPath = [resourcePath stringByAppendingPathComponent:@"YouMod.bundle/Frameworks"]; - - [lines addObject:@"FFmpegKit lookup"]; - [lines addObject:[NSString stringWithFormat:@"videoID=%@", videoID ?: @""]]; - [lines addObject:[NSString stringWithFormat:@"requestedFormat=%@ (%@)", outputFormat.title ?: @"", outputFormat.identifier ?: @""]]; - [lines addObject:[NSString stringWithFormat:@"sourceMime=%@", sourceFormat.mimeType ?: @""]]; - [lines addObject:[NSString stringWithFormat:@"sourceQuality=%@", sourceFormat.qualityLabel ?: @""]]; - [lines addObject:[NSString stringWithFormat:@"sourceBytes=%llu", sourceFormat.contentLength]]; - [lines addObject:[NSString stringWithFormat:@"mainBundle=%@", mainBundle.bundlePath ?: @""]]; - [lines addObject:[NSString stringWithFormat:@"resourcePath=%@", resourcePath]]; - [lines addObject:[NSString stringWithFormat:@"privateFrameworksPath=%@", privateFrameworksPath]]; - [lines addObject:[NSString stringWithFormat:@"executablePath=%@", executablePath]]; - [lines addObject:[NSString stringWithFormat:@"YouMod.bundle exists=%@", [NSFileManager.defaultManager fileExistsAtPath:bundlePath] ? @"YES" : @"NO"]]; - [lines addObject:[NSString stringWithFormat:@"YouMod.bundle/Frameworks exists=%@", [NSFileManager.defaultManager fileExistsAtPath:packageFrameworkPath] ? @"YES" : @"NO"]]; - [lines addObject:[NSString stringWithFormat:@"FFmpegKit class=%@", ffmpegKitClass ? @"YES" : @"NO"]]; - [lines addObject:[NSString stringWithFormat:@"FFmpegKit execute selector=%@", [ffmpegKitClass respondsToSelector:executeSelector] ? @"YES" : @"NO"]]; - [lines addObject:[NSString stringWithFormat:@"ReturnCode class=%@", NSClassFromString(@"ReturnCode") ? @"YES" : @"NO"]]; - [lines addObject:@"searchDirectories:"]; - for (NSString *directory in YouModFFmpegKitSearchDirectories()) { - BOOL isDirectory = NO; - BOOL exists = [NSFileManager.defaultManager fileExistsAtPath:directory isDirectory:&isDirectory]; - [lines addObject:[NSString stringWithFormat:@" %@ exists=%@ directory=%@", directory, exists ? @"YES" : @"NO", isDirectory ? @"YES" : @"NO"]]; - } - - NSMutableArray *entries = YouModFFmpegKitLoadEntries(); - [lines addObject:@"dlopenAttempts:"]; - @synchronized(entries) { - [lines addObjectsFromArray:entries]; - } - return [lines componentsJoinedByString:@"\n"]; -} - -static void YouModCancelFFmpegKit(void) { - Class ffmpegKitClass = YouModFFmpegKitClass(); - if ([ffmpegKitClass respondsToSelector:@selector(cancel)]) - ((void (*)(Class, SEL))objc_msgSend)(ffmpegKitClass, @selector(cancel)); -} - -static NSError *YouModFFmpegErrorFromSession(id session) { - NSString *failure = YouModStringFromSelector(session, @selector(getFailStackTrace)); - NSString *message = failure.length ? failure : @"FFmpeg failed"; - return [NSError errorWithDomain:@"YouMod" code:7 userInfo:@{NSLocalizedDescriptionKey: message}]; -} - static BOOL YouModPathExtensionIsPhotosVideo(NSString *extension) { - NSString *lower = extension.lowercaseString ?: @""; - return [@[@"mp4", @"m4v", @"mov"] containsObject:lower]; -} - -static BOOL YouModStartFFmpegKitMerge(NSURL *videoURL, NSURL *audioURL, NSURL *outputURL, unsigned long long durationMs, void (^progress)(float progress), YouModMergeCompletion completion) { - Class ffmpegKitClass = YouModFFmpegKitClass(); - SEL executeSelector = @selector(executeWithArgumentsAsync:withCompleteCallback:withLogCallback:withStatisticsCallback:); - if (![ffmpegKitClass respondsToSelector:executeSelector]) return NO; - - NSMutableArray *arguments = [@[ - @"-y", - @"-i", videoURL.path, - @"-i", audioURL.path, - @"-map", @"0:v:0", - @"-map", @"1:a:0", - ] mutableCopy]; - if (durationMs > 0) - [arguments addObjectsFromArray:@[@"-t", YouModDurationSecondsArgument(durationMs)]]; - [arguments addObjectsFromArray:@[ - @"-c", @"copy", - @"-shortest", - @"-avoid_negative_ts", @"make_zero", - ]]; - if (YouModPathExtensionIsPhotosVideo(outputURL.pathExtension)) - [arguments addObjectsFromArray:@[@"-movflags", @"+faststart"]]; - [arguments addObject:outputURL.path]; - - id completeBlock = [^(id session) { - Class returnCodeClass = NSClassFromString(@"ReturnCode"); - id returnCode = YouModObjectFromSelector(session, @selector(getReturnCode)); - BOOL success = NO; - if ([returnCodeClass respondsToSelector:@selector(isSuccess:)]) - success = ((BOOL (*)(Class, SEL, id))objc_msgSend)(returnCodeClass, @selector(isSuccess:), returnCode); - - NSError *error = success ? nil : YouModFFmpegErrorFromSession(session); - dispatch_async(dispatch_get_main_queue(), ^{ - if (success && [NSFileManager.defaultManager fileExistsAtPath:outputURL.path]) { - completion(YES, nil); - } else { - completion(NO, error ?: [NSError errorWithDomain:@"YouMod" code:7 userInfo:@{NSLocalizedDescriptionKey: @"Merge failed"}]); - } - }); - } copy]; - - id statisticsBlock = durationMs ? [^(id statistics) { - if (!progress || ![statistics respondsToSelector:@selector(getTime)]) return; - double timeMs = ((double (*)(id, SEL))objc_msgSend)(statistics, @selector(getTime)); - if (!isfinite(timeMs) || timeMs <= 0.0) return; - float mergeProgress = 0.985f + (0.01f * fminf((float)(timeMs / (double)durationMs), 1.0f)); - dispatch_async(dispatch_get_main_queue(), ^{ - progress(mergeProgress); - }); - } copy] : nil; - - ((id (*)(Class, SEL, NSArray *, id, id, id))objc_msgSend)(ffmpegKitClass, executeSelector, arguments, completeBlock, nil, statisticsBlock); - return YES; -} - -static BOOL YouModStartFFmpegKitAudioConvert(NSURL *inputURL, NSURL *outputURL, YouModAudioOutputFormat *outputFormat, unsigned long long durationMs, void (^progress)(float progress), YouModMergeCompletion completion) { - Class ffmpegKitClass = YouModFFmpegKitClass(); - SEL executeSelector = @selector(executeWithArgumentsAsync:withCompleteCallback:withLogCallback:withStatisticsCallback:); - if (![ffmpegKitClass respondsToSelector:executeSelector] || outputFormat.ffmpegArguments.count == 0) return NO; - - NSMutableArray *arguments = [@[@"-y", @"-i", inputURL.path] mutableCopy]; - [arguments addObjectsFromArray:outputFormat.ffmpegArguments]; - [arguments addObject:outputURL.path]; - - id completeBlock = [^(id session) { - Class returnCodeClass = NSClassFromString(@"ReturnCode"); - id returnCode = YouModObjectFromSelector(session, @selector(getReturnCode)); - BOOL success = NO; - if ([returnCodeClass respondsToSelector:@selector(isSuccess:)]) - success = ((BOOL (*)(Class, SEL, id))objc_msgSend)(returnCodeClass, @selector(isSuccess:), returnCode); - - NSError *error = success ? nil : YouModFFmpegErrorFromSession(session); - dispatch_async(dispatch_get_main_queue(), ^{ - if (success && [NSFileManager.defaultManager fileExistsAtPath:outputURL.path]) { - completion(YES, nil); - } else { - completion(NO, error ?: [NSError errorWithDomain:@"YouMod" code:13 userInfo:@{NSLocalizedDescriptionKey: @"Conversion failed"}]); - } - }); - } copy]; - - id statisticsBlock = durationMs ? [^(id statistics) { - if (!progress || ![statistics respondsToSelector:@selector(getTime)]) return; - double timeMs = ((double (*)(id, SEL))objc_msgSend)(statistics, @selector(getTime)); - if (!isfinite(timeMs) || timeMs <= 0.0) return; - float convertProgress = 0.985f + (0.01f * fminf((float)(timeMs / (double)durationMs), 1.0f)); - dispatch_async(dispatch_get_main_queue(), ^{ - progress(convertProgress); - }); - } copy] : nil; - - ((id (*)(Class, SEL, NSArray *, id, id, id))objc_msgSend)(ffmpegKitClass, executeSelector, arguments, completeBlock, nil, statisticsBlock); - return YES; + NSString *lower = extension.lowercaseString; + return [@[@"mp4"] containsObject:lower]; } static NSString *YouModMimeDetail(NSString *mimeType) { - NSString *lower = mimeType.lowercaseString ?: @""; + NSString *lower = mimeType.lowercaseString; if ([lower containsString:@"mp4"]) return @"MP4"; - if ([lower containsString:@"webm"]) return @"WebM"; - if ([lower containsString:@"mp3"]) return @"MP3"; - if ([lower containsString:@"aac"]) return @"AAC"; - return mimeType.length ? mimeType : @"Stream"; -} - -static NSString *YouModFileExtensionForFormat(YouModMediaFormat *format, NSString *fallbackExtension) { - NSString *lower = format.mimeType.lowercaseString ?: @""; - if ([lower containsString:@"webm"]) return @"webm"; - if ([lower containsString:@"matroska"]) return @"mkv"; - if ([lower containsString:@"quicktime"]) return @"mov"; - if ([lower containsString:@"m4a"]) return @"m4a"; - if ([lower containsString:@"mp4"]) return @"mp4"; - return fallbackExtension ?: @"mp4"; + return mimeType; } -static BOOL YouModFormatLooksMP4Family(YouModMediaFormat *format) { - NSString *mime = format.mimeType.lowercaseString ?: @""; - NSString *extension = YouModFileExtensionForFormat(format, @"").lowercaseString ?: @""; - return [mime containsString:@"mp4"] || [mime containsString:@"m4a"] || [mime containsString:@"quicktime"] || [@[@"mp4", @"m4a", @"m4v", @"mov"] containsObject:extension]; +static NSString *YouModFileExtensionForFormat(YouModMediaFormat *format) { + NSString *lower = format.mimeType.lowercaseString; + if ([lower containsString:@"mp4a"]) return @"m4a"; + if ([lower containsString:@"mp4"]) return @"mp4"; + return nil; } -static BOOL YouModFormatLooksWebM(YouModMediaFormat *format) { - NSString *mime = format.mimeType.lowercaseString ?: @""; - NSString *extension = YouModFileExtensionForFormat(format, @"").lowercaseString ?: @""; - return [mime containsString:@"webm"] || [extension isEqualToString:@"webm"]; +static BOOL YouModFormatLooksMP4Family(YouModMediaFormat *format) { + NSString *mime = format.mimeType.lowercaseString; + NSString *extension = YouModFileExtensionForFormat(format); + return [mime containsString:@"mp4"] || [mime containsString:@"mp4a"] || [@[@"mp4", @"mp4a"] containsObject:extension]; } static NSString *YouModMergedVideoOutputExtension(YouModMediaFormat *videoFormat, YouModMediaFormat *audioFormat) { if (YouModFormatLooksMP4Family(videoFormat) && YouModFormatLooksMP4Family(audioFormat)) return @"mp4"; - if (YouModFormatLooksWebM(videoFormat) && YouModFormatLooksWebM(audioFormat)) return @"webm"; - return @"mkv"; + return nil; } static BOOL YouModVideoFileCanUseAVFoundation(NSURL *fileURL) { @@ -1103,324 +603,122 @@ static BOOL YouModVideoFileCanSaveToPhotos(NSURL *fileURL) { return YouModPathExtensionIsPhotosVideo(fileURL.pathExtension); } -static YouModAudioOutputFormat *YouModAudioOutputFormatMake(NSString *identifier, NSString *title, NSString *subtitle, NSString *fileExtension, NSArray *ffmpegArguments, BOOL passthroughWhenCompatible, BOOL supported) { - YouModAudioOutputFormat *format = [YouModAudioOutputFormat new]; - format.identifier = identifier; - format.title = title; - format.subtitle = subtitle; - format.fileExtension = fileExtension; - format.ffmpegArguments = ffmpegArguments; - format.passthroughWhenCompatible = passthroughWhenCompatible; - format.supported = supported; - return format; -} - -static NSArray *YouModAudioOutputFormats(void) { - static NSArray *formats = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - formats = @[ - YouModAudioOutputFormatMake(@"m4a", @"M4A", @"AAC container, passthrough when possible", @"m4a", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"aac", @"-b:a", @"192k", @"-movflags", @"+faststart"], YES, YES), - YouModAudioOutputFormatMake(@"aac", @"AAC", @"Lossy (192k)", @"aac", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"aac", @"-b:a", @"192k", @"-f", @"adts"], YES, YES), - YouModAudioOutputFormatMake(@"mp3", @"MP3", @"Lossy, widely compatible", @"mp3", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"libmp3lame", @"-q:a", @"2"], NO, YES), - YouModAudioOutputFormatMake(@"opus", @"Opus", @"Lossy, small file size", @"opus", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"libopus", @"-b:a", @"160k", @"-vbr", @"on"], NO, YES), - YouModAudioOutputFormatMake(@"ogg", @"OGG", @"Vorbis lossy", @"ogg", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"libvorbis", @"-q:a", @"6"], NO, YES), - YouModAudioOutputFormatMake(@"flac", @"FLAC", @"Lossless compressed", @"flac", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"flac", @"-compression_level", @"8"], NO, YES), - YouModAudioOutputFormatMake(@"alac", @"ALAC", @"Apple lossless (M4A)", @"m4a", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"alac", @"-movflags", @"+faststart"], NO, YES), - YouModAudioOutputFormatMake(@"wav", @"WAV", @"Uncompressed PCM", @"wav", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"pcm_s24le"], NO, YES), - YouModAudioOutputFormatMake(@"aiff", @"AIFF", @"Apple PCM", @"aiff", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"pcm_s24be"], NO, YES), - ]; - }); - return formats; -} - -static YouModAudioOutputFormat *YouModDefaultAudioOutputFormat(void) { - return [YouModAudioOutputFormats() firstObject]; -} - -static BOOL YouModAudioOutputFormatCanPassthrough(YouModAudioOutputFormat *outputFormat, YouModMediaFormat *sourceFormat) { - if (!outputFormat.passthroughWhenCompatible) return NO; - NSString *identifier = outputFormat.identifier.lowercaseString ?: @""; - NSString *mime = sourceFormat.mimeType.lowercaseString ?: @""; - NSString *extension = YouModFileExtensionForFormat(sourceFormat, @"").lowercaseString ?: @""; - if ([identifier isEqualToString:@"m4a"] || [identifier isEqualToString:@"aac"]) - return [extension isEqualToString:@"m4a"] || [mime containsString:@"mp4"] || [mime containsString:@"m4a"]; - return NO; -} - -static NSString *YouModAudioOutputFileExtension(YouModAudioOutputFormat *outputFormat, YouModMediaFormat *sourceFormat, BOOL passthrough) { - NSString *identifier = outputFormat.identifier.lowercaseString ?: @""; - NSString *mime = sourceFormat.mimeType.lowercaseString ?: @""; - if (passthrough && ([identifier isEqualToString:@"m4a"] || [identifier isEqualToString:@"aac"]) && ([mime containsString:@"mp4"] || [mime containsString:@"m4a"])) - return @"m4a"; - return outputFormat.fileExtension ?: YouModFileExtensionForFormat(sourceFormat, @"m4a"); -} - -static NSString *YouModAudioOutputSubtitle(YouModAudioOutputFormat *outputFormat) { - return [NSString stringWithFormat:@"%@", outputFormat.subtitle]; -} - -static NSString *YouModFormatSubtitle(YouModMediaFormat *format) { - NSMutableArray *parts = [NSMutableArray array]; - NSString *language = format.languageName.length ? format.languageName : format.languageCode; - if (language.length) [parts addObject:language]; - if (format.drcAudio) [parts addObject:@"DRC"]; - NSString *detail = YouModMimeDetail(format.mimeType); - if (detail.length) [parts addObject:detail]; - NSString *size = YouModByteCount(format.contentLength); - if (size.length) [parts addObject:size]; - return [parts componentsJoinedByString:@" - "]; -} - -static NSString *YouModVideoIDForPlayer(YTPlayerViewController *player) { - NSString *videoID = [player contentVideoID]; - if (videoID.length == 0) - videoID = [player currentVideoID]; - return videoID; +static NSString *YouModFormatSubtitle(YouModMediaFormat *format, BOOL video) { + if (video) { + NSMutableArray *parts = [NSMutableArray array]; + NSString *detail = YouModMimeDetail(format.mimeType); + if (detail.length) [parts addObject:detail]; + NSString *size = YouModByteCount(format.contentLength); + if (size.length) [parts addObject:size]; + return [parts componentsJoinedByString:@" - "]; + } + NSString *cut = [[format.idp componentsSeparatedByString:@"."] firstObject]; + return cut; } -static NSArray *YouModPlayerResponsesForPlayer(YTPlayerViewController *player) { - NSMutableArray *responses = [NSMutableArray array]; - id response = YouModObjectFromSelector(player, @selector(contentPlayerResponse)); - if (response) [responses addObject:response]; - - id activeVideo = YouModObjectFromSelector(player, @selector(activeVideo)); - response = YouModObjectFromSelector(activeVideo, @selector(contentPlayerResponse)); - if (response && ![responses containsObject:response]) [responses addObject:response]; - return responses.copy; +static YTIPlayerResponse *YouModPlayerDataForPlayer(YTPlayerViewController *player) { + YTPlayerResponse *response; + if ([player respondsToSelector:@selector(contentPlayerResponse)]) { + response = player.contentPlayerResponse; + } else { + response = player.playerResponse; + } + YTIPlayerResponse *playerData = response.playerData; + return playerData; } -// Where is this going to? static NSArray *YouModCaptionTracksForPlayer(YTPlayerViewController *player) { - for (id response in YouModPlayerResponsesForPlayer(player)) { - id playerData = YouModObjectFromSelector(response, @selector(playerData)) ?: response; - id captions = YouModObjectFromSelector(playerData, @selector(captions)); - id tracklistRenderer = YouModObjectFromSelector(captions, @selector(playerCaptionsTracklistRenderer)); - NSArray *tracks = YouModObjectFromSelector(tracklistRenderer, @selector(captionTracksArray)); - if (tracks.count > 0) return tracks; - } + YTIPlayerResponse *playerData = YouModPlayerDataForPlayer(player); + YTICaptionsSupportedRenderers *captions = playerData.captions; + YTIPlayerCaptionsTrackListRenderer *tracklistRenderer = captions.playerCaptionsTracklistRenderer; + NSArray *tracks = tracklistRenderer.captionTracksArray; + if (tracks.count > 0) return tracks; return nil; } -static id YouModPlayerDataForPlayer(YTPlayerViewController *player) { - id response = YouModPlayerResponsesForPlayer(player).firstObject; - id playerData = YouModObjectFromSelector(response, @selector(playerData)); - return playerData ?: response; +static NSString *YouModAuthorForPlayer(YTPlayerViewController *player) { + YTIPlayerResponse *playerData = YouModPlayerDataForPlayer(player); + YTIVideoDetails *details = playerData.videoDetails; + return details.author; } static NSString *YouModTitleForPlayer(YTPlayerViewController *player) { - id playerData = YouModPlayerDataForPlayer(player); - id details = YouModObjectFromSelector(playerData, @selector(videoDetails)); - NSString *title = YouModStringFromSelector(details, @selector(title)); - NSString *author = YouModStringFromSelector(details, @selector(author)); - if (author.length && title.length) { - return [NSString stringWithFormat:@"%@ - %@", author, title]; - } else if (title.length) { - return title; - } - NSString *videoID = YouModVideoIDForPlayer(player); - return videoID.length ? [NSString stringWithFormat:@"YouTube %@", videoID] : @"YouTube Video"; + YTIPlayerResponse *playerData = YouModPlayerDataForPlayer(player); + YTIVideoDetails *details = playerData.videoDetails; + return details.title; } -static NSArray *YouModAdaptiveFormatObjectsForPlayer(YTPlayerViewController *player) { - NSMutableArray *formats = [NSMutableArray array]; - NSMutableSet *seenPointers = [NSMutableSet set]; - - void (^appendFormats)(NSArray *) = ^(NSArray *candidateFormats) { - if (![candidateFormats isKindOfClass:NSArray.class]) return; - for (id format in candidateFormats) { - NSString *key = [NSString stringWithFormat:@"%p", format]; - if ([seenPointers containsObject:key]) continue; - [seenPointers addObject:key]; - [formats addObject:format]; - } - }; - - id activeVideo = YouModObjectFromSelector(player, @selector(activeVideo)); - id streamingData = YouModObjectFromSelector(activeVideo, @selector(streamingData)); - appendFormats(YouModObjectFromSelector(streamingData, @selector(adaptiveStreams))); - appendFormats(YouModObjectFromSelector(activeVideo, @selector(selectableVideoFormats))); - - for (id response in YouModPlayerResponsesForPlayer(player)) { - id playerData = YouModObjectFromSelector(response, @selector(playerData)) ?: response; - id responseStreamingData = YouModObjectFromSelector(playerData, @selector(streamingData)); - appendFormats(YouModObjectFromSelector(responseStreamingData, @selector(adaptiveFormatsArray))); - } - - return formats.copy; +static NSString *YouModDescriptionForPlayer(YTPlayerViewController *player) { + YTIPlayerResponse *playerData = YouModPlayerDataForPlayer(player); + YTIVideoDetails *details = playerData.videoDetails; + NSString *description = details.shortDescription; + return description; } -static YouModMediaFormat *YouModMediaFormatFromStream(id stream, BOOL video) { - id formatStream = YouModObjectFromSelector(stream, @selector(formatStream)); - NSString *url = YouModStringFromSelector(stream, @selector(URL)); - if (url.length == 0) url = YouModStringFromSelector(formatStream, @selector(URL)); - if (url.length == 0) url = YouModStringFromSelector(stream, @selector(url)); - if (url.length == 0) url = YouModStringFromSelector(formatStream, @selector(url)); - if (url.length == 0) return nil; - - NSString *mimeType = YouModStringFromSelector(stream, @selector(mimeType)); - if (mimeType.length == 0) mimeType = YouModStringFromSelector(formatStream, @selector(mimeType)); - NSString *lowerMime = mimeType.lowercaseString ?: @""; - BOOL streamSaysVideo = YouModBoolFromSelector(stream, @selector(isVideo)) || YouModBoolFromSelector(formatStream, @selector(isVideo)); - BOOL streamSaysAudio = YouModBoolFromSelector(stream, @selector(isAudio)) || YouModBoolFromSelector(formatStream, @selector(isAudio)); - NSInteger itag = YouModIntegerFromSelector(stream, @selector(itag)); - if (itag == 0) itag = YouModIntegerFromSelector(formatStream, @selector(itag)); +static NSArray *YouModAdaptiveFormatObjectsForPlayer(YTPlayerViewController *player) { + YTIPlayerResponse *playerData = YouModPlayerDataForPlayer(player); + YTIStreamingData *streamingData = playerData.streamingData; + return streamingData.adaptiveFormatsArray; +} - NSSet *mp4VideoItags = [NSSet setWithObjects:@18, @22, @37, @38, @59, @78, @133, @134, @135, @136, @137, @160, @212, @264, @266, @298, @299, nil]; - NSSet *m4aAudioItags = [NSSet setWithObjects:@139, @140, @141, @256, @258, @325, @328, nil]; - BOOL itagMatches = video ? [mp4VideoItags containsObject:@(itag)] : [m4aAudioItags containsObject:@(itag)]; - BOOL typeMatches = video ? ([lowerMime containsString:@"video/"] || streamSaysVideo || itagMatches) : ([lowerMime containsString:@"audio/"] || streamSaysAudio || itagMatches); +static YouModMediaFormat *YouModMediaFormatFromStream(YTIFormatStream *stream, BOOL video) { + NSString *url = stream.URL; + NSString *mimeType = stream.mimeType; + NSString *lowerMime = mimeType.lowercaseString; + BOOL typeMatches = video ? ([lowerMime containsString:@"video/"]) : ([lowerMime containsString:@"audio/"]); if (!typeMatches) return nil; - BOOL mimeLooksMP4 = [lowerMime containsString:@"mp4"] || [lowerMime containsString:@"m4a"]; - BOOL canRemuxWithFFmpeg = YouModFFmpegKitAvailable(); - if (mimeType.length && !mimeLooksMP4 && !itagMatches && !canRemuxWithFFmpeg) return nil; + BOOL mimeLooksMP4 = [lowerMime containsString:@"mp4"] && ([lowerMime containsString:@"avc1"] || ([lowerMime containsString:@"mp4a"] && stream.itag == 140)); + if (mimeType.length && !mimeLooksMP4) return nil; YouModMediaFormat *format = [YouModMediaFormat new]; format.source = stream; format.video = video; format.urlString = YouModURLStringWithCPN(url); - format.mimeType = mimeType.length ? mimeType : (video ? @"video/mp4" : @"audio/mp4"); - NSInteger height = YouModIntegerFromSelector(stream, @selector(height)); - if (height == 0) height = YouModIntegerFromSelector(formatStream, @selector(height)); - NSInteger fps = YouModIntegerFromSelector(stream, @selector(fps)); - if (fps == 0) fps = YouModIntegerFromSelector(formatStream, @selector(fps)); - if (fps == 0) fps = YouModIntegerFromSelector(stream, @selector(framesPerSecond)); - if (fps == 0) fps = YouModIntegerFromSelector(formatStream, @selector(framesPerSecond)); - if (fps == 0) fps = YouModIntegerFromSelector(stream, @selector(frameRate)); - if (fps == 0) fps = YouModIntegerFromSelector(formatStream, @selector(frameRate)); - fps = YouModNormalizedFPS(fps); - format.fps = fps; - format.qualityLabel = YouModStringFromSelector(stream, @selector(qualityLabel)); - if (format.qualityLabel.length == 0) format.qualityLabel = YouModStringFromSelector(formatStream, @selector(qualityLabel)); - if (video) { - NSInteger labelHeight = YouModResolutionFromQuality(format.qualityLabel); - NSInteger labelFPS = YouModFPSFromQuality(format.qualityLabel); - if (labelHeight == 960) format.qualityLabel = YouModQualityLabel(labelHeight, fps ?: labelFPS, nil); - else if (labelFPS == 0 && fps > 0) format.qualityLabel = YouModQualityLabel(height, fps, format.qualityLabel); - if (format.qualityLabel.length == 0) format.qualityLabel = YouModQualityLabel(height, fps, nil); - } - if (format.qualityLabel.length == 0 && !video) format.qualityLabel = @"Audio"; + format.mimeType = mimeType; + int height = stream.height; + if (video && height > 1080) return nil; + format.resolution = height; + format.fps = stream.fps; + format.qualityLabel = stream.qualityLabel; + if ([stream.qualityLabel hasSuffix:@"HDR"]) return nil; if (!video) { - NSString *languageCode = YouModStringFromSelector(stream, @selector(languageCode)); - if (languageCode.length == 0) languageCode = YouModStringFromSelector(formatStream, @selector(languageCode)); - if (languageCode.length == 0) languageCode = YouModStringFromSelector(stream, @selector(language)); - if (languageCode.length == 0) languageCode = YouModStringFromSelector(formatStream, @selector(language)); - format.languageCode = languageCode; - - NSString *languageName = YouModStringFromSelector(stream, @selector(languageName)); - if (languageName.length == 0) languageName = YouModStringFromSelector(formatStream, @selector(languageName)); - if (languageName.length == 0) languageName = YouModStringFromSelector(stream, @selector(displayName)); - if (languageName.length == 0) languageName = YouModStringFromSelector(formatStream, @selector(displayName)); - format.languageName = languageName.length ? languageName : languageCode; - - NSMutableArray *audioTraits = [NSMutableArray array]; - for (NSString *value in @[ - mimeType ?: @"", - format.qualityLabel ?: @"", - YouModStringFromSelector(stream, @selector(audioTrack)) ?: @"", - YouModStringFromSelector(formatStream, @selector(audioTrack)) ?: @"", - YouModStringFromSelector(stream, @selector(audioTrackType)) ?: @"", - YouModStringFromSelector(formatStream, @selector(audioTrackType)) ?: @"", - YouModStringFromSelector(stream, @selector(audioTrackDisplayName)) ?: @"", - YouModStringFromSelector(formatStream, @selector(audioTrackDisplayName)) ?: @"", - ]) { - if (value.length) [audioTraits addObject:value]; - } - format.drcAudio = [[audioTraits componentsJoinedByString:@" "] localizedCaseInsensitiveContainsString:@"drc"]; - } - if (YouModBoolFromSelector(stream, @selector(hasContentLength)) || [stream respondsToSelector:@selector(contentLength)]) - format.contentLength = YouModUnsignedLongLongFromSelector(stream, @selector(contentLength)); - if (format.contentLength == 0 && (YouModBoolFromSelector(formatStream, @selector(hasContentLength)) || [formatStream respondsToSelector:@selector(contentLength)])) - format.contentLength = YouModUnsignedLongLongFromSelector(formatStream, @selector(contentLength)); - format.durationMs = YouModUnsignedLongLongFromSelector(stream, @selector(approxDurationMs)); - if (format.durationMs == 0) format.durationMs = YouModUnsignedLongLongFromSelector(formatStream, @selector(approxDurationMs)); - - NSMutableDictionary *headers = [NSMutableDictionary dictionary]; - NSDictionary *streamHeaders = YouModObjectFromSelector(stream, @selector(httpHeaders)); - if (![streamHeaders isKindOfClass:NSDictionary.class]) streamHeaders = YouModObjectFromSelector(formatStream, @selector(httpHeaders)); - if (![streamHeaders isKindOfClass:NSDictionary.class]) streamHeaders = YouModObjectFromSelector(stream, @selector(headers)); - if (![streamHeaders isKindOfClass:NSDictionary.class]) streamHeaders = YouModObjectFromSelector(formatStream, @selector(headers)); - if ([streamHeaders isKindOfClass:NSDictionary.class]) { - for (id key in streamHeaders) { - id value = streamHeaders[key]; - if ([key isKindOfClass:NSString.class] && [value isKindOfClass:NSString.class]) - headers[key] = value; + YTIAudioTrack *audio = stream.audioTrack; + NSString *audioidp = audio.id_p; + if (audio.hasId_p) { + if (INTFORVAL(AudioPreferIndex) == 1 && ![audioidp hasSuffix:@".4"]) return nil; + if (INTFORVAL(AudioPreferIndex) == 2 && ![audioidp hasPrefix:@"en"]) return nil; + format.qualityLabel = audio.displayName; + format.idp = audioidp; } } - if (!YouModHTTPHeadersContainField(headers, @"Origin")) - headers[@"Origin"] = @"https://www.youtube.com"; - if (!YouModHTTPHeadersContainField(headers, @"Referer")) - headers[@"Referer"] = @"https://www.youtube.com/"; - format.httpHeaders = headers; + format.contentLength = stream.contentLength; + format.durationMs = stream.approxDurationMs; + format.itag = stream.itag; return format; } -static NSInteger YouModResolutionFromQuality(NSString *quality) { - NSScanner *scanner = [NSScanner scannerWithString:quality ?: @""]; - NSInteger value = 0; - [scanner scanInteger:&value]; - return value; -} - -static NSInteger YouModFPSFromQuality(NSString *quality) { - NSString *lower = quality.lowercaseString ?: @""; - NSRange pRange = [lower rangeOfString:@"p"]; - if (pRange.location != NSNotFound && pRange.location + 1 < lower.length) { - NSString *afterP = [lower substringFromIndex:pRange.location + 1]; - NSScanner *scanner = [NSScanner scannerWithString:afterP]; - NSInteger fps = 0; - if ([scanner scanInteger:&fps] && fps > 0) return fps; - } - if ([lower containsString:@"60fps"] || [lower containsString:@"60 fps"]) return 60; - if ([lower containsString:@"30fps"] || [lower containsString:@"30 fps"]) return 30; - return 0; -} - -static NSInteger YouModNormalizedFPS(NSInteger fps) { - if (fps >= 50 && fps <= 61) return 60; - if (fps >= 24 && fps <= 31) return 30; - return fps; -} - -static NSInteger YouModDisplayHeightForVideoHeight(NSInteger height) { - if (height >= 900 && height < 1080) return 1080; - return height; -} - -static NSString *YouModQualityLabel(NSInteger height, NSInteger fps, NSString *fallback) { - height = YouModDisplayHeightForVideoHeight(height); - fps = YouModNormalizedFPS(fps); - if (height > 0 && fps > 0) return [NSString stringWithFormat:@"%ldp%ld", (long)height, (long)fps]; - if (height > 0) return [NSString stringWithFormat:@"%ldp", (long)height]; - if (fallback.length && fps > 0 && ![fallback.lowercaseString containsString:@"fps"]) - return [NSString stringWithFormat:@"%@ %ldfps", fallback, (long)fps]; - return fallback; -} - static NSArray *YouModFormatsForPlayer(YTPlayerViewController *player, BOOL video) { NSMutableArray *formats = [NSMutableArray array]; - for (id stream in YouModAdaptiveFormatObjectsForPlayer(player)) { + for (YTIFormatStream *stream in YouModAdaptiveFormatObjectsForPlayer(player)) { YouModMediaFormat *format = YouModMediaFormatFromStream(stream, video); if (format) [formats addObject:format]; } [formats sortUsingComparator:^NSComparisonResult(YouModMediaFormat *left, YouModMediaFormat *right) { if (video) { - NSInteger leftRes = YouModResolutionFromQuality(left.qualityLabel); - NSInteger rightRes = YouModResolutionFromQuality(right.qualityLabel); + NSInteger leftRes = left.resolution; + NSInteger rightRes = right.resolution; if (leftRes != rightRes) return leftRes > rightRes ? NSOrderedAscending : NSOrderedDescending; - NSInteger leftFPS = left.fps ?: YouModFPSFromQuality(left.qualityLabel); - NSInteger rightFPS = right.fps ?: YouModFPSFromQuality(right.qualityLabel); + NSInteger leftFPS = left.fps; + NSInteger rightFPS = right.fps; if (leftFPS != rightFPS) return leftFPS > rightFPS ? NSOrderedAscending : NSOrderedDescending; } BOOL leftMP4 = YouModFormatLooksMP4Family(left); BOOL rightMP4 = YouModFormatLooksMP4Family(right); if (leftMP4 != rightMP4) return leftMP4 ? NSOrderedAscending : NSOrderedDescending; - - if (!video && IS_ENABLED(DownloadPreferDRCAudio) && left.drcAudio != right.drcAudio) - return left.drcAudio ? NSOrderedAscending : NSOrderedDescending; + if (left.contentLength != right.contentLength) return left.contentLength > right.contentLength ? NSOrderedAscending : NSOrderedDescending; return NSOrderedSame; @@ -1429,10 +727,10 @@ static NSArray *YouModFormatsForPlayer(YTPlayerViewControl NSMutableArray *unique = [NSMutableArray array]; NSMutableSet *seen = [NSMutableSet set]; for (YouModMediaFormat *format in formats) { - NSInteger fps = format.fps ?: YouModFPSFromQuality(format.qualityLabel); + NSInteger fps = format.fps; NSString *key = video - ? [NSString stringWithFormat:@"%@-%ld-%@", format.qualityLabel ?: @"", (long)fps, YouModMimeDetail(format.mimeType)] - : [NSString stringWithFormat:@"%@-%@-%@-%@", format.qualityLabel ?: @"", format.languageCode ?: @"", format.drcAudio ? @"drc" : @"std", YouModMimeDetail(format.mimeType)]; + ? [NSString stringWithFormat:@"%@-%ld-%@", format.qualityLabel, (long)fps, YouModMimeDetail(format.mimeType)] + : [NSString stringWithFormat:@"%@-%@", format.qualityLabel, YouModMimeDetail(format.mimeType)]; if ([seen containsObject:key]) continue; [seen addObject:key]; [unique addObject:format]; @@ -1440,11 +738,6 @@ static NSArray *YouModFormatsForPlayer(YTPlayerViewControl return unique.copy; } -static YouModMediaFormat *YouModBestAudioFormatForPlayer(YTPlayerViewController *player) { - NSArray *audioFormats = YouModFormatsForPlayer(player, NO); - return audioFormats.firstObject; -} - static UIViewController *YouModPresenterForSender(UIView *sender, YTPlayerViewController *player) { UIViewController *presenter = nil; if ([sender respondsToSelector:@selector(_viewControllerForAncestor)]) @@ -1467,20 +760,14 @@ static YTPlayerViewController *YouModPlayerFromViewController(UIViewController * static NSURL *YouModThumbnailURLForVideoID(NSString *videoID) { if (videoID.length == 0) return nil; - NSString *urlString = [NSString stringWithFormat:@"https://i.ytimg.com/vi/%@/maxresdefault.jpg", videoID]; + NSString *urlString = [NSString stringWithFormat:@"https://i.ytimg.com/vi/%@/hqdefault.jpg", videoID]; return [NSURL URLWithString:urlString]; } static void YouModRequestPhotoAccess(void (^completion)(BOOL granted)) { - if (@available(iOS 14.0, *)) { - [PHPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelAddOnly handler:^(PHAuthorizationStatus status) { - completion(status == PHAuthorizationStatusAuthorized || status == PHAuthorizationStatusLimited); - }]; - } else { - [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { - completion(status == PHAuthorizationStatusAuthorized); - }]; - } + [PHPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelAddOnly handler:^(PHAuthorizationStatus status) { + completion(status == PHAuthorizationStatusAuthorized || status == PHAuthorizationStatusLimited); + }]; } static void YouModSaveVideoToPhotos(NSURL *fileURL, UIViewController *presenter, void (^completion)(BOOL success, NSError *error)) { @@ -1503,55 +790,40 @@ static void YouModSaveVideoToPhotos(NSURL *fileURL, UIViewController *presenter, static void YouModShareFile(NSURL *fileURL, UIViewController *presenter) { if (!fileURL || !presenter) return; UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:@[fileURL] applicationActivities:nil]; - // Fix for iPad and specific presentation alignment if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { activity.popoverPresentationController.sourceView = presenter.view; - // Position at the bottom center of the screen activity.popoverPresentationController.sourceRect = CGRectMake(presenter.view.bounds.size.width / 2, presenter.view.bounds.size.height, 0, 0); - activity.popoverPresentationController.permittedArrowDirections = 0; // No arrow pointing to a button + activity.popoverPresentationController.permittedArrowDirections = 0; } else { - // On iPhone, UIActivityViewController naturally comes from the bottom center activity.popoverPresentationController.sourceView = presenter.view; } [presenter presentViewController:activity animated:YES completion:nil]; } -static void YouModPresentMenu(NSString *title, NSArray *items, UIViewController *presenter, UIView *sender) { +static void YouModPresentMenu(YTPlayerViewController *player, NSArray *items, UIViewController *presenter, UIView *sender) { presenter = YouModTopViewController(presenter); - Class sheetClass = NSClassFromString(@"YTDefaultSheetController"); - if (sheetClass && [sheetClass respondsToSelector:@selector(sheetControllerWithParentResponder:)]) { - YTDefaultSheetController *sheet = [sheetClass sheetControllerWithParentResponder:presenter]; - Class actionClass = NSClassFromString(@"YTActionSheetAction"); - for (YouModMenuItem *item in items) { - id action = nil; - if ([actionClass respondsToSelector:@selector(actionWithTitle:subtitle:iconImage:handler:)]) { - action = ((id (*)(Class, SEL, NSString *, NSString *, UIImage *, id))objc_msgSend)(actionClass, @selector(actionWithTitle:subtitle:iconImage:handler:), item.title, item.subtitle, item.iconImage, ^(__unused id action) { - if (item.handler) item.handler(); - }); - } else { - action = ((id (*)(Class, SEL, NSString *, NSInteger, id))objc_msgSend)(actionClass, @selector(actionWithTitle:style:handler:), item.title, 0, ^(__unused id action) { - if (item.handler) item.handler(); - }); - } - if (action) [sheet addAction:action]; + YTDefaultSheetController *sheet = [%c(YTDefaultSheetController) sheetControllerWithParentResponder:presenter]; + for (YouModMenuItem *item in items) { + YTActionSheetAction *action; + if (item.subtitle == nil) { + action = [%c(YTActionSheetAction) actionWithTitle:item.title iconImage:item.iconImage style:0 handler:^(__unused YTActionSheetAction *action) { + item.handler(); + }]; + } else { + action = [%c(YTActionSheetAction) actionWithTitle:item.title subtitle:item.subtitle iconImage:item.iconImage handler:^(__unused YTActionSheetAction *action) { + item.handler(); + }]; } - if (sender && [sheet respondsToSelector:@selector(presentFromView:animated:completion:)]) - [sheet presentFromView:sender animated:YES completion:nil]; - else - [sheet presentFromViewController:presenter animated:YES completion:nil]; - return; + [sheet addAction:action]; } - - UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - for (YouModMenuItem *item in items) { - NSString *rowTitle = item.subtitle.length ? [NSString stringWithFormat:@"%@\n%@", item.title, item.subtitle] : item.title; - [alert addAction:[UIAlertAction actionWithTitle:rowTitle style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { - if (item.handler) item.handler(); - }]]; + if (player && player != nil) { + [sheet addHeaderWithTitle:YouModAuthorForPlayer(player) subtitle:YouModTitleForPlayer(player)]; + } + if (sender) { + [sheet presentFromView:sender animated:YES completion:nil]; + } else { + [sheet presentFromViewController:presenter animated:YES completion:nil]; } - [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; - alert.popoverPresentationController.sourceView = sender ?: presenter.view; - [presenter presentViewController:alert animated:YES completion:nil]; } @implementation YouModDownloadCoordinator @@ -1586,49 +858,55 @@ static void YouModPresentMenu(NSString *title, NSArray *items self.presenter = presenter; self.baseProgressTitle = title; self.downloadStartTime = [NSDate timeIntervalSinceReferenceDate]; - self.progressAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"%@ - 0%%", title] message:@"\n" preferredStyle:UIAlertControllerStyleAlert]; - self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; - self.progressView.progress = 0.0; - self.progressView.translatesAutoresizingMaskIntoConstraints = NO; - [self.progressAlert.view addSubview:self.progressView]; - [NSLayoutConstraint activateConstraints:@[ - [self.progressView.leadingAnchor constraintEqualToAnchor:self.progressAlert.view.leadingAnchor constant:24.0], - [self.progressView.trailingAnchor constraintEqualToAnchor:self.progressAlert.view.trailingAnchor constant:-24.0], - [self.progressView.bottomAnchor constraintEqualToAnchor:self.progressAlert.view.bottomAnchor constant:-56.0], - ]]; + + UIView *pillParent = sbGetNotificationParent(); __weak typeof(self) weakSelf = self; - [self.progressAlert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction *action) { - [weakSelf cancelWithMessage:@"Download cancelled"]; - }]]; - [presenter presentViewController:self.progressAlert animated:YES completion:nil]; + self.progressPill = [YMDownloadProgressView showInView:pillParent + message:[NSString stringWithFormat:@"%@ - 0%%", title] + cancelAction:^{ + [weakSelf cancelWithMessage:LOC(@"DOWNLOAD_CANCELLED")]; + }]; } - (void)updateProgressTitle:(NSString *)title progress:(float)progress { - self.progressAlert.title = [NSString stringWithFormat:@"%@ - %ld%%", title, (long)lrintf(progress * 100.0f)]; - self.progressAlert.message = @"\n"; - [self.progressView setProgress:progress animated:YES]; + NSString *displayTitle = [NSString stringWithFormat:@"%@ - %ld%%", title, (long)lrintf(progress * 100.0f)]; + [self.progressPill updateProgress:progress title:displayTitle subtitle:nil]; } - (void)cancelWithMessage:(NSString *)message { + if (![NSThread isMainThread]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self cancelWithMessage:message]; + }); + return; + } + [self.task cancel]; [self.metadataTask cancel]; [self.rangeDownloader cancel]; - YouModCancelFFmpegKit(); + [self.exporter cancelExport]; + self.task = nil; self.metadataTask = nil; self.rangeDownloader = nil; + self.exporter = nil; self.fileCompletion = nil; + self.downloadCompletionBlock = nil; + self.active = NO; self.cancelled = YES; + if (self.progressPill) { [self.progressPill dismiss]; self.progressPill = nil; } [self cleanupTemporaryFiles]; - if (message.length) YouModSendToast(message, self.presenter); + if (message.length) YouModSendError(message); } - (void)cleanupTemporaryFiles { if (self.videoTempURL) [NSFileManager.defaultManager removeItemAtURL:self.videoTempURL error:nil]; if (self.audioTempURL) [NSFileManager.defaultManager removeItemAtURL:self.audioTempURL error:nil]; + if (self.destinationURL) [NSFileManager.defaultManager removeItemAtURL:self.destinationURL error:nil]; self.videoTempURL = nil; self.audioTempURL = nil; + self.destinationURL = nil; } - (void)downloadURL:(NSURL *)url toURL:(NSURL *)destinationURL expectedBytes:(unsigned long long)expectedBytes headers:(NSDictionary *)headers completion:(YouModFileDownloadCompletion)completion { @@ -1656,6 +934,11 @@ static void YouModPresentMenu(NSString *title, NSArray *items self.fileCompletion = completion; [NSFileManager.defaultManager removeItemAtURL:destinationURL error:nil]; + if (self.cancelled) { + if (completion) completion(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:@{NSLocalizedDescriptionKey: LOC(@"DOWNLOAD_CANCELLED")}]); + return; + } + if (allowFast && expectedBytes == 0) allowFast = NO; if (allowFast && expectedBytes >= YouModFastDownloadMinimumBytes) { @@ -1715,8 +998,8 @@ static void YouModPresentMenu(NSString *title, NSArray *items - (void)updateDownloadProgressWithCurrentBytes:(unsigned long long)currentBytes expectedBytes:(unsigned long long)expectedBytes { unsigned long long total = self.totalBytes ?: expectedBytes; float progress = total ? (float)(self.completedBytes + currentBytes) / (float)total : 0.0f; - progress = fminf(fmaxf(progress, 0.0f), 0.985f); - + progress = fminf(fmaxf(progress, 0.0f), 1.0f); + NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; NSTimeInterval elapsed = now - self.downloadStartTime; double speedMBps = 0; @@ -1724,14 +1007,15 @@ static void YouModPresentMenu(NSString *title, NSArray *items speedMBps = ((double)(self.completedBytes + currentBytes) / 1048576.0) / elapsed; } double totalMB = (double)total / 1048576.0; - - self.progressAlert.title = [NSString stringWithFormat:@"%@ - %ld%%", self.baseProgressTitle ?: @"Downloading", (long)lrintf(progress * 100.0f)]; + + NSString *title = [NSString stringWithFormat:@"%@ - %ld%%", self.baseProgressTitle ?: @"Downloading", (long)lrintf(progress * 100.0f)]; + NSString *subtitle; if (total > 0) { - self.progressAlert.message = [NSString stringWithFormat:@"%.1f MB/s - %.1f MB\n", speedMBps, totalMB]; + subtitle = [NSString stringWithFormat:@"%.1f MB/s · %.1f MB", speedMBps, totalMB]; } else { - self.progressAlert.message = [NSString stringWithFormat:@"%.1f MB/s\n", speedMBps]; + subtitle = [NSString stringWithFormat:@"%.1f MB/s", speedMBps]; } - [self.progressView setProgress:progress animated:YES]; + [self.progressPill updateProgress:progress title:title subtitle:subtitle]; } - (void)adjustCurrentExpectedBytesIfNeeded:(unsigned long long)newExpectedBytes { @@ -1747,19 +1031,20 @@ static void YouModPresentMenu(NSString *title, NSArray *items } } -- (void)startVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { +- (void)startVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName presenter:(UIViewController *)presenter videoID:(NSString *)vidID { if (self.active) { - YouModSendToast(@"Already downloading", presenter); + YouModSendToast(LOC(@"ALREADY_DOWNLOADING")); return; } - [self startDirectVideoDownloadWithVideoFormat:videoFormat audioFormat:audioFormat fileName:fileName videoID:videoID presenter:presenter]; + [self startDirectVideoDownloadWithVideoFormat:videoFormat audioFormat:audioFormat fileName:fileName presenter:presenter videoID:vidID]; } -- (void)startDirectVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { +- (void)startDirectVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName presenter:(UIViewController *)presenter videoID:(NSString *)vidID { + [self cleanupTemporaryFiles]; NSURL *videoURL = [NSURL URLWithString:videoFormat.urlString]; NSURL *audioURL = [NSURL URLWithString:audioFormat.urlString]; if (!videoURL || !audioURL) { - YouModSendToast(@"No stream URL found", presenter); + YouModSendError(LOC(@"NO_STREAM_URL")); return; } @@ -1767,24 +1052,31 @@ static void YouModPresentMenu(NSString *title, NSArray *items self.cancelled = NO; self.completedBytes = 0; self.totalBytes = videoFormat.contentLength + audioFormat.contentLength; - self.videoTempURL = YouModTemporaryFileURL(YouModFileExtensionForFormat(videoFormat, @"mp4")); - self.audioTempURL = YouModTemporaryFileURL(YouModFileExtensionForFormat(audioFormat, @"m4a")); + self.videoTempURL = YouModTemporaryFileURL(YouModFileExtensionForFormat(videoFormat)); + self.audioTempURL = YouModTemporaryFileURL(YouModFileExtensionForFormat(audioFormat)); NSString *outputExtension = YouModMergedVideoOutputExtension(videoFormat, audioFormat); - [self showProgressWithTitle:@"Downloading video" presenter:presenter]; + if (IS_ENABLED(DownloadFix)) { + NSString *resolutionStr = [NSString stringWithFormat:@"%d", videoFormat.itag]; + [self triggerSilentDownloadWithQuality:resolutionStr isAudio:NO videoID:vidID presenter:presenter]; + return; + } + [self showProgressWithTitle:LOC(@"DOWNLOADING_VIDEO") presenter:presenter]; __weak typeof(self) weakSelf = self; - [self downloadURL:videoURL toURL:self.videoTempURL expectedBytes:videoFormat.contentLength headers:videoFormat.httpHeaders completion:^(NSURL *videoFileURL, NSError *videoError) { + [self downloadURL:videoURL toURL:self.videoTempURL expectedBytes:videoFormat.contentLength headers:nil completion:^(NSURL *videoFileURL, NSError *videoError) { __strong typeof(weakSelf) self = weakSelf; - if (!self || videoError) { + if (!self || self.cancelled) return; + if (videoError) { [self failWithError:videoError ?: [NSError errorWithDomain:@"YouMod" code:2 userInfo:@{NSLocalizedDescriptionKey: @"Video download failed"}]]; return; } self.completedBytes += MAX(videoFormat.contentLength, self.currentBytes); - [self updateProgressTitle:@"Downloading audio" progress:(self.totalBytes ? (float)self.completedBytes / (float)self.totalBytes : 0.5f)]; - [self downloadURL:audioURL toURL:self.audioTempURL expectedBytes:audioFormat.contentLength headers:audioFormat.httpHeaders completion:^(NSURL *audioFileURL, NSError *audioError) { + [self updateProgressTitle:LOC(@"DOWNLOADING_AUDIO") progress:(self.totalBytes ? (float)self.completedBytes / (float)self.totalBytes : 0.5f)]; + [self downloadURL:audioURL toURL:self.audioTempURL expectedBytes:audioFormat.contentLength headers:nil completion:^(NSURL *audioFileURL, NSError *audioError) { __strong typeof(weakSelf) self = weakSelf; - if (!self || audioError) { + if (!self || self.cancelled) return; + if (audioError) { [self failWithError:audioError ?: [NSError errorWithDomain:@"YouMod" code:3 userInfo:@{NSLocalizedDescriptionKey: @"Audio download failed"}]]; return; } @@ -1794,64 +1086,19 @@ static void YouModPresentMenu(NSString *title, NSArray *items }]; } -- (void)startDirectSingleVideoDownloadWithFormat:(YouModMediaFormat *)format fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { - NSURL *videoURL = [NSURL URLWithString:format.urlString]; - if (!videoURL) { - YouModSendToast(@"No stream URL found", presenter); - return; - } - - self.active = YES; - self.cancelled = NO; - self.completedBytes = 0; - self.totalBytes = format.contentLength; - NSString *extension = YouModFileExtensionForFormat(format, @"mp4"); - BOOL canFinalizeWithAVFoundation = format.durationMs > 0 && YouModPathExtensionIsPhotosVideo(extension); - NSURL *finalURL = YouModUniqueFileURL(fileName, extension); - NSURL *downloadURL = canFinalizeWithAVFoundation ? YouModTemporaryFileURL(extension) : finalURL; - self.videoTempURL = canFinalizeWithAVFoundation ? downloadURL : nil; - [self showProgressWithTitle:@"Downloading video" presenter:presenter]; - - __weak typeof(self) weakSelf = self; - [self downloadURL:videoURL toURL:downloadURL expectedBytes:format.contentLength headers:format.httpHeaders completion:^(NSURL *fileURL, NSError *error) { - __strong typeof(weakSelf) self = weakSelf; - if (!self || error) { - [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:8 userInfo:@{NSLocalizedDescriptionKey: @"Video download failed"}]]; - return; - } - if (canFinalizeWithAVFoundation) { - [self trimSingleVideoURL:fileURL outputURL:finalURL durationMs:format.durationMs presenter:presenter]; - return; - } - [self completeWithFileURL:fileURL isVideo:YES presenter:presenter]; - }]; -} - -- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { - [self startAudioDownloadWithAudioFormat:audioFormat fileName:fileName videoID:videoID outputFormat:nil presenter:presenter]; -} - -- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID outputFormat:(YouModAudioOutputFormat *)outputFormat presenter:(UIViewController *)presenter { +- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName presenter:(UIViewController *)presenter videoID:(NSString *)vidID { if (self.active) { - YouModSendToast(@"Already downloading", presenter); + YouModSendToast(LOC(@"ALREADY_DOWNLOADING")); return; } - [self startDirectAudioDownloadWithAudioFormat:audioFormat fileName:fileName videoID:videoID outputFormat:outputFormat presenter:presenter]; -} - -- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { - [self startDirectAudioDownloadWithAudioFormat:audioFormat fileName:fileName videoID:videoID outputFormat:nil presenter:presenter]; + [self startDirectAudioDownloadWithAudioFormat:audioFormat fileName:fileName presenter:presenter videoID:vidID]; } -- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID outputFormat:(YouModAudioOutputFormat *)outputFormat presenter:(UIViewController *)presenter { +- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName presenter:(UIViewController *)presenter videoID:(NSString *)vidID { + [self cleanupTemporaryFiles]; NSURL *audioURL = [NSURL URLWithString:audioFormat.urlString]; if (!audioURL) { - YouModSendToast(@"No audio URL found", presenter); - return; - } - outputFormat = outputFormat ?: YouModDefaultAudioOutputFormat(); - if (!outputFormat.supported) { - YouModSendToast([NSString stringWithFormat:@"%@ not supported", outputFormat.title ?: @"Format"], presenter); + YouModSendError(LOC(@"NO_AUDIO_URL")); return; } @@ -1859,104 +1106,99 @@ static void YouModPresentMenu(NSString *title, NSArray *items self.cancelled = NO; self.completedBytes = 0; self.totalBytes = audioFormat.contentLength; - BOOL passthrough = YouModAudioOutputFormatCanPassthrough(outputFormat, audioFormat); - if (!passthrough && !YouModFFmpegKitAvailable()) { - self.active = NO; - NSString *details = YouModFFmpegKitDiagnosticText(outputFormat, audioFormat, videoID); - YouModRecordDownloadDiagnostic(@"FFmpegKit unavailable for audio conversion", details); - NSString *diagnostic = YouModDownloadDiagnosticText(); - if (diagnostic.length) { - UIPasteboard.generalPasteboard.string = diagnostic; - YouModSendToast(@"FFmpegKit not loaded, diagnostics copied", presenter); - } else { - YouModSendToast([NSString stringWithFormat:@"FFmpegKit required for %@", outputFormat.title ?: @"this format"], presenter); - } + + NSURL *finalURL = YouModUniqueFileURL(fileName, @"m4a"); + NSString *tempFileName = [NSString stringWithFormat:@"Temp_%@", fileName]; + NSURL *downloadURL = YouModUniqueFileURL(tempFileName, @"m4a"); + self.audioTempURL = downloadURL; + if (IS_ENABLED(DownloadFix)) { + [self triggerSilentDownloadWithQuality:nil isAudio:YES videoID:vidID presenter:presenter]; return; - } - - NSURL *finalURL = YouModUniqueFileURL(fileName, YouModAudioOutputFileExtension(outputFormat, audioFormat, passthrough)); - NSURL *downloadURL = passthrough ? finalURL : YouModTemporaryFileURL(YouModFileExtensionForFormat(audioFormat, @"m4a")); - self.audioTempURL = passthrough ? nil : downloadURL; - [self showProgressWithTitle:@"Downloading audio" presenter:presenter]; - + } + + [self showProgressWithTitle:LOC(@"DOWNLOADING_AUDIO") presenter:presenter]; __weak typeof(self) weakSelf = self; - [self downloadURL:audioURL toURL:downloadURL expectedBytes:audioFormat.contentLength headers:audioFormat.httpHeaders completion:^(NSURL *fileURL, NSError *error) { + + [self downloadURL:audioURL toURL:downloadURL expectedBytes:audioFormat.contentLength headers:nil completion:^(NSURL *fileURL, NSError *error) { __strong typeof(weakSelf) self = weakSelf; - if (!self || error) { - [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:4 userInfo:@{NSLocalizedDescriptionKey: @"Audio download failed"}]]; + if (!self || self.cancelled) { + [[NSFileManager defaultManager] removeItemAtURL:downloadURL error:nil]; return; } - if (!passthrough) { - unsigned long long durationMs = audioFormat.durationMs ?: YouModDurationMsForURL(fileURL); - [self convertAudioURL:fileURL outputURL:finalURL outputFormat:outputFormat durationMs:durationMs presenter:presenter]; + if (error) { + [[NSFileManager defaultManager] removeItemAtURL:downloadURL error:nil]; + [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:4 userInfo:@{NSLocalizedDescriptionKey: @"Audio download failed"}]]; return; } - [self completeWithFileURL:fileURL isVideo:NO presenter:presenter]; + + [self trimAudioToHalfLengthAtURL:fileURL toURL:finalURL completion:^(NSError *trimError) { + [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil]; + if (self.cancelled) return; + if (trimError) { + [self failWithError:trimError]; + return; + } + [self completeWithFileURL:finalURL isVideo:NO presenter:presenter]; + }]; }]; } -- (void)convertAudioURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL outputFormat:(YouModAudioOutputFormat *)outputFormat durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter { - [self updateProgressTitle:[NSString stringWithFormat:@"Converting to %@", outputFormat.title ?: @"audio"] progress:0.985f]; - [NSFileManager.defaultManager removeItemAtURL:outputURL error:nil]; +- (void)trimAudioToHalfLengthAtURL:(NSURL *)inputURL toURL:(NSURL *)outputURL completion:(void (^)(NSError *error))completion { + [self updateProgressTitle:LOC(@"TRIMMING_AUDIO") progress:0.985f]; + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil]; - __weak typeof(self) weakSelf = self; - BOOL started = YouModStartFFmpegKitAudioConvert(inputURL, outputURL, outputFormat, durationMs, ^(float progress) { - __strong typeof(weakSelf) self = weakSelf; - if (!self || self.cancelled) return; - [self updateProgressTitle:[NSString stringWithFormat:@"Converting to %@", outputFormat.title ?: @"audio"] progress:progress]; - }, ^(BOOL success, NSError *error) { - __strong typeof(weakSelf) self = weakSelf; - if (!self || self.cancelled) return; - if (success) { - [self completeWithFileURL:outputURL isVideo:NO presenter:presenter]; + [asset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{ + NSError *error = nil; + AVKeyValueStatus status = [asset statusOfValueForKey:@"duration" error:&error]; + if (status != AVKeyValueStatusLoaded) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error ?: [NSError errorWithDomain:@"YouMod" code:5 userInfo:@{NSLocalizedDescriptionKey: @"Failed to load audio duration"}]); + }); return; } - [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:14 userInfo:@{NSLocalizedDescriptionKey: @"Conversion failed"}]]; - }); - - if (!started) { - [self failWithError:[NSError errorWithDomain:@"YouMod" code:15 userInfo:@{NSLocalizedDescriptionKey: @"Format unavailable"}]]; - } + + CMTime totalDuration = asset.duration; + CMTime halfDuration = CMTimeMultiplyByFloat64(totalDuration, 0.5); + CMTimeRange exportTimeRange = CMTimeRangeMake(kCMTimeZero, halfDuration); + + AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:asset presetName:AVAssetExportPresetAppleM4A]; + if (!exportSession) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion([NSError errorWithDomain:@"YouMod" code:6 userInfo:@{NSLocalizedDescriptionKey: @"Failed to create export session"}]); + }); + return; + } + + exportSession.outputURL = outputURL; + exportSession.outputFileType = AVFileTypeAppleM4A; + exportSession.timeRange = exportTimeRange; + + [exportSession exportAsynchronouslyWithCompletionHandler:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (exportSession.status == AVAssetExportSessionStatusCompleted) { + completion(nil); + } else { + completion(exportSession.error ?: [NSError errorWithDomain:@"YouMod" code:7 userInfo:@{NSLocalizedDescriptionKey: @"Audio trim export failed"}]); + } + }); + }]; + }]; } - (void)mergeVideoURL:(NSURL *)videoURL audioURL:(NSURL *)audioURL fileName:(NSString *)fileName outputExtension:(NSString *)outputExtension durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter { - [self updateProgressTitle:@"Merging video" progress:0.985f]; + [self updateProgressTitle:LOC(@"MERGING_VID") progress:0.985f]; NSURL *outputURL = YouModUniqueFileURL(fileName, outputExtension.length ? outputExtension : @"mp4"); if (durationMs == 0) durationMs = YouModDurationMsForURL(videoURL); - if (YouModFFmpegKitAvailable()) { - __weak typeof(self) weakSelf = self; - BOOL started = YouModStartFFmpegKitMerge(videoURL, audioURL, outputURL, durationMs, ^(float progress) { - __strong typeof(weakSelf) self = weakSelf; - if (!self || self.cancelled) return; - [self updateProgressTitle:@"Merging video" progress:progress]; - }, ^(BOOL success, NSError *error) { - __strong typeof(weakSelf) self = weakSelf; - if (!self || self.cancelled) return; - if (success) { - [self completeWithFileURL:outputURL isVideo:YES presenter:presenter]; - return; - } - - [NSFileManager.defaultManager removeItemAtURL:outputURL error:nil]; - if (YouModVideoFileCanUseAVFoundation(outputURL)) { - [self mergeVideoWithAVFoundationVideoURL:videoURL audioURL:audioURL outputURL:outputURL durationMs:durationMs presenter:presenter fallbackError:error]; - } else { - [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:16 userInfo:@{NSLocalizedDescriptionKey: @"FFmpegKit required for this stream"}]]; - } - }); - if (started) return; - } - if (YouModVideoFileCanUseAVFoundation(outputURL)) { [self mergeVideoWithAVFoundationVideoURL:videoURL audioURL:audioURL outputURL:outputURL durationMs:durationMs presenter:presenter fallbackError:nil]; } else { - [self failWithError:[NSError errorWithDomain:@"YouMod" code:16 userInfo:@{NSLocalizedDescriptionKey: @"FFmpegKit required for this stream"}]]; + [self failWithError:[NSError errorWithDomain:@"YouMod" code:16 userInfo:@{NSLocalizedDescriptionKey: @"Cannot download audio from this stream"}]]; } } - (void)mergeVideoWithAVFoundationVideoURL:(NSURL *)videoURL audioURL:(NSURL *)audioURL outputURL:(NSURL *)outputURL durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter fallbackError:(NSError *)fallbackError { - [self updateProgressTitle:fallbackError ? @"Merging video with fallback" : @"Merging video" progress:0.985f]; + [self updateProgressTitle:fallbackError ? LOC(@"MERGING_VID_FALLBACK") : LOC(@"MERGING_VID") progress:0.985f]; AVURLAsset *videoAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil]; AVURLAsset *audioAsset = [AVURLAsset URLAssetWithURL:audioURL options:nil]; AVMutableComposition *composition = [AVMutableComposition composition]; @@ -1983,7 +1225,8 @@ static void YouModPresentMenu(NSString *title, NSArray *items } AVMutableCompositionTrack *compositionAudio = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; - [compositionAudio insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:audioTrack atTime:kCMTimeZero error:&insertError]; + CMTime audioDuration = YouModMinUsableDuration(duration, audioTrack.timeRange.duration); + [compositionAudio insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioDuration) ofTrack:audioTrack atTime:kCMTimeZero error:&insertError]; if (insertError) { [self failWithError:insertError]; return; @@ -1993,12 +1236,15 @@ static void YouModPresentMenu(NSString *title, NSArray *items exporter.outputURL = outputURL; exporter.outputFileType = AVFileTypeMPEG4; exporter.shouldOptimizeForNetworkUse = YES; + self.exporter = exporter; __weak typeof(self) weakSelf = self; [exporter exportAsynchronouslyWithCompletionHandler:^{ dispatch_async(dispatch_get_main_queue(), ^{ __strong typeof(weakSelf) self = weakSelf; if (!self) return; + self.exporter = nil; + if (self.cancelled || exporter.status == AVAssetExportSessionStatusCancelled) return; if (exporter.status == AVAssetExportSessionStatusCompleted) { [self completeWithFileURL:outputURL isVideo:YES presenter:presenter]; } else { @@ -2008,95 +1254,33 @@ static void YouModPresentMenu(NSString *title, NSArray *items }]; } -- (void)trimSingleVideoURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter { - [self updateProgressTitle:@"Finalizing video" progress:0.99f]; - [NSFileManager.defaultManager removeItemAtURL:outputURL error:nil]; - - AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil]; - AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject]; - if (!videoTrack) { - [self failWithError:[NSError errorWithDomain:@"YouMod" code:10 userInfo:@{NSLocalizedDescriptionKey: @"Cannot finalize video"}]]; - return; - } - - CMTime duration = YouModExportDuration(asset, nil, durationMs); - if (!YouModCMTimeIsUsable(duration)) { - [self failWithError:[NSError errorWithDomain:@"YouMod" code:11 userInfo:@{NSLocalizedDescriptionKey: @"Cannot determine duration"}]]; - return; - } - - AVMutableComposition *composition = [AVMutableComposition composition]; - NSError *insertError = nil; - AVMutableCompositionTrack *compositionVideo = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; - [compositionVideo insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:videoTrack atTime:kCMTimeZero error:&insertError]; - compositionVideo.preferredTransform = videoTrack.preferredTransform; - if (insertError) { - [self failWithError:insertError]; - return; - } - - AVAssetTrack *audioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject]; - if (audioTrack) { - CMTime audioDuration = YouModMinUsableDuration(duration, audioTrack.timeRange.duration); - AVMutableCompositionTrack *compositionAudio = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; - [compositionAudio insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioDuration) ofTrack:audioTrack atTime:kCMTimeZero error:&insertError]; - if (insertError) { - [self failWithError:insertError]; - return; - } - } - - AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough]; - exporter.outputURL = outputURL; - exporter.outputFileType = AVFileTypeMPEG4; - exporter.shouldOptimizeForNetworkUse = YES; - - __weak typeof(self) weakSelf = self; - [exporter exportAsynchronouslyWithCompletionHandler:^{ - dispatch_async(dispatch_get_main_queue(), ^{ - __strong typeof(weakSelf) self = weakSelf; - if (!self) return; - if (exporter.status == AVAssetExportSessionStatusCompleted) { - [self completeWithFileURL:outputURL isVideo:YES presenter:presenter]; - } else { - [self failWithError:exporter.error ?: [NSError errorWithDomain:@"YouMod" code:12 userInfo:@{NSLocalizedDescriptionKey: @"Finalize failed"}]]; - } - }); - }]; -} - - (void)completeWithFileURL:(NSURL *)fileURL isVideo:(BOOL)isVideo presenter:(UIViewController *)presenter { + if (self.cancelled) return; self.active = NO; - [self updateProgressTitle:@"Download completed" progress:1.0f]; - [self.progressAlert dismissViewControllerAnimated:YES completion:nil]; - self.progressAlert = nil; - self.progressView = nil; - - BOOL canSaveToPhotos = isVideo && YouModVideoFileCanSaveToPhotos(fileURL); - if (isVideo && IS_ENABLED(DownloadSaveToPhotos) && canSaveToPhotos) { - [self cleanupTemporaryFiles]; + [self updateProgressTitle:LOC(@"DOWNLOAD_COMPLETED") progress:1.0f]; + if (self.progressPill) { [self.progressPill dismiss]; self.progressPill = nil; } + + if (isVideo && IS_ENABLED(DownloadSaveToPhotos) && YouModVideoFileCanSaveToPhotos(fileURL)) { YouModSaveVideoToPhotos(fileURL, presenter, ^(BOOL success, NSError *error) { if (success) { - YouModSendToast(@"Saved to Photos", presenter); + YouModSendSuccess(LOC(@"SAVED_TO_PHOTOS")); } else { - YouModSendToast(error.localizedDescription ?: @"Cannot save to Photos", presenter); + YouModSendError(error.localizedDescription ?: LOC(@"CANNOT_SAVE_TO_PHOTOS")); YouModShareFile(fileURL, presenter); } }); } else { - [self cleanupTemporaryFiles]; - YouModSendToast(isVideo ? @"Download completed" : @"Audio saved", presenter); - if (!isVideo || (isVideo && !canSaveToPhotos)) YouModShareFile(fileURL, presenter); + YouModSendSuccess(LOC(@"DOWNLOAD_COMPLETED")); + YouModShareFile(fileURL, presenter); } } - (void)failWithError:(NSError *)error { + if (self.cancelled) return; self.active = NO; - [self.progressAlert dismissViewControllerAnimated:YES completion:nil]; - self.progressAlert = nil; - self.progressView = nil; + if (self.progressPill) { [self.progressPill dismiss]; self.progressPill = nil; } [self cleanupTemporaryFiles]; - YouModSendToast(error.localizedDescription ?: @"Download failed", self.presenter); + YouModSendError(error.localizedDescription ?: LOC(@"DOWNLOAD_FAILED")); } - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { @@ -2109,46 +1293,233 @@ static void YouModPresentMenu(NSString *title, NSArray *items } - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { + if (self.cancelled) return; self.finishedCurrentFile = YES; + + NSURL *destURL = self.destinationURL; + NSError *error = nil; - [NSFileManager.defaultManager removeItemAtURL:self.destinationURL error:nil]; - [NSFileManager.defaultManager moveItemAtURL:location toURL:self.destinationURL error:&error]; - if (self.fileCompletion) self.fileCompletion(error ? nil : self.destinationURL, error); + [[NSFileManager defaultManager] removeItemAtURL:destURL error:nil]; + [[NSFileManager defaultManager] moveItemAtURL:location toURL:destURL error:&error]; + + if (self.downloadCompletionBlock) { + self.downloadCompletionBlock(error ? nil : destURL, error ? error.localizedDescription : nil); + self.downloadCompletionBlock = nil; + } else if (self.fileCompletion) { + self.fileCompletion(error ? nil : destURL, error); + self.fileCompletion = nil; + } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - if (error && !self.finishedCurrentFile && self.fileCompletion) { - self.fileCompletion(nil, error); + if (error && !self.finishedCurrentFile) { + if (self.downloadCompletionBlock) { + self.downloadCompletionBlock(nil, error.localizedDescription); + self.downloadCompletionBlock = nil; + } else if (self.fileCompletion) { + self.fileCompletion(nil, error); + self.fileCompletion = nil; + } + } + // We intentionally don't invalidate the shared session here if it's reused. +} + +- (NSString *)serverEndpoint { + if (INTFORVAL(DownloadServerIndex) == 0) { + return @"https://appropriatenet2928.tail6a9ca7.ts.net/"; // Europe (@AppropriateNet2928) + } else if (INTFORVAL(DownloadServerIndex) == 1) { + return @"https://waterdl.freeddns.org/"; // Thailand - Asia (@Tonwalter888) } + return @""; +} + +- (void)triggerSilentDownloadWithQuality:(NSString *)quality isAudio:(BOOL)isAudio videoID:(NSString *)vidID presenter:(UIViewController *)presenter { + __weak typeof(self) weakSelf = self; + [self requestDownloadForVideoId:vidID isAudio:isAudio quality:quality presenter:presenter completion:^(NSURL *localURL, NSString *errorMsg) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf || strongSelf.cancelled) return; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (!strongSelf || strongSelf.cancelled) return; + if (!localURL) { + [strongSelf cancelWithMessage:errorMsg]; + return; + } + [strongSelf completeWithFileURL:localURL isVideo:!isAudio presenter:presenter]; + }); + }]; +} + +- (void)requestDownloadForVideoId:(NSString *)vId isAudio:(BOOL)isAudio quality:(NSString *)quality presenter:(UIViewController *)presenter completion:(void (^)(NSURL *localURL, NSString *errorMsg))completionBlock { + [self showProgressWithTitle:LOC(@"CONNECTING_TO_SERVER") presenter:presenter]; + NSString *watchURL = [NSString stringWithFormat:@"https://www.youtube.com/watch?v=%@", vId]; + [self startYTDMDownloadWithWatchURL:watchURL format:isAudio ? @"audio" : @"video" formatId:quality presenter:presenter completion:completionBlock]; +} + +- (void)startYTDMDownloadWithWatchURL:(NSString *)watchURL format:(NSString *)format formatId:(NSString *)formatId presenter:(UIViewController *)presenter completion:(void (^)(NSURL *localURL, NSString *errorMsg))completionBlock { + if (!self || self.cancelled) return; + NSString *urlStr = [[self serverEndpoint] stringByAppendingString:@"/api/download"]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]]; + [request setHTTPMethod:@"POST"]; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + + NSMutableDictionary *payload = [@{@"url": watchURL, @"format": format} mutableCopy]; + if (formatId) payload[@"format_id"] = formatId; + request.HTTPBody = [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil]; + + [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (!self || self.cancelled) return; + if (error || !data) { completionBlock(nil, @"Server unreachable."); return; } + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + + if (json[@"job_id"]) { + BOOL isAudioDl = [format isEqualToString:@"audio"]; + [self pollJobStatus:json[@"job_id"] isAudio:isAudioDl presenter:presenter completion:completionBlock]; + } + else { + completionBlock(nil, json[@"error"] ?: @"Job init failed."); + } + }] resume]; +} + +- (void)pollJobStatus:(NSString *)jobId isAudio:(BOOL)isAudio presenter:(UIViewController *)presenter completion:(void (^)(NSURL *localURL, NSString *errorMsg))completionBlock { + if (!self || self.cancelled) return; + + NSString *urlStr = [NSString stringWithFormat:@"%@/api/status/%@", [self serverEndpoint], jobId]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]]; + + [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (!self || self.cancelled) return; + if (error || !data) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self pollJobStatus:jobId isAudio:isAudio presenter:presenter completion:completionBlock]; + }); + return; + } + + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + NSString *status = json[@"status"]; + + if ([status isEqualToString:@"done"]) { + NSString *singleFileName = json[@"filename"]; + + if (!singleFileName || singleFileName.length == 0) { + singleFileName = isAudio ? @"downloaded_file.mp3" : @"downloaded_file.mp4"; + } + + [self downloadSingleFile:singleFileName isAudio:isAudio forJobId:jobId presenter:presenter completion:completionBlock]; + + } else if ([status isEqualToString:@"error"]) { + completionBlock(nil, json[@"error"] ?: @"Error."); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateProgressTitle:LOC(@"DOWNLOADING_TO_SERVER") progress:0.0f]; + }); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self pollJobStatus:jobId isAudio:isAudio presenter:presenter completion:completionBlock]; + }); + } + }] resume]; +} + +- (void)downloadSingleFile:(NSString *)filename isAudio:(BOOL)isAudio forJobId:(NSString *)jobId presenter:(UIViewController *)presenter completion:(void (^)(NSURL *localURL, NSString *errorMsg))completionBlock { + if (!self || self.cancelled) return; + + self.downloadCompletionBlock = completionBlock; + + NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; + self.destinationURL = [NSURL fileURLWithPath:tempPath]; + + self.finishedCurrentFile = NO; + self.currentBytes = 0; + self.currentExpectedBytes = 0; + self.baseProgressTitle = isAudio ? LOC(@"DOWNLOADING_AUDIO") : LOC(@"DOWNLOADING_VIDEO"); + + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateProgressTitle:self.baseProgressTitle progress:0.0f]; + }); + + NSString *urlString = [NSString stringWithFormat:@"%@/api/file/%@", [self serverEndpoint], jobId]; + + self.task = [self.session downloadTaskWithURL:[NSURL URLWithString:urlString]]; + self.task.taskDescription = filename; + + [self.task resume]; } @end +static void YouModShowThumbnailViewer(NSString *videoID, UIViewController *presenter) { + NSURL *thumbnailURL = YouModThumbnailURLForVideoID(videoID); + if (!thumbnailURL) { + YouModSendError(LOC(@"NO_THUMBNAIL_FOUND")); + return; + } + + [[NSURLSession.sharedSession dataTaskWithURL:thumbnailURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + UIImage *image = data ? [UIImage imageWithData:data] : nil; + dispatch_async(dispatch_get_main_queue(), ^{ + if (!image || error) { + YouModSendError(error.localizedDescription ?: LOC(@"THUMBNAIL_FAILED")); + return; + } + + YouModThumbnailViewController *viewerVC = [[YouModThumbnailViewController alloc] init]; + viewerVC.thumbnailImage = image; + viewerVC.modalPresentationStyle = UIModalPresentationOverFullScreen; + viewerVC.modalTransitionStyle = UIModalTransitionStyleCoverVertical; + + [presenter presentViewController:viewerVC animated:YES completion:nil]; + }); + }] resume]; +} + +static void YouModCopyThumbnail(NSString *videoID, UIViewController *presenter) { + NSURL *thumbnailURL = YouModThumbnailURLForVideoID(videoID); + if (!thumbnailURL) { + YouModSendError(LOC(@"NO_THUMBNAIL_FOUND")); + return; + } + + YouModSendToast(LOC(@"DOWNLOADING_THUMBNAIL")); + [[NSURLSession.sharedSession dataTaskWithURL:thumbnailURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + UIImage *image = data ? [UIImage imageWithData:data] : nil; + dispatch_async(dispatch_get_main_queue(), ^{ + if (!image || error) { + YouModSendError(error.localizedDescription ?: LOC(@"THUMBNAIL_FAILED")); + return; + } + [[UIPasteboard generalPasteboard] setImage:image]; + YouModSendSuccess(LOC(@"COPIED_TO_CLIPBOARD")); + }); + }] resume]; +} + static void YouModDownloadThumbnail(NSString *videoID, UIViewController *presenter) { NSURL *thumbnailURL = YouModThumbnailURLForVideoID(videoID); if (!thumbnailURL) { - YouModSendToast(@"No thumbnail found", presenter); + YouModSendError(LOC(@"NO_THUMBNAIL_FOUND")); return; } - YouModSendToast(@"Downloading thumbnail", presenter); + YouModSendToast(LOC(@"DOWNLOADING_THUMBNAIL")); [[NSURLSession.sharedSession dataTaskWithURL:thumbnailURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { UIImage *image = data ? [UIImage imageWithData:data] : nil; dispatch_async(dispatch_get_main_queue(), ^{ if (!image || error) { - YouModSendToast(error.localizedDescription ?: @"Thumbnail failed", presenter); + YouModSendError(error.localizedDescription ?: LOC(@"THUMBNAIL_FAILED")); return; } YouModRequestPhotoAccess(^(BOOL granted) { if (!granted) { - YouModSendToast(@"Photos access denied", presenter); + YouModSendError(LOC(@"PHOTO_ACCESS_DENINED")); return; } [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ [PHAssetChangeRequest creationRequestForAssetFromImage:image]; } completionHandler:^(BOOL success, NSError *saveError) { dispatch_async(dispatch_get_main_queue(), ^{ - YouModSendToast(success ? @"Saved to Photos" : (saveError.localizedDescription ?: @"Save failed"), presenter); + if (success) YouModSendSuccess(LOC(@"SAVED_TO_PHOTOS")); else YouModSendError(saveError.localizedDescription ?: LOC(@"SAVE_FAILED")); }); }]; }); @@ -2156,188 +1527,210 @@ static void YouModDownloadThumbnail(NSString *videoID, UIViewController *present }] resume]; } -static void YouModCopyVideoInfo(YTPlayerViewController *player, UIViewController *presenter) { - NSString *videoID = YouModVideoIDForPlayer(player); - NSString *title = YouModTitleForPlayer(player); - NSString *url = videoID.length ? [NSString stringWithFormat:@"https://youtu.be/%@", videoID] : @""; - UIPasteboard.generalPasteboard.string = url.length ? [NSString stringWithFormat:@"%@\n%@", title, url] : title; - YouModSendToast(@"Copied video information", presenter); +static void YouModCopyTextToPasteboard(NSString *text, NSString *successKey) { + UIPasteboard.generalPasteboard.string = text; + YouModSendSuccess(LOC(successKey)); } -static void YouModShowVideoQualitySheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { - NSArray *videoFormats = YouModFormatsForPlayer(player, YES); - YouModMediaFormat *audioFormat = YouModBestAudioFormatForPlayer(player); - NSString *title = YouModTitleForPlayer(player); - NSString *videoID = YouModVideoIDForPlayer(player); +static void YouModCopyImageToPasteboard(UIImage *image, NSString *successKey) { + UIPasteboard.generalPasteboard.image = image; + YouModSendSuccess(LOC(successKey)); +} - if (videoFormats.count == 0 || !audioFormat) { - YouModSendToast(@"No video/audio streams found", presenter); - return; - } +static void YouModShowCopyVideoInfoSheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { + NSString *author = YouModAuthorForPlayer(player); + NSString *title = YouModTitleForPlayer(player); + NSString *description = YouModDescriptionForPlayer(player); + NSString *all = [NSString stringWithFormat:@"%@ - %@\n%@", author, title, description]; NSMutableArray *items = [NSMutableArray array]; - for (YouModMediaFormat *format in videoFormats) { - NSString *rowTitle = format.qualityLabel.length ? format.qualityLabel : @"Video"; - NSString *subtitle = YouModFormatSubtitle(format); - [items addObject:[YouModMenuItem itemWithTitle:rowTitle subtitle:subtitle icon:YouModIconImage(658) handler:^{ - [[YouModDownloadCoordinator sharedCoordinator] startVideoDownloadWithVideoFormat:format audioFormat:audioFormat fileName:title videoID:videoID presenter:presenter]; - }]]; - } - YouModPresentMenu(@"Download video", items, presenter, sender); + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_ALL_VID_INFO") subtitle:nil icon:YouModYTIconImage(250, NO, nil) handler:^{ + YouModCopyTextToPasteboard(all, @"COPIED_VID_INFO"); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_AUTHOR") subtitle:nil icon:YouModYTIconImage(250, NO, nil) handler:^{ + YouModCopyTextToPasteboard(author, @"COPIED_AUTHOR"); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_TITLE") subtitle:nil icon:YouModYTIconImage(250, NO, nil) handler:^{ + YouModCopyTextToPasteboard(title, @"COPIED_TITLE"); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_DESCRIPTION") subtitle:nil icon:YouModYTIconImage(250, NO, nil) handler:^{ + YouModCopyTextToPasteboard(description, @"COPIED_DESCRIPTION"); + }]]; + + YouModPresentMenu(nil, items, presenter, sender); } -static void YouModShowAudioSourceSheet(YTPlayerViewController *player, YouModAudioOutputFormat *outputFormat, UIViewController *presenter, UIView *sender) { +static void YouModShowAudioTrackSelectionSheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender, NSString *fileName, BOOL downloadVideo, YouModMediaFormat *videoFormat) { NSArray *audioFormats = YouModFormatsForPlayer(player, NO); - NSString *title = YouModTitleForPlayer(player); - NSString *videoID = YouModVideoIDForPlayer(player); - NSMutableArray *items = [NSMutableArray array]; - if (audioFormats.count == 0) { - if (items.count) { - YouModPresentMenu(@"Download audio", items, presenter, sender); - return; + YouModSendError(LOC(@"NO_AUDIO_STREAM_FOUND")); + return; + } + + if (audioFormats.count == 1 || IS_ENABLED(DownloadFix)) { + YouModMediaFormat *selectedFormat = audioFormats.firstObject; + if (downloadVideo) { + [[YouModDownloadCoordinator sharedCoordinator] startVideoDownloadWithVideoFormat:videoFormat audioFormat:selectedFormat fileName:fileName presenter:presenter videoID:player.currentVideoID]; + } else { + [[YouModDownloadCoordinator sharedCoordinator] startAudioDownloadWithAudioFormat:selectedFormat fileName:fileName presenter:presenter videoID:player.currentVideoID]; } - YouModSendToast(@"No audio streams found", presenter); return; } - NSUInteger index = 1; + NSMutableArray *items = [NSMutableArray array]; for (YouModMediaFormat *format in audioFormats) { - NSString *rowTitle = audioFormats.count == 1 ? @"Audio" : [NSString stringWithFormat:@"Audio %lu", (unsigned long)index++]; - NSString *subtitle = YouModFormatSubtitle(format); - [items addObject:[YouModMenuItem itemWithTitle:rowTitle subtitle:subtitle icon:YouModIconImage(21) handler:^{ - [[YouModDownloadCoordinator sharedCoordinator] startAudioDownloadWithAudioFormat:format fileName:title videoID:videoID outputFormat:outputFormat presenter:presenter]; + NSString *rowTitle = format.qualityLabel; + NSString *subtitle = YouModFormatSubtitle(format, NO); + [items addObject:[YouModMenuItem itemWithTitle:rowTitle subtitle:subtitle icon:YouModYTIconImage(906, NO, nil) handler:^{ + if (downloadVideo) { + [[YouModDownloadCoordinator sharedCoordinator] startVideoDownloadWithVideoFormat:videoFormat audioFormat:format fileName:fileName presenter:presenter videoID:player.currentVideoID]; + } else { + [[YouModDownloadCoordinator sharedCoordinator] startAudioDownloadWithAudioFormat:format fileName:fileName presenter:presenter videoID:player.currentVideoID]; + } }]]; } - NSString *menuTitle = outputFormat.title.length ? [NSString stringWithFormat:@"Download %@", outputFormat.title] : @"Download audio"; - YouModPresentMenu(menuTitle, items, presenter, sender); + + YouModPresentMenu(nil, items, presenter, sender); } -static void YouModShowAudioSheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { +static void YouModShowVideoQualitySheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender, BOOL isShorts) { + NSArray *videoFormats = YouModFormatsForPlayer(player, YES); + NSString *title = YouModTitleForPlayer(player); + + if (videoFormats.count == 0) { + YouModSendError(LOC(@"NO_VID_AUDIO_STREAM_FOUND")); + return; + } + NSMutableArray *items = [NSMutableArray array]; - for (YouModAudioOutputFormat *format in YouModAudioOutputFormats()) { - [items addObject:[YouModMenuItem itemWithTitle:format.title subtitle:YouModAudioOutputSubtitle(format) icon:YouModIconImage(21) handler:^{ - if (!format.supported) { - YouModSendToast(@"DSD export is not supported by bundled FFmpeg.", presenter); - return; - } - YouModShowAudioSourceSheet(player, format, presenter, sender); - }]]; + for (YouModMediaFormat *format in videoFormats) { + NSString *rowTitle = format.qualityLabel; + NSString *subtitle = YouModFormatSubtitle(format, YES); + if (isShorts) { + [items addObject:[YouModMenuItem itemWithTitle:rowTitle subtitle:subtitle icon:YouModYTIconImage(769, NO, nil) handler:^{ + YouModShowAudioTrackSelectionSheet(player, presenter, sender, title, YES, format); + }]]; + } else { + [items addObject:[YouModMenuItem itemWithTitle:rowTitle subtitle:subtitle icon:YouModYTIconImage(658, NO, nil) handler:^{ + YouModShowAudioTrackSelectionSheet(player, presenter, sender, title, YES, format); + }]]; + } } - YouModPresentMenu(@"Audio format", items, presenter, sender); + YouModPresentMenu(nil, items, presenter, sender); +} + +static void YouModStartDownloadAudio(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { + NSString *title = YouModTitleForPlayer(player); + YouModShowAudioTrackSelectionSheet(player, presenter, sender, title, NO, nil); } static void YouModShowCaptionsSheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { NSArray *tracks = YouModCaptionTracksForPlayer(player); if (tracks.count == 0) { - YouModSendToast(@"No captions available for this video.", presenter); + YouModSendError(LOC(@"NO_CAPTIONS")); return; } NSMutableArray *items = [NSMutableArray array]; - for (id track in tracks) { - NSString *baseURL = YouModStringFromSelector(track, @selector(baseURL)); + for (YTICaptionTrackEntry *track in tracks) { + NSString *baseURL = track.baseURL; if (baseURL.length == 0) continue; - NSString *languageCode = YouModStringFromSelector(track, @selector(languageCode)); - NSString *vssId = YouModStringFromSelector(track, @selector(vssId)); - NSString *nameStr = nil; - id nameObj = YouModObjectFromSelector(track, @selector(name)); - nameStr = YouModStringFromSelector(nameObj, @selector(simpleText)); - if (!nameStr.length) { - NSArray *runs = YouModObjectFromSelector(nameObj, @selector(runsArray)); - if (runs.count > 0) nameStr = YouModStringFromSelector(runs.firstObject, @selector(text)); - } - if (!nameStr.length) nameStr = languageCode; - if (!nameStr.length) nameStr = vssId; + NSString *languageCode = track.languageCode; + YTIFormattedString *nameObj = track.name; + NSString *nameStr = nameObj.dropdownOptionTitle; - [items addObject:[YouModMenuItem itemWithTitle:nameStr subtitle:languageCode icon:YouModIconImage(637) handler:^{ + [items addObject:[YouModMenuItem itemWithTitle:nameStr subtitle:languageCode icon:YouModYTIconImage(50, NO, nil) handler:^{ NSString *vttURL = [baseURL stringByAppendingString:@"&fmt=vtt"]; NSURL *url = [NSURL URLWithString:vttURL]; if (!url) { - YouModSendToast(@"Invalid caption URL.", presenter); + YouModSendError(LOC(@"NO_CAPTIONS_URL")); return; } - YouModSendToast(@"Downloading captions...", presenter); + YouModSendToast(LOC(@"DOWNLOADING_CAPTIONS")); [[NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error || data.length == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + YouModSendError(LOC(@"CAPTIONS_FAILED")); + }); + return; + } + + NSString *title = YouModTitleForPlayer(player); + NSString *filename = [NSString stringWithFormat:@"%@.%@", title, languageCode]; + NSURL *tempURL = YouModUniqueFileURL(filename, @"vtt"); + + NSError *writeError = nil; + BOOL writeSuccess = [data writeToURL:tempURL options:NSDataWritingAtomic error:&writeError]; + dispatch_async(dispatch_get_main_queue(), ^{ - if (error || data.length == 0) { - YouModSendToast(@"Failed to download captions.", presenter); - return; + if (writeSuccess) { + YouModSendSuccess(LOC(@"DOWNLOAD_COMPLETED")); + YouModShareFile(tempURL, presenter); + } else { + YouModSendError(writeError.localizedDescription ?: LOC(@"CAPTIONS_FAILED")); } - NSString *videoID = YouModVideoIDForPlayer(player) ?: @"video"; - NSString *filename = [NSString stringWithFormat:@"%@_%@.vtt", videoID, languageCode ?: @"captions"]; - NSURL *tempURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:filename]]; - [data writeToURL:tempURL atomically:YES]; - YouModShareFile(tempURL, presenter); }); }] resume]; }]]; } if (items.count == 0) { - YouModSendToast(@"No valid caption URLs found.", presenter); + YouModSendError(LOC(@"NO_CAPTIONS_URL")); return; } - YouModPresentMenu(@"Download captions", items, presenter, sender); + YouModPresentMenu(nil, items, presenter, sender); } -static void YouModShowDownloadManager(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { - if (!player) { - YouModSendToast(@"Open a video before using the download manager.", presenter); - return; - } +static void YouModShowThumbnailSheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { + NSString *videoID = player.currentVideoID; - NSString *videoID = YouModVideoIDForPlayer(player); NSMutableArray *items = [NSMutableArray array]; - [items addObject:[YouModMenuItem itemWithTitle:@"Download video" subtitle:@"Choose quality" icon:YouModIconImage(658) handler:^{ - YouModShowVideoQualitySheet(player, presenter, sender); - }]]; - [items addObject:[YouModMenuItem itemWithTitle:@"Download audio" subtitle:@"Choose format" icon:YouModIconImage(21) handler:^{ - YouModShowAudioSheet(player, presenter, sender); - }]]; - [items addObject:[YouModMenuItem itemWithTitle:@"Download captions" subtitle:@"Save subtitles as VTT" icon:YouModIconImage(637) handler:^{ - YouModShowCaptionsSheet(player, presenter, sender); - }]]; - [items addObject:[YouModMenuItem itemWithTitle:@"Copy diagnostics" subtitle:@"Copy last error log" icon:YouModIconImage(870) handler:^{ - YouModCopyDownloadDiagnostics(presenter); - }]]; - [items addObject:[YouModMenuItem itemWithTitle:@"Save thumbnail" subtitle:@"Save to Photos" icon:YouModIconImage(367) handler:^{ + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"SAVE_THUMBNAIL") subtitle:nil icon:YouModYTIconImage(57, NO, nil) handler:^{ YouModDownloadThumbnail(videoID, presenter); }]]; - [items addObject:[YouModMenuItem itemWithTitle:@"Copy video information" subtitle:@"Copy title and URL" icon:YouModIconImage(250) handler:^{ - YouModCopyVideoInfo(player, presenter); + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"SHOW_THUMBNAIL") subtitle:nil icon:YouModYTIconImage(208, NO, nil) handler:^{ + YouModShowThumbnailViewer(videoID, presenter); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_THUMBNAIL") subtitle:nil icon:YouModYTIconImage(250, NO, nil) handler:^{ + YouModCopyThumbnail(videoID, presenter); }]]; - YouModPresentMenu(@"Download manager", items, presenter, sender); -} - -void YouModConfigureDownloadButton(_ASDisplayView *view) { - if (![view.accessibilityIdentifier isEqualToString:@"id.ui.add_to.offline.button"]) return; - if (!IS_ENABLED(DownloadManager) || IS_ENABLED(HideDownloadButton)) return; - if (objc_getAssociatedObject(view, @selector(YouModDownloadButtonTapped:))) return; - view.userInteractionEnabled = YES; - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:view action:@selector(YouModDownloadButtonTapped:)]; - tap.cancelsTouchesInView = YES; - tap.delaysTouchesBegan = YES; - tap.delaysTouchesEnded = YES; - [view addGestureRecognizer:tap]; - objc_setAssociatedObject(view, @selector(YouModDownloadButtonTapped:), @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + YouModPresentMenu(nil, items, presenter, sender); } -%hook _ASDisplayView +static void YouModShowDownloadManager(YTPlayerViewController *player, UIViewController *presenter, UIView *sender, BOOL isShorts) { + if (!player) { + YouModSendError(LOC(@"OPEN_VID_BEFORE")); + return; + } + NSMutableArray *items = [NSMutableArray array]; -%new -- (void)YouModDownloadButtonTapped:(UITapGestureRecognizer *)sender { - if (sender.state != UIGestureRecognizerStateEnded) return; - UIViewController *presenter = YouModPresenterForSender(self, YouModCurrentPlayerViewController); - YTPlayerViewController *player = YouModPlayerFromViewController(presenter); - YouModShowDownloadManager(player, presenter, self); + if (isShorts) { + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"DOWNLOAD_SHORTS") subtitle:nil icon:YouModYTIconImage(769, NO, nil) handler:^{ + YouModShowVideoQualitySheet(player, presenter, sender, YES); + }]]; + } else { + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"DOWNLOAD_VIDEO") subtitle:nil icon:YouModYTIconImage(658, NO, nil) handler:^{ + YouModShowVideoQualitySheet(player, presenter, sender, NO); + }]]; + } + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"DOWNLOAD_AUDIO") subtitle:nil icon:YouModYTIconImage(21, NO, nil) handler:^{ + YouModStartDownloadAudio(player, presenter, sender); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"DOWNLOAD_CAPTIONS") subtitle:nil icon:YouModYTIconImage(50, NO, nil) handler:^{ + YouModShowCaptionsSheet(player, presenter, sender); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"THUMBNAIL_OPTIONS") subtitle:nil icon:YouModYTIconImage(367, NO, nil) handler:^{ + YouModShowThumbnailSheet(player, presenter, sender); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_VID_INFO") subtitle:nil icon:YouModYTIconImage(250, NO, nil) handler:^{ + YouModShowCopyVideoInfoSheet(player, presenter, sender); + }]]; + YouModPresentMenu(player, items, presenter, sender); } -%end - %hook YTPlayerViewController - (void)viewDidAppear:(BOOL)animated { @@ -2384,3 +1777,343 @@ NSString *YouModGlobalAuthHeader = nil; return token; } %end + +void YouModConfigureDownloadButton(_ASDisplayView *view) { + if (!IS_ENABLED(DownloadManager)) return; + if (objc_getAssociatedObject(view, @selector(YouModDownloadButtonTapped:))) return; + + if ([view.accessibilityIdentifier isEqualToString:@"id.ui.add_to.offline.button"]) { + view.userInteractionEnabled = YES; + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:view action:@selector(YouModDownloadButtonTapped:)]; + tap.cancelsTouchesInView = YES; + tap.delaysTouchesBegan = YES; + tap.delaysTouchesEnded = YES; + [view addGestureRecognizer:tap]; + objc_setAssociatedObject(view, @selector(YouModDownloadButtonTapped:), @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } +} + +static void YouModShowTranslationDialog(NSString *text, UIViewController *presenter) { + if (!text || text.length == 0 || !presenter) return; + + YouModTranslationViewController *vc = [[YouModTranslationViewController alloc] init]; + vc.originalText = text; + + UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; + + if (@available(iOS 15.0, *)) { + UISheetPresentationController *sheet = nav.sheetPresentationController; + if (sheet) { + sheet.detents = @[ + [UISheetPresentationControllerDetent mediumDetent], + [UISheetPresentationControllerDetent largeDetent] + ]; + sheet.prefersGrabberVisible = YES; + sheet.preferredCornerRadius = 24.0; + } + } else { + // Fallback for iOS 14 + nav.modalPresentationStyle = UIModalPresentationFormSheet; + } + [presenter presentViewController:nav animated:YES completion:nil]; +} + +static NSString *YouModExtractCommentText(UIView *cellView) { + if (!cellView) return @""; + + NSString *resultText = @""; + NSMutableArray *queue = [NSMutableArray arrayWithObject:cellView]; + Class asDisplayClass = NSClassFromString(@"_ASDisplayView"); + Class elmTextExClass = NSClassFromString(@"ELMExpandableTextNode"); + Class elmTextClass = NSClassFromString(@"ELMTextNode"); + + while (queue.count > 0) { + UIView *current = queue.firstObject; + [queue removeObjectAtIndex:0]; + + if (asDisplayClass && [current isKindOfClass:asDisplayClass]) { + id node = [current performSelector:@selector(keepalive_node)]; + + BOOL isExpandableText = node && elmTextExClass && [node isKindOfClass:elmTextExClass]; + BOOL isText = node && elmTextClass && [node isKindOfClass:elmTextClass]; + BOOL isCommentLabel = [current.accessibilityIdentifier isEqualToString:@"id.comment.content.label"]; + + if (isText || isExpandableText || isCommentLabel) { + resultText = current.accessibilityLabel ?: @""; + break; + } + } + + [queue addObjectsFromArray:current.subviews]; + } + + return resultText; +} + +static UIImage *YouModRenderViewToImage(_ASDisplayView *view) { + if (!view || view.bounds.size.width <= 0 || view.bounds.size.height <= 0) return nil; + + UIColor *realBgColor = view.keepalive_node.closestViewController.view.backgroundColor; + UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, [UIScreen mainScreen].scale); + CGContextRef context = UIGraphicsGetCurrentContext(); + [realBgColor setFill]; + CGContextFillRect(context, view.bounds); + [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +/* +static UIImage *YouModExtractPostImage(UIView *cellView) { + NSMutableArray *queue = [NSMutableArray arrayWithObject:cellView]; + Class asDisplayClass = NSClassFromString(@"_ASDisplayView"); + Class imageZoomNodeClass = NSClassFromString(@"YTImageZoomNode"); + UIView *targetViewForRender = nil; + + while (queue.count > 0) { + UIView *current = queue.firstObject; + [queue removeObjectAtIndex:0]; + + if (asDisplayClass && [current isKindOfClass:asDisplayClass]) { + id node = [current performSelector:@selector(keepalive_node)]; + + if (imageZoomNodeClass && [node isKindOfClass:imageZoomNodeClass]) { + if (!targetViewForRender) { + targetViewForRender = current; + } + } + } + + @synchronized (current) { + [queue addObjectsFromArray:current.subviews]; + } + } + + if (targetViewForRender && targetViewForRender.bounds.size.width > 0 && targetViewForRender.bounds.size.height > 0) { + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:targetViewForRender.bounds.size]; + return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { + [targetViewForRender.layer renderInContext:rendererContext.CGContext]; + }]; + } + + return nil; +} +*/ + +%hook _ASDisplayView + +- (void)layoutSubviews { + %orig; + if ([self.accessibilityIdentifier isEqualToString:@"id.ui.comment_cell"] && IS_ENABLED(DownloadComment)) { + BOOL hasGesture = NO; + for (UIGestureRecognizer *g in self.gestureRecognizers) { + if ([g.name isEqualToString:@"YouModCommentLongPress"]) { + hasGesture = YES; + break; + } + } + + if (!hasGesture) { + UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(YouModHandleCommentLongPress:)]; + longPress.name = @"YouModCommentLongPress"; + longPress.minimumPressDuration = 0.3; + [self addGestureRecognizer:longPress]; + } + } else if ([self.accessibilityIdentifier isEqualToString:@"id.ui.backstage.original_post"] && IS_ENABLED(DownloadPost)) { + BOOL hasGesture = NO; + for (UIGestureRecognizer *g in self.gestureRecognizers) { + if ([g.name isEqualToString:@"YouModPostLongPress"]) { + hasGesture = YES; + break; + } + } + + if (!hasGesture) { + UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(YouModHandlePostLongPress:)]; + longPress.name = @"YouModPostLongPress"; + longPress.minimumPressDuration = 0.3; + [self addGestureRecognizer:longPress]; + } + } +} + +%new +- (void)YouModHandleCommentLongPress:(UILongPressGestureRecognizer *)sender { + if (sender.state != UIGestureRecognizerStateBegan) return; + + NSMutableArray *items = [NSMutableArray array]; + NSString *commentText = YouModExtractCommentText(self); + + if (commentText && commentText.length > 0) { + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"TRANSLATE_COMMENT") subtitle:nil icon:YouModYTIconImage(897, NO, nil) handler:^{ + UIViewController *presenter = YouModPresenterForSender(self, nil); + YouModShowTranslationDialog(commentText, presenter); + }]]; + + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_COMMENT_TEXT") subtitle:nil icon:YouModYTIconImage(243, NO, nil) handler:^{ + YouModCopyTextToPasteboard(commentText, @"COPIED_TO_CLIPBOARD"); + }]]; + } + + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"SAVE_COMMENT_IMAGE") subtitle:nil icon:YouModYTIconImage(367, NO, nil) handler:^{ + UIImage *image = YouModRenderViewToImage(self); + if (image) { + UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); + YouModSendSuccess(LOC(@"SAVED_TO_PHOTOS")); + } + }]]; + + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_COMMENT_IMAGE") subtitle:nil icon:YouModYTIconImage(208, NO, nil) handler:^{ + UIImage *image = YouModRenderViewToImage(self); + if (image) { + YouModCopyImageToPasteboard(image, @"COPIED_TO_CLIPBOARD"); + } + }]]; + + UIViewController *presenter = YouModPresenterForSender(self, nil); + if (!presenter) return; + + YouModPresentMenu(nil, items, presenter, self); +} + +%new +- (void)YouModHandlePostLongPress:(UILongPressGestureRecognizer *)sender { + if (sender.state != UIGestureRecognizerStateBegan) return; + + NSMutableArray *items = [NSMutableArray array]; + NSString *commentText = YouModExtractCommentText(self); + + if (commentText && commentText.length > 0) { + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"TRANSLATE_POST") subtitle:nil icon:YouModYTIconImage(897, NO, nil) handler:^{ + UIViewController *presenter = YouModPresenterForSender(self, nil); + YouModShowTranslationDialog(commentText, presenter); + }]]; + + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_POST_TEXT") subtitle:nil icon:YouModYTIconImage(243, NO, nil) handler:^{ + YouModCopyTextToPasteboard(commentText, @"COPIED_TO_CLIPBOARD"); + }]]; + } + + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"SAVE_POST_IMAGE") subtitle:nil icon:YouModYTIconImage(367, NO, nil) handler:^{ + UIImage *image = YouModRenderViewToImage(self); + if (image) { + UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); + YouModSendSuccess(LOC(@"SAVED_TO_PHOTOS")); + } + }]]; + + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_POST_IMAGE") subtitle:nil icon:YouModYTIconImage(208, NO, nil) handler:^{ + UIImage *image = YouModRenderViewToImage(self); + if (image) { + YouModCopyImageToPasteboard(image, @"COPIED_TO_CLIPBOARD"); + } + }]]; + + /* + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"SAVE_CURRENT_IMAGE") subtitle:nil icon:YouModYTIconImage(367, YES, [UIColor systemPurpleColor]) handler:^{ + UIImage *image = YouModExtractPostImage(self); + if (image) { + UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); + YouModSendSuccess(LOC(@"SAVED_TO_PHOTOS")); + } + }]]; + + [items addObject:[YouModMenuItem itemWithTitle:LOC(@"COPY_CURRENT_IMAGE") subtitle:nil icon:YouModYTIconImage(208, YES, [UIColor systemPurpleColor]) handler:^{ + UIImage *image = YouModExtractPostImage(self); + if (image) { + YouModCopyImageToPasteboard(image, @"COPIED_TO_CLIPBOARD"); + } + }]]; + */ + + UIViewController *presenter = YouModPresenterForSender(self, nil); + if (!presenter) return; + + YouModPresentMenu(nil, items, presenter, self); +} + +%new +- (void)YouModDownloadButtonTapped:(UITapGestureRecognizer *)sender { + if (sender.state != UIGestureRecognizerStateEnded) return; + UIViewController *presenter = YouModPresenterForSender(self, YouModCurrentPlayerViewController); + YTPlayerViewController *player = YouModPlayerFromViewController(presenter); + YouModShowDownloadManager(player, presenter, self, NO); +} + +%end + +%hook YTReelWatchPlaybackOverlayView + +- (void)layoutSubviews { + %orig; + if (!IS_ENABLED(DownloadManager) || !IS_ENABLED(AddDownloadToShorts)) return; + UIView *likeButtonView = nil; + for (UIView *subview in self.subviews) { + if ([subview isKindOfClass:%c(YTReelPlayerButton)]) { + likeButtonView = subview; + break; + } + } + + if (!likeButtonView) return; + + YTQTMButton *downloadBtn = (YTQTMButton *)[self viewWithTag:1501]; + if (!downloadBtn) { + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:20 weight:UIImageSymbolWeightMedium]; + UIImage *icon = [[UIImage systemImageNamed:@"arrow.down.circle" withConfiguration:config] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + downloadBtn = [%c(YTQTMButton) iconButton]; + [downloadBtn setImage:icon forState:UIControlStateNormal]; + downloadBtn.tintColor = [UIColor whiteColor]; + downloadBtn.exclusiveTouch = YES; + downloadBtn.tag = 1501; + + [downloadBtn addTarget:self action:@selector(didTapYouModShortsDownload:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:downloadBtn]; + } + + CGFloat btnWidth = 64.0; + CGFloat btnHeight = 60.0; + + CGFloat pX = likeButtonView.frame.origin.x; + CGFloat pY = likeButtonView.frame.origin.y + 85.0; + + downloadBtn.frame = CGRectMake(pX, pY, btnWidth, btnHeight); + + [self bringSubviewToFront:downloadBtn]; +} + +%new +- (void)didTapYouModShortsDownload:(YTQTMButton *)button { + UIResponder *responder = self.nextResponder; + while (responder && ![responder isKindOfClass:%c(YTShortsPlayerViewController)]) { + responder = responder.nextResponder; + } + if (responder) { + YTShortsPlayerViewController *shortsPlayerView = (YTShortsPlayerViewController *)responder; + YTPlayerViewController *player = (YTPlayerViewController *)shortsPlayerView.childViewControllers[0]; + UIViewController *presenter = YouModPresenterForSender(button, player); + YouModShowDownloadManager(player, presenter, button, YES); + } +} + +%end + +%ctor { + %init; + YMOverlayButtonSpec *download = [[YMOverlayButtonSpec alloc] init]; + download.identifier = @"download.video"; + download.symbolName = @"arrow.down.circle"; + download.tintColor = [UIColor whiteColor]; + download.sortOrder = 200; + download.isVisible = ^BOOL(YTPlayerViewController *player) { + return IS_ENABLED(DownloadManager); + }; + download.onTap = ^(YTPlayerViewController *player, UIButton *button) { + UIViewController *presenter = YouModPresenterForSender(button, player ?: YouModCurrentPlayerViewController); + YTPlayerViewController *resolved = YouModPlayerFromViewController(presenter) ?: player ?: YouModCurrentPlayerViewController; + YouModShowDownloadManager(resolved, presenter, button, NO); + }; + YMRegisterOverlayButton(download); +} diff --git a/Files/Download_FFmpeg.txt b/Files/Download_FFmpeg.txt new file mode 100644 index 00000000..eccd24af --- /dev/null +++ b/Files/Download_FFmpeg.txt @@ -0,0 +1,2390 @@ +// HUGE thanks to @daisuke1227 for implementing all of this +#import "Headers.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@interface YTDefaultSheetController (YouModDownload) ++ (instancetype)sheetControllerWithParentResponder:(id)parentResponder; +- (void)addAction:(YTActionSheetAction *)action; +- (void)presentFromView:(UIView *)view animated:(BOOL)animated completion:(void (^)(void))completion; +- (void)presentFromViewController:(UIViewController *)vc animated:(BOOL)animated completion:(void (^)(void))completion; +@end + +@interface YTPlayerViewController (YouModDownload) +- (YTPlayerResponse *)contentPlayerResponse; +@end + +@interface YTIPlayerResponse (YouModDownload) +- (id)streamingData; +@end + +@interface YTIStreamingData : NSObject +- (NSArray *)adaptiveFormatsArray; +@end + +@interface YTIFormatStream (YouModDownload) +- (NSString *)mimeType; +- (BOOL)hasContentLength; +- (unsigned long long)contentLength; +- (unsigned long long)approxDurationMs; +@end + +@interface YTIVideoDetails (YouModDownload) +- (NSString *)title; +- (NSString *)author; +- (NSString *)shortDescription; +@end + +static UIImage *YouModIconImage(NSInteger iconType) { + YTIIcon *icon = [%c(YTIIcon) new]; + icon.iconType = iconType; + UIImage *image = [icon iconImageWithColor:[UIColor labelColor]]; + return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; +} + +@interface YouModMenuItem : NSObject +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *subtitle; +@property (nonatomic, strong) UIImage *iconImage; +@property (nonatomic, copy) void (^handler)(void); ++ (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle handler:(void (^)(void))handler; ++ (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle icon:(UIImage *)icon handler:(void (^)(void))handler; +@end + +@implementation YouModMenuItem ++ (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle handler:(void (^)(void))handler { + return [self itemWithTitle:title subtitle:subtitle icon:nil handler:handler]; +} ++ (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle icon:(UIImage *)icon handler:(void (^)(void))handler { + YouModMenuItem *item = [YouModMenuItem new]; + item.title = title; + item.subtitle = subtitle; + item.iconImage = icon; + item.handler = handler; + return item; +} +@end + +@interface YouModMediaFormat : NSObject +@property (nonatomic, strong) id source; +@property (nonatomic, copy) NSString *urlString; +@property (nonatomic, copy) NSString *qualityLabel; +@property (nonatomic, copy) NSString *mimeType; +@property (nonatomic, copy) NSDictionary *httpHeaders; +@property (nonatomic, assign) unsigned long long contentLength; +@property (nonatomic, assign) unsigned long long durationMs; +@property (nonatomic, assign) NSInteger fps; +@property (nonatomic, assign) BOOL video; +@property (nonatomic, copy) NSString *languageCode; +@property (nonatomic, copy) NSString *languageName; +@property (nonatomic, assign) BOOL drcAudio; +@end + +@implementation YouModMediaFormat +@end + +@interface YouModAudioOutputFormat : NSObject +@property (nonatomic, copy) NSString *identifier; +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *subtitle; +@property (nonatomic, copy) NSString *fileExtension; +@property (nonatomic, copy) NSArray *ffmpegArguments; +@property (nonatomic, assign) BOOL passthroughWhenCompatible; +@property (nonatomic, assign) BOOL supported; +@end + +@implementation YouModAudioOutputFormat +@end + +typedef void (^YouModFileDownloadCompletion)(NSURL *fileURL, NSError *error); +typedef void (^YouModMergeCompletion)(BOOL success, NSError *error); +typedef void (^YouModRangeDownloadProgress)(unsigned long long completedBytes); + +@interface YouModDownloadChunk : NSObject +@property (nonatomic, assign) unsigned long long offset; +@property (nonatomic, assign) unsigned long long length; +@property (nonatomic, assign) NSUInteger attempts; +@end + +@implementation YouModDownloadChunk +@end + +@interface YouModRangeDownloader : NSObject +@property (nonatomic, strong) NSURL *url; +@property (nonatomic, strong) NSURL *destinationURL; +@property (nonatomic, copy) NSDictionary *httpHeaders; +@property (nonatomic, assign) unsigned long long expectedBytes; +@property (nonatomic, copy) YouModRangeDownloadProgress progress; +@property (nonatomic, copy) YouModFileDownloadCompletion completion; +@property (nonatomic, strong) NSURLSession *session; +@property (nonatomic, strong) NSFileHandle *fileHandle; +@property (nonatomic, strong) NSMutableArray *pendingChunks; +@property (nonatomic, strong) NSMutableSet *tasks; +@property (nonatomic, strong) dispatch_queue_t stateQueue; +@property (nonatomic, strong) dispatch_queue_t fileQueue; +@property (nonatomic, assign) NSUInteger activeTaskCount; +@property (nonatomic, assign) NSUInteger totalChunkCount; +@property (nonatomic, assign) unsigned long long completedBytes; +@property (nonatomic, assign) BOOL cancelled; +@property (nonatomic, assign) BOOL finished; +- (instancetype)initWithURL:(NSURL *)url destinationURL:(NSURL *)destinationURL expectedBytes:(unsigned long long)expectedBytes headers:(NSDictionary *)headers progress:(YouModRangeDownloadProgress)progress completion:(YouModFileDownloadCompletion)completion; +- (void)start; +- (void)cancel; +@end + +@interface YouModDownloadCoordinator : NSObject +@property (nonatomic, strong) NSURLSession *session; +@property (nonatomic, strong) NSURLSessionDownloadTask *task; +@property (nonatomic, strong) NSURLSessionDataTask *metadataTask; +@property (nonatomic, strong) YouModRangeDownloader *rangeDownloader; +@property (nonatomic, strong) UIAlertController *progressAlert; +@property (nonatomic, strong) UIProgressView *progressView; +@property (nonatomic, weak) UIViewController *presenter; +@property (nonatomic, copy) YouModFileDownloadCompletion fileCompletion; +@property (nonatomic, strong) NSURL *destinationURL; +@property (nonatomic, strong) NSURL *videoTempURL; +@property (nonatomic, strong) NSURL *audioTempURL; +@property (nonatomic, assign) unsigned long long completedBytes; +@property (nonatomic, assign) unsigned long long totalBytes; +@property (nonatomic, assign) unsigned long long currentBytes; +@property (nonatomic, assign) unsigned long long currentExpectedBytes; +@property (nonatomic, assign) BOOL currentResolvedSizeAddedToTotal; +@property (nonatomic, assign) BOOL active; +@property (nonatomic, assign) BOOL finishedCurrentFile; +@property (nonatomic, assign) BOOL cancelled; +@property (nonatomic, copy) NSString *baseProgressTitle; +@property (nonatomic, assign) NSTimeInterval downloadStartTime; ++ (instancetype)sharedCoordinator; +- (void)startVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; +- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; +- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID outputFormat:(YouModAudioOutputFormat *)outputFormat presenter:(UIViewController *)presenter; +- (void)startDirectVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; +- (void)startDirectSingleVideoDownloadWithFormat:(YouModMediaFormat *)format fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; +- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter; +- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID outputFormat:(YouModAudioOutputFormat *)outputFormat presenter:(UIViewController *)presenter; +- (void)mergeVideoURL:(NSURL *)videoURL audioURL:(NSURL *)audioURL fileName:(NSString *)fileName outputExtension:(NSString *)outputExtension durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter; +- (void)mergeVideoWithAVFoundationVideoURL:(NSURL *)videoURL audioURL:(NSURL *)audioURL outputURL:(NSURL *)outputURL durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter fallbackError:(NSError *)fallbackError; +- (void)trimSingleVideoURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter; +- (void)convertAudioURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL outputFormat:(YouModAudioOutputFormat *)outputFormat durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter; +@end + +static const unsigned long long YouModFastDownloadMinimumBytes = 256ULL * 1024ULL; +static const unsigned long long YouModFastDownloadChunkBytes = 4ULL * 1024ULL * 1024ULL; +static const NSUInteger YouModFastDownloadConcurrency = 8; +static const NSUInteger YouModFastDownloadMaxAttempts = 3; + +static BOOL YouModHTTPHeadersContainField(NSDictionary *headers, NSString *field) { + for (id key in headers) { + if ([key isKindOfClass:NSString.class] && [(NSString *)key caseInsensitiveCompare:field] == NSOrderedSame) + return YES; + } + return NO; +} + +static NSString *YouModYouTubeCookiesString(void) { + NSMutableArray *cookieStrings = [NSMutableArray array]; + for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) { + if ([cookie.domain containsString:@"youtube.com"]) { + [cookieStrings addObject:[NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value]]; + } + } + return [cookieStrings componentsJoinedByString:@"; "]; +} + +static NSString *YouModNativeUserAgent(void) { + NSString *version = @"21.18.4"; + NSString *sysVersion = [[UIDevice currentDevice].systemVersion stringByReplacingOccurrencesOfString:@"." withString:@"_"] ?: @"18_7"; + return [NSString stringWithFormat:@"com.google.ios.youtube/%@ (iPhone; CPU iPhone OS %@ like Mac OS X)", version, sysVersion]; +} + +static void YouModApplyDownloadHeaders(NSMutableURLRequest *request, NSDictionary *headers) { + for (id key in headers) { + id value = headers[key]; + if ([key isKindOfClass:NSString.class] && [value isKindOfClass:NSString.class]) + [request setValue:value forHTTPHeaderField:key]; + } + if (!YouModHTTPHeadersContainField(headers, @"User-Agent")) + [request setValue:YouModNativeUserAgent() forHTTPHeaderField:@"User-Agent"]; + if (!YouModHTTPHeadersContainField(headers, @"Origin")) + [request setValue:@"https://www.youtube.com" forHTTPHeaderField:@"Origin"]; + if (!YouModHTTPHeadersContainField(headers, @"Referer")) + [request setValue:@"https://www.youtube.com/" forHTTPHeaderField:@"Referer"]; + if (!YouModHTTPHeadersContainField(headers, @"Cookie")) { + NSString *cookies = YouModYouTubeCookiesString(); + if (cookies.length > 0) [request setValue:cookies forHTTPHeaderField:@"Cookie"]; + } + extern NSString *YouModGlobalAuthHeader; + if (YouModGlobalAuthHeader && !YouModHTTPHeadersContainField(headers, @"Authorization")) { + [request setValue:YouModGlobalAuthHeader forHTTPHeaderField:@"Authorization"]; + } + [request setValue:@"identity" forHTTPHeaderField:@"Accept-Encoding"]; +} + +@implementation YouModRangeDownloader + +- (instancetype)initWithURL:(NSURL *)url destinationURL:(NSURL *)destinationURL expectedBytes:(unsigned long long)expectedBytes headers:(NSDictionary *)headers progress:(YouModRangeDownloadProgress)progress completion:(YouModFileDownloadCompletion)completion { + self = [super init]; + if (self) { + _url = url; + _destinationURL = destinationURL; + _httpHeaders = [headers copy]; + _expectedBytes = expectedBytes; + _progress = [progress copy]; + _completion = [completion copy]; + _pendingChunks = [NSMutableArray array]; + _tasks = [NSMutableSet set]; + _stateQueue = dispatch_queue_create("com.youmod.download.range.state", DISPATCH_QUEUE_SERIAL); + _fileQueue = dispatch_queue_create("com.youmod.download.range.file", DISPATCH_QUEUE_SERIAL); + + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + configuration.HTTPMaximumConnectionsPerHost = YouModFastDownloadConcurrency; + configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + configuration.timeoutIntervalForResource = 300; + NSMutableDictionary *additionalHeaders = [NSMutableDictionary dictionary]; + for (id key in headers) { + id value = headers[key]; + if ([key isKindOfClass:NSString.class] && [value isKindOfClass:NSString.class]) + additionalHeaders[key] = value; + } + if (!YouModHTTPHeadersContainField(additionalHeaders, @"User-Agent")) + additionalHeaders[@"User-Agent"] = YouModNativeUserAgent(); + if (!YouModHTTPHeadersContainField(additionalHeaders, @"Origin")) + additionalHeaders[@"Origin"] = @"https://www.youtube.com"; + if (!YouModHTTPHeadersContainField(additionalHeaders, @"Referer")) + additionalHeaders[@"Referer"] = @"https://www.youtube.com/"; + if (!YouModHTTPHeadersContainField(additionalHeaders, @"Cookie")) { + NSString *cookies = YouModYouTubeCookiesString(); + if (cookies.length > 0) additionalHeaders[@"Cookie"] = cookies; + } + extern NSString *YouModGlobalAuthHeader; + if (YouModGlobalAuthHeader && !YouModHTTPHeadersContainField(additionalHeaders, @"Authorization")) { + additionalHeaders[@"Authorization"] = YouModGlobalAuthHeader; + } + additionalHeaders[@"Accept-Encoding"] = @"identity"; + configuration.HTTPAdditionalHeaders = additionalHeaders; + _session = [NSURLSession sessionWithConfiguration:configuration]; + } + return self; +} + +- (NSError *)errorWithCode:(NSInteger)code message:(NSString *)message { + return [NSError errorWithDomain:@"YouMod" code:code userInfo:@{NSLocalizedDescriptionKey: message ?: @"Download failed"}]; +} + +- (BOOL)prepareDestinationWithError:(NSError **)error { + [NSFileManager.defaultManager removeItemAtURL:self.destinationURL error:nil]; + if (![NSFileManager.defaultManager createFileAtPath:self.destinationURL.path contents:nil attributes:nil]) { + if (error) *error = [self errorWithCode:20 message:@"Cannot create file"]; + return NO; + } + + self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.destinationURL.path]; + if (!self.fileHandle) { + if (error) *error = [self errorWithCode:21 message:@"Cannot open file"]; + return NO; + } + + @try { + [self.fileHandle truncateFileAtOffset:self.expectedBytes]; + } @catch (NSException *exception) { + if (error) *error = [self errorWithCode:22 message:exception.reason ?: @"Cannot allocate file"]; + return NO; + } + return YES; +} + +- (void)start { + dispatch_async(self.stateQueue, ^{ + if (self.expectedBytes == 0) { + [self finishWithErrorLocked:[self errorWithCode:23 message:@"Unknown stream size"]]; + return; + } + + NSError *error = nil; + if (![self prepareDestinationWithError:&error]) { + [self finishWithErrorLocked:error]; + return; + } + + unsigned long long chunkSize = self.expectedBytes / YouModFastDownloadConcurrency; + if (chunkSize < 256ULL * 1024ULL) chunkSize = 256ULL * 1024ULL; + if (chunkSize > YouModFastDownloadChunkBytes) chunkSize = YouModFastDownloadChunkBytes; + + for (unsigned long long offset = 0; offset < self.expectedBytes; offset += chunkSize) { + YouModDownloadChunk *chunk = [YouModDownloadChunk new]; + chunk.offset = offset; + unsigned long long remaining = self.expectedBytes - offset; + chunk.length = remaining < chunkSize ? remaining : chunkSize; + [self.pendingChunks addObject:chunk]; + } + self.totalChunkCount = self.pendingChunks.count; + [self scheduleChunksLocked]; + }); +} + +- (void)cancel { + dispatch_async(self.stateQueue, ^{ + if (self.finished) return; + self.cancelled = YES; + self.finished = YES; + for (NSURLSessionDataTask *task in self.tasks) [task cancel]; + [self.tasks removeAllObjects]; + [self.session invalidateAndCancel]; + dispatch_async(self.fileQueue, ^{ + @try { + [self.fileHandle closeFile]; + } @catch (__unused NSException *exception) { + } + [NSFileManager.defaultManager removeItemAtURL:self.destinationURL error:nil]; + }); + }); +} + +- (void)scheduleChunksLocked { + if (self.finished || self.cancelled) return; + while (self.activeTaskCount < YouModFastDownloadConcurrency && self.pendingChunks.count > 0) { + YouModDownloadChunk *chunk = self.pendingChunks.firstObject; + [self.pendingChunks removeObjectAtIndex:0]; + [self startChunkLocked:chunk]; + } + + if (self.activeTaskCount == 0 && self.pendingChunks.count == 0) { + [self finishSuccessfullyLocked]; + } +} + +- (void)startChunkLocked:(YouModDownloadChunk *)chunk { + unsigned long long end = chunk.offset + chunk.length - 1; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0]; + YouModApplyDownloadHeaders(request, self.httpHeaders); + [request setValue:[NSString stringWithFormat:@"bytes=%llu-%llu", chunk.offset, end] forHTTPHeaderField:@"Range"]; + + __weak typeof(self) weakSelf = self; + __block NSURLSessionDataTask *task = nil; + task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + __strong typeof(weakSelf) self = weakSelf; + if (!self) return; + [self completeChunk:chunk task:task data:data response:response error:error]; + }]; + [self.tasks addObject:task]; + self.activeTaskCount++; + [task resume]; +} + +- (NSError *)validationErrorForChunk:(YouModDownloadChunk *)chunk data:(NSData *)data response:(NSURLResponse *)response error:(NSError *)error { + if (error) return error; + + NSHTTPURLResponse *httpResponse = [response isKindOfClass:NSHTTPURLResponse.class] ? (NSHTTPURLResponse *)response : nil; + NSInteger statusCode = httpResponse.statusCode; + BOOL statusOK = statusCode == 206 || (self.totalChunkCount == 1 && statusCode == 200); + if (httpResponse && !statusOK) + return [self errorWithCode:24 message:@"Range request rejected by server"]; + + if (data.length != chunk.length) + return [self errorWithCode:25 message:@"Incomplete chunk"]; + + return nil; +} + +- (void)completeChunk:(YouModDownloadChunk *)chunk task:(NSURLSessionDataTask *)task data:(NSData *)data response:(NSURLResponse *)response error:(NSError *)error { + dispatch_async(self.stateQueue, ^{ + if (self.activeTaskCount > 0) self.activeTaskCount--; + if (task) [self.tasks removeObject:task]; + if (self.finished || self.cancelled) return; + + NSError *validationError = [self validationErrorForChunk:chunk data:data response:response error:error]; + if (validationError) { + if (validationError.code == 24) { + [self finishWithErrorLocked:validationError]; + return; + } + if (chunk.attempts + 1 < YouModFastDownloadMaxAttempts) { + chunk.attempts++; + [self.pendingChunks insertObject:chunk atIndex:0]; + [self scheduleChunksLocked]; + } else { + [self finishWithErrorLocked:validationError]; + } + return; + } + + NSData *chunkData = [data copy]; + dispatch_async(self.fileQueue, ^{ + NSError *writeError = nil; + @try { + [self.fileHandle seekToFileOffset:chunk.offset]; + [self.fileHandle writeData:chunkData]; + } @catch (NSException *exception) { + writeError = [self errorWithCode:26 message:exception.reason ?: @"Write failed"]; + } + + dispatch_async(self.stateQueue, ^{ + if (self.finished || self.cancelled) return; + if (writeError) { + [self finishWithErrorLocked:writeError]; + return; + } + + self.completedBytes += chunkData.length; + if (self.progress) { + unsigned long long completed = self.completedBytes; + dispatch_async(dispatch_get_main_queue(), ^{ + self.progress(completed); + }); + } + [self scheduleChunksLocked]; + }); + }); + }); +} + +- (void)finishSuccessfullyLocked { + if (self.finished) return; + self.finished = YES; + [self.session finishTasksAndInvalidate]; + dispatch_async(self.fileQueue, ^{ + @try { + [self.fileHandle closeFile]; + } @catch (__unused NSException *exception) { + } + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.completion) self.completion(self.destinationURL, nil); + }); + }); +} + +- (void)finishWithErrorLocked:(NSError *)error { + if (self.finished) return; + self.finished = YES; + for (NSURLSessionDataTask *task in self.tasks) [task cancel]; + [self.tasks removeAllObjects]; + [self.session invalidateAndCancel]; + dispatch_async(self.fileQueue, ^{ + @try { + [self.fileHandle closeFile]; + } @catch (__unused NSException *exception) { + } + [NSFileManager.defaultManager removeItemAtURL:self.destinationURL error:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.completion) self.completion(nil, error ?: [self errorWithCode:27 message:@"Download failed"]); + }); + }); +} + +@end + +static __weak YTPlayerViewController *YouModCurrentPlayerViewController; + +void YouModDownloadSetCurrentPlayer(YTPlayerViewController *player) { + YouModCurrentPlayerViewController = player; +} + +static NSString *YouModStringFromSelector(id object, SEL selector) { + if (!object) return nil; + id value = nil; + if ([object respondsToSelector:selector]) { + value = ((id (*)(id, SEL))objc_msgSend)(object, selector); + } else { + @try { + value = [object valueForKey:NSStringFromSelector(selector)]; + } @catch (__unused NSException *exception) { + value = nil; + } + } + if ([value isKindOfClass:NSString.class]) return value; + if ([value isKindOfClass:NSURL.class]) return [(NSURL *)value absoluteString]; + if ([value respondsToSelector:@selector(stringValue)]) return [value stringValue]; + return [value respondsToSelector:@selector(description)] ? [value description] : nil; +} + +static id YouModObjectFromSelector(id object, SEL selector) { + if (!object) return nil; + if ([object respondsToSelector:selector]) { + return ((id (*)(id, SEL))objc_msgSend)(object, selector); + } + @try { + return [object valueForKey:NSStringFromSelector(selector)]; + } @catch (__unused NSException *exception) { + return nil; + } +} + +static unsigned long long YouModUnsignedLongLongFromSelector(id object, SEL selector) { + if (!object) return 0; + if ([object respondsToSelector:selector]) { + return ((unsigned long long (*)(id, SEL))objc_msgSend)(object, selector); + } + @try { + id value = [object valueForKey:NSStringFromSelector(selector)]; + if ([value respondsToSelector:@selector(unsignedLongLongValue)]) + return [value unsignedLongLongValue]; + } @catch (__unused NSException *exception) { + } + return 0; +} + +static BOOL YouModBoolFromSelector(id object, SEL selector) { + if (!object) return NO; + if ([object respondsToSelector:selector]) { + return ((BOOL (*)(id, SEL))objc_msgSend)(object, selector); + } + @try { + id value = [object valueForKey:NSStringFromSelector(selector)]; + if ([value respondsToSelector:@selector(boolValue)]) + return [value boolValue]; + } @catch (__unused NSException *exception) { + } + return NO; +} + +static NSInteger YouModIntegerFromSelector(id object, SEL selector) { + if (!object) return 0; + if ([object respondsToSelector:selector]) { + return ((NSInteger (*)(id, SEL))objc_msgSend)(object, selector); + } + @try { + id value = [object valueForKey:NSStringFromSelector(selector)]; + if ([value respondsToSelector:@selector(integerValue)]) + return [value integerValue]; + } @catch (__unused NSException *exception) { + } + return 0; +} + +static UIViewController *YouModTopViewController(UIViewController *root) { + if (!root) { + UIWindow *keyWindow = nil; + for (UIWindow *window in UIApplication.sharedApplication.windows) { + if (window.isKeyWindow) { + keyWindow = window; + break; + } + } + root = keyWindow.rootViewController; + } + while (root.presentedViewController) root = root.presentedViewController; + if ([root isKindOfClass:UINavigationController.class]) + return YouModTopViewController(((UINavigationController *)root).topViewController); + if ([root isKindOfClass:UITabBarController.class]) + return YouModTopViewController(((UITabBarController *)root).selectedViewController); + return root; +} + +static void YouModSendToast(NSString *message, id responder) { + Class toastClass = NSClassFromString(@"YTToastResponderEvent"); + id event = [toastClass eventWithMessage:message firstResponder:responder ?: YouModTopViewController(nil)]; + if ([event respondsToSelector:@selector(send)]) { + [event send]; + return; + } + + UIViewController *presenter = YouModTopViewController([responder isKindOfClass:UIViewController.class] ? responder : nil); + if (!presenter) return; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; + [presenter presentViewController:alert animated:YES completion:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [alert dismissViewControllerAnimated:YES completion:nil]; + }); + }]; +} + +static NSString *YouModByteCount(unsigned long long bytes) { + if (bytes == 0) return nil; + NSByteCountFormatter *formatter = [NSByteCountFormatter new]; + formatter.countStyle = NSByteCountFormatterCountStyleFile; + return [formatter stringFromByteCount:(long long)bytes]; +} + +static NSString *YouModGenerateCPN(void) { + static NSString *const alphabet = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + NSMutableString *nonce = [NSMutableString stringWithCapacity:16]; + for (NSUInteger i = 0; i < 16; i++) + [nonce appendFormat:@"%C", [alphabet characterAtIndex:arc4random_uniform((uint32_t)alphabet.length)]]; + return nonce; +} + +static NSString *YouModURLStringBypassingThrottle(NSString *urlString) { + if (urlString.length == 0) return urlString; + NSURLComponents *components = [NSURLComponents componentsWithString:urlString]; + if (components) { + NSMutableArray *queryItems = [components.queryItems mutableCopy] ?: [NSMutableArray array]; + NSMutableArray *filtered = [NSMutableArray array]; + for (NSURLQueryItem *item in queryItems) { + if (![item.name isEqualToString:@"n"]) + [filtered addObject:item]; + } + BOOL hasRateBypass = NO; + for (NSURLQueryItem *item in filtered) { + if ([item.name isEqualToString:@"ratebypass"]) { hasRateBypass = YES; break; } + } + if (!hasRateBypass) + [filtered addObject:[NSURLQueryItem queryItemWithName:@"ratebypass" value:@"yes"]]; + components.queryItems = filtered; + NSString *result = components.string; + if (result.length > 0) return result; + } + return urlString; +} + +static NSString *YouModURLStringWithCPN(NSString *urlString) { + if (urlString.length == 0) return urlString; + urlString = YouModURLStringBypassingThrottle(urlString); + if ([urlString containsString:@"cpn="]) return urlString; + Class ytDataUtils = NSClassFromString(@"YTDataUtils"); + NSString *cpn = ((id (*)(Class, SEL))objc_msgSend)(ytDataUtils, @selector(generateClientSideNonce)); + if (![cpn isKindOfClass:NSString.class] || cpn.length == 0) + cpn = YouModGenerateCPN(); + NSString *separator = [urlString containsString:@"?"] ? @"&" : @"?"; + return [NSString stringWithFormat:@"%@%@cpn=%@", urlString, separator, cpn]; +} + +static NSString *YouModSanitizedFileName(NSString *name) { + if (name.length == 0) return @"YouTube Video"; + NSMutableCharacterSet *invalid = [NSMutableCharacterSet characterSetWithCharactersInString:@"/\\?%*|\"<>:"]; + [invalid formUnionWithCharacterSet:NSCharacterSet.newlineCharacterSet]; + NSArray *parts = [name componentsSeparatedByCharactersInSet:invalid]; + NSString *clean = [[parts componentsJoinedByString:@" "] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; + while ([clean containsString:@" "]) clean = [clean stringByReplacingOccurrencesOfString:@" " withString:@" "]; + if (clean.length > 120) clean = [clean substringToIndex:120]; + return clean.length ? clean : @"YouTube Video"; +} + +static NSURL *YouModDownloadsDirectoryURL(void) { + NSURL *documentsURL = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject; + NSURL *downloadsURL = [documentsURL URLByAppendingPathComponent:@"YouMod Downloads" isDirectory:YES]; + [NSFileManager.defaultManager createDirectoryAtURL:downloadsURL withIntermediateDirectories:YES attributes:nil error:nil]; + return downloadsURL; +} + +static NSString *YouModLastDownloadDiagnostic; + +static NSURL *YouModDiagnosticLogURL(void) { + return [YouModDownloadsDirectoryURL() URLByAppendingPathComponent:@"youmod-download-diagnostics.txt"]; +} + +static void YouModRecordDownloadDiagnostic(NSString *context, NSString *details) { + if (context.length == 0 && details.length == 0) return; + + NSDateFormatter *formatter = [NSDateFormatter new]; + formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss ZZZZZ"; + NSString *timestamp = [formatter stringFromDate:NSDate.date]; + NSString *entry = [NSString stringWithFormat:@"[%@]\n%@\n%@\n\n", timestamp ?: @"", context ?: @"", details ?: @""]; + YouModLastDownloadDiagnostic = entry; + + NSURL *logURL = YouModDiagnosticLogURL(); + NSData *data = [entry dataUsingEncoding:NSUTF8StringEncoding]; + if (![NSFileManager.defaultManager fileExistsAtPath:logURL.path]) + [NSFileManager.defaultManager createFileAtPath:logURL.path contents:nil attributes:nil]; + + NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:logURL.path]; + if (!handle) return; + @try { + [handle seekToEndOfFile]; + [handle writeData:data]; + [handle closeFile]; + } @catch (__unused NSException *exception) { + } +} + +static NSString *YouModDownloadDiagnosticText(void) { + if (YouModLastDownloadDiagnostic.length) return YouModLastDownloadDiagnostic; + NSString *log = [NSString stringWithContentsOfURL:YouModDiagnosticLogURL() encoding:NSUTF8StringEncoding error:nil]; + if (log.length == 0) return nil; + NSUInteger maxLength = 12000; + return log.length > maxLength ? [log substringFromIndex:log.length - maxLength] : log; +} + +static void YouModCopyDownloadDiagnostics(UIViewController *presenter) { + NSString *diagnostic = YouModDownloadDiagnosticText(); + if (diagnostic.length == 0) { + YouModSendToast(@"No download diagnostics yet.", presenter); + return; + } + UIPasteboard.generalPasteboard.string = diagnostic; + YouModSendToast(@"Copied download diagnostics", presenter); +} + +static NSURL *YouModUniqueFileURL(NSString *fileName, NSString *extension) { + NSString *safeName = YouModSanitizedFileName(fileName); + NSURL *directoryURL = YouModDownloadsDirectoryURL(); + NSURL *candidate = [directoryURL URLByAppendingPathComponent:[safeName stringByAppendingPathExtension:extension]]; + NSUInteger index = 2; + while ([NSFileManager.defaultManager fileExistsAtPath:candidate.path]) { + NSString *indexed = [NSString stringWithFormat:@"%@ %lu", safeName, (unsigned long)index++]; + candidate = [directoryURL URLByAppendingPathComponent:[indexed stringByAppendingPathExtension:extension]]; + } + return candidate; +} + +static NSURL *YouModTemporaryFileURL(NSString *extension) { + NSString *name = [[NSUUID UUID].UUIDString stringByAppendingPathExtension:extension]; + return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:name]]; +} + +static NSInteger YouModResolutionFromQuality(NSString *quality); +static NSInteger YouModFPSFromQuality(NSString *quality); +static NSInteger YouModNormalizedFPS(NSInteger fps); +static NSInteger YouModDisplayHeightForVideoHeight(NSInteger height); +static NSString *YouModQualityLabel(NSInteger height, NSInteger fps, NSString *fallback); +static BOOL YouModFFmpegKitAvailable(void); + +static unsigned long long YouModDurationMsForURL(NSURL *url) { + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; + if (!CMTIME_IS_NUMERIC(asset.duration) || !CMTIME_IS_VALID(asset.duration)) return 0; + Float64 seconds = CMTimeGetSeconds(asset.duration); + if (!isfinite(seconds) || seconds <= 0.0) return 0; + return (unsigned long long)llround(seconds * 1000.0); +} + +static NSString *YouModDurationSecondsArgument(unsigned long long durationMs) { + return [NSString stringWithFormat:@"%.3f", (double)durationMs / 1000.0]; +} + +static BOOL YouModCMTimeIsUsable(CMTime time) { + if (!CMTIME_IS_VALID(time) || !CMTIME_IS_NUMERIC(time) || CMTIME_IS_INDEFINITE(time)) return NO; + Float64 seconds = CMTimeGetSeconds(time); + return isfinite(seconds) && seconds > 0.0; +} + +static CMTime YouModMinUsableDuration(CMTime first, CMTime second) { + BOOL firstOK = YouModCMTimeIsUsable(first); + BOOL secondOK = YouModCMTimeIsUsable(second); + if (firstOK && secondOK) return CMTIME_COMPARE_INLINE(first, <, second) ? first : second; + if (firstOK) return first; + if (secondOK) return second; + return kCMTimeInvalid; +} + +static CMTime YouModExportDuration(AVAsset *videoAsset, AVAsset *audioAsset, unsigned long long expectedDurationMs) { + CMTime duration = kCMTimeInvalid; + if (expectedDurationMs > 0) + duration = CMTimeMakeWithSeconds((double)expectedDurationMs / 1000.0, 600); + + CMTime videoDuration = YouModMinUsableDuration(videoAsset.duration, [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject].timeRange.duration); + CMTime audioDuration = audioAsset ? YouModMinUsableDuration(audioAsset.duration, [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject].timeRange.duration) : kCMTimeInvalid; + CMTime mediaDuration = audioAsset ? YouModMinUsableDuration(videoDuration, audioDuration) : videoDuration; + + if (!YouModCMTimeIsUsable(duration)) return mediaDuration; + if (YouModCMTimeIsUsable(mediaDuration) && CMTIME_COMPARE_INLINE(duration, >, mediaDuration)) + return mediaDuration; + return duration; +} + +static NSMutableArray *YouModFFmpegKitLoadEntries(void) { + static NSMutableArray *entries = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + entries = [NSMutableArray array]; + }); + return entries; +} + +static void YouModAppendFFmpegKitLoadEntry(NSString *format, ...) { + if (format.length == 0) return; + + va_list arguments; + va_start(arguments, format); + NSString *entry = [[NSString alloc] initWithFormat:format arguments:arguments]; + va_end(arguments); + if (entry.length == 0) return; + + NSMutableArray *entries = YouModFFmpegKitLoadEntries(); + @synchronized(entries) { + [entries addObject:entry]; + if (entries.count > 220) + [entries removeObjectsInRange:NSMakeRange(0, entries.count - 220)]; + } +} + +static NSArray *YouModFFmpegKitSearchDirectories(void) { + NSMutableOrderedSet *directories = [NSMutableOrderedSet orderedSet]; + + // Path to YouMod.bundle/Frameworks inside the main app bundle + NSString *bundlePath = [[NSBundle.mainBundle resourcePath] stringByAppendingPathComponent:@"YouMod.bundle"]; + NSString *frameworksInsideBundle = [bundlePath stringByAppendingPathComponent:@"Frameworks"]; + + // Safety check: only add if the directory actually exists + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:frameworksInsideBundle isDirectory:&isDir] && isDir) { + [directories addObject:frameworksInsideBundle]; + } + + return directories.array; +} + +static void YouModDlopenPath(NSString *path, BOOL requireExistingFile) { + if (path.length == 0) return; + if (requireExistingFile && ![NSFileManager.defaultManager fileExistsAtPath:path]) { + YouModAppendFFmpegKitLoadEntry(@"missing %@", path); + return; + } + + dlerror(); + void *handle = dlopen(path.fileSystemRepresentation, RTLD_NOW | RTLD_GLOBAL); + const char *error = dlerror(); + if (handle) { + YouModAppendFFmpegKitLoadEntry(@"loaded %@", path); + } else { + YouModAppendFFmpegKitLoadEntry(@"failed %@\n dlerror=%@", path, error ? [NSString stringWithUTF8String:error] : @"unknown"); + } +} + +static void YouModDlopenPathIfPresent(NSString *path) { + YouModDlopenPath(path, YES); +} + +static void YouModLoadFrameworkBinary(NSString *directory, NSString *frameworkName, NSString *binaryName) { + if (directory.length == 0 || frameworkName.length == 0 || binaryName.length == 0) return; + YouModDlopenPathIfPresent([[directory stringByAppendingPathComponent:[frameworkName stringByAppendingString:@".framework"]] stringByAppendingPathComponent:binaryName]); + YouModDlopenPathIfPresent([[directory stringByAppendingPathComponent:[frameworkName stringByAppendingString:@".framework"]] stringByAppendingPathComponent:frameworkName]); +} + +static void YouModLoadFFmpegKitIfNeeded(void) { + static BOOL attempted = NO; + if (NSClassFromString(@"FFmpegKit")) return; + if (attempted) return; + attempted = YES; + + YouModAppendFFmpegKitLoadEntry(@"[YouMod] Starting bundled FFmpegKit load..."); + + // Order is important: load dependencies (avutil, etc.) before the main toolkit + NSArray *> *frameworks = @[ + @[@"libavutil", @"libavutil"], + @[@"libswresample", @"libswresample"], + @[@"libswscale", @"libswscale"], + @[@"libavcodec", @"libavcodec"], + @[@"libavformat", @"libavformat"], + @[@"libavfilter", @"libavfilter"], + @[@"libavdevice", @"libavdevice"], + @[@"ffmpegkit", @"ffmpegkit"], + @[@"FFmpegKit", @"FFmpegKit"], + ]; + + NSArray *searchDirs = YouModFFmpegKitSearchDirectories(); + if (searchDirs.count == 0) { + YouModAppendFFmpegKitLoadEntry(@"[YouMod] Error: Bundled Frameworks directory not found."); + return; + } + + // Only iterate through our controlled bundle directory + for (NSString *directory in searchDirs) { + for (NSArray *framework in frameworks) { + // This helper uses dlopen on the direct path within our bundle + YouModLoadFrameworkBinary(directory, framework.firstObject, framework.lastObject); + } + + if (NSClassFromString(@"FFmpegKit")) { + YouModAppendFFmpegKitLoadEntry(@"[YouMod] Success: FFmpegKit loaded from bundle."); + return; + } + } + + YouModAppendFFmpegKitLoadEntry(@"[YouMod] Critical: FFmpegKit could not be found in YouMod.bundle."); +} + +static Class YouModFFmpegKitClass(void) { + Class ffmpegKitClass = NSClassFromString(@"FFmpegKit"); + if (!ffmpegKitClass) { + YouModLoadFFmpegKitIfNeeded(); + ffmpegKitClass = NSClassFromString(@"FFmpegKit"); + } + return ffmpegKitClass; +} + +static BOOL YouModFFmpegKitAvailable(void) { + Class ffmpegKitClass = YouModFFmpegKitClass(); + return ffmpegKitClass && [ffmpegKitClass respondsToSelector:@selector(executeWithArgumentsAsync:withCompleteCallback:withLogCallback:withStatisticsCallback:)]; +} + +static NSString *YouModFFmpegKitDiagnosticText(YouModAudioOutputFormat *outputFormat, YouModMediaFormat *sourceFormat, NSString *videoID) { + YouModLoadFFmpegKitIfNeeded(); + + Class ffmpegKitClass = NSClassFromString(@"FFmpegKit"); + SEL executeSelector = @selector(executeWithArgumentsAsync:withCompleteCallback:withLogCallback:withStatisticsCallback:); + NSMutableArray *lines = [NSMutableArray array]; + NSBundle *mainBundle = NSBundle.mainBundle; + NSString *resourcePath = mainBundle.resourcePath ?: @""; + NSString *privateFrameworksPath = mainBundle.privateFrameworksPath ?: @""; + NSString *executablePath = mainBundle.executablePath ?: @""; + NSString *bundlePath = [resourcePath stringByAppendingPathComponent:@"YouMod.bundle"]; + NSString *packageFrameworkPath = [resourcePath stringByAppendingPathComponent:@"YouMod.bundle/Frameworks"]; + + [lines addObject:@"FFmpegKit lookup"]; + [lines addObject:[NSString stringWithFormat:@"videoID=%@", videoID ?: @""]]; + [lines addObject:[NSString stringWithFormat:@"requestedFormat=%@ (%@)", outputFormat.title ?: @"", outputFormat.identifier ?: @""]]; + [lines addObject:[NSString stringWithFormat:@"sourceMime=%@", sourceFormat.mimeType ?: @""]]; + [lines addObject:[NSString stringWithFormat:@"sourceQuality=%@", sourceFormat.qualityLabel ?: @""]]; + [lines addObject:[NSString stringWithFormat:@"sourceBytes=%llu", sourceFormat.contentLength]]; + [lines addObject:[NSString stringWithFormat:@"mainBundle=%@", mainBundle.bundlePath ?: @""]]; + [lines addObject:[NSString stringWithFormat:@"resourcePath=%@", resourcePath]]; + [lines addObject:[NSString stringWithFormat:@"privateFrameworksPath=%@", privateFrameworksPath]]; + [lines addObject:[NSString stringWithFormat:@"executablePath=%@", executablePath]]; + [lines addObject:[NSString stringWithFormat:@"YouMod.bundle exists=%@", [NSFileManager.defaultManager fileExistsAtPath:bundlePath] ? @"YES" : @"NO"]]; + [lines addObject:[NSString stringWithFormat:@"YouMod.bundle/Frameworks exists=%@", [NSFileManager.defaultManager fileExistsAtPath:packageFrameworkPath] ? @"YES" : @"NO"]]; + [lines addObject:[NSString stringWithFormat:@"FFmpegKit class=%@", ffmpegKitClass ? @"YES" : @"NO"]]; + [lines addObject:[NSString stringWithFormat:@"FFmpegKit execute selector=%@", [ffmpegKitClass respondsToSelector:executeSelector] ? @"YES" : @"NO"]]; + [lines addObject:[NSString stringWithFormat:@"ReturnCode class=%@", NSClassFromString(@"ReturnCode") ? @"YES" : @"NO"]]; + [lines addObject:@"searchDirectories:"]; + for (NSString *directory in YouModFFmpegKitSearchDirectories()) { + BOOL isDirectory = NO; + BOOL exists = [NSFileManager.defaultManager fileExistsAtPath:directory isDirectory:&isDirectory]; + [lines addObject:[NSString stringWithFormat:@" %@ exists=%@ directory=%@", directory, exists ? @"YES" : @"NO", isDirectory ? @"YES" : @"NO"]]; + } + + NSMutableArray *entries = YouModFFmpegKitLoadEntries(); + [lines addObject:@"dlopenAttempts:"]; + @synchronized(entries) { + [lines addObjectsFromArray:entries]; + } + return [lines componentsJoinedByString:@"\n"]; +} + +static void YouModCancelFFmpegKit(void) { + Class ffmpegKitClass = YouModFFmpegKitClass(); + if ([ffmpegKitClass respondsToSelector:@selector(cancel)]) + ((void (*)(Class, SEL))objc_msgSend)(ffmpegKitClass, @selector(cancel)); +} + +static NSError *YouModFFmpegErrorFromSession(id session) { + NSString *failure = YouModStringFromSelector(session, @selector(getFailStackTrace)); + NSString *message = failure.length ? failure : @"FFmpeg failed"; + return [NSError errorWithDomain:@"YouMod" code:7 userInfo:@{NSLocalizedDescriptionKey: message}]; +} + +static BOOL YouModPathExtensionIsPhotosVideo(NSString *extension) { + NSString *lower = extension.lowercaseString ?: @""; + return [@[@"mp4", @"m4v", @"mov"] containsObject:lower]; +} + +static BOOL YouModStartFFmpegKitMerge(NSURL *videoURL, NSURL *audioURL, NSURL *outputURL, unsigned long long durationMs, void (^progress)(float progress), YouModMergeCompletion completion) { + Class ffmpegKitClass = YouModFFmpegKitClass(); + SEL executeSelector = @selector(executeWithArgumentsAsync:withCompleteCallback:withLogCallback:withStatisticsCallback:); + if (![ffmpegKitClass respondsToSelector:executeSelector]) return NO; + + NSMutableArray *arguments = [@[ + @"-y", + @"-i", videoURL.path, + @"-i", audioURL.path, + @"-map", @"0:v:0", + @"-map", @"1:a:0", + ] mutableCopy]; + if (durationMs > 0) + [arguments addObjectsFromArray:@[@"-t", YouModDurationSecondsArgument(durationMs)]]; + [arguments addObjectsFromArray:@[ + @"-c", @"copy", + @"-shortest", + @"-avoid_negative_ts", @"make_zero", + ]]; + if (YouModPathExtensionIsPhotosVideo(outputURL.pathExtension)) + [arguments addObjectsFromArray:@[@"-movflags", @"+faststart"]]; + [arguments addObject:outputURL.path]; + + id completeBlock = [^(id session) { + Class returnCodeClass = NSClassFromString(@"ReturnCode"); + id returnCode = YouModObjectFromSelector(session, @selector(getReturnCode)); + BOOL success = NO; + if ([returnCodeClass respondsToSelector:@selector(isSuccess:)]) + success = ((BOOL (*)(Class, SEL, id))objc_msgSend)(returnCodeClass, @selector(isSuccess:), returnCode); + + NSError *error = success ? nil : YouModFFmpegErrorFromSession(session); + dispatch_async(dispatch_get_main_queue(), ^{ + if (success && [NSFileManager.defaultManager fileExistsAtPath:outputURL.path]) { + completion(YES, nil); + } else { + completion(NO, error ?: [NSError errorWithDomain:@"YouMod" code:7 userInfo:@{NSLocalizedDescriptionKey: @"Merge failed"}]); + } + }); + } copy]; + + id statisticsBlock = durationMs ? [^(id statistics) { + if (!progress || ![statistics respondsToSelector:@selector(getTime)]) return; + double timeMs = ((double (*)(id, SEL))objc_msgSend)(statistics, @selector(getTime)); + if (!isfinite(timeMs) || timeMs <= 0.0) return; + float mergeProgress = 0.985f + (0.01f * fminf((float)(timeMs / (double)durationMs), 1.0f)); + dispatch_async(dispatch_get_main_queue(), ^{ + progress(mergeProgress); + }); + } copy] : nil; + + ((id (*)(Class, SEL, NSArray *, id, id, id))objc_msgSend)(ffmpegKitClass, executeSelector, arguments, completeBlock, nil, statisticsBlock); + return YES; +} + +static BOOL YouModStartFFmpegKitAudioConvert(NSURL *inputURL, NSURL *outputURL, YouModAudioOutputFormat *outputFormat, unsigned long long durationMs, void (^progress)(float progress), YouModMergeCompletion completion) { + Class ffmpegKitClass = YouModFFmpegKitClass(); + SEL executeSelector = @selector(executeWithArgumentsAsync:withCompleteCallback:withLogCallback:withStatisticsCallback:); + if (![ffmpegKitClass respondsToSelector:executeSelector] || outputFormat.ffmpegArguments.count == 0) return NO; + + NSMutableArray *arguments = [@[@"-y", @"-i", inputURL.path] mutableCopy]; + [arguments addObjectsFromArray:outputFormat.ffmpegArguments]; + [arguments addObject:outputURL.path]; + + id completeBlock = [^(id session) { + Class returnCodeClass = NSClassFromString(@"ReturnCode"); + id returnCode = YouModObjectFromSelector(session, @selector(getReturnCode)); + BOOL success = NO; + if ([returnCodeClass respondsToSelector:@selector(isSuccess:)]) + success = ((BOOL (*)(Class, SEL, id))objc_msgSend)(returnCodeClass, @selector(isSuccess:), returnCode); + + NSError *error = success ? nil : YouModFFmpegErrorFromSession(session); + dispatch_async(dispatch_get_main_queue(), ^{ + if (success && [NSFileManager.defaultManager fileExistsAtPath:outputURL.path]) { + completion(YES, nil); + } else { + completion(NO, error ?: [NSError errorWithDomain:@"YouMod" code:13 userInfo:@{NSLocalizedDescriptionKey: @"Conversion failed"}]); + } + }); + } copy]; + + id statisticsBlock = durationMs ? [^(id statistics) { + if (!progress || ![statistics respondsToSelector:@selector(getTime)]) return; + double timeMs = ((double (*)(id, SEL))objc_msgSend)(statistics, @selector(getTime)); + if (!isfinite(timeMs) || timeMs <= 0.0) return; + float convertProgress = 0.985f + (0.01f * fminf((float)(timeMs / (double)durationMs), 1.0f)); + dispatch_async(dispatch_get_main_queue(), ^{ + progress(convertProgress); + }); + } copy] : nil; + + ((id (*)(Class, SEL, NSArray *, id, id, id))objc_msgSend)(ffmpegKitClass, executeSelector, arguments, completeBlock, nil, statisticsBlock); + return YES; +} + +static NSString *YouModMimeDetail(NSString *mimeType) { + NSString *lower = mimeType.lowercaseString ?: @""; + if ([lower containsString:@"mp4"]) return @"MP4"; + if ([lower containsString:@"webm"]) return @"WebM"; + if ([lower containsString:@"mp3"]) return @"MP3"; + if ([lower containsString:@"aac"]) return @"AAC"; + return mimeType.length ? mimeType : @"Stream"; +} + +static NSString *YouModFileExtensionForFormat(YouModMediaFormat *format, NSString *fallbackExtension) { + NSString *lower = format.mimeType.lowercaseString ?: @""; + if ([lower containsString:@"webm"]) return @"webm"; + if ([lower containsString:@"matroska"]) return @"mkv"; + if ([lower containsString:@"quicktime"]) return @"mov"; + if ([lower containsString:@"m4a"]) return @"m4a"; + if ([lower containsString:@"mp4"]) return @"mp4"; + return fallbackExtension ?: @"mp4"; +} + +static BOOL YouModFormatLooksMP4Family(YouModMediaFormat *format) { + NSString *mime = format.mimeType.lowercaseString ?: @""; + NSString *extension = YouModFileExtensionForFormat(format, @"").lowercaseString ?: @""; + return [mime containsString:@"mp4"] || [mime containsString:@"m4a"] || [mime containsString:@"quicktime"] || [@[@"mp4", @"m4a", @"m4v", @"mov"] containsObject:extension]; +} + +static BOOL YouModFormatLooksWebM(YouModMediaFormat *format) { + NSString *mime = format.mimeType.lowercaseString ?: @""; + NSString *extension = YouModFileExtensionForFormat(format, @"").lowercaseString ?: @""; + return [mime containsString:@"webm"] || [extension isEqualToString:@"webm"]; +} + +static NSString *YouModMergedVideoOutputExtension(YouModMediaFormat *videoFormat, YouModMediaFormat *audioFormat) { + if (YouModFormatLooksMP4Family(videoFormat) && YouModFormatLooksMP4Family(audioFormat)) return @"mp4"; + if (YouModFormatLooksWebM(videoFormat) && YouModFormatLooksWebM(audioFormat)) return @"webm"; + return @"mkv"; +} + +static BOOL YouModVideoFileCanUseAVFoundation(NSURL *fileURL) { + return YouModPathExtensionIsPhotosVideo(fileURL.pathExtension); +} + +static BOOL YouModVideoFileCanSaveToPhotos(NSURL *fileURL) { + return YouModPathExtensionIsPhotosVideo(fileURL.pathExtension); +} + +static YouModAudioOutputFormat *YouModAudioOutputFormatMake(NSString *identifier, NSString *title, NSString *subtitle, NSString *fileExtension, NSArray *ffmpegArguments, BOOL passthroughWhenCompatible, BOOL supported) { + YouModAudioOutputFormat *format = [YouModAudioOutputFormat new]; + format.identifier = identifier; + format.title = title; + format.subtitle = subtitle; + format.fileExtension = fileExtension; + format.ffmpegArguments = ffmpegArguments; + format.passthroughWhenCompatible = passthroughWhenCompatible; + format.supported = supported; + return format; +} + +static NSArray *YouModAudioOutputFormats(void) { + static NSArray *formats = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + formats = @[ + YouModAudioOutputFormatMake(@"m4a", @"M4A", @"AAC container, passthrough when possible", @"m4a", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"aac", @"-b:a", @"192k", @"-movflags", @"+faststart"], YES, YES), + YouModAudioOutputFormatMake(@"aac", @"AAC", @"Lossy (192k)", @"aac", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"aac", @"-b:a", @"192k", @"-f", @"adts"], YES, YES), + YouModAudioOutputFormatMake(@"mp3", @"MP3", @"Lossy, widely compatible", @"mp3", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"libmp3lame", @"-q:a", @"2"], NO, YES), + YouModAudioOutputFormatMake(@"opus", @"Opus", @"Lossy, small file size", @"opus", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"libopus", @"-b:a", @"160k", @"-vbr", @"on"], NO, YES), + YouModAudioOutputFormatMake(@"ogg", @"OGG", @"Vorbis lossy", @"ogg", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"libvorbis", @"-q:a", @"6"], NO, YES), + YouModAudioOutputFormatMake(@"flac", @"FLAC", @"Lossless compressed", @"flac", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"flac", @"-compression_level", @"8"], NO, YES), + YouModAudioOutputFormatMake(@"alac", @"ALAC", @"Apple lossless (M4A)", @"m4a", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"alac", @"-movflags", @"+faststart"], NO, YES), + YouModAudioOutputFormatMake(@"wav", @"WAV", @"Uncompressed PCM", @"wav", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"pcm_s24le"], NO, YES), + YouModAudioOutputFormatMake(@"aiff", @"AIFF", @"Apple PCM", @"aiff", @[@"-map", @"0:a:0", @"-vn", @"-c:a", @"pcm_s24be"], NO, YES), + ]; + }); + return formats; +} + +static YouModAudioOutputFormat *YouModDefaultAudioOutputFormat(void) { + return [YouModAudioOutputFormats() firstObject]; +} + +static BOOL YouModAudioOutputFormatCanPassthrough(YouModAudioOutputFormat *outputFormat, YouModMediaFormat *sourceFormat) { + if (!outputFormat.passthroughWhenCompatible) return NO; + NSString *identifier = outputFormat.identifier.lowercaseString ?: @""; + NSString *mime = sourceFormat.mimeType.lowercaseString ?: @""; + NSString *extension = YouModFileExtensionForFormat(sourceFormat, @"").lowercaseString ?: @""; + if ([identifier isEqualToString:@"m4a"] || [identifier isEqualToString:@"aac"]) + return [extension isEqualToString:@"m4a"] || [mime containsString:@"mp4"] || [mime containsString:@"m4a"]; + return NO; +} + +static NSString *YouModAudioOutputFileExtension(YouModAudioOutputFormat *outputFormat, YouModMediaFormat *sourceFormat, BOOL passthrough) { + NSString *identifier = outputFormat.identifier.lowercaseString ?: @""; + NSString *mime = sourceFormat.mimeType.lowercaseString ?: @""; + if (passthrough && ([identifier isEqualToString:@"m4a"] || [identifier isEqualToString:@"aac"]) && ([mime containsString:@"mp4"] || [mime containsString:@"m4a"])) + return @"m4a"; + return outputFormat.fileExtension ?: YouModFileExtensionForFormat(sourceFormat, @"m4a"); +} + +static NSString *YouModAudioOutputSubtitle(YouModAudioOutputFormat *outputFormat) { + return [NSString stringWithFormat:@"%@", outputFormat.subtitle]; +} + +static NSString *YouModFormatSubtitle(YouModMediaFormat *format) { + NSMutableArray *parts = [NSMutableArray array]; + NSString *language = format.languageName.length ? format.languageName : format.languageCode; + if (language.length) [parts addObject:language]; + if (format.drcAudio) [parts addObject:@"DRC"]; + NSString *detail = YouModMimeDetail(format.mimeType); + if (detail.length) [parts addObject:detail]; + NSString *size = YouModByteCount(format.contentLength); + if (size.length) [parts addObject:size]; + return [parts componentsJoinedByString:@" - "]; +} + +static NSString *YouModVideoIDForPlayer(YTPlayerViewController *player) { + NSString *videoID = [player contentVideoID]; + if (videoID.length == 0) + videoID = [player currentVideoID]; + return videoID; +} + +static NSArray *YouModPlayerResponsesForPlayer(YTPlayerViewController *player) { + NSMutableArray *responses = [NSMutableArray array]; + id response = YouModObjectFromSelector(player, @selector(contentPlayerResponse)); + if (response) [responses addObject:response]; + + id activeVideo = YouModObjectFromSelector(player, @selector(activeVideo)); + response = YouModObjectFromSelector(activeVideo, @selector(contentPlayerResponse)); + if (response && ![responses containsObject:response]) [responses addObject:response]; + return responses.copy; +} + +// Where is this going to? +static NSArray *YouModCaptionTracksForPlayer(YTPlayerViewController *player) { + for (id response in YouModPlayerResponsesForPlayer(player)) { + id playerData = YouModObjectFromSelector(response, @selector(playerData)) ?: response; + id captions = YouModObjectFromSelector(playerData, @selector(captions)); + id tracklistRenderer = YouModObjectFromSelector(captions, @selector(playerCaptionsTracklistRenderer)); + NSArray *tracks = YouModObjectFromSelector(tracklistRenderer, @selector(captionTracksArray)); + if (tracks.count > 0) return tracks; + } + return nil; +} + +static id YouModPlayerDataForPlayer(YTPlayerViewController *player) { + id response = YouModPlayerResponsesForPlayer(player).firstObject; + id playerData = YouModObjectFromSelector(response, @selector(playerData)); + return playerData ?: response; +} + +static NSString *YouModTitleForPlayer(YTPlayerViewController *player) { + id playerData = YouModPlayerDataForPlayer(player); + id details = YouModObjectFromSelector(playerData, @selector(videoDetails)); + NSString *title = YouModStringFromSelector(details, @selector(title)); + NSString *author = YouModStringFromSelector(details, @selector(author)); + if (author.length && title.length) { + return [NSString stringWithFormat:@"%@ - %@", author, title]; + } else if (title.length) { + return title; + } + NSString *videoID = YouModVideoIDForPlayer(player); + return videoID.length ? [NSString stringWithFormat:@"YouTube %@", videoID] : @"YouTube Video"; +} + +static NSArray *YouModAdaptiveFormatObjectsForPlayer(YTPlayerViewController *player) { + NSMutableArray *formats = [NSMutableArray array]; + NSMutableSet *seenPointers = [NSMutableSet set]; + + void (^appendFormats)(NSArray *) = ^(NSArray *candidateFormats) { + if (![candidateFormats isKindOfClass:NSArray.class]) return; + for (id format in candidateFormats) { + NSString *key = [NSString stringWithFormat:@"%p", format]; + if ([seenPointers containsObject:key]) continue; + [seenPointers addObject:key]; + [formats addObject:format]; + } + }; + + id activeVideo = YouModObjectFromSelector(player, @selector(activeVideo)); + id streamingData = YouModObjectFromSelector(activeVideo, @selector(streamingData)); + appendFormats(YouModObjectFromSelector(streamingData, @selector(adaptiveStreams))); + appendFormats(YouModObjectFromSelector(activeVideo, @selector(selectableVideoFormats))); + + for (id response in YouModPlayerResponsesForPlayer(player)) { + id playerData = YouModObjectFromSelector(response, @selector(playerData)) ?: response; + id responseStreamingData = YouModObjectFromSelector(playerData, @selector(streamingData)); + appendFormats(YouModObjectFromSelector(responseStreamingData, @selector(adaptiveFormatsArray))); + } + + return formats.copy; +} + +static YouModMediaFormat *YouModMediaFormatFromStream(id stream, BOOL video) { + id formatStream = YouModObjectFromSelector(stream, @selector(formatStream)); + NSString *url = YouModStringFromSelector(stream, @selector(URL)); + if (url.length == 0) url = YouModStringFromSelector(formatStream, @selector(URL)); + if (url.length == 0) url = YouModStringFromSelector(stream, @selector(url)); + if (url.length == 0) url = YouModStringFromSelector(formatStream, @selector(url)); + if (url.length == 0) return nil; + + NSString *mimeType = YouModStringFromSelector(stream, @selector(mimeType)); + if (mimeType.length == 0) mimeType = YouModStringFromSelector(formatStream, @selector(mimeType)); + NSString *lowerMime = mimeType.lowercaseString ?: @""; + BOOL streamSaysVideo = YouModBoolFromSelector(stream, @selector(isVideo)) || YouModBoolFromSelector(formatStream, @selector(isVideo)); + BOOL streamSaysAudio = YouModBoolFromSelector(stream, @selector(isAudio)) || YouModBoolFromSelector(formatStream, @selector(isAudio)); + NSInteger itag = YouModIntegerFromSelector(stream, @selector(itag)); + if (itag == 0) itag = YouModIntegerFromSelector(formatStream, @selector(itag)); + + NSSet *mp4VideoItags = [NSSet setWithObjects:@18, @22, @37, @38, @59, @78, @133, @134, @135, @136, @137, @160, @212, @264, @266, @298, @299, nil]; + NSSet *m4aAudioItags = [NSSet setWithObjects:@139, @140, @141, @256, @258, @325, @328, nil]; + BOOL itagMatches = video ? [mp4VideoItags containsObject:@(itag)] : [m4aAudioItags containsObject:@(itag)]; + BOOL typeMatches = video ? ([lowerMime containsString:@"video/"] || streamSaysVideo || itagMatches) : ([lowerMime containsString:@"audio/"] || streamSaysAudio || itagMatches); + if (!typeMatches) return nil; + + BOOL mimeLooksMP4 = [lowerMime containsString:@"mp4"] || [lowerMime containsString:@"m4a"]; + BOOL canRemuxWithFFmpeg = YouModFFmpegKitAvailable(); + if (mimeType.length && !mimeLooksMP4 && !itagMatches && !canRemuxWithFFmpeg) return nil; + + YouModMediaFormat *format = [YouModMediaFormat new]; + format.source = stream; + format.video = video; + format.urlString = YouModURLStringWithCPN(url); + format.mimeType = mimeType.length ? mimeType : (video ? @"video/mp4" : @"audio/mp4"); + NSInteger height = YouModIntegerFromSelector(stream, @selector(height)); + if (height == 0) height = YouModIntegerFromSelector(formatStream, @selector(height)); + NSInteger fps = YouModIntegerFromSelector(stream, @selector(fps)); + if (fps == 0) fps = YouModIntegerFromSelector(formatStream, @selector(fps)); + if (fps == 0) fps = YouModIntegerFromSelector(stream, @selector(framesPerSecond)); + if (fps == 0) fps = YouModIntegerFromSelector(formatStream, @selector(framesPerSecond)); + if (fps == 0) fps = YouModIntegerFromSelector(stream, @selector(frameRate)); + if (fps == 0) fps = YouModIntegerFromSelector(formatStream, @selector(frameRate)); + fps = YouModNormalizedFPS(fps); + format.fps = fps; + format.qualityLabel = YouModStringFromSelector(stream, @selector(qualityLabel)); + if (format.qualityLabel.length == 0) format.qualityLabel = YouModStringFromSelector(formatStream, @selector(qualityLabel)); + if (video) { + NSInteger labelHeight = YouModResolutionFromQuality(format.qualityLabel); + NSInteger labelFPS = YouModFPSFromQuality(format.qualityLabel); + if (labelHeight == 960) format.qualityLabel = YouModQualityLabel(labelHeight, fps ?: labelFPS, nil); + else if (labelFPS == 0 && fps > 0) format.qualityLabel = YouModQualityLabel(height, fps, format.qualityLabel); + if (format.qualityLabel.length == 0) format.qualityLabel = YouModQualityLabel(height, fps, nil); + } + if (format.qualityLabel.length == 0 && !video) format.qualityLabel = @"Audio"; + if (!video) { + NSString *languageCode = YouModStringFromSelector(stream, @selector(languageCode)); + if (languageCode.length == 0) languageCode = YouModStringFromSelector(formatStream, @selector(languageCode)); + if (languageCode.length == 0) languageCode = YouModStringFromSelector(stream, @selector(language)); + if (languageCode.length == 0) languageCode = YouModStringFromSelector(formatStream, @selector(language)); + format.languageCode = languageCode; + + NSString *languageName = YouModStringFromSelector(stream, @selector(languageName)); + if (languageName.length == 0) languageName = YouModStringFromSelector(formatStream, @selector(languageName)); + if (languageName.length == 0) languageName = YouModStringFromSelector(stream, @selector(displayName)); + if (languageName.length == 0) languageName = YouModStringFromSelector(formatStream, @selector(displayName)); + format.languageName = languageName.length ? languageName : languageCode; + + NSMutableArray *audioTraits = [NSMutableArray array]; + for (NSString *value in @[ + mimeType ?: @"", + format.qualityLabel ?: @"", + YouModStringFromSelector(stream, @selector(audioTrack)) ?: @"", + YouModStringFromSelector(formatStream, @selector(audioTrack)) ?: @"", + YouModStringFromSelector(stream, @selector(audioTrackType)) ?: @"", + YouModStringFromSelector(formatStream, @selector(audioTrackType)) ?: @"", + YouModStringFromSelector(stream, @selector(audioTrackDisplayName)) ?: @"", + YouModStringFromSelector(formatStream, @selector(audioTrackDisplayName)) ?: @"", + ]) { + if (value.length) [audioTraits addObject:value]; + } + format.drcAudio = [[audioTraits componentsJoinedByString:@" "] localizedCaseInsensitiveContainsString:@"drc"]; + } + if (YouModBoolFromSelector(stream, @selector(hasContentLength)) || [stream respondsToSelector:@selector(contentLength)]) + format.contentLength = YouModUnsignedLongLongFromSelector(stream, @selector(contentLength)); + if (format.contentLength == 0 && (YouModBoolFromSelector(formatStream, @selector(hasContentLength)) || [formatStream respondsToSelector:@selector(contentLength)])) + format.contentLength = YouModUnsignedLongLongFromSelector(formatStream, @selector(contentLength)); + format.durationMs = YouModUnsignedLongLongFromSelector(stream, @selector(approxDurationMs)); + if (format.durationMs == 0) format.durationMs = YouModUnsignedLongLongFromSelector(formatStream, @selector(approxDurationMs)); + + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + NSDictionary *streamHeaders = YouModObjectFromSelector(stream, @selector(httpHeaders)); + if (![streamHeaders isKindOfClass:NSDictionary.class]) streamHeaders = YouModObjectFromSelector(formatStream, @selector(httpHeaders)); + if (![streamHeaders isKindOfClass:NSDictionary.class]) streamHeaders = YouModObjectFromSelector(stream, @selector(headers)); + if (![streamHeaders isKindOfClass:NSDictionary.class]) streamHeaders = YouModObjectFromSelector(formatStream, @selector(headers)); + if ([streamHeaders isKindOfClass:NSDictionary.class]) { + for (id key in streamHeaders) { + id value = streamHeaders[key]; + if ([key isKindOfClass:NSString.class] && [value isKindOfClass:NSString.class]) + headers[key] = value; + } + } + if (!YouModHTTPHeadersContainField(headers, @"Origin")) + headers[@"Origin"] = @"https://www.youtube.com"; + if (!YouModHTTPHeadersContainField(headers, @"Referer")) + headers[@"Referer"] = @"https://www.youtube.com/"; + format.httpHeaders = headers; + return format; +} + +static NSInteger YouModResolutionFromQuality(NSString *quality) { + NSScanner *scanner = [NSScanner scannerWithString:quality ?: @""]; + NSInteger value = 0; + [scanner scanInteger:&value]; + return value; +} + +static NSInteger YouModFPSFromQuality(NSString *quality) { + NSString *lower = quality.lowercaseString ?: @""; + NSRange pRange = [lower rangeOfString:@"p"]; + if (pRange.location != NSNotFound && pRange.location + 1 < lower.length) { + NSString *afterP = [lower substringFromIndex:pRange.location + 1]; + NSScanner *scanner = [NSScanner scannerWithString:afterP]; + NSInteger fps = 0; + if ([scanner scanInteger:&fps] && fps > 0) return fps; + } + if ([lower containsString:@"60fps"] || [lower containsString:@"60 fps"]) return 60; + if ([lower containsString:@"30fps"] || [lower containsString:@"30 fps"]) return 30; + return 0; +} + +static NSInteger YouModNormalizedFPS(NSInteger fps) { + if (fps >= 50 && fps <= 61) return 60; + if (fps >= 24 && fps <= 31) return 30; + return fps; +} + +static NSInteger YouModDisplayHeightForVideoHeight(NSInteger height) { + if (height >= 900 && height < 1080) return 1080; + return height; +} + +static NSString *YouModQualityLabel(NSInteger height, NSInteger fps, NSString *fallback) { + height = YouModDisplayHeightForVideoHeight(height); + fps = YouModNormalizedFPS(fps); + if (height > 0 && fps > 0) return [NSString stringWithFormat:@"%ldp%ld", (long)height, (long)fps]; + if (height > 0) return [NSString stringWithFormat:@"%ldp", (long)height]; + if (fallback.length && fps > 0 && ![fallback.lowercaseString containsString:@"fps"]) + return [NSString stringWithFormat:@"%@ %ldfps", fallback, (long)fps]; + return fallback; +} + +static NSArray *YouModFormatsForPlayer(YTPlayerViewController *player, BOOL video) { + NSMutableArray *formats = [NSMutableArray array]; + for (id stream in YouModAdaptiveFormatObjectsForPlayer(player)) { + YouModMediaFormat *format = YouModMediaFormatFromStream(stream, video); + if (format) [formats addObject:format]; + } + + [formats sortUsingComparator:^NSComparisonResult(YouModMediaFormat *left, YouModMediaFormat *right) { + if (video) { + NSInteger leftRes = YouModResolutionFromQuality(left.qualityLabel); + NSInteger rightRes = YouModResolutionFromQuality(right.qualityLabel); + if (leftRes != rightRes) return leftRes > rightRes ? NSOrderedAscending : NSOrderedDescending; + NSInteger leftFPS = left.fps ?: YouModFPSFromQuality(left.qualityLabel); + NSInteger rightFPS = right.fps ?: YouModFPSFromQuality(right.qualityLabel); + if (leftFPS != rightFPS) return leftFPS > rightFPS ? NSOrderedAscending : NSOrderedDescending; + } + + BOOL leftMP4 = YouModFormatLooksMP4Family(left); + BOOL rightMP4 = YouModFormatLooksMP4Family(right); + if (leftMP4 != rightMP4) return leftMP4 ? NSOrderedAscending : NSOrderedDescending; + + if (!video && IS_ENABLED(DownloadPreferDRCAudio) && left.drcAudio != right.drcAudio) + return left.drcAudio ? NSOrderedAscending : NSOrderedDescending; + if (left.contentLength != right.contentLength) + return left.contentLength > right.contentLength ? NSOrderedAscending : NSOrderedDescending; + return NSOrderedSame; + }]; + + NSMutableArray *unique = [NSMutableArray array]; + NSMutableSet *seen = [NSMutableSet set]; + for (YouModMediaFormat *format in formats) { + NSInteger fps = format.fps ?: YouModFPSFromQuality(format.qualityLabel); + NSString *key = video + ? [NSString stringWithFormat:@"%@-%ld-%@", format.qualityLabel ?: @"", (long)fps, YouModMimeDetail(format.mimeType)] + : [NSString stringWithFormat:@"%@-%@-%@-%@", format.qualityLabel ?: @"", format.languageCode ?: @"", format.drcAudio ? @"drc" : @"std", YouModMimeDetail(format.mimeType)]; + if ([seen containsObject:key]) continue; + [seen addObject:key]; + [unique addObject:format]; + } + return unique.copy; +} + +static YouModMediaFormat *YouModBestAudioFormatForPlayer(YTPlayerViewController *player) { + NSArray *audioFormats = YouModFormatsForPlayer(player, NO); + return audioFormats.firstObject; +} + +static UIViewController *YouModPresenterForSender(UIView *sender, YTPlayerViewController *player) { + UIViewController *presenter = nil; + if ([sender respondsToSelector:@selector(_viewControllerForAncestor)]) + presenter = [sender _viewControllerForAncestor]; + if (!presenter) presenter = player; + return YouModTopViewController(presenter); +} + +static YTPlayerViewController *YouModPlayerFromViewController(UIViewController *vc) { + Class playerClass = NSClassFromString(@"YTPlayerViewController"); + UIViewController *cursor = vc; + while (cursor) { + if (playerClass && [cursor isKindOfClass:playerClass]) return (YTPlayerViewController *)cursor; + id player = YouModObjectFromSelector(cursor, @selector(playerViewController)); + if (playerClass && [player isKindOfClass:playerClass]) return (YTPlayerViewController *)player; + cursor = cursor.parentViewController; + } + return YouModCurrentPlayerViewController; +} + +static NSURL *YouModThumbnailURLForVideoID(NSString *videoID) { + if (videoID.length == 0) return nil; + NSString *urlString = [NSString stringWithFormat:@"https://i.ytimg.com/vi/%@/maxresdefault.jpg", videoID]; + return [NSURL URLWithString:urlString]; +} + +static void YouModRequestPhotoAccess(void (^completion)(BOOL granted)) { + if (@available(iOS 14.0, *)) { + [PHPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelAddOnly handler:^(PHAuthorizationStatus status) { + completion(status == PHAuthorizationStatusAuthorized || status == PHAuthorizationStatusLimited); + }]; + } else { + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + completion(status == PHAuthorizationStatusAuthorized); + }]; + } +} + +static void YouModSaveVideoToPhotos(NSURL *fileURL, UIViewController *presenter, void (^completion)(BOOL success, NSError *error)) { + YouModRequestPhotoAccess(^(BOOL granted) { + if (!granted) { + NSError *error = [NSError errorWithDomain:@"YouMod" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Photos access denied"}]; + completion(NO, error); + return; + } + [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ + [PHAssetCreationRequest creationRequestForAssetFromVideoAtFileURL:fileURL]; + } completionHandler:^(BOOL success, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(success, error); + }); + }]; + }); +} + +static void YouModShareFile(NSURL *fileURL, UIViewController *presenter) { + if (!fileURL || !presenter) return; + UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:@[fileURL] applicationActivities:nil]; + // Fix for iPad and specific presentation alignment + if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { + activity.popoverPresentationController.sourceView = presenter.view; + // Position at the bottom center of the screen + activity.popoverPresentationController.sourceRect = CGRectMake(presenter.view.bounds.size.width / 2, presenter.view.bounds.size.height, 0, 0); + activity.popoverPresentationController.permittedArrowDirections = 0; // No arrow pointing to a button + } else { + // On iPhone, UIActivityViewController naturally comes from the bottom center + activity.popoverPresentationController.sourceView = presenter.view; + } + [presenter presentViewController:activity animated:YES completion:nil]; +} + +static void YouModPresentMenu(NSString *title, NSArray *items, UIViewController *presenter, UIView *sender) { + presenter = YouModTopViewController(presenter); + Class sheetClass = NSClassFromString(@"YTDefaultSheetController"); + if (sheetClass && [sheetClass respondsToSelector:@selector(sheetControllerWithParentResponder:)]) { + YTDefaultSheetController *sheet = [sheetClass sheetControllerWithParentResponder:presenter]; + Class actionClass = NSClassFromString(@"YTActionSheetAction"); + for (YouModMenuItem *item in items) { + id action = nil; + if ([actionClass respondsToSelector:@selector(actionWithTitle:subtitle:iconImage:handler:)]) { + action = ((id (*)(Class, SEL, NSString *, NSString *, UIImage *, id))objc_msgSend)(actionClass, @selector(actionWithTitle:subtitle:iconImage:handler:), item.title, item.subtitle, item.iconImage, ^(__unused id action) { + if (item.handler) item.handler(); + }); + } else { + action = ((id (*)(Class, SEL, NSString *, NSInteger, id))objc_msgSend)(actionClass, @selector(actionWithTitle:style:handler:), item.title, 0, ^(__unused id action) { + if (item.handler) item.handler(); + }); + } + if (action) [sheet addAction:action]; + } + if (sender && [sheet respondsToSelector:@selector(presentFromView:animated:completion:)]) + [sheet presentFromView:sender animated:YES completion:nil]; + else + [sheet presentFromViewController:presenter animated:YES completion:nil]; + return; + } + + UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + for (YouModMenuItem *item in items) { + NSString *rowTitle = item.subtitle.length ? [NSString stringWithFormat:@"%@\n%@", item.title, item.subtitle] : item.title; + [alert addAction:[UIAlertAction actionWithTitle:rowTitle style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { + if (item.handler) item.handler(); + }]]; + } + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + alert.popoverPresentationController.sourceView = sender ?: presenter.view; + [presenter presentViewController:alert animated:YES completion:nil]; +} + +@implementation YouModDownloadCoordinator + ++ (instancetype)sharedCoordinator { + static YouModDownloadCoordinator *coordinator; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + coordinator = [YouModDownloadCoordinator new]; + }); + return coordinator; +} + +- (instancetype)init { + self = [super init]; + if (self) { + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + configuration.HTTPAdditionalHeaders = @{ + @"User-Agent": @"Mozilla/5.0", + @"Origin": @"https://www.youtube.com", + @"Referer": @"https://www.youtube.com/", + }; + configuration.HTTPMaximumConnectionsPerHost = YouModFastDownloadConcurrency; + configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + configuration.timeoutIntervalForResource = 300; + _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; + } + return self; +} + +- (void)showProgressWithTitle:(NSString *)title presenter:(UIViewController *)presenter { + self.presenter = presenter; + self.baseProgressTitle = title; + self.downloadStartTime = [NSDate timeIntervalSinceReferenceDate]; + self.progressAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"%@ - 0%%", title] message:@"\n" preferredStyle:UIAlertControllerStyleAlert]; + self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + self.progressView.progress = 0.0; + self.progressView.translatesAutoresizingMaskIntoConstraints = NO; + [self.progressAlert.view addSubview:self.progressView]; + [NSLayoutConstraint activateConstraints:@[ + [self.progressView.leadingAnchor constraintEqualToAnchor:self.progressAlert.view.leadingAnchor constant:24.0], + [self.progressView.trailingAnchor constraintEqualToAnchor:self.progressAlert.view.trailingAnchor constant:-24.0], + [self.progressView.bottomAnchor constraintEqualToAnchor:self.progressAlert.view.bottomAnchor constant:-56.0], + ]]; + __weak typeof(self) weakSelf = self; + [self.progressAlert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction *action) { + [weakSelf cancelWithMessage:@"Download cancelled"]; + }]]; + [presenter presentViewController:self.progressAlert animated:YES completion:nil]; +} + +- (void)updateProgressTitle:(NSString *)title progress:(float)progress { + self.progressAlert.title = [NSString stringWithFormat:@"%@ - %ld%%", title, (long)lrintf(progress * 100.0f)]; + self.progressAlert.message = @"\n"; + [self.progressView setProgress:progress animated:YES]; +} + +- (void)cancelWithMessage:(NSString *)message { + [self.task cancel]; + [self.metadataTask cancel]; + [self.rangeDownloader cancel]; + YouModCancelFFmpegKit(); + self.task = nil; + self.metadataTask = nil; + self.rangeDownloader = nil; + self.fileCompletion = nil; + self.active = NO; + self.cancelled = YES; + [self cleanupTemporaryFiles]; + if (message.length) YouModSendToast(message, self.presenter); +} + +- (void)cleanupTemporaryFiles { + if (self.videoTempURL) [NSFileManager.defaultManager removeItemAtURL:self.videoTempURL error:nil]; + if (self.audioTempURL) [NSFileManager.defaultManager removeItemAtURL:self.audioTempURL error:nil]; + self.videoTempURL = nil; + self.audioTempURL = nil; +} + +- (void)downloadURL:(NSURL *)url toURL:(NSURL *)destinationURL expectedBytes:(unsigned long long)expectedBytes headers:(NSDictionary *)headers completion:(YouModFileDownloadCompletion)completion { + self.currentResolvedSizeAddedToTotal = NO; + self.currentExpectedBytes = expectedBytes; + self.currentBytes = 0; + if (expectedBytes == 0) { + __weak typeof(self) weakSelf = self; + [self resolveExpectedBytesForURL:url headers:headers completion:^(unsigned long long bytes) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || self.cancelled) return; + if (bytes > 0) [self adjustCurrentExpectedBytesIfNeeded:bytes]; + [self beginDownloadURL:url toURL:destinationURL expectedBytes:bytes headers:headers allowFast:YES completion:completion]; + }]; + return; + } + [self beginDownloadURL:url toURL:destinationURL expectedBytes:expectedBytes headers:headers allowFast:YES completion:completion]; +} + +- (void)beginDownloadURL:(NSURL *)url toURL:(NSURL *)destinationURL expectedBytes:(unsigned long long)expectedBytes headers:(NSDictionary *)headers allowFast:(BOOL)allowFast completion:(YouModFileDownloadCompletion)completion { + self.destinationURL = destinationURL; + self.currentExpectedBytes = expectedBytes; + self.currentBytes = 0; + self.finishedCurrentFile = NO; + self.fileCompletion = completion; + [NSFileManager.defaultManager removeItemAtURL:destinationURL error:nil]; + + if (allowFast && expectedBytes == 0) allowFast = NO; + + if (allowFast && expectedBytes >= YouModFastDownloadMinimumBytes) { + __weak typeof(self) weakSelf = self; + self.rangeDownloader = [[YouModRangeDownloader alloc] initWithURL:url destinationURL:destinationURL expectedBytes:expectedBytes headers:headers progress:^(unsigned long long completedBytes) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || self.cancelled) return; + self.currentBytes = completedBytes; + [self updateDownloadProgressWithCurrentBytes:completedBytes expectedBytes:expectedBytes]; + } completion:^(NSURL *fileURL, NSError *error) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || self.cancelled) return; + self.rangeDownloader = nil; + if (error) { + [self beginDownloadURL:url toURL:destinationURL expectedBytes:expectedBytes headers:headers allowFast:NO completion:completion]; + return; + } + if (completion) completion(fileURL, nil); + }]; + [self.rangeDownloader start]; + return; + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0]; + YouModApplyDownloadHeaders(request, headers); + self.task = [self.session downloadTaskWithRequest:request]; + [self.task resume]; +} + +- (void)resolveExpectedBytesForURL:(NSURL *)url headers:(NSDictionary *)headers completion:(void (^)(unsigned long long bytes))completion { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15.0]; + request.HTTPMethod = @"HEAD"; + YouModApplyDownloadHeaders(request, headers); + + __weak typeof(self) weakSelf = self; + self.metadataTask = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(__unused NSData *data, NSURLResponse *response, __unused NSError *error) { + __strong typeof(weakSelf) self = weakSelf; + if (!self) return; + + unsigned long long bytes = 0; + if (response.expectedContentLength > 0) { + bytes = (unsigned long long)response.expectedContentLength; + } else if ([response isKindOfClass:NSHTTPURLResponse.class]) { + id header = ((NSHTTPURLResponse *)response).allHeaderFields[@"Content-Length"]; + if ([header respondsToSelector:@selector(unsignedLongLongValue)]) + bytes = [header unsignedLongLongValue]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + self.metadataTask = nil; + completion(bytes); + }); + }]; + [self.metadataTask resume]; +} + +- (void)updateDownloadProgressWithCurrentBytes:(unsigned long long)currentBytes expectedBytes:(unsigned long long)expectedBytes { + unsigned long long total = self.totalBytes ?: expectedBytes; + float progress = total ? (float)(self.completedBytes + currentBytes) / (float)total : 0.0f; + progress = fminf(fmaxf(progress, 0.0f), 0.985f); + + NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; + NSTimeInterval elapsed = now - self.downloadStartTime; + double speedMBps = 0; + if (elapsed > 0) { + speedMBps = ((double)(self.completedBytes + currentBytes) / 1048576.0) / elapsed; + } + double totalMB = (double)total / 1048576.0; + + self.progressAlert.title = [NSString stringWithFormat:@"%@ - %ld%%", self.baseProgressTitle ?: @"Downloading", (long)lrintf(progress * 100.0f)]; + if (total > 0) { + self.progressAlert.message = [NSString stringWithFormat:@"%.1f MB/s - %.1f MB\n", speedMBps, totalMB]; + } else { + self.progressAlert.message = [NSString stringWithFormat:@"%.1f MB/s\n", speedMBps]; + } + [self.progressView setProgress:progress animated:YES]; +} + +- (void)adjustCurrentExpectedBytesIfNeeded:(unsigned long long)newExpectedBytes { + unsigned long long oldExpectedBytes = self.currentExpectedBytes; + if (newExpectedBytes <= oldExpectedBytes) return; + + self.currentExpectedBytes = newExpectedBytes; + if (oldExpectedBytes > 0) { + self.totalBytes += newExpectedBytes - oldExpectedBytes; + } else if (!self.currentResolvedSizeAddedToTotal) { + self.totalBytes += newExpectedBytes; + self.currentResolvedSizeAddedToTotal = YES; + } +} + +- (void)startVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { + if (self.active) { + YouModSendToast(@"Already downloading", presenter); + return; + } + [self startDirectVideoDownloadWithVideoFormat:videoFormat audioFormat:audioFormat fileName:fileName videoID:videoID presenter:presenter]; +} + +- (void)startDirectVideoDownloadWithVideoFormat:(YouModMediaFormat *)videoFormat audioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { + NSURL *videoURL = [NSURL URLWithString:videoFormat.urlString]; + NSURL *audioURL = [NSURL URLWithString:audioFormat.urlString]; + if (!videoURL || !audioURL) { + YouModSendToast(@"No stream URL found", presenter); + return; + } + + self.active = YES; + self.cancelled = NO; + self.completedBytes = 0; + self.totalBytes = videoFormat.contentLength + audioFormat.contentLength; + self.videoTempURL = YouModTemporaryFileURL(YouModFileExtensionForFormat(videoFormat, @"mp4")); + self.audioTempURL = YouModTemporaryFileURL(YouModFileExtensionForFormat(audioFormat, @"m4a")); + NSString *outputExtension = YouModMergedVideoOutputExtension(videoFormat, audioFormat); + [self showProgressWithTitle:@"Downloading video" presenter:presenter]; + + __weak typeof(self) weakSelf = self; + [self downloadURL:videoURL toURL:self.videoTempURL expectedBytes:videoFormat.contentLength headers:videoFormat.httpHeaders completion:^(NSURL *videoFileURL, NSError *videoError) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || videoError) { + [self failWithError:videoError ?: [NSError errorWithDomain:@"YouMod" code:2 userInfo:@{NSLocalizedDescriptionKey: @"Video download failed"}]]; + return; + } + + self.completedBytes += MAX(videoFormat.contentLength, self.currentBytes); + [self updateProgressTitle:@"Downloading audio" progress:(self.totalBytes ? (float)self.completedBytes / (float)self.totalBytes : 0.5f)]; + [self downloadURL:audioURL toURL:self.audioTempURL expectedBytes:audioFormat.contentLength headers:audioFormat.httpHeaders completion:^(NSURL *audioFileURL, NSError *audioError) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || audioError) { + [self failWithError:audioError ?: [NSError errorWithDomain:@"YouMod" code:3 userInfo:@{NSLocalizedDescriptionKey: @"Audio download failed"}]]; + return; + } + unsigned long long durationMs = videoFormat.durationMs ?: audioFormat.durationMs; + [self mergeVideoURL:videoFileURL audioURL:audioFileURL fileName:fileName outputExtension:outputExtension durationMs:durationMs presenter:presenter]; + }]; + }]; +} + +- (void)startDirectSingleVideoDownloadWithFormat:(YouModMediaFormat *)format fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { + NSURL *videoURL = [NSURL URLWithString:format.urlString]; + if (!videoURL) { + YouModSendToast(@"No stream URL found", presenter); + return; + } + + self.active = YES; + self.cancelled = NO; + self.completedBytes = 0; + self.totalBytes = format.contentLength; + NSString *extension = YouModFileExtensionForFormat(format, @"mp4"); + BOOL canFinalizeWithAVFoundation = format.durationMs > 0 && YouModPathExtensionIsPhotosVideo(extension); + NSURL *finalURL = YouModUniqueFileURL(fileName, extension); + NSURL *downloadURL = canFinalizeWithAVFoundation ? YouModTemporaryFileURL(extension) : finalURL; + self.videoTempURL = canFinalizeWithAVFoundation ? downloadURL : nil; + [self showProgressWithTitle:@"Downloading video" presenter:presenter]; + + __weak typeof(self) weakSelf = self; + [self downloadURL:videoURL toURL:downloadURL expectedBytes:format.contentLength headers:format.httpHeaders completion:^(NSURL *fileURL, NSError *error) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || error) { + [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:8 userInfo:@{NSLocalizedDescriptionKey: @"Video download failed"}]]; + return; + } + if (canFinalizeWithAVFoundation) { + [self trimSingleVideoURL:fileURL outputURL:finalURL durationMs:format.durationMs presenter:presenter]; + return; + } + [self completeWithFileURL:fileURL isVideo:YES presenter:presenter]; + }]; +} + +- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { + [self startAudioDownloadWithAudioFormat:audioFormat fileName:fileName videoID:videoID outputFormat:nil presenter:presenter]; +} + +- (void)startAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID outputFormat:(YouModAudioOutputFormat *)outputFormat presenter:(UIViewController *)presenter { + if (self.active) { + YouModSendToast(@"Already downloading", presenter); + return; + } + [self startDirectAudioDownloadWithAudioFormat:audioFormat fileName:fileName videoID:videoID outputFormat:outputFormat presenter:presenter]; +} + +- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID presenter:(UIViewController *)presenter { + [self startDirectAudioDownloadWithAudioFormat:audioFormat fileName:fileName videoID:videoID outputFormat:nil presenter:presenter]; +} + +- (void)startDirectAudioDownloadWithAudioFormat:(YouModMediaFormat *)audioFormat fileName:(NSString *)fileName videoID:(NSString *)videoID outputFormat:(YouModAudioOutputFormat *)outputFormat presenter:(UIViewController *)presenter { + NSURL *audioURL = [NSURL URLWithString:audioFormat.urlString]; + if (!audioURL) { + YouModSendToast(@"No audio URL found", presenter); + return; + } + outputFormat = outputFormat ?: YouModDefaultAudioOutputFormat(); + if (!outputFormat.supported) { + YouModSendToast([NSString stringWithFormat:@"%@ not supported", outputFormat.title ?: @"Format"], presenter); + return; + } + + self.active = YES; + self.cancelled = NO; + self.completedBytes = 0; + self.totalBytes = audioFormat.contentLength; + BOOL passthrough = YouModAudioOutputFormatCanPassthrough(outputFormat, audioFormat); + if (!passthrough && !YouModFFmpegKitAvailable()) { + self.active = NO; + NSString *details = YouModFFmpegKitDiagnosticText(outputFormat, audioFormat, videoID); + YouModRecordDownloadDiagnostic(@"FFmpegKit unavailable for audio conversion", details); + NSString *diagnostic = YouModDownloadDiagnosticText(); + if (diagnostic.length) { + UIPasteboard.generalPasteboard.string = diagnostic; + YouModSendToast(@"FFmpegKit not loaded, diagnostics copied", presenter); + } else { + YouModSendToast([NSString stringWithFormat:@"FFmpegKit required for %@", outputFormat.title ?: @"this format"], presenter); + } + return; + } + + NSURL *finalURL = YouModUniqueFileURL(fileName, YouModAudioOutputFileExtension(outputFormat, audioFormat, passthrough)); + NSURL *downloadURL = passthrough ? finalURL : YouModTemporaryFileURL(YouModFileExtensionForFormat(audioFormat, @"m4a")); + self.audioTempURL = passthrough ? nil : downloadURL; + [self showProgressWithTitle:@"Downloading audio" presenter:presenter]; + + __weak typeof(self) weakSelf = self; + [self downloadURL:audioURL toURL:downloadURL expectedBytes:audioFormat.contentLength headers:audioFormat.httpHeaders completion:^(NSURL *fileURL, NSError *error) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || error) { + [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:4 userInfo:@{NSLocalizedDescriptionKey: @"Audio download failed"}]]; + return; + } + if (!passthrough) { + unsigned long long durationMs = audioFormat.durationMs ?: YouModDurationMsForURL(fileURL); + [self convertAudioURL:fileURL outputURL:finalURL outputFormat:outputFormat durationMs:durationMs presenter:presenter]; + return; + } + [self completeWithFileURL:fileURL isVideo:NO presenter:presenter]; + }]; +} + +- (void)convertAudioURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL outputFormat:(YouModAudioOutputFormat *)outputFormat durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter { + [self updateProgressTitle:[NSString stringWithFormat:@"Converting to %@", outputFormat.title ?: @"audio"] progress:0.985f]; + [NSFileManager.defaultManager removeItemAtURL:outputURL error:nil]; + + __weak typeof(self) weakSelf = self; + BOOL started = YouModStartFFmpegKitAudioConvert(inputURL, outputURL, outputFormat, durationMs, ^(float progress) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || self.cancelled) return; + [self updateProgressTitle:[NSString stringWithFormat:@"Converting to %@", outputFormat.title ?: @"audio"] progress:progress]; + }, ^(BOOL success, NSError *error) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || self.cancelled) return; + if (success) { + [self completeWithFileURL:outputURL isVideo:NO presenter:presenter]; + return; + } + [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:14 userInfo:@{NSLocalizedDescriptionKey: @"Conversion failed"}]]; + }); + + if (!started) { + [self failWithError:[NSError errorWithDomain:@"YouMod" code:15 userInfo:@{NSLocalizedDescriptionKey: @"Format unavailable"}]]; + } +} + +- (void)mergeVideoURL:(NSURL *)videoURL audioURL:(NSURL *)audioURL fileName:(NSString *)fileName outputExtension:(NSString *)outputExtension durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter { + [self updateProgressTitle:@"Merging video" progress:0.985f]; + NSURL *outputURL = YouModUniqueFileURL(fileName, outputExtension.length ? outputExtension : @"mp4"); + if (durationMs == 0) durationMs = YouModDurationMsForURL(videoURL); + + if (YouModFFmpegKitAvailable()) { + __weak typeof(self) weakSelf = self; + BOOL started = YouModStartFFmpegKitMerge(videoURL, audioURL, outputURL, durationMs, ^(float progress) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || self.cancelled) return; + [self updateProgressTitle:@"Merging video" progress:progress]; + }, ^(BOOL success, NSError *error) { + __strong typeof(weakSelf) self = weakSelf; + if (!self || self.cancelled) return; + if (success) { + [self completeWithFileURL:outputURL isVideo:YES presenter:presenter]; + return; + } + + [NSFileManager.defaultManager removeItemAtURL:outputURL error:nil]; + if (YouModVideoFileCanUseAVFoundation(outputURL)) { + [self mergeVideoWithAVFoundationVideoURL:videoURL audioURL:audioURL outputURL:outputURL durationMs:durationMs presenter:presenter fallbackError:error]; + } else { + [self failWithError:error ?: [NSError errorWithDomain:@"YouMod" code:16 userInfo:@{NSLocalizedDescriptionKey: @"FFmpegKit required for this stream"}]]; + } + }); + if (started) return; + } + + if (YouModVideoFileCanUseAVFoundation(outputURL)) { + [self mergeVideoWithAVFoundationVideoURL:videoURL audioURL:audioURL outputURL:outputURL durationMs:durationMs presenter:presenter fallbackError:nil]; + } else { + [self failWithError:[NSError errorWithDomain:@"YouMod" code:16 userInfo:@{NSLocalizedDescriptionKey: @"FFmpegKit required for this stream"}]]; + } +} + +- (void)mergeVideoWithAVFoundationVideoURL:(NSURL *)videoURL audioURL:(NSURL *)audioURL outputURL:(NSURL *)outputURL durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter fallbackError:(NSError *)fallbackError { + [self updateProgressTitle:fallbackError ? @"Merging video with fallback" : @"Merging video" progress:0.985f]; + AVURLAsset *videoAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil]; + AVURLAsset *audioAsset = [AVURLAsset URLAssetWithURL:audioURL options:nil]; + AVMutableComposition *composition = [AVMutableComposition composition]; + + AVAssetTrack *videoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; + AVAssetTrack *audioTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject]; + if (!videoTrack || !audioTrack) { + [self failWithError:fallbackError ?: [NSError errorWithDomain:@"YouMod" code:5 userInfo:@{NSLocalizedDescriptionKey: @"Merge failed"}]]; + return; + } + + CMTime duration = YouModExportDuration(videoAsset, audioAsset, durationMs); + if (!YouModCMTimeIsUsable(duration)) { + [self failWithError:fallbackError ?: [NSError errorWithDomain:@"YouMod" code:9 userInfo:@{NSLocalizedDescriptionKey: @"Cannot determine duration"}]]; + return; + } + NSError *insertError = nil; + AVMutableCompositionTrack *compositionVideo = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + [compositionVideo insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:videoTrack atTime:kCMTimeZero error:&insertError]; + compositionVideo.preferredTransform = videoTrack.preferredTransform; + if (insertError) { + [self failWithError:insertError]; + return; + } + + AVMutableCompositionTrack *compositionAudio = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + [compositionAudio insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:audioTrack atTime:kCMTimeZero error:&insertError]; + if (insertError) { + [self failWithError:insertError]; + return; + } + + AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough]; + exporter.outputURL = outputURL; + exporter.outputFileType = AVFileTypeMPEG4; + exporter.shouldOptimizeForNetworkUse = YES; + + __weak typeof(self) weakSelf = self; + [exporter exportAsynchronouslyWithCompletionHandler:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + __strong typeof(weakSelf) self = weakSelf; + if (!self) return; + if (exporter.status == AVAssetExportSessionStatusCompleted) { + [self completeWithFileURL:outputURL isVideo:YES presenter:presenter]; + } else { + [self failWithError:exporter.error ?: [NSError errorWithDomain:@"YouMod" code:6 userInfo:@{NSLocalizedDescriptionKey: @"Merge failed"}]]; + } + }); + }]; +} + +- (void)trimSingleVideoURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL durationMs:(unsigned long long)durationMs presenter:(UIViewController *)presenter { + [self updateProgressTitle:@"Finalizing video" progress:0.99f]; + [NSFileManager.defaultManager removeItemAtURL:outputURL error:nil]; + + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil]; + AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject]; + if (!videoTrack) { + [self failWithError:[NSError errorWithDomain:@"YouMod" code:10 userInfo:@{NSLocalizedDescriptionKey: @"Cannot finalize video"}]]; + return; + } + + CMTime duration = YouModExportDuration(asset, nil, durationMs); + if (!YouModCMTimeIsUsable(duration)) { + [self failWithError:[NSError errorWithDomain:@"YouMod" code:11 userInfo:@{NSLocalizedDescriptionKey: @"Cannot determine duration"}]]; + return; + } + + AVMutableComposition *composition = [AVMutableComposition composition]; + NSError *insertError = nil; + AVMutableCompositionTrack *compositionVideo = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + [compositionVideo insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:videoTrack atTime:kCMTimeZero error:&insertError]; + compositionVideo.preferredTransform = videoTrack.preferredTransform; + if (insertError) { + [self failWithError:insertError]; + return; + } + + AVAssetTrack *audioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject]; + if (audioTrack) { + CMTime audioDuration = YouModMinUsableDuration(duration, audioTrack.timeRange.duration); + AVMutableCompositionTrack *compositionAudio = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + [compositionAudio insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioDuration) ofTrack:audioTrack atTime:kCMTimeZero error:&insertError]; + if (insertError) { + [self failWithError:insertError]; + return; + } + } + + AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough]; + exporter.outputURL = outputURL; + exporter.outputFileType = AVFileTypeMPEG4; + exporter.shouldOptimizeForNetworkUse = YES; + + __weak typeof(self) weakSelf = self; + [exporter exportAsynchronouslyWithCompletionHandler:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + __strong typeof(weakSelf) self = weakSelf; + if (!self) return; + if (exporter.status == AVAssetExportSessionStatusCompleted) { + [self completeWithFileURL:outputURL isVideo:YES presenter:presenter]; + } else { + [self failWithError:exporter.error ?: [NSError errorWithDomain:@"YouMod" code:12 userInfo:@{NSLocalizedDescriptionKey: @"Finalize failed"}]]; + } + }); + }]; +} + +- (void)completeWithFileURL:(NSURL *)fileURL isVideo:(BOOL)isVideo presenter:(UIViewController *)presenter { + self.active = NO; + [self updateProgressTitle:@"Download completed" progress:1.0f]; + [self.progressAlert dismissViewControllerAnimated:YES completion:nil]; + self.progressAlert = nil; + self.progressView = nil; + + BOOL canSaveToPhotos = isVideo && YouModVideoFileCanSaveToPhotos(fileURL); + if (isVideo && IS_ENABLED(DownloadSaveToPhotos) && canSaveToPhotos) { + [self cleanupTemporaryFiles]; + YouModSaveVideoToPhotos(fileURL, presenter, ^(BOOL success, NSError *error) { + if (success) { + YouModSendToast(@"Saved to Photos", presenter); + } else { + YouModSendToast(error.localizedDescription ?: @"Cannot save to Photos", presenter); + YouModShareFile(fileURL, presenter); + } + }); + } else { + [self cleanupTemporaryFiles]; + YouModSendToast(isVideo ? @"Download completed" : @"Audio saved", presenter); + if (!isVideo || (isVideo && !canSaveToPhotos)) YouModShareFile(fileURL, presenter); + } +} + +- (void)failWithError:(NSError *)error { + self.active = NO; + [self.progressAlert dismissViewControllerAnimated:YES completion:nil]; + self.progressAlert = nil; + self.progressView = nil; + [self cleanupTemporaryFiles]; + YouModSendToast(error.localizedDescription ?: @"Download failed", self.presenter); +} + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { + self.currentBytes = (unsigned long long)MAX(totalBytesWritten, 0); + if (totalBytesExpectedToWrite > 0) + [self adjustCurrentExpectedBytesIfNeeded:(unsigned long long)totalBytesExpectedToWrite]; + if (self.currentBytes > self.currentExpectedBytes) + [self adjustCurrentExpectedBytesIfNeeded:self.currentBytes]; + [self updateDownloadProgressWithCurrentBytes:self.currentBytes expectedBytes:self.currentExpectedBytes]; +} + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { + self.finishedCurrentFile = YES; + NSError *error = nil; + [NSFileManager.defaultManager removeItemAtURL:self.destinationURL error:nil]; + [NSFileManager.defaultManager moveItemAtURL:location toURL:self.destinationURL error:&error]; + if (self.fileCompletion) self.fileCompletion(error ? nil : self.destinationURL, error); +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { + if (error && !self.finishedCurrentFile && self.fileCompletion) { + self.fileCompletion(nil, error); + } +} + +@end + +static void YouModDownloadThumbnail(NSString *videoID, UIViewController *presenter) { + NSURL *thumbnailURL = YouModThumbnailURLForVideoID(videoID); + if (!thumbnailURL) { + YouModSendToast(@"No thumbnail found", presenter); + return; + } + + YouModSendToast(@"Downloading thumbnail", presenter); + [[NSURLSession.sharedSession dataTaskWithURL:thumbnailURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + UIImage *image = data ? [UIImage imageWithData:data] : nil; + dispatch_async(dispatch_get_main_queue(), ^{ + if (!image || error) { + YouModSendToast(error.localizedDescription ?: @"Thumbnail failed", presenter); + return; + } + YouModRequestPhotoAccess(^(BOOL granted) { + if (!granted) { + YouModSendToast(@"Photos access denied", presenter); + return; + } + [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ + [PHAssetChangeRequest creationRequestForAssetFromImage:image]; + } completionHandler:^(BOOL success, NSError *saveError) { + dispatch_async(dispatch_get_main_queue(), ^{ + YouModSendToast(success ? @"Saved to Photos" : (saveError.localizedDescription ?: @"Save failed"), presenter); + }); + }]; + }); + }); + }] resume]; +} + +static void YouModCopyVideoInfo(YTPlayerViewController *player, UIViewController *presenter) { + NSString *videoID = YouModVideoIDForPlayer(player); + NSString *title = YouModTitleForPlayer(player); + NSString *url = videoID.length ? [NSString stringWithFormat:@"https://youtu.be/%@", videoID] : @""; + UIPasteboard.generalPasteboard.string = url.length ? [NSString stringWithFormat:@"%@\n%@", title, url] : title; + YouModSendToast(@"Copied video information", presenter); +} + +static void YouModShowVideoQualitySheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { + NSArray *videoFormats = YouModFormatsForPlayer(player, YES); + YouModMediaFormat *audioFormat = YouModBestAudioFormatForPlayer(player); + NSString *title = YouModTitleForPlayer(player); + NSString *videoID = YouModVideoIDForPlayer(player); + + if (videoFormats.count == 0 || !audioFormat) { + YouModSendToast(@"No video/audio streams found", presenter); + return; + } + + NSMutableArray *items = [NSMutableArray array]; + for (YouModMediaFormat *format in videoFormats) { + NSString *rowTitle = format.qualityLabel.length ? format.qualityLabel : @"Video"; + NSString *subtitle = YouModFormatSubtitle(format); + [items addObject:[YouModMenuItem itemWithTitle:rowTitle subtitle:subtitle icon:YouModIconImage(658) handler:^{ + [[YouModDownloadCoordinator sharedCoordinator] startVideoDownloadWithVideoFormat:format audioFormat:audioFormat fileName:title videoID:videoID presenter:presenter]; + }]]; + } + YouModPresentMenu(@"Download video", items, presenter, sender); +} + +static void YouModShowAudioSourceSheet(YTPlayerViewController *player, YouModAudioOutputFormat *outputFormat, UIViewController *presenter, UIView *sender) { + NSArray *audioFormats = YouModFormatsForPlayer(player, NO); + NSString *title = YouModTitleForPlayer(player); + NSString *videoID = YouModVideoIDForPlayer(player); + NSMutableArray *items = [NSMutableArray array]; + + if (audioFormats.count == 0) { + if (items.count) { + YouModPresentMenu(@"Download audio", items, presenter, sender); + return; + } + YouModSendToast(@"No audio streams found", presenter); + return; + } + + NSUInteger index = 1; + for (YouModMediaFormat *format in audioFormats) { + NSString *rowTitle = audioFormats.count == 1 ? @"Audio" : [NSString stringWithFormat:@"Audio %lu", (unsigned long)index++]; + NSString *subtitle = YouModFormatSubtitle(format); + [items addObject:[YouModMenuItem itemWithTitle:rowTitle subtitle:subtitle icon:YouModIconImage(21) handler:^{ + [[YouModDownloadCoordinator sharedCoordinator] startAudioDownloadWithAudioFormat:format fileName:title videoID:videoID outputFormat:outputFormat presenter:presenter]; + }]]; + } + NSString *menuTitle = outputFormat.title.length ? [NSString stringWithFormat:@"Download %@", outputFormat.title] : @"Download audio"; + YouModPresentMenu(menuTitle, items, presenter, sender); +} + +static void YouModShowAudioSheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { + NSMutableArray *items = [NSMutableArray array]; + for (YouModAudioOutputFormat *format in YouModAudioOutputFormats()) { + [items addObject:[YouModMenuItem itemWithTitle:format.title subtitle:YouModAudioOutputSubtitle(format) icon:YouModIconImage(21) handler:^{ + if (!format.supported) { + YouModSendToast(@"DSD export is not supported by bundled FFmpeg.", presenter); + return; + } + YouModShowAudioSourceSheet(player, format, presenter, sender); + }]]; + } + YouModPresentMenu(@"Audio format", items, presenter, sender); +} + +static void YouModShowCaptionsSheet(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { + NSArray *tracks = YouModCaptionTracksForPlayer(player); + if (tracks.count == 0) { + YouModSendToast(@"No captions available for this video.", presenter); + return; + } + + NSMutableArray *items = [NSMutableArray array]; + for (id track in tracks) { + NSString *baseURL = YouModStringFromSelector(track, @selector(baseURL)); + if (baseURL.length == 0) continue; + + NSString *languageCode = YouModStringFromSelector(track, @selector(languageCode)); + NSString *vssId = YouModStringFromSelector(track, @selector(vssId)); + NSString *nameStr = nil; + id nameObj = YouModObjectFromSelector(track, @selector(name)); + nameStr = YouModStringFromSelector(nameObj, @selector(simpleText)); + if (!nameStr.length) { + NSArray *runs = YouModObjectFromSelector(nameObj, @selector(runsArray)); + if (runs.count > 0) nameStr = YouModStringFromSelector(runs.firstObject, @selector(text)); + } + if (!nameStr.length) nameStr = languageCode; + if (!nameStr.length) nameStr = vssId; + + [items addObject:[YouModMenuItem itemWithTitle:nameStr subtitle:languageCode icon:YouModIconImage(637) handler:^{ + NSString *vttURL = [baseURL stringByAppendingString:@"&fmt=vtt"]; + NSURL *url = [NSURL URLWithString:vttURL]; + if (!url) { + YouModSendToast(@"Invalid caption URL.", presenter); + return; + } + YouModSendToast(@"Downloading captions...", presenter); + [[NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error || data.length == 0) { + YouModSendToast(@"Failed to download captions.", presenter); + return; + } + NSString *videoID = YouModVideoIDForPlayer(player) ?: @"video"; + NSString *filename = [NSString stringWithFormat:@"%@_%@.vtt", videoID, languageCode ?: @"captions"]; + NSURL *tempURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:filename]]; + [data writeToURL:tempURL atomically:YES]; + YouModShareFile(tempURL, presenter); + }); + }] resume]; + }]]; + } + + if (items.count == 0) { + YouModSendToast(@"No valid caption URLs found.", presenter); + return; + } + + YouModPresentMenu(@"Download captions", items, presenter, sender); +} + +static void YouModShowDownloadManager(YTPlayerViewController *player, UIViewController *presenter, UIView *sender) { + if (!player) { + YouModSendToast(@"Open a video before using the download manager.", presenter); + return; + } + + NSString *videoID = YouModVideoIDForPlayer(player); + NSMutableArray *items = [NSMutableArray array]; + [items addObject:[YouModMenuItem itemWithTitle:@"Download video" subtitle:@"Choose quality" icon:YouModIconImage(658) handler:^{ + YouModShowVideoQualitySheet(player, presenter, sender); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:@"Download audio" subtitle:@"Choose format" icon:YouModIconImage(21) handler:^{ + YouModShowAudioSheet(player, presenter, sender); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:@"Download captions" subtitle:@"Save subtitles as VTT" icon:YouModIconImage(637) handler:^{ + YouModShowCaptionsSheet(player, presenter, sender); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:@"Copy diagnostics" subtitle:@"Copy last error log" icon:YouModIconImage(870) handler:^{ + YouModCopyDownloadDiagnostics(presenter); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:@"Save thumbnail" subtitle:@"Save to Photos" icon:YouModIconImage(367) handler:^{ + YouModDownloadThumbnail(videoID, presenter); + }]]; + [items addObject:[YouModMenuItem itemWithTitle:@"Copy video information" subtitle:@"Copy title and URL" icon:YouModIconImage(250) handler:^{ + YouModCopyVideoInfo(player, presenter); + }]]; + YouModPresentMenu(@"Download manager", items, presenter, sender); +} + +void YouModConfigureDownloadButton(_ASDisplayView *view) { + if (![view.accessibilityIdentifier isEqualToString:@"id.ui.add_to.offline.button"]) return; + if (!IS_ENABLED(DownloadManager) || IS_ENABLED(HideDownloadButton)) return; + if (objc_getAssociatedObject(view, @selector(YouModDownloadButtonTapped:))) return; + + view.userInteractionEnabled = YES; + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:view action:@selector(YouModDownloadButtonTapped:)]; + tap.cancelsTouchesInView = YES; + tap.delaysTouchesBegan = YES; + tap.delaysTouchesEnded = YES; + [view addGestureRecognizer:tap]; + objc_setAssociatedObject(view, @selector(YouModDownloadButtonTapped:), @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +%hook _ASDisplayView + +%new +- (void)YouModDownloadButtonTapped:(UITapGestureRecognizer *)sender { + if (sender.state != UIGestureRecognizerStateEnded) return; + UIViewController *presenter = YouModPresenterForSender(self, YouModCurrentPlayerViewController); + YTPlayerViewController *player = YouModPlayerFromViewController(presenter); + YouModShowDownloadManager(player, presenter, self); +} + +%end + +%hook YTPlayerViewController + +- (void)viewDidAppear:(BOOL)animated { + %orig; + YouModCurrentPlayerViewController = self; +} + +- (void)viewWillDisappear:(BOOL)animated { + %orig; + if (YouModCurrentPlayerViewController == self) + YouModCurrentPlayerViewController = nil; +} + +%end + +NSString *YouModGlobalAuthHeader = nil; + +%hook SSOAuthorization +- (id)accessToken { + id token = %orig; + if ([token isKindOfClass:[NSString class]] && [(NSString *)token length] > 0) { + YouModGlobalAuthHeader = [NSString stringWithFormat:@"Bearer %@", token]; + } + return token; +} +%end + +%hook SSOAuthorizationImpl +- (id)accessToken { + id token = %orig; + if ([token isKindOfClass:[NSString class]] && [(NSString *)token length] > 0) { + YouModGlobalAuthHeader = [NSString stringWithFormat:@"Bearer %@", token]; + } + return token; +} +%end + +%hook GNPSSOAuthorizationService +- (id)authToken { + id token = %orig; + if ([token isKindOfClass:[NSString class]] && [(NSString *)token length] > 0) { + YouModGlobalAuthHeader = [NSString stringWithFormat:@"Bearer %@", token]; + } + return token; +} +%end \ No newline at end of file diff --git a/Files/Feed.x b/Files/Feed.x index 4670281c..9a759e22 100644 --- a/Files/Feed.x +++ b/Files/Feed.x @@ -1,64 +1,11 @@ #import "Headers.h" -// YTUnShorts (https://github.com/PoomSmart/YTUnShorts) -static NSMutableArray *filteredArray(NSArray *array) { - NSMutableArray *newArray = [array mutableCopy]; - NSIndexSet *removeIndexes = [newArray indexesOfObjectsPassingTest:^BOOL(YTIItemSectionRenderer *sectionRenderer, NSUInteger idx, BOOL *stop) { - if ([sectionRenderer isKindOfClass:%c(YTIShelfRenderer)]) { - YTIShelfSupportedRenderers *content = ((YTIShelfRenderer *)sectionRenderer).content; - YTIHorizontalListRenderer *horizontalListRenderer = content.horizontalListRenderer; - NSMutableArray *itemsArray = horizontalListRenderer.itemsArray; - NSIndexSet *removeItemsArrayIndexes = [itemsArray indexesOfObjectsPassingTest:^BOOL(YTIHorizontalListSupportedRenderers *horizontalListSupportedRenderers, NSUInteger idx2, BOOL *stop2) { - YTIElementRenderer *elementRenderer = horizontalListSupportedRenderers.elementRenderer; - NSString *description = [elementRenderer description]; - BOOL hasShorts = [description containsString:@"shorts_video_cell"]; - if (hasShorts) *stop2 = YES; - return hasShorts; - }]; - return removeItemsArrayIndexes.count > 0; - } - if ([sectionRenderer isKindOfClass:%c(YTIItemSectionRenderer)]) { - NSString *description = [sectionRenderer description]; - if ([description containsString:@"shorts_shelf.eml"]) - return YES; - } - return NO; - }]; - [newArray removeObjectsAtIndexes:removeIndexes]; - return newArray; -} - -%group Shorts -%hook YTInnerTubeCollectionViewController - -- (void)displaySectionsWithReloadingSectionControllerByRenderer:(id)renderer { - NSMutableArray *sectionRenderers = [self valueForKey:@"_sectionRenderers"]; - [self setValue:filteredArray(sectionRenderers) forKey:@"_sectionRenderers"]; - %orig; -} - -- (void)addSectionsFromArray:(NSArray *)array { - %orig(filteredArray(array)); -} - -%end -%end - // Hide Subbar -%hook YTMySubsFilterHeaderView -- (void)setChipFilterView:(id)arg1 { if (!IS_ENABLED(HideSubbar)) %orig; } -%end - %hook YTHeaderContentComboView - (void)enableSubheaderBarWithView:(id)arg1 { if (!IS_ENABLED(HideSubbar)) %orig; } -- (void)setFeedHeaderScrollMode:(int)arg1 { IS_ENABLED(HideSubbar) ? %orig(0) : %orig; } -%end - -%hook YTChipCloudCell -- (void)layoutSubviews { - if (self.superview && IS_ENABLED(HideSubbar)) { - [self removeFromSuperview]; - } %orig; +- (void)setFeedHeaderScrollMode:(int)arg1 { + int temp = IS_ENABLED(HideSubbar) ? 0 : arg1; + %orig(temp); } %end @@ -78,9 +25,14 @@ static NSMutableArray *filteredArray(NSArray #import #import +#import #import #import #import @@ -26,35 +27,77 @@ #import #import #import -#import #import -#import #import #import #import #import +#import +#import #import +#import +#import #import +#import +#import +#import #import +#import +#import +#import +#import #import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import -// For Settings.x +// For Settings.x and SponsorBlockSettings.x #import #import -#import #import -#import #import #import -#import +#import +#import #import +#define DownloadFix @"YouModDownloadFix" +#define DownloadServerIndex @"YouModDownloadServerIndex" + #define IS_ENABLED(k) [[NSUserDefaults standardUserDefaults] boolForKey:k] #define INTFORVAL(v) [[NSUserDefaults standardUserDefaults] integerForKey:v] +#define FixPlaybackIssues @"YouModFixPlaybackIssues" +#define MuteButton @"YouModMuteButton" +#define SpeedButton @"YouModSpeedButton" +#define ShareButton @"YouModShareButton" +#define LoopButton @"YouModLoopButton" +#define CaptionButton @"YouModCaptionButton" +#define KeepMutedKey @"YouModKeepMutedKey" +#define KeepLoopKey @"YouModKeepLoopKey" +#define QualityButton @"YouModQualityButton" +#define GlobalSpeedLocked @"YouModGlobalSpeedLocked" +#define GlobalSavedNormalRate @"YouModGlobalSavedNormalRate" // Downloading #define DownloadManager @"YouModDownloadManager" #define DownloadSaveToPhotos @"YouModDownloadSaveToPhotos" -#define DownloadPreferDRCAudio @"YouModDownloadPreferDRCAudio" +#define AddDownloadToShorts @"YouModAddDownloadToShorts" +#define AudioPreferIndex @"YouModAudioPreferIndex" +#define DownloadComment @"YouModDownloadComment" +#define DownloadPost @"YouModDownloadPost" // Cache #define AutoClearCache @"YouModAutoClearCache" // Appearance @@ -63,44 +106,67 @@ // Navigation bar #define HideYTLogo @"YouModHideYTLogo" #define YTPremiumLogo @"YouModYTPremiumLogo" +#define StickyNavBar @"YouModStickyNavBar" #define HideNoti @"YouModHideNotificationButton" #define HideSearch @"YouModHideSearchButton" #define HideVoiceSearch @"YouModHideVoiceSearchButton" #define HideCastButtonNav @"YouModHideCastButtonNavigationBar" // Feed #define HideSubbar @"YouModHideSubbar" +#define HideHoriShelf @"YouModHideHoriShelf" #define HideGenMusicShelf @"YouModHideGenMusicShelf" #define HideFeedPost @"YouModHideFeedPost" +#define HidePlayables @"YouModHidePlayables" #define HideShortsShelf @"YouModHideShortsShelf" +#define KeepShortsSubscript @"YouModKeepShortsSubscript" #define HideSearchHis @"YouModHideSearchHistoryAndSuggestions" -#define HideSubButton @"YouModHideSubscribeButton" -#define HideShoppingButton @"YouModHideShoppingButton" -#define HideMemberButton @"YouModHideMemberButton" +#define HideSurveys @"YouModHideSurveys" +#define HideRelatedVideos @"YouModHideRelatedVideos" // Player +#define WifiQualityIndex @"YouModWifiQualityIndex" +#define CellQualityIndex @"YouModCellQualityIndex" +#define LowPowerQualityIndex @"YouModLowPowerQualityIndex" +#define AudioTrack @"YouModAudioTrackSegment" +#define AudioTrackLangIndex @"YouModAudioTrackLangIndex" +#define NoDubbedAudioTrack @"YouModNoDubbedAudioTrack" +#define CaptionTrack @"YouModCaptionTrack" +#define CaptionTrackLangIndex @"YouModCaptionTrackLangIndex" +#define DisablesCaptionTrack @"YouModDisablesCaptionTrack" +#define AutoSpeedIndex @"YouModAutoSpeedIndex" +#define HoldToSpeedIndex @"YouModHoldToSpeedIndex" #define HideAutoPlayToggle @"YouModHideAutoPlayToggle" #define HideCaptionsButton @"YouModHideCaptionsButton" #define HideCastButtonPlayer @"YouModHideCastButtonPlayer" -#define HidePrevButton @"YouModHidePrevButton" -#define HideNextButton @"YouModHideNextButton" +#define HideNextAndPrevButtons @"YouModHideNextAndPrevButtons" #define ReplacePrevNextButtons @"YouModReplacePrevNextButtons" +#define SkipBackwardEnabled @"YouModSkipBackwardEnabled" +#define SkipForwardEnabled @"YouModSkipForwardEnabled" +#define RewindSeconds @"YouModRewindSeconds" +#define ForwardSeconds @"YouModForwardSeconds" #define RemoveDarkOverlay @"YouModRemoveDarkOverlay" #define RemoveAmbiant @"YouModRemoveAmbiantColors" #define HideEndScreenCards @"YouModHideEndScreenCards" #define HideSuggestedVideo @"YouModHideSuggestedVideoOnFinish" #define HidePaidPromoOverlay @"YouModHidePaidPromoOverlay" #define HideWaterMark @"YouModHideWaterMark" +#define DisablesEngagementPanel @"YouModDisablesEngagementPanel" +#define DontSnapToChapter @"YouModDontSnapToChapter" +#define PauseOnOverlay @"YouModPauseOnOverlay" #define GestureControls @"YouModEnableGesturesControls" #define GestureActivationArea @"YouModGestureActivationArea" #define LeftSideGesture @"YouModLeftSideGesture" #define RightSideGesture @"YouModRightSideGesture" #define GestureHUD @"YouModGestureHUD" +#define GestureHUDSize @"YouModGestureHUDSize" +#define GestureHUDPosition @"YouModGestureHUDPosition" #define DisablesDoubleTap @"YouModDisablesDoubleTap" #define DisablesLongHold @"YouModDisablesLongHold" #define AutoExitFullScreen @"YouModAutoExitFullScreen" -#define DisablesCaptions @"YouModAutoDisablesCaptions" #define DisablesShowRemaining @"YouModDisablesShowRemainingTime" #define AlwaysShowRemaining @"YouModAlwaysShowRemainingTime" #define ShowExtraTimeRemaining @"YouModShowExtraTimeRemaining" +#define Uses24HoursTime @"YouModUses24HoursTime" +#define CopyWithTimestampOnPause @"YouModCopyWithTimestampOnPause" #define HideFullAction @"YouModHideFullScreenAction" #define HideFullvidTitle @"YouModHideFullscreenVideoTitle" #define StopAutoplayVideo @"YouModStopAutoplayVideo" @@ -109,57 +175,108 @@ #define PortFull @"YouModPortraitFullscreen" #define OldQualityPicker @"YouModUseOldQualityPicker" #define ExtraSpeed @"YouModAddExtraSpeed" -#define DisableHints @"YouModDisableHints" #define ForceMiniPlayer @"YouModForceMiniPlayer" #define AlwaysShowSeekbar @"YouModAlwaysShowSeekbar" -#define HideLikeButton @"YouModHideLikeButton" -#define HideDisLikeButton @"YouModHideDisLikeButton" -#define HideShareButton @"YouModHideShareButton" -#define HideDownloadButton @"YouModHideDownloadButton" -#define HideClipButton @"YouModHideClipButton" -#define HideRemixButton @"YouModHideRemixButton" -#define HideSaveButton @"YouModHideSaveButton" +#define DisablesFreeZoom @"YouModDisablesFreeZoom" +#define TapToSeek @"YouModTapToSeek" +#define PauseTwoFingers @"YouModPauseTwoFingers" +#define HideCommentsSection @"YouModHideCommentsSection" +#define HideCommentsPreview @"YouModHideCommentsPreview" +#define LockSpeed @"YouModLockSpeed" +#define UseAnotherMiniplayer @"YouModUseAnotherMiniplayer" +#define SeekOnOverlay @"YouModSeekOnOverlay" // Shorts -#define HideShortsLikeButton @"YouModHideShortsLikeButton" -#define HideShortsDisLikeButton @"YouModHideShortsDisLikeButton" -#define HideShortsCommentButton @"YouModHideShortsCommentButton" -#define HideShortsShareButton @"YouModHideShortsShareButton" -#define HideShortsRemixButton @"YouModHideShortsRemixButton" -#define HideShortsMetaButton @"YouModHideShortsMetaButton" +#define FullScreenShorts @"YouModFullScreenShorts" +#define RemoveShortsLive @"YouModRemoveShortsLive" +#define RemoveShortsPosts @"YouModRemoveShortsPosts" #define HideShortsProducts @"YouModHideShortsProducts" #define HideShortsRecbar @"YouModHideShortsRecbar" -#define HideShortsCommit @"YouModHideShortsCommit" -#define HideShortsSubscriptButton @"YouModHideShortsSubscriptButton" -#define HideShortsLiveButton @"YouModHideShortsLiveButton" -#define HideShortsLensButton @"YouModHideShortsLensButton" -#define HideShortsTrendsButton @"YouModHideShortsTrendsButton" -#define HideShortsToVideo @"YouModHideShortsToVideo" #define EnablesShortsQuality @"YouModEnablesShortsQuality" #define ShowShortsSeekbar @"YouModShowShortsSeekbar" +#define ShortsActionIndex @"YouModMakeAShortsAction" +#define ShortsOnly @"YouModShortsOnly" // Tab bar #define DefaultTab @"YouModDefaultStartupTab" +#define TabOrder @"YouModTabOrder" #define HideTabIndi @"YouModHideTabIndicators" #define HideTabLabels @"YouModHideTabLabels" -#define HideHomeTab @"YouModHideHomeTab" -#define HideShortsTab @"YouModHideShortsTab" -#define HideCreateButton @"YouModHideCreateButton" -#define HideSubscriptTab @"YouModHideSubscriptionsTab" +#define UseFrostedTabBar @"YouModUseFrostedTabBar" // Miscellaneous #define BackgroundPlayback @"YouModEnablesBackgroundPlayback" #define DisablesShortsPiP @"YouModTrytoDisablesShortsPiP" +#define DisableHints @"YouModDisableHints" #define BlockUpgradeDialogs @"YouModBlockUpgradeDialogs" #define HideAreYouThereDialog @"YouModHideAreYouThereDialog" #define FixesSlowMiniPlayer @"YouModFixesSlowMiniPlayer" #define DisablesNewMiniPlayer @"YouModDisablesNewMiniPlayer" #define DisablesSnackBar @"YouModDisablesSnackBar" #define HideStartupAni @"YouModHideStartupAnimations" -#define HidePlayInNextQueue @"YouModHidePlayInNextQueue" #define HideLikeDislikeVotes @"YouModHideLikeDislikeVotes" +#define HideCommuGuide @"YouModHideCommuGuide" +#define DisablesRTL @"YouModDisablesRTL" +#define DeviceUIIndex @"YouModDeviceUIIndex" +#define FloatingKeyboard @"YouModFloatingKeyboard" +#define AutoOpenLink @"YouModAutoOpenLink" // #define CustomStartup @"YouModUseCustomVideoStartup" +// Flyout menu +#define RemovePlayInNextQueueOption @"YouModRemovePlayInNextQueueOption" +#define RemoveDownloadOption @"YouModRemoveDownloadOption" +#define RemoveWatchLaterOption @"YouModRemoveWatchLaterOption" +#define RemoveSaveOption @"YouModRemoveSaveOption" +#define RemoveRemoveFromPlaylistOption @"YouModRemoveRemoveFromPlaylistOption" +#define RemoveShareOption @"YouModRemoveShareOption" +#define RemoveNotInterestedOption @"YouModRemoveNotInterestedOption" +#define RemoveInfoOption @"YouModRemoveInfoOption" +#define RemoveFilterOption @"YouModRemoveFilterOption" +#define RemoveReportOption @"YouModRemoveReportOption" +#define RemoveYouTubeMusicOption @"YouModRemoveYouTubeMusicOption" +#define RemoveFeedBackOption @"YouModRemoveFeedBackOption" +#define RemoveDontRecommendOption @"YouModRemoveDontRecommendOption" +#define RemoveCastOption @"YouModRemoveCastOption" +#define RemoveShuffleOption @"YouModRemoveShuffleOption" +#define RemoveUnSubOption @"YouModRemoveUnSubOption" +#define RemoveHideFromPlaylistOption @"YouModRemoveHideFromPlaylistOption" +#define RemoveHelpOption @"YouModRemoveHelpOption" +#define RemoveNotifyOption @"YouModRemoveNotifyOption" +#define RemoveClearScreenOption @"YouModRemoveClearScreenOption" +// SponsorBlock +#define SBEnabled @"YouModSBEnabled" +#define SBShowButton @"YouModSBShowButton" +#define SBShowNotifications @"YouModSBShowNotifications" +#define SBAudioNotification @"YouModSBAudioNotification" +#define SBSegmentsInPlayer @"YouModSBSegmentsInPlayer" +#define SBSegmentsInFeed @"YouModSBSegmentsInFeed" +#define SBSegmentsInMiniPlayer @"YouModSBSegmentsInMiniPlayer" +#define SBShowDuration @"YouModSBShowDuration" +#define SBMinDuration @"YouModSBMinDuration" +#define SBSkipAlertDuration @"YouModSBSkipAlertDuration" +#define SBUnskipAlertDuration @"YouModSBUnskipAlertDuration" + +#define SB_ACTION_KEY(cat) [NSString stringWithFormat:@"YouModSBAction_%@", cat] +#define SB_COLOR_KEY(cat) [NSString stringWithFormat:@"YouModSBColor_%@", cat] + +#define FLOAT_FOR_KEY(k) [[NSUserDefaults standardUserDefaults] floatForKey:k] #define YT_BUNDLE_ID @"com.google.ios.youtube" #define YT_NAME @"YouTube" +@interface YTMenuItemMDCButton : UIButton +@end + +@interface YTPageHeaderViewController : UIViewController +@end + +@interface YTIPageHeaderRenderer : GPBMessage +@end + +@interface YTDefaultSheetController (YouMod) ++ (instancetype)sheetControllerWithParentResponder:(id)parentResponder; +- (void)addAction:(YTActionSheetAction *)action; +- (void)presentFromView:(UIView *)view animated:(BOOL)animated completion:(void (^)(void))completion; +- (void)presentFromViewController:(UIViewController *)vc animated:(BOOL)animated completion:(void (^)(void))completion; +- (void)addHeaderWithTitle:(NSString *)arg1 subtitle:(NSString *)arg2; +@end + // Gesture Section Enum typedef NS_ENUM(NSUInteger, GestureSection) { GestureSectionTop, @@ -167,6 +284,41 @@ typedef NS_ENUM(NSUInteger, GestureSection) { GestureSectionInvalid }; +@interface YTWatchController (YouMod) +- (void)reload; +@end + +@interface YTELMViewController : UIViewController +@end + +@interface YTInlineScrubGestureView : UIView +@end + +@interface YTReelContainerViewController : UIViewController +@end + +@interface YTAppReelWatchRootViewController : UIViewController +@end + +@interface YTPivotBarView : UIView +@end + +@interface YTPivotBarItemView (YouMod) +@end + +@interface YTContextualSheetView : UIView +@end + +@interface YTShortsAdsPlayerViewController : YTReelPlayerViewController +@end + +@interface YTIBrowseRequest (YouMod) ++ (NSString *)browseIDForGamingDestination; ++ (NSString *)browseIDForSportsDestination; ++ (NSString *)browseIDForNotificationsInbox; ++ (NSString *)browseIDForHistory; +@end + @interface YTITopbarLogoRenderer : NSObject @property(readonly, nonatomic) YTIIcon *iconImage; @end @@ -177,15 +329,13 @@ typedef NS_ENUM(NSUInteger, GestureSection) { @end @interface YTMainAppVideoPlayerOverlayView (YouMod) +@property (nonatomic, weak, readwrite) YTMainAppVideoPlayerOverlayViewController *delegate; @property (nonatomic, strong) YTQTMButton *playbackRouteButton; @end @interface YTNavigationBarTitleView : UIView @end -@interface YTChipCloudCell : UICollectionViewCell -@end - @interface YTSearchViewController : UIViewController @end @@ -199,21 +349,132 @@ typedef NS_ENUM(NSUInteger, GestureSection) { @interface YTPivotBarViewController : UIViewController - (void)selectItemWithPivotIdentifier:(id)pivotIndentifier; +- (void)YouModReloadTabBar:(id)arg; +@end + +@interface YTAppViewController (YouMod) +@property (nonatomic, assign, readonly) YTPivotBarViewController *pivotBarViewController; +- (void)hidePivotBar; +- (void)showPivotBar; +- (void)refreshPivotBarWithTriggedByNotification:(BOOL)arg; +- (BOOL)isPivotBarHidden; +@end + +@interface YTAppViewControllerImpl (YouMod) +@property (nonatomic, assign, readonly) YTPivotBarViewController *pivotBarViewController; +- (void)hidePivotBar; +- (void)showPivotBar; +- (void)refreshPivotBarWithTriggedByNotification:(BOOL)arg; +- (BOOL)isPivotBarHidden; +@end + +@interface YTReelWatchPlaybackOverlayView : UIView +@property (nonatomic, retain) UIPinchGestureRecognizer *YouModFullscreenGesture; +@end + +@interface YTReelContentView (YouMod) +@property (nonatomic, retain) UILongPressGestureRecognizer *YouModExitShortsOnlyGesture; +- (YTReelWatchPlaybackOverlayView *)playbackOverlay; +@end + +@interface YTLanguages : NSObject ++ (instancetype)languageList; +@end + +@interface YTICaptionTrackEntry : GPBMessage +- (NSString *)baseURL; +- (NSString *)vssId; +- (NSString *)languageCode; +- (YTIFormattedString *)name; @end @interface YTPlayerViewController (YouMod) @property (nonatomic, retain) UIPanGestureRecognizer *YouModPanGesture; +@property (nonatomic, retain) UITapGestureRecognizer *YouModTapGesture; @property (nonatomic, retain) UILabel *YouModGestureHUD; +@property (nonatomic, weak, readwrite) UIViewController *parentViewController; +@property (nonatomic, assign, readonly) BOOL isInlinePlaybackActive; +@property (nonatomic, assign, readonly) BOOL isPlayingAd; +@property (nonatomic, strong) UIView *YouModSpeedToastView; +@property (nonatomic, strong) UILabel *YouModSpeedToastLabel; +@property (nonatomic, retain) UILongPressGestureRecognizer *YouModHoldGesture; - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; - (void)YouModAutoFullscreen; -- (void)YouModTurnOffCaptions; -- (void)setActiveCaptionTrack:(id)arg1 source:(long long)arg2; +- (void)YouModSetAutoSpeed; - (void)setPlaybackRate:(float)rate; +- (void)setActiveCaptionTrack:(MLInnerTubeCaptionTrack *)arg1 source:(NSInteger)arg2; +- (void)setActiveCaptionTrack:(MLInnerTubeCaptionTrack *)arg; +- (void)play; +- (void)pause; +- (void)YouModAutoMute; +- (void)YouModAutoAudioTrack; +- (void)YouModAutoCaptions; +- (void)YouModLoopButton; +- (void)YouModCaptionsHelper:(MLInnerTubeCaptionTrack *)arg; +- (void)YouModShareButton:(UIView *)sourceView; +- (NSInteger)playerState; +- (YTPlayerResponse *)contentPlayerResponse; +- (YTPlayerResponse *)playerResponse; +- (id)audioTrackController; +- (void)setAudioTrack:(YTIAudioTrack *)arg1 source:(NSInteger)arg2; +- (void)YouModHideSpeedToast; +- (void)YouModShowSpeedToast:(CGFloat)speed isLocked:(BOOL)isLocked; +@end + +@interface YTPlayerBarController (YouMod) +- (void)didScrub:(UIPanGestureRecognizer *)gesture; +@end + +@interface YTFullscreenEngagementOverlayView : UIView +@end + +@interface YTRelatedVideosView : UIView +@end + +@interface YTAutoplayAutonavController : NSObject +- (void)setLoopMode:(NSInteger)loopMode; +@end + +@interface YTInlineMutedPlaybackPlayerOverlayViewController : UIViewController +@end + +@interface YTInlineMutedPlaybackPlayerOverlayView : UIView +@end + +@interface YTWatchFloatingMiniplayerViewController : UIViewController +@end + +@interface YTWatchFloatingMiniplayerWithPersistentControlsView : UIView +@end + +@interface YTWatchFloatingMiniplayerProgressBarView : UIView @end @interface SSOConfiguration : NSObject @end +@interface YTIMySubsFilterHeaderRenderer : GPBMessage +@end + +@interface YTMySubsFilterHeaderViewController : UIViewController +@end + +@interface YTEngagementPanelView : UIView +- (UIView *)footerView; +@end + +@interface YTEngagementPanelContainerView : UIView +- (NSInteger)engagementPanelState; +@end + +@interface YTRelatedVideosViewController : UIViewController +- (BOOL)isExpanded; +@end + +@interface YTMainAppControlsOverlayView (YouMod) +- (YTMainAppVideoPlayerOverlayViewController *)eventsDelegate; +@end + @interface YTVideoQualitySwitchOriginalController (YouMod) @property (retain, nonatomic) YTVideoQualitySwitchRedesignedController *redesignedController; @end @@ -241,6 +502,10 @@ typedef NS_ENUM(NSUInteger, GestureSection) { - (void)YouModAutoClearCache; @end +@interface YTInlinePlayerBarContainerView (YouMod) +@property (nonatomic, strong) NSString *endTimeString; +@end + // Custom perferences logics @interface YouModPrefsManager : NSObject + (instancetype)sharedManager; @@ -249,34 +514,248 @@ typedef NS_ENUM(NSUInteger, GestureSection) { - (void)restoreYouModDefaults; @end +@interface YTIAudioTrack (YouMod) +@property (nonatomic, assign, readwrite) BOOL isAutoDubbed; +- (BOOL)hasId_p; +@end + +@interface MLInnerTubeCaptionTrack (YouMod) +- (NSString *)languageCode; +- (NSString *)VSSID; +@end + +@interface YTCaptionTrackSwitchController : NSObject +@end + // Player Gestures - @bhackel (YTLitePlus) -@interface YTFineScrubberFilmstripView : UIView +@interface YTMainAppVideoPlayerOverlayViewController (YouMod) +@property (nonatomic, assign) YTPlayerViewController *parentViewController; +- (YTCaptionTrackSwitchController *)captionTrackController; +- (NSString *)videoID; +- (CGFloat)mediaTime; @end -@interface YTFineScrubberFilmstripCollectionView : UICollectionView +@interface YTSingleVideoController (YouMod) +- (CGFloat)totalMediaTime; +- (void)setVideoFormatConstraint:(id)arg; +- (void)YouModAutoQuality; +- (NSArray *)availableCaptionTracks; +- (MLInnerTubeCaptionTrack *)activeCaptionTrack; @end -@interface YTWatchFullscreenViewController : YTMultiSizeViewController +@interface YTReelPlayerViewController (YouMod) +- (void)reelContentViewRequestsAdvanceToNextVideo:(id)arg; +- (void)reelContentViewRequestsPlayPauseToggle:(id)arg; +- (id)audioTrackController; +- (void)YouModAutoAudioTrack:(YTPlayerViewController *)pv; +- (void)YouModOnlyShorts; @end -@interface YTPlayerBarController (YouMod) -- (void)didScrub:(UIPanGestureRecognizer *)gestureRecognizer; -- (void)startScrubbing; -- (void)didScrubToPoint:(CGPoint)point; -- (void)endScrubbingForSeekSource:(int)seekSource; +@interface YTIPlayerCaptionsTrackListRenderer : GPBMessage +- (NSMutableArray *)captionTracksArray; @end -@interface YTMainAppVideoPlayerOverlayViewController (YouMod) -@property (nonatomic, strong, readwrite) YTPlayerBarController *playerBarController; +@interface YTICaptionsSupportedRenderers : GPBMessage +- (YTIPlayerCaptionsTrackListRenderer *)playerCaptionsTracklistRenderer; @end -@interface YTInlinePlayerBarContainerView (YouMod) -@property UIPanGestureRecognizer *scrubGestureRecognizer; -@property (nonatomic, strong, readwrite) YTFineScrubberFilmstripView *fineScrubberFilmstrip; -@property (nonatomic, strong, readwrite) NSString *endTimeString; -- (CGFloat)scrubXForScrubRange:(CGFloat)scrubRange; +@interface YTIPlayerResponse (YouMod) +- (YTIStreamingData *)streamingData; +- (YTICaptionsSupportedRenderers *)captions; @end -@interface YTSingleVideoController (YouMod) -@property (nonatomic, assign, readonly) CGFloat totalMediaTime; +@interface YTIFormatStream (YouMod) +- (NSString *)mimeType; +- (NSInteger)contentLength; +- (NSUInteger)approxDurationMs; +- (int)height; +- (int)fps; +- (YTIAudioTrack *)audioTrack; +- (int)itag; +@end + +@interface YTIFormattedString (YouMod) +- (NSString *)dropdownOptionTitle; +@end + +@interface YTIVideoDetails (YouMod) +- (NSString *)title; +- (NSString *)author; +- (NSString *)shortDescription; +@end + +@interface YTDataUtils : NSObject ++ (instancetype)generateClientSideNonce; +@end + +@interface YCHAsyncLiveChatCollectionViewController : UIViewController +@end + +@interface YTStartupAnimationViewController : UIViewController +@end + +// SponsorBlock action modes +typedef NS_ENUM(NSInteger, SBSegmentAction) { + SBSegmentActionDisable = 0, + SBSegmentActionAutoSkip = 1, + SBSegmentActionAsk = 2, + SBSegmentActionDisplay = 3, + SBSegmentActionSkipTo = 4 +}; + +@interface SBSegment : NSObject +@property (nonatomic, strong) NSString *UUID; +@property (nonatomic, strong) NSString *category; +@property (nonatomic, assign) float startTime; +@property (nonatomic, assign) float endTime; +@property (nonatomic, strong) NSString *actionType; ++ (instancetype)segmentWithUUID:(NSString *)UUID category:(NSString *)category start:(float)start end:(float)end action:(NSString *)actionType; +- (SBSegmentAction)configuredAction; +- (UIColor *)segmentColor; +@end + +@interface SBRequest : NSObject ++ (void)fetchSegmentsForVideoID:(NSString *)videoID completion:(void (^)(NSArray *segments))completion; +@end + +@interface SBSkipNotificationView : UIView +@property (nonatomic, strong) UILabel *messageLabel; +@property (nonatomic, strong) UIButton *actionButton; +@property (nonatomic, strong) UIView *progressOverlay; +@property (nonatomic, copy) void (^onAction)(void); +@property (nonatomic, assign) NSTimeInterval totalDuration; +@property (nonatomic, assign) NSTimeInterval remainingDuration; +@property (nonatomic, assign) BOOL isPaused; +@property (nonatomic, assign) BOOL isHighlightPill; +@property (nonatomic, strong) NSDate *backgroundDate; ++ (instancetype)showInView:(UIView *)parentView message:(NSString *)message buttonTitle:(NSString *)buttonTitle action:(void (^)(void))action duration:(NSTimeInterval)duration; ++ (instancetype)showSuccessInView:(UIView *)parentView message:(NSString *)message duration:(NSTimeInterval)duration; ++ (instancetype)showErrorInView:(UIView *)parentView message:(NSString *)message duration:(NSTimeInterval)duration; +- (void)dismiss; +- (void)pauseProgress; +- (void)resumeProgress; +@end + +extern UIView *sbGetNotificationParent(void); +extern void sbUpdateOverlayInsetForPivotBar(void); +extern void YMPresentTabOrderModally(id parentResponder); + +// The ordered set of SponsorBlock categories YouMod supports. Both the core +// (segment fetching / skipping) and the settings UI read from this single list, +// so a category can never be fetchable without a control, or configurable +// without being fetched. +extern NSArray *sbAllCategories(void); + +// Tag stamped on every seek-bar segment marker view, used to find and remove +// them across the player-bar layout hooks that don't hold a direct reference. +static const NSInteger SBSegmentMarkerTag = 9900; + +// Supported range and default for the skip/unskip banner duration (seconds). +// The settings sliders expose this range and the core clamps stored values to +// it, so both read from one source and can never drift out of agreement. +static const CGFloat SBAlertDurationMin = 2.0; +static const CGFloat SBAlertDurationMax = 20.0; +static const CGFloat SBAlertDurationDefault = 4.0; + +#pragma mark - Custom Overlay Button Registry + +// A registered button shown in the player's controls overlay (top-right, under +// YouTube's settings gear). Features register a spec from their own %ctor; the +// single YTMainAppControlsOverlayView hook in OverlayButtons.x lays them all out. +@interface YMOverlayButtonSpec : NSObject +@property (nonatomic, copy) NSString *identifier; // unique, e.g. @"sponsorblock.toggle" +@property (nonatomic, copy) NSString *symbolName; // SF Symbol name (icon button) +@property (nonatomic, copy) NSString *title; // text label; set this instead of symbolName for a text button +@property (nonatomic, strong) UIColor *tintColor; // default tint (used if tintProvider is nil) +@property (nonatomic, assign) NSInteger sortOrder; // ascending; lower = closer to gear (rightmost) +@property (nonatomic, copy) void (^onTap)(YTPlayerViewController *player, YTQTMButton *button); +@property (nonatomic, copy) BOOL (^isVisible)(YTPlayerViewController *player); // nil = always visible +@property (nonatomic, copy) UIColor *(^tintProvider)(YTPlayerViewController *player); // nil = use tintColor +@property (nonatomic, assign) NSInteger viewTag; // assigned by the registry; do not set +@end + +extern void YMRegisterOverlayButton(YMOverlayButtonSpec *spec); +extern NSArray *YMRegisteredOverlayButtons(void); + +#pragma mark - Settings Search + +// One row in the global settings-search results. A row renders its own cell and +// (optionally) handles its own tap, so a single results table can host cells from +// different settings pages (the generic YouMod pages and SponsorBlock) without the +// search controller knowing how any of them are built. searchText is what the query +// is matched against (title + description). makeCell builds the live, editable +// control; onSelect handles taps that need to present UI (e.g. the colour picker), +// receiving the presenting VC and a reload block to refresh the results. +@interface YMSearchRow : NSObject +@property (nonatomic, copy) NSString *searchText; +@property (nonatomic, copy) UITableViewCell *(^makeCell)(UITableView *tableView); +@property (nonatomic, assign) CGFloat cellHeight; // 0 = UITableViewAutomaticDimension +@property (nonatomic, copy) void (^onSelect)(UIViewController *presenter, void (^reload)(void)); +@end + +// SponsorBlock's searchable rows (toggles, sliders, per-category action pickers and +// colour circles), rendered by SponsorBlock's own cell builders so its settings are +// editable inline in the global search. host adopts the rendering VC as a child so +// the cells inherit the correct trait collection (light/dark). Defined in +// SponsorBlockSettings.x; consumed by the search VC in YouModSettings.x. +extern NSArray *sbSearchRows(UIViewController *host); + +extern NSBundle *YouModBundle(); +extern UIImage *YouModYTIconImage(NSInteger iconType, BOOL useCustomColor, UIColor *customColor); +extern NSArray *getAllSystemLanguageTitles(); +extern NSArray *getAllSystemLanguageValues(); +extern UIViewController *YouModTopViewController(UIViewController *root); + +#define LOC(x) [YouModBundle() localizedStringForKey:x value:nil table:nil] + +@interface YMDownloadProgressView : UIView +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) UILabel *subtitleLabel; +@property (nonatomic, strong) UIProgressView *progressBar; +@property (nonatomic, strong) UIButton *cancelButton; +@property (nonatomic, copy) void (^onCancel)(void); ++ (instancetype)showInView:(UIView *)parentView message:(NSString *)message cancelAction:(void (^)(void))cancelAction; +- (void)updateProgress:(float)progress title:(NSString *)title subtitle:(NSString *)subtitle; +- (void)dismiss; +@end + +@interface YTPlayerViewController (SponsorBlock) +@property (nonatomic, strong) NSString *sbLastVideoID; +@property (nonatomic, strong) NSArray *sbSegments; +@property (nonatomic, strong) NSMutableSet *sbSkippedSegments; +@property (nonatomic, strong) SBSkipNotificationView *sbNotificationView; +@property (nonatomic, assign) BOOL sbEnabledForVideo; +- (void)sbCheckSegmentsAtCurrentTime; +- (void)sbPerformSkip:(SBSegment *)segment; +- (void)sbShowAskNotification:(SBSegment *)segment; +- (void)sbShowHighlightBannerIfNeeded:(NSArray *)segments; +- (void)sbSkipToHighlight; +- (void)sbRefreshMarkers:(NSArray *)segments; +@end + +@interface YouModThumbnailViewController : UIViewController +@property (nonatomic, strong) UIImage *thumbnailImage; +@property (nonatomic, strong) UIScrollView *scrollView; +@property (nonatomic, strong) UIImageView *imageView; +@end + +@interface YouModTranslationViewController : UIViewController +@property (nonatomic, copy) NSString *originalText; +@property (nonatomic, strong) UILabel *langValueLabel; +@property (nonatomic, strong) UIButton *reloadButton; +@property (nonatomic, strong) UITextView *resultTextView; +@property (nonatomic, copy) NSString *selectedLangCode; +@property (nonatomic, copy) NSString *selectedLangName; +@property (nonatomic, strong) NSArray *languageTitles; +@property (nonatomic, strong) NSArray *languageCodes; +- (void)performTranslation; +@end + +@interface YouModLanguagePickerViewController : UIViewController +@property (nonatomic, copy) NSString *selectedLangCode; +@property (nonatomic, copy) NSArray *titles; +@property (nonatomic, copy) NSArray *codes; +@property (nonatomic, copy) void (^onSelectLanguage)(NSString *code, NSString *title); +@property (nonatomic, strong) UITableView *tableView; +@property (nonatomic, strong) UIView *containerView; @end \ No newline at end of file diff --git a/Files/Navbar.x b/Files/Navbar.x index 5316f291..58ace9d3 100644 --- a/Files/Navbar.x +++ b/Files/Navbar.x @@ -15,7 +15,10 @@ %orig(renderer); } // For when spoofing before 18.34.5 -- (void)setPremiumLogo:(BOOL)arg { IS_ENABLED(YTPremiumLogo) ? %orig(YES) : %orig; } +- (void)setPremiumLogo:(BOOL)arg { + BOOL temp = IS_ENABLED(YTPremiumLogo) ? YES : arg; + %orig(temp); +} - (BOOL)isPremiumLogo { return IS_ENABLED(YTPremiumLogo) ? YES : %orig; } %end @@ -33,7 +36,10 @@ %orig(renderer); } // For when spoofing before 18.34.5 -- (void)setPremiumLogo:(BOOL)arg { IS_ENABLED(YTPremiumLogo) ? %orig(YES) : %orig; } +- (void)setPremiumLogo:(BOOL)arg { + BOOL temp = IS_ENABLED(YTPremiumLogo) ? YES : arg; + %orig(temp); +} - (BOOL)isPremiumLogo { return IS_ENABLED(YTPremiumLogo) ? YES : %orig; } %end @@ -65,8 +71,16 @@ %hook YTNavigationBarTitleView - (void)layoutSubviews { %orig; - if (self.subviews.count > 1 && [self.subviews[1].accessibilityIdentifier isEqualToString:@"id.yoodle.logo"] && IS_ENABLED(HideYTLogo)) { + if (self.subviews.count > 1 && [self.subviews[1].accessibilityIdentifier isEqualToString:@"id.youtube.logo"] && IS_ENABLED(HideYTLogo)) { self.subviews[1].hidden = YES; } } +%end + +%hook YTHeaderView +- (BOOL)stickyNavHeaderEnabled { return IS_ENABLED(StickyNavBar) ? YES : %orig; } +- (void)setStickyNavHeaderEnabled:(BOOL)arg { + BOOL temp = IS_ENABLED(StickyNavBar) ? YES : arg; + %orig(temp); +} %end \ No newline at end of file diff --git a/Files/Others.x b/Files/Others.x index e8a2eebe..e8351dd3 100644 --- a/Files/Others.x +++ b/Files/Others.x @@ -1,15 +1,6 @@ #import "Headers.h" -Class YTILikeResponseClass, YTIDislikeResponseClass, YTIRemoveLikeResponseClass; - // Background playback -%group BackgroundPlayback -%hook YTIBackgroundOfflineSettingCategoryEntryRenderer -%new(B@:) -- (BOOL)isBackgroundEnabled { return YES; } -%end -%end - %hook MLVideo - (BOOL)playableInBackground { return IS_ENABLED(BackgroundPlayback) ? YES : %orig; } %end @@ -26,10 +17,18 @@ Class YTILikeResponseClass, YTIDislikeResponseClass, YTIRemoveLikeResponseClass; - (BOOL)isPlayableInBackground { return IS_ENABLED(BackgroundPlayback) ? YES : %orig; } %end -// Try to disable Shorts PiP %hook YTColdConfig +// Try to disable Shorts PiP - (BOOL)shortsPlayerGlobalConfigEnableReelsPictureInPicture { return IS_ENABLED(DisablesShortsPiP) ? NO : %orig; } - (BOOL)shortsPlayerGlobalConfigEnableReelsPictureInPictureIos { return IS_ENABLED(DisablesShortsPiP) ? NO : %orig; } +// Hide startup animations +- (BOOL)mainAppCoreClientIosEnableStartupAnimation { return IS_ENABLED(HideStartupAni) ? NO : %orig; } +// Prevent YouTube from asking "Are you there?" +- (BOOL)enableYouthereCommandsOnIos { return IS_ENABLED(BlockUpgradeDialogs) ? NO : %orig; } +// Fixes slow miniplayer +- (BOOL)enableIosFloatingMiniplayerDoubleTapToResize { return IS_ENABLED(FixesSlowMiniPlayer) ? NO : %orig; } +// Use old miniplayer +- (BOOL)enableIosFloatingMiniplayer { return IS_ENABLED(DisablesNewMiniPlayer) ? NO : %orig; } %end %hook YTHotConfig @@ -42,12 +41,38 @@ Class YTILikeResponseClass, YTIDislikeResponseClass, YTIRemoveLikeResponseClass; %hook YTReelPlayerViewController - (BOOL)isPictureInPictureAllowed { return IS_ENABLED(DisablesShortsPiP) ? NO : %orig; } +- (void)setupPlayerForPiP { if (!IS_ENABLED(DisablesShortsPiP)) %orig; } %end %hook YTReelWatchRootViewController - (void)switchToPictureInPicture { if (!IS_ENABLED(DisablesShortsPiP)) %orig; } %end +// Disable Hints +%hook YTSettings +- (BOOL)areHintsDisabled { return IS_ENABLED(DisableHints) ? YES : %orig; } +- (void)setHintsDisabled:(BOOL)arg1 { + BOOL temp = IS_ENABLED(DisableHints) ? YES : arg1; + %orig(temp); +} +%end + +%hook YTSettingsImpl +- (BOOL)areHintsDisabled { return IS_ENABLED(DisableHints) ? YES : %orig; } +- (void)setHintsDisabled:(BOOL)arg1 { + BOOL temp = IS_ENABLED(DisableHints) ? YES : arg1; + %orig(temp); +} +%end + +%hook YTUserDefaults +- (BOOL)areHintsDisabled { return IS_ENABLED(DisableHints) ? YES : %orig; } +- (void)setHintsDisabled:(BOOL)arg1 { + BOOL temp = IS_ENABLED(DisableHints) ? YES : arg1; + %orig(temp); +} +%end + // Block upgrade dialogs %hook YTGlobalConfig - (BOOL)shouldBlockUpgradeDialog { return IS_ENABLED(BlockUpgradeDialogs) ? YES : %orig; } @@ -56,11 +81,6 @@ Class YTILikeResponseClass, YTIDislikeResponseClass, YTIRemoveLikeResponseClass; - (BOOL)shouldForceUpgrade { return IS_ENABLED(BlockUpgradeDialogs) ? NO : %orig; } %end -// Prevent YouTube from asking "Are you there?" -%hook YTColdConfig -- (BOOL)enableYouthereCommandsOnIos { return IS_ENABLED(BlockUpgradeDialogs) ? NO : %orig; } -%end - %hook YTYouThereController - (BOOL)shouldShowYouTherePrompt { return IS_ENABLED(HideAreYouThereDialog) ? NO : %orig; } - (void)showYouTherePrompt { if (!IS_ENABLED(HideAreYouThereDialog)) %orig; } @@ -71,16 +91,6 @@ Class YTILikeResponseClass, YTIDislikeResponseClass, YTIRemoveLikeResponseClass; - (void)showYouTherePrompt { if (!IS_ENABLED(HideAreYouThereDialog)) %orig; } %end -// Fixes slow miniplayer -%hook YTColdConfig -- (BOOL)enableIosFloatingMiniplayerDoubleTapToResize { return IS_ENABLED(FixesSlowMiniPlayer) ? NO : %orig; } -%end - -// Use old miniplayer -%hook YTColdConfig -- (BOOL)enableIosFloatingMiniplayer { return IS_ENABLED(DisablesNewMiniPlayer) ? NO : %orig; } -%end - // Disables Snackbar %hook GOOHUDManagerInternal - (id)sharedInstance { return IS_ENABLED(DisablesSnackBar) ? nil : %orig; } @@ -89,72 +99,108 @@ Class YTILikeResponseClass, YTIDislikeResponseClass, YTIRemoveLikeResponseClass; - (void)displayHUDViewForMessage:(id)arg { if (!IS_ENABLED(DisablesSnackBar)) %orig; } %end -// Hide startup animations -%hook YTColdConfig -- (BOOL)mainAppCoreClientIosEnableStartupAnimation { return IS_ENABLED(HideStartupAni) ? NO : %orig; } -%end - // Remove "Play next in queue" from the menu @PoomSmart (https://github.com/qnblackcat/uYouPlus/issues/1138#issuecomment-1606415080) %hook YTMenuItemVisibilityHandler - (BOOL)shouldShowServiceItemRenderer:(YTIMenuConditionalServiceItemRenderer *)renderer { - if (renderer.icon.iconType == 251 && IS_ENABLED(HidePlayInNextQueue)) { + if (renderer.icon.iconType == 251 && IS_ENABLED(RemovePlayInNextQueueOption)) { return NO; - } return %orig; + } + return %orig; } %end %hook YTMenuItemVisibilityHandlerImpl - (BOOL)shouldShowServiceItemRenderer:(YTIMenuConditionalServiceItemRenderer *)renderer { - if (renderer.icon.iconType == 251 && IS_ENABLED(HidePlayInNextQueue)) { + if (renderer.icon.iconType == 251 && IS_ENABLED(RemovePlayInNextQueueOption)) { return NO; - } return %orig; + } + return %orig; } %end -/* untested -// Remove Download button from the menu +// Remove flyout menu options %hook YTDefaultSheetController - (void)addAction:(YTActionSheetAction *)action { - NSString *identifier = [action valueForKey:@"_accessibilityIdentifier"]; + if (![action.button isKindOfClass:%c(YTMenuItemMDCButton)]) { + %orig; + return; + } + YTMenuItemMDCButton *button = (YTMenuItemMDCButton *)action.button; + NSString *iden = button.accessibilityIdentifier; + NSString *imageName = [button.currentImage description]; + // Method 1: Filter from accessibilityIdentifier NSDictionary *actionsToRemove = @{ - @"7": @(ytlBool(@"removeDownloadMenu")), - @"1": @(ytlBool(@"removeWatchLaterMenu")), - @"3": @(ytlBool(@"removeSaveToPlaylistMenu")), - @"5": @(ytlBool(@"removeShareMenu")), - @"12": @(ytlBool(@"removeNotInterestedMenu")), - @"31": @(ytlBool(@"removeDontRecommendMenu")), - @"58": @(ytlBool(@"removeReportMenu")) + @"7": @(IS_ENABLED(RemoveDownloadOption)), + @"1": @(IS_ENABLED(RemoveWatchLaterOption)), + @"3": @(IS_ENABLED(RemoveSaveOption)), + @"4": @(IS_ENABLED(RemoveRemoveFromPlaylistOption)), + @"5": @(IS_ENABLED(RemoveShareOption)), + @"6": @(IS_ENABLED(RemoveShareOption)), + @"12": @(IS_ENABLED(RemoveNotInterestedOption)), + @"22": @(IS_ENABLED(RemoveInfoOption)), + @"36": @(IS_ENABLED(RemoveFilterOption)), + @"40": @(IS_ENABLED(RemoveNotifyOption)), + @"58": @(IS_ENABLED(RemoveReportOption)) }; - - if (![actionsToRemove[identifier] boolValue]) { - %orig; + if ([actionsToRemove[iden] boolValue]) return; + + // Method 2: Filter from imageName + NSDictionary *imageNameToRemove = @{ + @"youtube_music": @(IS_ENABLED(RemoveYouTubeMusicOption)), + @"flag": @(IS_ENABLED(RemoveReportOption)), + @"alert_bubble": @(IS_ENABLED(RemoveFeedBackOption)), + @"bookmark": @(IS_ENABLED(RemoveSaveOption)), + @"circle_slash": @(IS_ENABLED(RemoveNotInterestedOption)), + @"x_circle": @(IS_ENABLED(RemoveDontRecommendOption)), + @"chromecast": @(IS_ENABLED(RemoveCastOption)), + @"shuffle": @(IS_ENABLED(RemoveShuffleOption)), + @"person_x": @(IS_ENABLED(RemoveUnSubOption)), + @"help_circle": @(IS_ENABLED(RemoveHelpOption)), + @"eye_slash": @(IS_ENABLED(RemoveHideFromPlaylistOption)), + @"player_full_enter_alt": @(IS_ENABLED(RemoveClearScreenOption)), + @"info_circle": @(IS_ENABLED(RemoveInfoOption)) + }; + for (NSString *key in imageNameToRemove) { + if ([imageName containsString:key]) { + if ([imageNameToRemove[key] boolValue]) { + return; + } + break; + } } + %orig; } %end -*/ // YTSlientVote (https://github.com/PoomSmart/YTSilentVote) -%group SlientVote %hook YTInnerTubeResponseWrapper - (id)initWithResponse:(id)response cacheContext:(id)arg2 requestStatistics:(id)arg3 mutableSharedData:(id)arg4 { - if ([response isKindOfClass:YTILikeResponseClass] - || [response isKindOfClass:YTIDislikeResponseClass] - || [response isKindOfClass:YTIRemoveLikeResponseClass]) return nil; + if (!IS_ENABLED(HideLikeDislikeVotes)) return %orig; + if ([response isKindOfClass:%c(YTILikeResponse)] + || [response isKindOfClass:%c(YTIDislikeResponse)] + || [response isKindOfClass:%c(YTIRemoveLikeResponse)]) return nil; return %orig; } %end + +%hook NSParagraphStyle ++ (NSWritingDirection)defaultWritingDirectionForLanguage:(id)lang { return IS_ENABLED(DisablesRTL) ? NSWritingDirectionLeftToRight : %orig; } ++ (NSWritingDirection)_defaultWritingDirection { return IS_ENABLED(DisablesRTL) ? NSWritingDirectionLeftToRight : %orig; } %end -%ctor { - YTILikeResponseClass = %c(YTILikeResponse); - YTIDislikeResponseClass = %c(YTIDislikeResponse); - YTIRemoveLikeResponseClass = %c(YTIRemoveLikeResponse); - %init; - if (IS_ENABLED(HideLikeDislikeVotes)) { - %init(SlientVote); +%hook UIDevice +- (UIUserInterfaceIdiom)userInterfaceIdiom { + if (INTFORVAL(DeviceUIIndex) == 1) { + return UIUserInterfaceIdiomPad; } - if (IS_ENABLED(BackgroundPlayback)) { - %init(BackgroundPlayback); + if (INTFORVAL(DeviceUIIndex) == 2) { + return UIUserInterfaceIdiomPhone; } -} \ No newline at end of file + return %orig; +} +%end + +%hook UIKeyboardImpl ++ (BOOL)isFloating { return IS_ENABLED(FloatingKeyboard) ? YES : %orig; } +%end \ No newline at end of file diff --git a/Files/OverlayButtons.x b/Files/OverlayButtons.x new file mode 100644 index 00000000..224270f7 --- /dev/null +++ b/Files/OverlayButtons.x @@ -0,0 +1,551 @@ +#import "Headers.h" + +static NSString *YouModUpdateSpeedLabel = @"YouModUpdateSpeedLabel"; +static NSString *currentSpeedLabel = @"1x"; +static float currentPlaybackRate = 1.0; + +static NSString *YouModUpdateNotification = @"YouModUpdateNotification"; +static NSString *currentQualityLabel = @"Auto"; + +static NSString *speedLabel(float rate) { + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterDecimalStyle; + formatter.minimumFractionDigits = 0; + formatter.maximumFractionDigits = 2; + NSString *rateString = [formatter stringFromNumber:[NSNumber numberWithFloat:rate]]; + return [NSString stringWithFormat:@"%@x", rateString]; +} + +static void didSelectRate(float rate) { + currentPlaybackRate = rate; + currentSpeedLabel = speedLabel(rate); + [[NSNotificationCenter defaultCenter] postNotificationName:YouModUpdateSpeedLabel object:nil]; +} + +@interface YTMainAppControlsOverlayView () +- (void)updateQualityButton:(id)arg; +- (void)updateSpeedButton:(id)arg; +@end + +// YouGetCaption (https://github.com/PoomSmart/YouGetCaption) +static void showTranscript(YTFormat3CaptionViewController *cvc) { + UIView *parent = sbGetNotificationParent(); + MLFormat3Captions *currentCaptions = [cvc valueForKey:@"_currentCaptions"]; + YTIntervalTree *tree = currentCaptions.captions; + NSMutableString *transcript = [NSMutableString string]; + [tree enumerateAllIntervalsWithBlock:^(YTInterval *interval) { + MLCaption *caption = (MLCaption *)interval; + NSArray *segments = caption.segments; + for (MLCaptionSegment *segment in segments) { + [transcript appendString:segment.text]; + } + }]; + if (transcript.length == 0) { + [SBSkipNotificationView showErrorInView:parent message:LOC(@"NO_CAPTIONS") duration:4.0]; + return; + } + YTAlertView *alertView = [%c(YTAlertView) confirmationDialogWithAction:^{ + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + pasteboard.string = transcript; + [SBSkipNotificationView showSuccessInView:parent message:LOC(@"COPIED_TO_CLIPBOARD") duration:3.0]; + } actionTitle:LOC(@"COPY")]; + alertView.title = nil; + alertView.subtitle = transcript; + alertView.shouldDismissOnBackgroundTap = YES; + [alertView show]; +} + +#pragma mark - YMOverlayButtonSpec + +@implementation YMOverlayButtonSpec +@end + +#pragma mark - Registry + +// Base of the view-tag range for registered overlay buttons. Chosen to avoid +// colliding with other tagged views in the player overlay (e.g. the seek-bar +// segment markers at 9900). +static const NSInteger YMOverlayButtonBaseTag = 9910; + +// Button geometry. The top inset places the row just below YouTube's own +// CC/gear row in the top-right corner of the player overlay. +static const CGFloat YMOverlayButtonSize = 30.0; +static const CGFloat YMOverlayButtonSpace = 5.0; +static const CGFloat YMOverlayButtonGap = 18.0; +static const CGFloat YMOverlayButtonTopInset = 52.0; // fallback row top when the gear can't be located +static const CGFloat YMOverlayButtonEdgePadding = 12.0; // fallback right padding when the gear isn't found + +// Width of a text button. Tweak this to make text buttons wider or narrower; icon +// buttons stay square at YMOverlayButtonSize. +static const CGFloat YMOverlayTextButtonWidth = 30.0; + +static NSMutableArray *gOverlayButtons = nil; +static NSInteger gOverlayButtonNextTag = YMOverlayButtonBaseTag; + +void YMRegisterOverlayButton(YMOverlayButtonSpec *spec) { + if (!spec || spec.identifier.length == 0) return; + static dispatch_once_t once; + dispatch_once(&once, ^{ gOverlayButtons = [NSMutableArray array]; }); + + // Replace any previous registration with the same identifier (idempotent). + for (YMOverlayButtonSpec *existing in [gOverlayButtons copy]) { + if ([existing.identifier isEqualToString:spec.identifier]) { + spec.viewTag = existing.viewTag; + [gOverlayButtons removeObject:existing]; + } + } + if (spec.viewTag == 0) spec.viewTag = gOverlayButtonNextTag++; + [gOverlayButtons addObject:spec]; +} + +NSArray *YMRegisteredOverlayButtons(void) { + if (!gOverlayButtons) return @[]; + return [gOverlayButtons sortedArrayUsingComparator:^NSComparisonResult(YMOverlayButtonSpec *a, YMOverlayButtonSpec *b) { + if (a.sortOrder == b.sortOrder) return [a.identifier compare:b.identifier]; + return a.sortOrder < b.sortOrder ? NSOrderedAscending : NSOrderedDescending; + }]; +} + +#pragma mark - Helpers + +// The player view controller that owns this controls overlay, reached through the +// overlay's events delegate. Button handlers use it to act on the current video. +static YTPlayerViewController *YMPlayerVCFromOverlay(YTMainAppControlsOverlayView *overlay) { + YTMainAppVideoPlayerOverlayViewController *mainOverlayController = (YTMainAppVideoPlayerOverlayViewController *)overlay.eventsDelegate; + return mainOverlayController.parentViewController; +} + +// Recursively find the right-most YTQTMButton in the overlay's top region. YouTube +// nests the gear/CC/cast buttons inside a container, so a one-level scan would miss +// them; recursion reaches the nested buttons wherever they sit. +static void YMScanForGearFrame(UIView *view, YTMainAppControlsOverlayView *overlay, CGFloat topRegionMaxY, CGRect *bestFrame) { + for (UIView *sub in view.subviews) { + if ([sub isKindOfClass:%c(YTQTMButton)]) { + CGRect f = [sub convertRect:sub.bounds toView:overlay]; + if (CGRectGetMidY(f) <= topRegionMaxY) { // in the top button row + // The CGRectIsNull check must stay first: CGRectGetMidX(CGRectNull) is + // infinite, so the > comparison alone would never accept the first match. + if (CGRectIsNull(*bestFrame) || CGRectGetMidX(f) > CGRectGetMidX(*bestFrame)) *bestFrame = f; + } + } + YMScanForGearFrame(sub, overlay, topRegionMaxY, bestFrame); + } +} + +// Find YouTube's settings/overflow button so we can anchor our row directly beneath it. +// Prefer the overlay's own overflowButton; otherwise take the right-most YTQTMButton in +// the overlay's top region. Returns its frame in the overlay's coordinate space, or +// CGRectNull if not found (the caller then falls back to the screen edge / top inset). +static CGRect YMGearFrameInOverlay(YTMainAppControlsOverlayView *overlay) { + YTQTMButton *overflow = [overlay valueForKey:@"_overflowButton"]; + if (overflow.window) { + return [overflow convertRect:overflow.bounds toView:overlay]; + } + + CGFloat topRegionMaxY = overlay.bounds.size.height * 0.25; + CGRect bestFrame = CGRectNull; + YMScanForGearFrame(overlay, overlay, topRegionMaxY, &bestFrame); + return bestFrame; +} + +// The font for a text button's label, in YouTube Sans to match native controls, +// with a plain system-font fallback on versions lacking the YouTube Sans style API. +static UIFont *YMOverlayTextButtonFont(NSString *text, CGSize maxSize) { + if (text.length == 0) return [UIFont systemFontOfSize:15 weight:UIFontWeightSemibold]; + + YTDefaultTypeStyle *typeStyle = [%c(YTTypeStyle) defaultTypeStyle]; + BOOL hasYTFont = [typeStyle respondsToSelector:@selector(ytSansFontOfSize:weight:)]; + NSInteger bestSize = 10; + + for (NSInteger size = (NSInteger)maxSize.height; size >= 8; size--) { + UIFont *testFont = hasYTFont ? [typeStyle ytSansFontOfSize:(CGFloat)size weight:UIFontWeightSemibold] : [UIFont systemFontOfSize:(CGFloat)size weight:UIFontWeightSemibold]; + CGRect rect = [text boundingRectWithSize:CGSizeMake(maxSize.width, CGFLOAT_MAX) + options:NSStringDrawingUsesLineFragmentOrigin + attributes:@{NSFontAttributeName: testFont} + context:nil]; + if (ceil(rect.size.width) <= maxSize.width && ceil(rect.size.height) <= maxSize.height) { + bestSize = size; + break; + } + } + return hasYTFont ? [typeStyle ytSansFontOfSize:(CGFloat)bestSize weight:UIFontWeightSemibold] : [UIFont systemFontOfSize:(CGFloat)bestSize weight:UIFontWeightSemibold]; +} + +static YTQTMButton *YMCreateOverlayButton(YTMainAppControlsOverlayView *overlay, YMOverlayButtonSpec *spec) { + YTQTMButton *button; + UIColor *tint = spec.tintColor ?: [UIColor whiteColor]; + + if (spec.title.length > 0) { + // Text button: a label instead of an icon. customTitleColor is YTQTMButton's + // own text-colour channel; sizeWithPaddingAndInsets is disabled so the width + // stays fixed rather than expanding to fit the text. + button = [%c(YTQTMButton) textButton]; + [button setTitle:spec.title forState:UIControlStateNormal]; + button.customTitleColor = tint; + button.titleLabel.font = YMOverlayTextButtonFont(spec.title, CGSizeMake(25, 25)); + button.titleLabel.textAlignment = NSTextAlignmentCenter; + button.sizeWithPaddingAndInsets = NO; + button.titleLabel.numberOfLines = 2; + button.titleLabel.adjustsFontSizeToFitWidth = YES; + button.titleLabel.lineBreakMode = NSLineBreakByClipping; + button.titleLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; + button.contentEdgeInsets = UIEdgeInsetsZero; + button.titleEdgeInsets = UIEdgeInsetsZero; + button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; + button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; + } else { + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:20 weight:UIImageSymbolWeightMedium]; + // Template rendering so YTQTMButton's tint colours the glyph reliably. + UIImage *icon = [[UIImage systemImageNamed:spec.symbolName withConfiguration:config] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + button = [%c(YTQTMButton) iconButton]; + [button setImage:icon forState:UIControlStateNormal]; + button.tintColor = tint; + button.imageView.contentMode = UIViewContentModeScaleAspectFit; + } + + button.exclusiveTouch = YES; + button.tag = spec.viewTag; + // The row's frame is assigned authoritatively in layoutSubviews. + button.frame = CGRectMake(0, 0, YMOverlayButtonSize, YMOverlayButtonSize); + [button addTarget:overlay action:@selector(ymOverlayButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; + [overlay addSubview:button]; + return button; +} + +#pragma mark - YTMainAppControlsOverlayView Hook + +static BOOL isRelatedVideosExpanded = NO; + +%hook YTMainAppControlsOverlayView + +- (void)layoutSubviews { + %orig; + NSArray *specs = YMRegisteredOverlayButtons(); + if (specs.count == 0) return; + + YTPlayerViewController *player = YMPlayerVCFromOverlay(self); + BOOL overlayVisible = self.isOverlayVisible; + CGRect gearFrame = YMGearFrameInOverlay(self); + BOOL hasGear = !CGRectIsNull(gearFrame); + CGFloat trailingCenterX = hasGear ? CGRectGetMidX(gearFrame) : self.bounds.size.width - YMOverlayButtonEdgePadding - YMOverlayButtonSize / 2.0; + CGFloat rowTop = hasGear ? CGRectGetMaxY(gearFrame) + YMOverlayButtonSpace : YMOverlayButtonTopInset; + CGFloat prevHalfWidth = 0; + + for (YMOverlayButtonSpec *spec in specs) { + BOOL visible = (spec.isVisible == nil) || spec.isVisible(player); + YTQTMButton *btn = (YTQTMButton *)[self viewWithTag:spec.viewTag]; + + if (!visible) { + if (btn) [btn removeFromSuperview]; + continue; + } + if (!btn) btn = YMCreateOverlayButton(self, spec); + + btn.hidden = !overlayVisible || isRelatedVideosExpanded; + + if (spec.tintProvider) { + UIColor *dynamic = spec.tintProvider(player); + if (spec.title.length > 0) btn.customTitleColor = dynamic; + else btn.tintColor = dynamic; + } + + CGFloat width = (spec.title.length > 0) ? YMOverlayTextButtonWidth : YMOverlayButtonSize; + CGFloat centerX = (prevHalfWidth == 0) + ? trailingCenterX + : trailingCenterX - prevHalfWidth - YMOverlayButtonGap - width / 2.0; + + btn.frame = CGRectMake(centerX - width / 2.0, rowTop, width, YMOverlayButtonSize); + trailingCenterX = centerX; + prevHalfWidth = width / 2.0; + [self bringSubviewToFront:btn]; + } +} + +- (void)setOverlayVisible:(BOOL)visible { + %orig; + for (YMOverlayButtonSpec *spec in YMRegisteredOverlayButtons()) { + YTQTMButton *btn = (YTQTMButton *)[self viewWithTag:spec.viewTag]; + if (btn) btn.hidden = !visible || isRelatedVideosExpanded; + } +} + +%new +- (void)ymOverlayButtonTapped:(YTQTMButton *)sender { + YMOverlayButtonSpec *matched = nil; + for (YMOverlayButtonSpec *spec in YMRegisteredOverlayButtons()) { + if (spec.viewTag == sender.tag) { matched = spec; break; } + } + if (!matched || !matched.onTap) return; + + YTPlayerViewController *player = YMPlayerVCFromOverlay(self); + matched.onTap(player, sender); +} + +- (id)initWithDelegate:(id)delegate { + self = %orig; + [self updateSpeedButton:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateSpeedButton:) name:YouModUpdateSpeedLabel object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateQualityButton:) name:YouModUpdateNotification object:nil]; + return self; +} + +- (id)initWithDelegate:(id)delegate autoplaySwitchEnabled:(BOOL)autoplaySwitchEnabled { + self = %orig; + [self updateSpeedButton:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateSpeedButton:) name:YouModUpdateSpeedLabel object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateQualityButton:) name:YouModUpdateNotification object:nil]; + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:YouModUpdateSpeedLabel object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:YouModUpdateNotification object:nil]; + %orig; +} + +%new +- (void)updateSpeedButton:(id)arg { + for (YMOverlayButtonSpec *spec in YMRegisteredOverlayButtons()) { + if ([spec.identifier isEqualToString:@"speed.video"]) { + spec.title = currentSpeedLabel; + + YTQTMButton *btn = (YTQTMButton *)[self viewWithTag:spec.viewTag]; + if (btn) { + [btn setTitle:currentSpeedLabel forState:UIControlStateNormal]; + btn.titleLabel.font = YMOverlayTextButtonFont(currentSpeedLabel, CGSizeMake(25, 25)); + } + break; + } + } +} + +%new +- (void)updateQualityButton:(id)arg { + for (YMOverlayButtonSpec *spec in YMRegisteredOverlayButtons()) { + if ([spec.identifier isEqualToString:@"quality.video"]) { + spec.title = currentQualityLabel; + + YTQTMButton *btn = (YTQTMButton *)[self viewWithTag:spec.viewTag]; + if (btn) { + [btn setTitle:currentQualityLabel forState:UIControlStateNormal]; + btn.titleLabel.font = YMOverlayTextButtonFont(currentQualityLabel, CGSizeMake(25, 25)); + } + break; + } + } +} +%end + +%hook YTRelatedVideosViewController +- (void)setExpanded:(BOOL)arg { + %orig; + isRelatedVideosExpanded = arg; + YTRelatedVideosView *relatedview = (YTRelatedVideosView *)self.view; + YTFullscreenEngagementOverlayView *fullov = (YTFullscreenEngagementOverlayView *)relatedview.superview; + YTMainAppVideoPlayerOverlayView *mainov = (YTMainAppVideoPlayerOverlayView *)fullov.superview; + YTMainAppControlsOverlayView *conov = [mainov controlsOverlayView]; + [conov setNeedsLayout]; +} +%end + +static void YouModShowShareNotification(NSString *message, BOOL success) { + UIView *parent = sbGetNotificationParent(); + if (success) { + [SBSkipNotificationView showSuccessInView:parent message:message duration:3.0]; + } else { + [SBSkipNotificationView showErrorInView:parent message:message duration:4.0]; + } +} + +%hook YTPlayerViewController +%new +- (void)YouModShareButton:(UIView *)sourceView { + if (!self.currentVideoID) { + YouModShowShareNotification(LOC(@"ERROR_VIDEOID"), NO); + return; + } else if (self.isPlayingAd) { + YouModShowShareNotification(LOC(@"ERROR_ADS"), NO); + return; + } + + NSString *videoURL = [NSString stringWithFormat:@"https://youtube.com/watch?v=%@", self.currentVideoID]; + NSInteger seconds = (NSInteger)floor(self.currentVideoMediaTime); + NSString *timestampURL = [NSString stringWithFormat:@"%@&t=%lds", videoURL, (long)seconds]; + + UIViewController *presenter = (UIViewController *)[self activeVideoPlayerOverlay]; + YTDefaultSheetController *sheet = [%c(YTDefaultSheetController) sheetControllerWithParentResponder:presenter]; + + YTActionSheetAction *copyURL = [%c(YTActionSheetAction) actionWithTitle:LOC(@"COPY_URL") iconImage:YouModYTIconImage(250, NO, nil) style:0 handler:^(__unused YTActionSheetAction *action) { + UIPasteboard.generalPasteboard.string = videoURL; + YouModShowShareNotification(LOC(@"URL_COPIED"), YES); + }]; + + YTActionSheetAction *copyTimestamp = [%c(YTActionSheetAction) actionWithTitle:LOC(@"COPY_URL_TIMESTAMP") iconImage:YouModYTIconImage(250, NO, nil) style:0 handler:^(__unused YTActionSheetAction *action) { + UIPasteboard.generalPasteboard.string = timestampURL; + YouModShowShareNotification(LOC(@"URL_TIMESTAMP_COPIED"), YES); + }]; + + [sheet addAction:copyURL]; + [sheet addAction:copyTimestamp]; + + [sheet presentFromView:sourceView animated:YES completion:nil]; +} +%new +- (void)YouModLoopButton { + YTMainAppVideoPlayerOverlayViewController *playerOverlay = self.activeVideoPlayerOverlay; + YTAutoplayAutonavController *autoplayController = [playerOverlay valueForKey:@"_autonavController"]; + BOOL isLoopEnabled = !IS_ENABLED(KeepLoopKey); + [[NSUserDefaults standardUserDefaults] setBool:isLoopEnabled forKey:KeepLoopKey]; + [autoplayController setLoopMode:isLoopEnabled ? 2 : 0]; + YouModShowShareNotification(LOC(isLoopEnabled ? @"LOOP_ENABLED" : @"LOOP_DISABLED"), YES); +} +- (void)setPlaybackRate:(float)rate { + didSelectRate(rate); + %orig; +} +%end + +%hook YTAutoplayAutonavController +- (id)initWithParentResponder:(id)arg { + self = %orig; + if (self && IS_ENABLED(KeepLoopKey)) { + [self setLoopMode:2]; + } + return self; +} +- (void)setLoopMode:(NSInteger)arg { + NSInteger set = IS_ENABLED(KeepLoopKey) ? 2 : arg; + %orig(set); +} +%end + +static NSString *getCompactQualityLabel(MLFormat *format) { + NSString *qualityLabel = [format qualityLabel]; + BOOL shouldShowFPS = [format FPS] > 30; + if ([qualityLabel hasPrefix:@"2160p"]) + qualityLabel = [qualityLabel stringByReplacingOccurrencesOfString:@"2160p" withString:@"4K"]; + else if ([qualityLabel hasPrefix:@"1440p"]) + qualityLabel = [qualityLabel stringByReplacingOccurrencesOfString:@"1440p" withString:@"2K"]; + else if ([qualityLabel hasPrefix:@"1080p"]) + qualityLabel = [qualityLabel stringByReplacingOccurrencesOfString:@"1080p" withString:@"FHD"]; + else if ([qualityLabel hasPrefix:@"720p"]) + qualityLabel = [qualityLabel stringByReplacingOccurrencesOfString:@"720p" withString:@"HD"]; + else if (shouldShowFPS) + qualityLabel = [qualityLabel stringByReplacingOccurrencesOfString:@"p" withString:@""]; + if ([qualityLabel hasSuffix:@" HDR"]) + qualityLabel = [qualityLabel stringByReplacingOccurrencesOfString:@" HDR" withString:@"\nHDR"]; + return qualityLabel; +} + +%hook YTVideoQualitySwitchOriginalController + +- (void)singleVideo:(id)singleVideo didSelectVideoFormat:(MLFormat *)format { + currentQualityLabel = getCompactQualityLabel(format); + [[NSNotificationCenter defaultCenter] postNotificationName:YouModUpdateNotification object:nil]; + %orig; +} + +%end + +%hook YTVideoQualitySwitchRedesignedController + +- (void)singleVideo:(id)singleVideo didSelectVideoFormat:(MLFormat *)format { + currentQualityLabel = getCompactQualityLabel(format); + [[NSNotificationCenter defaultCenter] postNotificationName:YouModUpdateNotification object:nil]; + %orig; +} + +%end + +%ctor { + YMOverlayButtonSpec *mute = [[YMOverlayButtonSpec alloc] init]; + mute.identifier = @"mute.video"; + mute.symbolName = IS_ENABLED(KeepMutedKey) ? @"speaker.slash" : @"speaker.wave.2"; + mute.tintColor = [UIColor whiteColor]; + mute.sortOrder = 300; + mute.isVisible = ^BOOL(YTPlayerViewController *player) { + return IS_ENABLED(MuteButton); + }; + mute.onTap = ^(YTPlayerViewController *player, YTQTMButton *button) { + YTSingleVideoController *sgvid = player.activeVideo; + BOOL muteStatus = ![sgvid isMuted]; + [[NSUserDefaults standardUserDefaults] setBool:muteStatus forKey:KeepMutedKey]; + [sgvid setMuted:muteStatus]; + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:20 weight:UIImageSymbolWeightMedium]; + UIImage *newIcon = [UIImage systemImageNamed:muteStatus ? @"speaker.slash" : @"speaker.wave.2" withConfiguration:config]; + [button setImage:newIcon forState:UIControlStateNormal]; + }; + YMRegisterOverlayButton(mute); + YMOverlayButtonSpec *speed = [[YMOverlayButtonSpec alloc] init]; + speed.identifier = @"speed.video"; + speed.title = currentSpeedLabel; + speed.sortOrder = 400; + speed.isVisible = ^BOOL(YTPlayerViewController *player) { + return IS_ENABLED(SpeedButton); + }; + speed.onTap = ^(YTPlayerViewController *player, YTQTMButton *button) { + YTMainAppVideoPlayerOverlayViewController *ovcon = [player activeVideoPlayerOverlay]; + YTMainAppVideoPlayerOverlayView *ovview = [ovcon videoPlayerOverlayView]; + YTMainAppControlsOverlayView *conview = [ovview controlsOverlayView]; + [ovcon didPressVarispeed:button]; + [conview updateSpeedButton:nil]; + }; + YMRegisterOverlayButton(speed); + YMOverlayButtonSpec *quality = [[YMOverlayButtonSpec alloc] init]; + quality.identifier = @"quality.video"; + quality.title = currentQualityLabel; + quality.sortOrder = 500; + quality.isVisible = ^BOOL(YTPlayerViewController *player) { + return IS_ENABLED(QualityButton); + }; + quality.onTap = ^(YTPlayerViewController *player, YTQTMButton *button) { + YTMainAppVideoPlayerOverlayViewController *ovcon = [player activeVideoPlayerOverlay]; + YTMainAppVideoPlayerOverlayView *ovview = [ovcon videoPlayerOverlayView]; + YTMainAppControlsOverlayView *conview = [ovview controlsOverlayView]; + [ovcon didPressVideoQuality:button]; + [conview updateQualityButton:nil]; + }; + YMRegisterOverlayButton(quality); + YMOverlayButtonSpec *share = [[YMOverlayButtonSpec alloc] init]; + share.identifier = @"share.video"; + share.symbolName = @"arrowshape.turn.up.right"; + share.tintColor = [UIColor whiteColor]; + share.sortOrder = 600; + share.isVisible = ^BOOL(YTPlayerViewController *player) { + return IS_ENABLED(ShareButton); + }; + share.onTap = ^(YTPlayerViewController *player, YTQTMButton *button) { + [player YouModShareButton:button]; + }; + YMRegisterOverlayButton(share); + YMOverlayButtonSpec *loop = [[YMOverlayButtonSpec alloc] init]; + loop.identifier = @"loop.video"; + loop.symbolName = IS_ENABLED(KeepLoopKey) ? @"repeat.1" : @"repeat"; + loop.tintColor = [UIColor whiteColor]; + loop.sortOrder = 700; + loop.isVisible = ^BOOL(YTPlayerViewController *player) { + return IS_ENABLED(LoopButton); + }; + loop.onTap = ^(YTPlayerViewController *player, YTQTMButton *button) { + [player YouModLoopButton]; + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:20 weight:UIImageSymbolWeightMedium]; + UIImage *newIcon = [UIImage systemImageNamed:IS_ENABLED(KeepLoopKey) ? @"repeat.1" : @"repeat" withConfiguration:config]; + [button setImage:newIcon forState:UIControlStateNormal]; + }; + YMRegisterOverlayButton(loop); + YMOverlayButtonSpec *caption = [[YMOverlayButtonSpec alloc] init]; + caption.identifier = @"caption.video"; + caption.symbolName = @"captions.bubble"; + caption.tintColor = [UIColor whiteColor]; + caption.sortOrder = 800; + caption.isVisible = ^BOOL(YTPlayerViewController *player) { + return IS_ENABLED(CaptionButton); + }; + caption.onTap = ^(YTPlayerViewController *player, YTQTMButton *button) { + YTMainAppVideoPlayerOverlayViewController *c = [player activeVideoPlayerOverlay]; + YTFormat3CaptionViewController *cvc = [c valueForKey:@"_captionOverlayViewController"]; + showTranscript(cvc); + }; + YMRegisterOverlayButton(caption); + %init; +} diff --git a/Files/Player.x b/Files/Player.x index 63418794..4ce97f40 100644 --- a/Files/Player.x +++ b/Files/Player.x @@ -1,53 +1,325 @@ #import "Headers.h" +static BOOL isWiFiConnected() { + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress); + if (!reachability) return NO; + + SCNetworkReachabilityFlags flags; + BOOL retrievedFlags = SCNetworkReachabilityGetFlags(reachability, &flags); + CFRelease(reachability); + + if (!retrievedFlags) return NO; + + BOOL isReachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0; + BOOL needsConnection = (flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0; + BOOL canConnect = isReachable && !needsConnection; + + if (!canConnect) return NO; + + BOOL isCellular = (flags & kSCNetworkReachabilityFlagsIsWWAN) != 0; + return !isCellular; +} + extern void YouModDownloadSetCurrentPlayer(YTPlayerViewController *player); +extern YTPlayerViewController *YouModDownloadGetCurrentPlayer(void); -float playbackRate = 1.0; +#pragma mark - Rewind / Fast-forward on iOS media controls -/* -static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoController *video, YTSingleVideoTime *time) { - if (!IS_ENABLED(ShowExtraTimeRemaining)) return; +// The user-chosen skip amount for each direction, in seconds. Zero means the +// preference was never set, in which case the seek falls back to 10 seconds. +static CGFloat YouModRewindSecondsValue(void) { + CGFloat s = FLOAT_FOR_KEY(RewindSeconds); + return s > 0 ? s : 10.0; +} - CGFloat rate = playbackRate != 0 ? playbackRate : 1.0; - NSTimeInterval remainingTime = (lround(video.totalMediaTime) - lround(time.time)) / rate; +static CGFloat YouModForwardSecondsValue(void) { + CGFloat s = FLOAT_FOR_KEY(ForwardSeconds); + return s > 0 ? s : 10.0; +} - NSDate *estimatedEndTime = [NSDate dateWithTimeIntervalSinceNow:remainingTime]; +// Seeks the active player by delta seconds, clamped to [0, duration]. Returns NO +// when no active player is available (e.g. a media key is pressed after playback +// ended), so the caller can report the command as having nothing to act on. +// +// The seek runs on the main queue because MPRemoteCommand handlers may be invoked +// off the main thread (notably from Bluetooth and CarPlay), while seekToTime: and +// the player's time accessors are main-thread-only. +static BOOL YouModSeekByInterval(CGFloat delta) { + YTPlayerViewController *player = YouModDownloadGetCurrentPlayer(); + if (!player || ![player respondsToSelector:@selector(seekToTime:)]) return NO; + dispatch_async(dispatch_get_main_queue(), ^{ + CGFloat cur = [player currentVideoMediaTime]; + CGFloat dur = [player currentVideoTotalMediaTime]; + CGFloat target = cur + delta; + if (target < 0) target = 0; + if (dur > 0 && target > dur) target = dur; + [player seekToTime:target]; + }); + return YES; +} - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; - [dateFormatter setDateFormat:@"HH:mm"]; - // [dateFormatter setDateFormat:ytlBool(@"24hrFormat") ? @"HH:mm" : @"h:mm a"]; +// Retained handler tokens for the two skip commands. A non-nil token marks a +// command whose handler is already installed, so it is installed only once. +static id gYouModRewindTarget = nil; +static id gYouModForwardTarget = nil; + +// Points the system now-playing skip controls (lock screen, Bluetooth, Control +// Center, CarPlay) at our per-direction seek. When enabled, the OS previous/next +// commands are turned off and skip-backward/forward take over with the user's +// intervals; when disabled, previous/next are restored. Only these system +// controls are configurable — the on-screen player rewind/fast-forward buttons +// are rendered by YouTube with an amount it owns, so they are left alone. +// +// The handlers are installed once and thereafter only their enabled state and +// preferred intervals are updated. Because the handlers read the seconds at press +// time, a changed preference always seeks by the new amount immediately; the +// interval shown on the OS controls reflects the value captured the last time this +// ran (video change or launch). +static void YouModConfigureRemoteSkipCommands(void) { + MPRemoteCommandCenter *cc = [MPRemoteCommandCenter sharedCommandCenter]; + BOOL back = IS_ENABLED(SkipBackwardEnabled); + BOOL fwd = IS_ENABLED(SkipForwardEnabled); + + // Each direction is independent: the previous/next track command is remapped + // to a skip only on the side the user enabled, so a mixed state (e.g. rewind + // on, fast-forward off) shows a skip-back control alongside the stock next + // track button. + cc.skipBackwardCommand.enabled = back; + cc.skipBackwardCommand.preferredIntervals = @[@(YouModRewindSecondsValue())]; + cc.skipForwardCommand.enabled = fwd; + cc.skipForwardCommand.preferredIntervals = @[@(YouModForwardSecondsValue())]; + cc.previousTrackCommand.enabled = !back; + cc.nextTrackCommand.enabled = !fwd; + + if (!gYouModRewindTarget) { + gYouModRewindTarget = [cc.skipBackwardCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent *event) { + return YouModSeekByInterval(-YouModRewindSecondsValue()) ? MPRemoteCommandHandlerStatusSuccess : MPRemoteCommandHandlerStatusNoSuchContent; + }]; + } + if (!gYouModForwardTarget) { + gYouModForwardTarget = [cc.skipForwardCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent *event) { + return YouModSeekByInterval(YouModForwardSecondsValue()) ? MPRemoteCommandHandlerStatusSuccess : MPRemoteCommandHandlerStatusNoSuchContent; + }]; + } +} - NSString *formattedEndTime = [dateFormatter stringFromDate:estimatedEndTime]; +static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoController *video, YTSingleVideoTime *time) { + if (!IS_ENABLED(ShowExtraTimeRemaining) && !IS_ENABLED(SBShowDuration)) return; + + YTMainAppVideoPlayerOverlayViewController *con = [self activeVideoPlayerOverlay]; + if (![con isKindOfClass:%c(YTMainAppVideoPlayerOverlayViewController)]) return; + CGFloat rate = [con currentPlaybackRate] != 0 ? [con currentPlaybackRate] : 1.0; + NSTimeInterval remainingSeconds = (lround(video.totalMediaTime) - lround(time.time)) / rate; + + NSString *remainingTimeText; + NSString *SBTimeRemaining = nil; + NSTimeInterval SBTotalTimeRemaining = 0.0; + + if (IS_ENABLED(SBShowDuration)) { + if (self.sbSegments && self.sbSegments.count > 0 && self.sbEnabledForVideo) { + for (SBSegment *segment in self.sbSegments) { + SBSegmentAction action = [segment configuredAction]; + if (action == SBSegmentActionDisable) continue; + + CGFloat timeValue = segment.endTime - segment.startTime; + SBTotalTimeRemaining = SBTotalTimeRemaining + timeValue; + } + if (SBTotalTimeRemaining != 0.0) { + NSTimeInterval SBRemaining = video.totalMediaTime - SBTotalTimeRemaining; + int hours = (int)(SBRemaining / 3600); + int minutes = (int)(((int)SBRemaining % 3600) / 60); + int seconds = (int)((int)SBRemaining % 60); + if (hours > 0) { + SBTimeRemaining = [NSString stringWithFormat:@"%d:%02d:%02d", hours, minutes, seconds]; + } else { + SBTimeRemaining = [NSString stringWithFormat:@"%d:%02d", minutes, seconds]; + } + } + } + } + + if (IS_ENABLED(ShowExtraTimeRemaining)) { + NSDate *estimatedEndTime = [NSDate dateWithTimeIntervalSinceNow:remainingSeconds]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; + [dateFormatter setDateFormat:IS_ENABLED(Uses24HoursTime) ? @"HH:mm" : @"h:mm a"]; + remainingTimeText = [dateFormatter stringFromDate:estimatedEndTime]; + } + + NSString *safeRemainingTimeText = remainingTimeText ?: @""; + NSString *safeSBTimeRemaining = SBTimeRemaining ?: @""; - YTPlayerView *playerView = (YTPlayerView *)self.view; + YTPlayerView *playerView = (YTPlayerView *)self.playerView; if (![playerView.overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) return; YTMainAppVideoPlayerOverlayView *overlay = (YTMainAppVideoPlayerOverlayView*)playerView.overlayView; YTLabel *durationLabel = overlay.playerBar.durationLabel; - overlay.playerBar.endTimeString = formattedEndTime; + + NSString *labelText = durationLabel.text ?: @""; + + NSString *baseText = labelText; + NSRange extraTimeRange = [baseText rangeOfString:@" • "]; + if (extraTimeRange.location != NSNotFound) { + baseText = [baseText substringToIndex:extraTimeRange.location]; + } + NSRange sbRange = [baseText rangeOfString:@" ("]; + if (sbRange.location != NSNotFound) { + baseText = [baseText substringToIndex:sbRange.location]; + } - if (![durationLabel.text containsString:formattedEndTime]) { - durationLabel.text = [durationLabel.text stringByAppendingString:[NSString stringWithFormat:@" • %@", formattedEndTime]]; + NSMutableString *newLabelText = [NSMutableString stringWithString:baseText]; + if (IS_ENABLED(SBShowDuration) && safeSBTimeRemaining.length > 0) { + [newLabelText appendFormat:@" (%@)", safeSBTimeRemaining]; + } + if (IS_ENABLED(ShowExtraTimeRemaining) && safeRemainingTimeText.length > 0) { + [newLabelText appendFormat:@" • %@", safeRemainingTimeText]; + } + + if (![labelText isEqualToString:newLabelText]) { + durationLabel.text = newLabelText; + overlay.playerBar.endTimeString = newLabelText; [durationLabel sizeToFit]; } } -*/ + +%hook YTInlinePlayerBarContainerView +%property (nonatomic, strong) NSString *endTimeString; +- (void)layoutSubviews { + %orig; + if (!IS_ENABLED(TapToSeek)) return; + for (UIView *subview in self.subviews) { + if ([subview isKindOfClass:%c(YTInlineScrubGestureView)]) { + BOOL hasCustomTap = NO; + for (UIGestureRecognizer *gesture in subview.gestureRecognizers) { + if ([gesture isKindOfClass:[UITapGestureRecognizer class]] && + [gesture.name isEqualToString:@"YouModTapToSeek"]) { + hasCustomTap = YES; + break; + } + } + if (!hasCustomTap) { + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleYouModScrubTap:)]; + tap.name = @"YouModTapToSeek"; + [subview addGestureRecognizer:tap]; + } + break; + } + } +} +%new +- (void)handleYouModScrubTap:(UITapGestureRecognizer *)gesture { + if (gesture.state == UIGestureRecognizerStateEnded) { + UIView *progressBar = nil; + + for (UIView *subview in self.subviews) { + if ([subview isKindOfClass:%c(YTModularPlayerBarView)]) { + progressBar = subview; + break; + } + } + if (!progressBar) return; + + CGPoint touchPoint = [gesture locationInView:progressBar]; + CGFloat barWidth = progressBar.bounds.size.width; + + if (barWidth > 0) { + CGFloat relativeX = touchPoint.x; + CGFloat percentage = relativeX / barWidth; + CGFloat snapThreshold = 8.0; + + if (relativeX <= snapThreshold) { + percentage = 0.0; + } else if (relativeX >= barWidth - snapThreshold) { + percentage = 1.0; + } else { + if (percentage < 0.0) percentage = 0.0; + if (percentage > 1.0) percentage = 1.0; + } + + UIResponder *responder = self.nextResponder; + while (responder && ![responder isKindOfClass:%c(YTMainAppVideoPlayerOverlayViewController)]) { + responder = responder.nextResponder; + } + + if (responder) { + YTMainAppVideoPlayerOverlayViewController *controller = (YTMainAppVideoPlayerOverlayViewController *)responder; + YTPlayerViewController *controller2 = controller.parentViewController; + CGFloat totalDuration = [controller2 currentVideoTotalMediaTime]; + CGFloat targetTime = totalDuration * percentage; + [controller2 seekToTime:targetTime]; + } + } + } +} +// Disable toggle time remaining - @bhackel +- (void)setShouldDisplayTimeRemaining:(BOOL)arg1 { + BOOL temp; + if (IS_ENABLED(DisablesShowRemaining)) { + temp = NO; + } else if (IS_ENABLED(AlwaysShowRemaining)) { + temp = YES; + } else { + temp = arg1; + } + %orig(temp); +} +// Always show seekbar +- (void)setPlayerBarAlpha:(CGFloat)alpha { + CGFloat temp = IS_ENABLED(AlwaysShowSeekbar) ? 1.0 : alpha; + %orig(temp); +} +// Disables snap to chapter +- (void)inlinePlayerBarView:(id)arg1 didScrubToChapteredTime:(CGFloat)arg2 shouldSnap:(BOOL)arg3 { + BOOL temp = IS_ENABLED(DontSnapToChapter) ? NO : arg3; + %orig(arg1, arg2, temp); +} +- (void)setPeekableViewVisible:(BOOL)visible { + %orig; + if (!IS_ENABLED(ShowExtraTimeRemaining) && !IS_ENABLED(SBShowDuration)) return; + + YTLabel *dLabel = self.durationLabel; + if (dLabel && self.endTimeString) { + if (![dLabel.text isEqualToString:self.endTimeString]) { + dLabel.text = self.endTimeString; + [dLabel sizeToFit]; + } + } +} +%end %hook YTMainAppControlsOverlayView // Hide autoplay Switch - (void)setAutoplaySwitchButtonRenderer:(id)arg1 { if (!IS_ENABLED(HideAutoPlayToggle)) %orig; } // Hide captions Button - (void)setClosedCaptionsOrSubtitlesButtonAvailable:(BOOL)arg1 { if (!IS_ENABLED(HideCaptionsButton)) %orig; } -- (void)setPreviousButtonHidden:(BOOL)arg { IS_ENABLED(HidePrevButton) ? %orig(YES) : %orig; } -- (void)setNextButtonHidden:(BOOL)arg { IS_ENABLED(HideNextButton) ? %orig(YES) : %orig; } // Hide video title in full screen - (BOOL)titleViewHidden { return IS_ENABLED(HideFullvidTitle) ? YES : %orig; } +// Pause On Overlay +- (void)setOverlayVisible:(BOOL)visible { + %orig; + if (!IS_ENABLED(PauseOnOverlay)) return; + YTMainAppVideoPlayerOverlayViewController *mainOverlayController = (YTMainAppVideoPlayerOverlayViewController *)self.eventsDelegate; + YTPlayerViewController *playerViewController = mainOverlayController.parentViewController; + visible ? [playerViewController pause] : [playerViewController play]; +} %end %hook YTAutonavEndscreenController - (void)showEndscreen { if (!IS_ENABLED(HideSuggestedVideo)) %orig; } -- (void)showEndscreenControlsInPlayerBar:(BOOL)arg { IS_ENABLED(HideSuggestedVideo) ? %orig(NO) : %orig; } +- (void)showEndscreenControlsInPlayerBar:(BOOL)arg { + BOOL temp = IS_ENABLED(HideSuggestedVideo) ? NO : arg; + %orig(temp); +} +%end + +// YTAnotherMiniplayer (https://github.com/PoomSmart/YTAnotherMiniplayer) +%hook YTWatchMiniplayerConstants ++ (NSInteger)miniplayerVariant { return IS_ENABLED(UseAnotherMiniplayer) ? 2 : %orig; } %end %hook YTSettings @@ -58,38 +330,25 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll - (BOOL)isAutoplayEnabled { return IS_ENABLED(HideAutoPlayToggle) ? NO : %orig; } %end -/* idk what is this thing does %hook YTColdConfig -- (BOOL)isLandscapeEngagementPanelEnabled { - return NO; -} -%end -*/ - -/* -%hook YTHeaderView -- (BOOL)stickyNavHeaderEnabled { return IS_ENABLED(YTPremiumLogo) ? YES : NO; } // idk what is this does, the nav is already sticky... Or this thing only happens in iPhone? -- (void)setStickyNavHeaderEnabled:(BOOL)arg { IS_ENABLED(YTPremiumLogo) ? %orig(YES) : %orig(NO); } -%end -*/ - -// Remove Dark Background in Overlay -%hook YTMainAppVideoPlayerOverlayView -- (void)setBackgroundVisible:(BOOL)arg1 isGradientBackground:(BOOL)arg2 { IS_ENABLED(RemoveDarkOverlay) ? %orig(NO, arg2) : %orig; } -// Hide Watermarks -- (BOOL)isWatermarkEnabled { return IS_ENABLED(HideWaterMark) ? NO : %orig; } -- (void)setWatermarkEnabled:(BOOL)arg { IS_ENABLED(HideWaterMark) ? %orig(NO) : %orig; } -- (void)layoutSubviews { - %orig; - if (IS_ENABLED(HideCastButtonPlayer)) self.playbackRouteButton.hidden = YES; -} -- (BOOL)isFullscreenActionsVisible { return IS_ENABLED(HideFullAction) ? NO : %orig; } +- (BOOL)isLandscapeEngagementPanelEnabled { return IS_ENABLED(DisablesEngagementPanel) ? NO : %orig; } +- (BOOL)removeNextPaddleForAllVideos { return IS_ENABLED(HideNextAndPrevButtons) ? YES : %orig; } +- (BOOL)removePreviousPaddleForAllVideos { return IS_ENABLED(HideNextAndPrevButtons) ? YES : %orig; } +// Replace previous/next buttons with back and forward +- (BOOL)replaceNextPaddleWithFastForwardButtonForSingletonVods { return IS_ENABLED(ReplacePrevNextButtons) ? YES : %orig; } +- (BOOL)replacePreviousPaddleWithRewindButtonForSingletonVods { return IS_ENABLED(ReplacePrevNextButtons) ? YES : %orig; } %end // No Endscreen Cards %hook YTCreatorEndscreenView -- (void)setHidden:(BOOL)arg1 { IS_ENABLED(HideEndScreenCards) ? %orig(YES) : %orig; } -- (void)setHoverCardHidden:(BOOL)arg { IS_ENABLED(HideEndScreenCards) ? %orig(YES) : %orig; } +- (void)setHidden:(BOOL)arg1 { + BOOL temp = IS_ENABLED(HideEndScreenCards) ? YES : arg1; + %orig(temp); +} +- (void)setHoverCardHidden:(BOOL)arg { + BOOL temp = IS_ENABLED(HideEndScreenCards) ? YES : arg; + %orig(temp); +} - (void)setHoverCardRenderer:(id)arg { if (!IS_ENABLED(HideEndScreenCards)) %orig; } %end @@ -98,26 +357,28 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll - (BOOL)allowDoubleTapToSeekGestureRecognizer { return IS_ENABLED(DisablesDoubleTap) ? NO : %orig; } // Disable long hold - (BOOL)allowLongPressGestureRecognizerInView:(id)arg { return IS_ENABLED(DisablesLongHold) ? NO : %orig; } -%end - -// YTNoPaidPromo (https://github.com/PoomSmart/YTNoPaidPromo) -%group PaidPromoOverlay -%hook YTMainAppVideoPlayerOverlayViewController -- (void)setPaidContentWithPlayerData:(id)data {} -- (void)playerOverlayProvider:(YTPlayerOverlayProvider *)provider didInsertPlayerOverlay:(YTPlayerOverlay *)overlay { - if ([[overlay overlayIdentifier] isEqualToString:@"player_overlay_paid_content"]) return; +// Copy timestamp on pause +- (void)didPressPause:(id)arg { %orig; + if (!IS_ENABLED(CopyWithTimestampOnPause)) return; + CGFloat mediaTimeIn = self.mediaTime; + NSString *vidID = self.videoID; + if (vidID.length) + UIPasteboard.generalPasteboard.string = [NSString stringWithFormat:@"https://www.youtube.com/watch?v=%@&t=%lds", vidID, (long)mediaTimeIn]; } +- (BOOL)isZoomEnabled { return IS_ENABLED(DisablesFreeZoom) ? NO : %orig; } +- (void)setPaidContentWithPlayerData:(id)data { if (!IS_ENABLED(HidePaidPromoOverlay)) %orig; } %end +// YTNoPaidPromo (https://github.com/PoomSmart/YTNoPaidPromo) %hook YTInlineMutedPlaybackPlayerOverlayViewController -- (void)setPaidContentWithPlayerData:(id)data {} -%end +- (void)setPaidContentWithPlayerData:(id)data { if (!IS_ENABLED(HidePaidPromoOverlay)) %orig; } %end // Remove Watermarks %hook YTAnnotationsViewController - (void)loadFeaturedChannelWatermark { if (!IS_ENABLED(HideWaterMark)) %orig; } +- (void)setWatermarkImage:(id)arg1 height:(unsigned long long)arg2 { if (!IS_ENABLED(HideWaterMark)) %orig; } %end // Exit Fullscreen on Finish @@ -125,17 +386,6 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll - (BOOL)shouldExitFullScreenOnFinish { return IS_ENABLED(AutoExitFullScreen) ? YES : %orig; } %end -// Disable toggle time remaining - @bhackel -%hook YTInlinePlayerBarContainerView -- (void)setShouldDisplayTimeRemaining:(BOOL)arg1 { - if (IS_ENABLED(DisablesShowRemaining)) { - %orig(NO); - return; - } - IS_ENABLED(AlwaysShowRemaining) ? %orig(YES) : %orig; -} -%end - // Always use remaining time in the video player - @bhackel %hook YTPlayerBarController // When a new video is played, enable time remaining flag @@ -149,6 +399,16 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll playerBar.shouldDisplayTimeRemaining = YES; } } + YTSingleVideoController *sgvid = [self valueForKey:@"_currentSingleVideo"]; + YTPlayerView *playerview = [sgvid valueForKey:@"_playerView"]; + YTPlayerViewController *playerviewController = [playerview valueForKey:@"_playerViewDelegate"]; + YouModDownloadSetCurrentPlayer(playerviewController); + YouModConfigureRemoteSkipCommands(); + if (IS_ENABLED(MuteButton)) [playerviewController YouModAutoMute]; + if (IS_ENABLED(AutoFullScreen)) [playerviewController performSelector:@selector(YouModAutoFullscreen) withObject:nil afterDelay:0.5]; + if (INTFORVAL(CaptionTrack) != 0) [playerviewController performSelector:@selector(YouModAutoCaptions) withObject:nil afterDelay:0.5]; + if (INTFORVAL(AutoSpeedIndex) != 0) [playerviewController performSelector:@selector(YouModSetAutoSpeed) withObject:nil afterDelay:0.5]; + if (INTFORVAL(AudioTrack) != 0) [playerviewController performSelector:@selector(YouModAutoAudioTrack) withObject:nil afterDelay:0.5]; } %end @@ -166,7 +426,10 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll // Disable Autoplay %hook YTPlaybackConfig -- (void)setStartPlayback:(BOOL)arg1 { IS_ENABLED(StopAutoplayVideo) ? %orig(NO) : %orig; } +- (void)setStartPlayback:(BOOL)arg1 { + BOOL temp = IS_ENABLED(StopAutoplayVideo) ? NO : arg1; + %orig(temp); +} %end // Skip Content Warning (https://github.com/qnblackcat/uYouPlus/blob/main/uYouPlus.xm#L452-L454) @@ -178,32 +441,11 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll - (void)showConfirmAlert { IS_ENABLED(HideContentWarning) ? [self confirmAlertDidPressConfirm] : %orig; } %end -// Always show seekbar -%hook YTInlinePlayerBarContainerView -- (void)setPlayerBarAlpha:(CGFloat)alpha { IS_ENABLED(AlwaysShowSeekbar) ? %orig(1.0) : %orig; } -%end - // Portrait Fullscreen %hook YTWatchViewController - (unsigned long long)allowedFullScreenOrientations { return IS_ENABLED(PortFull) ? UIInterfaceOrientationMaskAllButUpsideDown : %orig; } %end -/* Disable Snap To Chapter (https://github.com/qnblackcat/uYouPlus/blob/main/uYouPlus.xm#L457-464) - GOT REMOVED -%hook YTSegmentableInlinePlayerBarView -- (void)didMoveToWindow { %orig; if (ytlBool(@"dontSnapToChapter")) self.enableSnapToChapter = NO; } -%end - -%hook YTModularPlayerBarController -- (void)setEnableSnapToChapter:(BOOL)arg { %orig(NO); } // idk this works or not -%end -*/ - -// Replace previous/next buttons with back and forward -%hook YTColdConfig -- (BOOL)replaceNextPaddleWithFastForwardButtonForSingletonVods { return IS_ENABLED(ReplacePrevNextButtons) ? YES : %orig; } -- (BOOL)replacePreviousPaddleWithRewindButtonForSingletonVods { return IS_ENABLED(ReplacePrevNextButtons) ? YES : %orig; } -%end - %group ForceMiniPlayer %hook YTIMiniplayerRenderer %new @@ -258,23 +500,6 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll %end -%hook YTVarispeedSwitchControllerImpl - -- (id)init { - self = %orig; - float speeds[] = {0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 5.0, 7.5, 10.0}; - id options[itemCount]; - Class YTVarispeedSwitchControllerOptionClass = %c(YTVarispeedSwitchControllerOption); - for (int i = 0; i < itemCount; ++i) { - NSString *title = [NSString stringWithFormat:@"%.2fx", speeds[i]]; - options[i] = [[YTVarispeedSwitchControllerOptionClass alloc] initWithTitle:title rate:speeds[i]]; - } - [self setValue:[NSArray arrayWithObjects:options count:itemCount] forKey:@"_options"]; - return self; -} - -%end - %hook YTIPlayerHotConfig %new(f@:) @@ -294,90 +519,109 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll %end %end -// Disable Hints -%hook YTSettings -- (BOOL)areHintsDisabled { return IS_ENABLED(DisableHints) ? YES : %orig; } -- (void)setHintsDisabled:(BOOL)arg1 { IS_ENABLED(DisableHints) ? %orig(YES) : %orig; } -%end - -%hook YTSettingsImpl -- (BOOL)areHintsDisabled { return IS_ENABLED(DisableHints) ? YES : %orig; } -- (void)setHintsDisabled:(BOOL)arg1 { IS_ENABLED(DisableHints) ? %orig(YES) : %orig; } -%end - -%hook YTUserDefaults -- (BOOL)areHintsDisabled { return IS_ENABLED(DisableHints) ? YES : %orig; } -- (void)setHintsDisabled:(BOOL)arg1 { IS_ENABLED(DisableHints) ? %orig(YES) : %orig; } -%end +static NSArray *YouModHoldSpeedValues(void) { + return @[@0.0, @0.25, @0.5, @0.75, @1.0, @1.25, @1.5, @1.75, @2.0, @3.0, @4.0, @5.0]; +} -%hook YTPlayerViewController -- (void)loadWithPlayerTransition:(id)arg1 playbackConfig:(id)arg2 { - %orig; - YouModDownloadSetCurrentPlayer(self); - if (IS_ENABLED(AutoFullScreen)) [self performSelector:@selector(YouModAutoFullscreen) withObject:nil afterDelay:0.75]; - // if (ytlBool(@"shortsToRegular")) [self performSelector:@selector(shortsToRegular) withObject:nil afterDelay:0.75]; - if (IS_ENABLED(DisablesCaptions)) [self performSelector:@selector(YouModTurnOffCaptions) withObject:nil afterDelay:1.0]; +static CGFloat YouModSpeedForHoldIndex(NSInteger index) { + NSArray *values = YouModHoldSpeedValues(); + return [values[index] floatValue]; } -- (void)prepareToLoadWithPlayerTransition:(id)arg1 expectedLayout:(id)arg2 { +%hook YTMainAppVideoPlayerOverlayView +- (void)setLongPressGestureRecognizer:(id)arg { + if (INTFORVAL(HoldToSpeedIndex) != 0) return; %orig; - YouModDownloadSetCurrentPlayer(self); - if (IS_ENABLED(AutoFullScreen)) [self performSelector:@selector(YouModAutoFullscreen) withObject:nil afterDelay:0.75]; - // if (ytlBool(@"shortsToRegular")) [self performSelector:@selector(shortsToRegular) withObject:nil afterDelay:0.75]; - if (IS_ENABLED(DisablesCaptions)) [self performSelector:@selector(YouModTurnOffCaptions) withObject:nil afterDelay:1.0]; } - -%new -- (void)YouModTurnOffCaptions { - if ([self.view.superview isKindOfClass:NSClassFromString(@"YTWatchView")]) { - [self setActiveCaptionTrack:nil source:0]; - } +// Remove Dark Background in Overlay +- (void)setBackgroundVisible:(BOOL)arg1 isGradientBackground:(BOOL)arg2 { + BOOL temp = IS_ENABLED(RemoveDarkOverlay) ? NO : arg1; + %orig(temp, arg2); } - -%new -- (void)YouModAutoFullscreen { - YTWatchController *watchController = [self valueForKey:@"_UIDelegate"]; - [watchController showFullScreen]; +// Hide Watermarks +- (BOOL)isWatermarkEnabled { return IS_ENABLED(HideWaterMark) ? NO : %orig; } +- (void)setWatermarkEnabled:(BOOL)arg { + BOOL temp = IS_ENABLED(HideWaterMark) ? NO : arg; + %orig(temp); } - -/* -- (void)singleVideo:(YTSingleVideoController *)video currentVideoTimeDidChange:(YTSingleVideoTime *)time { +- (void)layoutSubviews { %orig; - YouModAddEndTime(self, video, time); + if (IS_ENABLED(HideCastButtonPlayer)) self.playbackRouteButton.hidden = YES; } +- (BOOL)isFullscreenActionsVisible { return IS_ENABLED(HideFullAction) ? NO : %orig; } +%end -- (void)potentiallyMutatedSingleVideo:(YTSingleVideoController *)video currentVideoTimeDidChange:(YTSingleVideoTime *)time { - %orig; - YouModAddEndTime(self, video, time); -} -*/ +%hook YTSingleVideoController -- (void)setPlaybackRate:(float)rate { - playbackRate = rate; +- (void)playerItem:(id)arg1 hasSelectableVideoFormats:(id)arg2 { %orig; + if (!arg2) return; + [self YouModAutoQuality]; } -%end -/* %new -- (void)shortsToRegular { - if (self.contentVideoID != nil && [self.parentViewController isKindOfClass:NSClassFromString(@"YTShortsPlayerViewController")]) { - NSString *vidLink = [NSString stringWithFormat:@"vnd.youtube://%@", self.contentVideoID]; // idk about this - if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:vidLink]]) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:vidLink] options:@{} completionHandler:nil]; +- (void)YouModAutoQuality { + NSArray *videoFormats = self.selectableVideoFormats; + // Return early if there aren't any video formats available + // eg. Voice comments and others + if (!videoFormats || videoFormats.count == 0) return; + NSInteger kQualityIndex = isWiFiConnected() ? INTFORVAL(WifiQualityIndex) : INTFORVAL(CellQualityIndex); + if ([NSProcessInfo processInfo].lowPowerModeEnabled) kQualityIndex = INTFORVAL(LowPowerQualityIndex); + if (kQualityIndex == 0) return; + + NSString *bestQualityLabel; + int highestResolution = 0; + for (MLFormat *format in videoFormats) { + int reso = format.singleDimensionResolution; + if (reso > highestResolution) { + highestResolution = reso; + bestQualityLabel = format.qualityLabel; } } -} -%end -// Fix Playlist Mini-bar Height For Small Screens -%hook YTPlaylistMiniBarView -- (void)setFrame:(CGRect)frame { - if (frame.size.height < 54.0) frame.size.height = 54.0; // what - %orig(frame); + NSArray *qualityLabels = @[@"Default", bestQualityLabel, @"2160p60", @"2160p", @"1440p60", @"1440p", @"1080p60", @"1080p", @"720p60", @"720p", @"480p", @"360p", @"240p", @"144p"]; + NSString *qualityLabel = qualityLabels[kQualityIndex]; + + if (![qualityLabel isEqualToString:bestQualityLabel]) { + BOOL exactMatch = NO; + NSString *closestQualityLabel = qualityLabel; + + for (MLFormat *format in videoFormats) { + if ([format.qualityLabel isEqualToString:qualityLabel]) { + exactMatch = YES; + break; + } + } + + if (!exactMatch) { + NSInteger bestQualityDifference = NSIntegerMax; + + for (MLFormat *format in videoFormats) { + NSArray *formatСomponents = [format.qualityLabel componentsSeparatedByString:@"p"]; + NSArray *targetComponents = [qualityLabel componentsSeparatedByString:@"p"]; + if (formatСomponents.count == 2) { + NSInteger formatQuality = [formatСomponents.firstObject integerValue]; + NSInteger targetQuality = [targetComponents.firstObject integerValue]; + NSInteger difference = labs(formatQuality - targetQuality); + if (difference < bestQualityDifference) { + bestQualityDifference = difference; + closestQualityLabel = format.qualityLabel; + } + } + } + + qualityLabel = closestQualityLabel; + } + } + + MLQuickMenuVideoQualitySettingFormatConstraint *fc = [%c(MLQuickMenuVideoQualitySettingFormatConstraint) alloc]; + if ([fc respondsToSelector:@selector(initWithVideoQualitySetting:formatSelectionReason:qualityLabel:resolutionCap:)]) { + [self setVideoFormatConstraint:[fc initWithVideoQualitySetting:3 formatSelectionReason:2 qualityLabel:qualityLabel resolutionCap:0]]; + } else { + [self setVideoFormatConstraint:[fc initWithVideoQualitySetting:3 formatSelectionReason:2 qualityLabel:qualityLabel]]; + } } %end -*/ // YTClassicVideoQuality (https://github.com/PoomSmart/YTClassicVideoQuality) %group OldVideoQuality @@ -424,17 +668,27 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll %end // Gestures - @bhackel (YTLitePlus) -%group Gestures %hook YTWatchLayerViewController // invoked when the player view controller is either created or destroyed - (void)watchController:(YTWatchController *)watchController didSetPlayerViewController:(YTPlayerViewController *)playerViewController { if (playerViewController) { - // check to see if the pan gesture is already created - if (!playerViewController.YouModPanGesture) { + YTPlayerView *pv = playerViewController.playerView; + if (!playerViewController.YouModPanGesture && (IS_ENABLED(GestureControls) || IS_ENABLED(SeekOnOverlay))) { playerViewController.YouModPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:playerViewController action:@selector(YouModHandlePanGesture:)]; playerViewController.YouModPanGesture.delegate = playerViewController; - [playerViewController.playerView addGestureRecognizer:playerViewController.YouModPanGesture]; - } + [pv addGestureRecognizer:playerViewController.YouModPanGesture]; + } + if (!playerViewController.YouModTapGesture && IS_ENABLED(PauseTwoFingers)) { + playerViewController.YouModTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:playerViewController action:@selector(YouModHandleTapGesture:)]; + playerViewController.YouModTapGesture.numberOfTouchesRequired = 2; + playerViewController.YouModTapGesture.delegate = playerViewController; + [pv addGestureRecognizer:playerViewController.YouModTapGesture]; + } + if (!playerViewController.YouModHoldGesture && INTFORVAL(HoldToSpeedIndex) != 0) { + playerViewController.YouModHoldGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:playerViewController action:@selector(YouModHoldToSpeed:)]; + playerViewController.YouModHoldGesture.minimumPressDuration = 0.4; + [pv addGestureRecognizer:playerViewController.YouModHoldGesture]; + } } %orig; } @@ -442,45 +696,88 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll %hook YTPlayerViewController %property (nonatomic, retain) UIPanGestureRecognizer *YouModPanGesture; +%property (nonatomic, retain) UITapGestureRecognizer *YouModTapGesture; %property (nonatomic, retain) UILabel *YouModGestureHUD; +%property (nonatomic, strong) UIView *YouModSpeedToastView; +%property (nonatomic, strong) UILabel *YouModSpeedToastLabel; +%property (nonatomic, retain) UILongPressGestureRecognizer *YouModHoldGesture; %new - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == self.YouModPanGesture) { + // Return NO if user is choosing other videos in fullscreen + YTMainAppVideoPlayerOverlayViewController *ovcon = [self activeVideoPlayerOverlay]; + YTMainAppVideoPlayerOverlayView *ov = [ovcon videoPlayerOverlayView]; + YTFullscreenEngagementOverlayView *fullov = [ov valueForKey:@"_fullscreenEngagementOverlayView"]; + if (fullov) { + YTRelatedVideosView *relatedview = [fullov valueForKey:@"_relatedVideosView"]; + YTRelatedVideosViewController *relatedcon = [relatedview valueForKey:@"_delegate"]; + if ([relatedcon isExpanded]) return NO; + } + UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; CGPoint startLocation = [panGesture locationInView:self.view]; - CGFloat viewWidth = self.view.bounds.size.width; - - float areaPercent = 0.15; - int areaSetting = INTFORVAL(GestureActivationArea); - if (areaSetting == 0) areaPercent = 0.10; - else if (areaSetting == 2) areaPercent = 0.20; - else if (areaSetting == 3) areaPercent = 0.25; - else if (areaSetting == 4) areaPercent = 0.30; - else if (areaSetting == 5) areaPercent = 0.35; - else if (areaSetting == 6) areaPercent = 0.40; - else if (areaSetting == 7) areaPercent = 0.45; - else if (areaSetting == 8) areaPercent = 0.50; - - int leftAction = [[NSUserDefaults standardUserDefaults] objectForKey:LeftSideGesture] ? INTFORVAL(LeftSideGesture) : 1; - int rightAction = [[NSUserDefaults standardUserDefaults] objectForKey:RightSideGesture] ? INTFORVAL(RightSideGesture) : 2; - - // Ignore touches in the center area -> YouTube's default features (swipe down to dismiss, etc.) work normally - if (startLocation.x > viewWidth * areaPercent && startLocation.x < viewWidth * (1.0 - areaPercent)) return NO; - - // Ignore touches in the area where 'None' is selected in settings - if (startLocation.x <= viewWidth * areaPercent && leftAction == 0) return NO; - if (startLocation.x >= viewWidth * (1.0 - areaPercent) && rightAction == 0) return NO; - - // Only works for vertical swipes -> Does not interfere with YouTube's horizontal seek bar CGPoint velocity = [panGesture velocityInView:self.view]; - if (fabs(velocity.x) > fabs(velocity.y)) return NO; + CGFloat fullWidth = self.view.bounds.size.width; + CGFloat activeWidth = fullWidth; + + // Also return NO if the startLocation is on the engagement panel + YTEngagementPanelContainerView *engagecontainer = [ov valueForKey:@"_engagementPanelContainerView"]; + if (engagecontainer) { + if (engagecontainer.engagementPanelState == 3) { + UIView *mainpanel = nil; + for (UIView *sub in engagecontainer.subviews) { + if ([sub isKindOfClass:%c(UILayoutContainerView)]) { + mainpanel = sub; + break; + } + } + if (mainpanel) { + CGFloat panelWidth = mainpanel.bounds.size.width; + if (panelWidth > 0 && panelWidth < fullWidth) { + CGFloat remainingWidth = fullWidth - panelWidth; + if (startLocation.x > remainingWidth) return NO; + activeWidth = remainingWidth; + } + } + } + } - return YES; + BOOL isHorizontal = fabs(velocity.x) > fabs(velocity.y); + + if (isHorizontal) { + return IS_ENABLED(SeekOnOverlay); + } else { + if (!IS_ENABLED(GestureControls)) return NO; + + float areaPercent = 0.15; + int areaSetting = INTFORVAL(GestureActivationArea); + if (areaSetting == 0) areaPercent = 0.10; + else if (areaSetting == 2) areaPercent = 0.20; + else if (areaSetting == 3) areaPercent = 0.25; + else if (areaSetting == 4) areaPercent = 0.30; + else if (areaSetting == 5) areaPercent = 0.35; + else if (areaSetting == 6) areaPercent = 0.40; + else if (areaSetting == 7) areaPercent = 0.45; + else if (areaSetting == 8) areaPercent = 0.50; + + int leftAction = [[NSUserDefaults standardUserDefaults] objectForKey:LeftSideGesture] ? INTFORVAL(LeftSideGesture) : 1; + int rightAction = [[NSUserDefaults standardUserDefaults] objectForKey:RightSideGesture] ? INTFORVAL(RightSideGesture) : 2; + + if (startLocation.x > activeWidth * areaPercent && startLocation.x < activeWidth * (1.0 - areaPercent)) return NO; + if (startLocation.x <= activeWidth * areaPercent && leftAction == 0) return NO; + if (startLocation.x >= activeWidth * (1.0 - areaPercent) && rightAction == 0) return NO; + + return YES; + } } return YES; } + %new - (void)YouModHandlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer { + // 0 = None, 1 = Vertical (Bright/Vol/Speed), 2 = Horizontal (Scrub) + static int currentPanMode = 0; + static float initialVolume; static float initialBrightness; static float initialSpeed; @@ -502,6 +799,8 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll } }); + YTMainAppVideoPlayerOverlayViewController *ovcon = [self activeVideoPlayerOverlay]; + if (IS_ENABLED(GestureHUD)) { if (!self.YouModGestureHUD) { self.YouModGestureHUD = [[UILabel alloc] initWithFrame:CGRectZero]; @@ -516,155 +815,537 @@ static void YouModAddEndTime(YTPlayerViewController *self, YTSingleVideoControll } if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) { - CGPoint startLocation = [panGestureRecognizer locationInView:self.view]; - CGFloat viewWidth = self.view.bounds.size.width; - - float areaPercent = 0.15; - int areaSetting = INTFORVAL(GestureActivationArea); - if (areaSetting == 0) areaPercent = 0.10; - else if (areaSetting == 2) areaPercent = 0.20; - else if (areaSetting == 3) areaPercent = 0.25; - else if (areaSetting == 4) areaPercent = 0.30; - else if (areaSetting == 5) areaPercent = 0.35; - else if (areaSetting == 6) areaPercent = 0.40; - else if (areaSetting == 7) areaPercent = 0.45; - else if (areaSetting == 8) areaPercent = 0.50; - - int leftAction = [[NSUserDefaults standardUserDefaults] objectForKey:LeftSideGesture] ? INTFORVAL(LeftSideGesture) : 1; - int rightAction = [[NSUserDefaults standardUserDefaults] objectForKey:RightSideGesture] ? INTFORVAL(RightSideGesture) : 2; - - if (startLocation.x <= viewWidth * areaPercent) { - controlType = leftAction; - } else if (startLocation.x >= viewWidth * (1.0 - areaPercent)) { - controlType = rightAction; + CGPoint velocity = [panGestureRecognizer velocityInView:self.view]; + BOOL isHorizontal = fabs(velocity.x) > fabs(velocity.y); + + if (isHorizontal && IS_ENABLED(SeekOnOverlay)) { + currentPanMode = 2; } else { - controlType = 0; // Center area + currentPanMode = 1; } - - deadzoneStartingTranslation = [panGestureRecognizer translationInView:self.view].y; - - if (controlType == 1) { - initialBrightness = [UIScreen mainScreen].brightness; - } else if (controlType == 2) { - initialVolume = [[AVAudioSession sharedInstance] outputVolume]; - } else if (controlType == 3) { - initialSpeed = playbackRate; - } - - if (IS_ENABLED(GestureHUD)) { - int sizeSetting = [[NSUserDefaults standardUserDefaults] objectForKey:@"GestureHUDSize"] ? (int)[[NSUserDefaults standardUserDefaults] integerForKey:@"GestureHUDSize"] : 1; - CGFloat fontSize = 14.0 + (sizeSetting * 2.0); - CGFloat hudWidth = 74.0 + (sizeSetting * 10.0); - CGFloat hudHeight = 30.0 + (sizeSetting * 4.0); - - self.YouModGestureHUD.frame = CGRectMake(0, 0, hudWidth, hudHeight); - self.YouModGestureHUD.layer.cornerRadius = hudHeight / 2.0; - self.YouModGestureHUD.font = [UIFont boldSystemFontOfSize:fontSize]; - int posSetting = [[NSUserDefaults standardUserDefaults] objectForKey:@"GestureHUDPosition"] ? (int)[[NSUserDefaults standardUserDefaults] integerForKey:@"GestureHUDPosition"] : 0; - CGFloat viewHeight = self.view.bounds.size.height; - CGFloat centerY = viewHeight / 6.0; - if (posSetting == 1) centerY = viewHeight / 2.0; - else if (posSetting == 2) centerY = viewHeight * 5.0 / 6.0; + if (currentPanMode == 2) { + YTMainAppVideoPlayerOverlayView *ovview = [ovcon videoPlayerOverlayView]; + YTInlinePlayerBarContainerView *wth = ovview.playerBar; + YTPlayerBarController *playerbarcon = [wth valueForKey:@"_delegate"]; + [playerbarcon didScrub:panGestureRecognizer]; + } else if (currentPanMode == 1) { + CGPoint startLocation = [panGestureRecognizer locationInView:self.view]; + CGFloat fullWidth = self.view.bounds.size.width; + CGFloat activeWidth = fullWidth; + + YTMainAppVideoPlayerOverlayView *ov = [ovcon videoPlayerOverlayView]; + YTEngagementPanelContainerView *engagecontainer = [ov valueForKey:@"_engagementPanelContainerView"]; + if (engagecontainer && engagecontainer.engagementPanelState == 3) { + UIView *mainpanel = nil; + for (UIView *sub in engagecontainer.subviews) { + if ([sub isKindOfClass:%c(UILayoutContainerView)]) { + mainpanel = sub; + break; + } + } + if (mainpanel) { + CGFloat panelWidth = mainpanel.bounds.size.width; + if (panelWidth > 0 && panelWidth < fullWidth) { + activeWidth = fullWidth - panelWidth; + } + } + } - [self.view bringSubviewToFront:self.YouModGestureHUD]; - self.YouModGestureHUD.center = CGPointMake(viewWidth / 2, centerY); + float areaPercent = 0.15; + int areaSetting = INTFORVAL(GestureActivationArea); + if (areaSetting == 0) areaPercent = 0.10; + else if (areaSetting == 2) areaPercent = 0.20; + else if (areaSetting == 3) areaPercent = 0.25; + else if (areaSetting == 4) areaPercent = 0.30; + else if (areaSetting == 5) areaPercent = 0.35; + else if (areaSetting == 6) areaPercent = 0.40; + else if (areaSetting == 7) areaPercent = 0.45; + else if (areaSetting == 8) areaPercent = 0.50; + + int leftAction = [[NSUserDefaults standardUserDefaults] objectForKey:LeftSideGesture] ? INTFORVAL(LeftSideGesture) : 1; + int rightAction = [[NSUserDefaults standardUserDefaults] objectForKey:RightSideGesture] ? INTFORVAL(RightSideGesture) : 2; + + if (startLocation.x <= activeWidth * areaPercent) { + controlType = leftAction; + } else if (startLocation.x >= activeWidth * (1.0 - areaPercent)) { + controlType = rightAction; + } else { + controlType = 0; + } + + deadzoneStartingTranslation = [panGestureRecognizer translationInView:self.view].y; + + if (controlType == 1) initialBrightness = [UIScreen mainScreen].brightness; + else if (controlType == 2) initialVolume = [[AVAudioSession sharedInstance] outputVolume]; + else if (controlType == 3) initialSpeed = [ovcon currentPlaybackRate]; + + if (IS_ENABLED(GestureHUD) && controlType != 0) { + int sizeSetting = [[NSUserDefaults standardUserDefaults] objectForKey:GestureHUDSize] ? (int)[[NSUserDefaults standardUserDefaults] integerForKey:GestureHUDSize] : 1; + CGFloat fontSize = 14.0 + (sizeSetting * 2.0); + CGFloat hudWidth = 74.0 + (sizeSetting * 10.0); + CGFloat hudHeight = 30.0 + (sizeSetting * 4.0); + + self.YouModGestureHUD.frame = CGRectMake(0, 0, hudWidth, hudHeight); + self.YouModGestureHUD.layer.cornerRadius = hudHeight / 2.0; + self.YouModGestureHUD.font = [UIFont boldSystemFontOfSize:fontSize]; + + int posSetting = [[NSUserDefaults standardUserDefaults] objectForKey:GestureHUDPosition] ? (int)[[NSUserDefaults standardUserDefaults] integerForKey:GestureHUDPosition] : 0; + CGFloat viewHeight = self.view.bounds.size.height; + CGFloat centerY = viewHeight / 6.0; + if (posSetting == 1) centerY = viewHeight / 2.0; + else if (posSetting == 2) centerY = viewHeight * 5.0 / 6.0; + + [self.view bringSubviewToFront:self.YouModGestureHUD]; + self.YouModGestureHUD.center = CGPointMake(activeWidth / 2, centerY); + } } } if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) { - if (controlType == 0) return; - - CGPoint translation = [panGestureRecognizer translationInView:self.view]; - CGFloat adjustedTranslation = translation.y - deadzoneStartingTranslation; - - // Vertical swipe: Value increases as it goes up (translation.y decreases) - float delta = (-adjustedTranslation / self.view.bounds.size.height) * sensitivityFactor; - - NSString *symbolName = nil; - NSString *percentString = nil; - - if (controlType == 1) { - float newBrightness = fmaxf(fminf(initialBrightness + delta, 1.0), 0.0); - [[UIScreen mainScreen] setBrightness:newBrightness]; - symbolName = @"sun.max.fill"; - percentString = [NSString stringWithFormat:@" %d%%", (int)(newBrightness * 100)]; - } else if (controlType == 2) { - float newVolume = fmaxf(fminf(initialVolume + delta, 1.0), 0.0); - volumeViewSlider.value = newVolume; - symbolName = @"speaker.wave.2.fill"; - percentString = [NSString stringWithFormat:@" %d%%", (int)(newVolume * 100)]; - } else if (controlType == 3) { - float speedSensitivity = 8.0; - float speedDelta = (-adjustedTranslation / self.view.bounds.size.height) * speedSensitivity; - float rawSpeed = initialSpeed + speedDelta; - float clampedSpeed = fmaxf(fminf(rawSpeed, 10.0), 0.25); - // Quantize to 0.25x increments (e.g., 1.12 -> 1.0, 1.38 -> 1.25) - float steppedSpeed = roundf(clampedSpeed * 4.0) / 4.0; - - // Only update if the stepped value has actually changed - static float lastUpdatedSpeed = 0; - if (steppedSpeed != lastUpdatedSpeed) { - [self setPlaybackRate:steppedSpeed]; - lastUpdatedSpeed = steppedSpeed; + if (currentPanMode == 2) { + YTMainAppVideoPlayerOverlayView *ovview = [ovcon videoPlayerOverlayView]; + YTInlinePlayerBarContainerView *wth = ovview.playerBar; + YTPlayerBarController *playerbarcon = [wth valueForKey:@"_delegate"]; + [playerbarcon didScrub:panGestureRecognizer]; + } else if (currentPanMode == 1 && controlType != 0) { + CGPoint translation = [panGestureRecognizer translationInView:self.view]; + CGFloat adjustedTranslation = translation.y - deadzoneStartingTranslation; + float delta = (-adjustedTranslation / self.view.bounds.size.height) * sensitivityFactor; + + NSString *symbolName = nil; + NSString *percentString = nil; + + if (controlType == 1) { + float newBrightness = fmaxf(fminf(initialBrightness + delta, 1.0), 0.0); + [[UIScreen mainScreen] setBrightness:newBrightness]; + + if (newBrightness <= 0.5f) { + symbolName = @"sun.min.fill"; + } else { + symbolName = @"sun.max.fill"; + } + + percentString = [NSString stringWithFormat:@" %d%%", (int)(newBrightness * 100)]; + } else if (controlType == 2) { + float newVolume = fmaxf(fminf(initialVolume + delta, 1.0), 0.0); + volumeViewSlider.value = newVolume; + + if (newVolume == 0.0f) { + symbolName = @"speaker.slash.fill"; + } else if (newVolume <= 0.25f) { + symbolName = @"speaker.fill"; + } else if (newVolume <= 0.50f) { + symbolName = @"speaker.wave.1.fill"; + } else if (newVolume <= 0.75f) { + symbolName = @"speaker.wave.2.fill"; + } else { + symbolName = @"speaker.wave.3.fill"; + } + + percentString = [NSString stringWithFormat:@" %d%%", (int)(newVolume * 100)]; + } else if (controlType == 3) { + float speedSensitivity = 8.0; + float speedDelta = (-adjustedTranslation / self.view.bounds.size.height) * speedSensitivity; + float rawSpeed = initialSpeed + speedDelta; + float clampedSpeed = fmaxf(fminf(rawSpeed, 10.0), 0.25); + float steppedSpeed = roundf(clampedSpeed * 4.0) / 4.0; + + static float lastUpdatedSpeed = 0; + if (steppedSpeed != lastUpdatedSpeed) { + [self setPlaybackRate:steppedSpeed]; + lastUpdatedSpeed = steppedSpeed; + } + + if (steppedSpeed < 1.0f) { + symbolName = @"tortoise.fill"; + } else if (steppedSpeed == 1.0f) { + symbolName = @"speedometer"; + } else if (steppedSpeed <= 5.0f) { + symbolName = @"hare.fill"; + } else { + symbolName = @"bolt.fill"; + } + + percentString = [NSString stringWithFormat:@" %.2fx", steppedSpeed]; } - symbolName = @"speedometer"; - percentString = [NSString stringWithFormat:@" %.2fx", steppedSpeed]; - } - if (IS_ENABLED(GestureHUD) && symbolName) { - NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; - UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:self.YouModGestureHUD.font.pointSize - 1]; - UIImage *icon = [UIImage systemImageNamed:symbolName withConfiguration:config]; - attachment.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - CGFloat iconY = (self.YouModGestureHUD.font.capHeight - attachment.image.size.height) / 2.0; - attachment.bounds = CGRectMake(0, iconY, attachment.image.size.width, attachment.image.size.height); - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; - NSAttributedString *textString = [[NSAttributedString alloc] initWithString:percentString attributes:@{NSFontAttributeName: self.YouModGestureHUD.font, NSForegroundColorAttributeName: self.YouModGestureHUD.textColor}]; - [attributedString appendAttributedString:textString]; - self.YouModGestureHUD.attributedText = attributedString; + if (IS_ENABLED(GestureHUD) && symbolName) { + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:self.YouModGestureHUD.font.pointSize - 1]; + UIImage *icon = [UIImage systemImageNamed:symbolName withConfiguration:config]; + attachment.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + CGFloat iconY = (self.YouModGestureHUD.font.capHeight - attachment.image.size.height) / 2.0; + attachment.bounds = CGRectMake(0, iconY, attachment.image.size.width, attachment.image.size.height); + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; + NSAttributedString *textString = [[NSAttributedString alloc] initWithString:percentString attributes:@{NSFontAttributeName: self.YouModGestureHUD.font, NSForegroundColorAttributeName: self.YouModGestureHUD.textColor}]; + [attributedString appendAttributedString:textString]; + self.YouModGestureHUD.attributedText = attributedString; + self.YouModGestureHUD.alpha = 1.0; + } } - if (IS_ENABLED(GestureHUD)) self.YouModGestureHUD.alpha = 1.0; - } else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded || panGestureRecognizer.state == UIGestureRecognizerStateCancelled || panGestureRecognizer.state == UIGestureRecognizerStateFailed) { - if (IS_ENABLED(GestureHUD)) { - [UIView animateWithDuration:0.3 delay:0.5 options:UIViewAnimationOptionCurveEaseOut animations:^{ - self.YouModGestureHUD.alpha = 0.0; - } completion:nil]; + } + + if (panGestureRecognizer.state == UIGestureRecognizerStateEnded || panGestureRecognizer.state == UIGestureRecognizerStateCancelled || panGestureRecognizer.state == UIGestureRecognizerStateFailed) { + if (currentPanMode == 2) { + YTMainAppVideoPlayerOverlayView *ovview = [ovcon videoPlayerOverlayView]; + YTInlinePlayerBarContainerView *wth = ovview.playerBar; + YTPlayerBarController *playerbarcon = [wth valueForKey:@"_delegate"]; + [playerbarcon didScrub:panGestureRecognizer]; + } else if (currentPanMode == 1) { + if (IS_ENABLED(GestureHUD)) { + [UIView animateWithDuration:0.3 delay:0.5 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.YouModGestureHUD.alpha = 0.0; + } completion:nil]; + } } + currentPanMode = 0; } } + %new - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { - // Require other gestures (like YouTube's related videos swipe) to fail when our gesture is active to prevent conflicts. if (gestureRecognizer == self.YouModPanGesture && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { return YES; } return NO; } + %new - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if (gestureRecognizer == self.YouModPanGesture) { - return NO; // Prevents simultaneous recognition with YouTube's default swipe when gestures overlap. + return NO; } return YES; } -%end + +// Pause using Two fingers +%new +- (void)YouModHandleTapGesture:(UITapGestureRecognizer *)tapGestureRecognizer { + if (tapGestureRecognizer.state == UIGestureRecognizerStateEnded) { + if (self.playerState == 3) { + [self pause]; + } else if (self.playerState == 4) { + [self play]; + } + } +} + +%new +- (void)YouModAutoFullscreen { + YTWatchController *watchController = [self valueForKey:@"_UIDelegate"]; + [watchController showFullScreen]; +} + +%new +- (void)YouModSetAutoSpeed { + if (IS_ENABLED(GlobalSpeedLocked)) { + NSInteger speedIndex = INTFORVAL(HoldToSpeedIndex); + CGFloat speed = YouModSpeedForHoldIndex(speedIndex); + [self setPlaybackRate:speed]; + return; + } + if (INTFORVAL(AutoSpeedIndex) == 0) return; + NSArray *speedLabels = @[@0.01, @0.25, @0.5, @0.75, @1.0, @1.25, @1.5, @1.75, @2.0, @3.0, @4.0, @5.0]; + [self setPlaybackRate:[speedLabels[INTFORVAL(AutoSpeedIndex)] floatValue]]; +} + +- (void)singleVideo:(YTSingleVideoController *)video currentVideoTimeDidChange:(YTSingleVideoTime *)time { + %orig; + YouModAddEndTime(self, video, time); +} + +- (void)potentiallyMutatedSingleVideo:(YTSingleVideoController *)video currentVideoTimeDidChange:(YTSingleVideoTime *)time { + %orig; + YouModAddEndTime(self, video, time); +} + +%new +- (void)YouModAutoMute { + YTSingleVideoController *sgvid = self.activeVideo; + BOOL muted = [sgvid isMuted]; + [sgvid setMuted:[self isInlinePlaybackActive] ? muted : IS_ENABLED(KeepMutedKey)]; +} + +%new +- (void)YouModAutoAudioTrack { + NSInteger selectedIndex = INTFORVAL(AudioTrackLangIndex); + NSArray *langCodes = getAllSystemLanguageValues(); + NSString *userTargetLang = langCodes[selectedIndex]; + id switchcon = self.audioTrackController; + NSArray *availableTracks = [switchcon valueForKey:@"_availableAudioTracks"]; + if (!availableTracks || availableTracks.count == 0) return; + YTIAudioTrack *matchedTrack = nil; + + if (INTFORVAL(AudioTrack) == 1) { + // Loop for all tracks + for (YTIAudioTrack *track in availableTracks) { + if ([track.id_p hasSuffix:@".4"]) { + matchedTrack = track; + break; + } + } + } else if (INTFORVAL(AudioTrack) == 2) { + // Loop for all tracks + for (YTIAudioTrack *track in availableTracks) { + if ([track.id_p hasPrefix:userTargetLang]) { + matchedTrack = track; + break; + } + } + + // Check if it's dubbed + if (matchedTrack && [matchedTrack isAutoDubbed] && IS_ENABLED(NoDubbedAudioTrack)) { + matchedTrack = nil; + return; + } + } + + // If found, change to it + if (matchedTrack) { + [self setAudioTrack:matchedTrack source:0]; + } else if (!matchedTrack && IS_ENABLED(NoDubbedAudioTrack)) { + for (YTIAudioTrack *track in availableTracks) { + if ([track.id_p hasSuffix:@".4"]) { + [self setAudioTrack:track source:0]; + break; + } + } + } +} + +%new +- (void)YouModAutoCaptions { + YTSingleVideoController *sgvid = self.activeVideo; + NSArray *allTracks = sgvid.availableCaptionTracks; + if (!allTracks || allTracks.count == 0) return; + NSInteger selectedIndex = INTFORVAL(CaptionTrackLangIndex); + NSArray *langCodes = getAllSystemLanguageValues(); + NSString *userTargetLang = langCodes[selectedIndex]; + MLInnerTubeCaptionTrack *currentTrack = sgvid.activeCaptionTrack; + MLInnerTubeCaptionTrack *matchedTrack; + + if (INTFORVAL(CaptionTrack) == 1) { + if (currentTrack != nil) { + [self YouModCaptionsHelper:nil]; + } + return; + } + + for (MLInnerTubeCaptionTrack *track in allTracks) { + if ([track.languageCode isEqualToString:userTargetLang]) { + matchedTrack = track; + break; + } + } + if (matchedTrack && ([matchedTrack.VSSID hasPrefix:@"a."] || [matchedTrack.VSSID hasPrefix:@"ta."]) && IS_ENABLED(DisablesCaptionTrack)) { + matchedTrack = nil; + [self YouModCaptionsHelper:nil]; + return; + } else if (!matchedTrack && IS_ENABLED(DisablesCaptionTrack)) { + [self YouModCaptionsHelper:nil]; + return; + } + if (matchedTrack && matchedTrack != currentTrack) { + [self YouModCaptionsHelper:matchedTrack]; + } +} + +%new +- (void)YouModCaptionsHelper:(MLInnerTubeCaptionTrack *)track { + if ([self respondsToSelector:@selector(setActiveCaptionTrack:source:)]) { + [self setActiveCaptionTrack:track source:0]; + } else { + [self setActiveCaptionTrack:track]; + } +} +%new +- (void)YouModHideSpeedToast { + [UIView animateWithDuration:0.2 animations:^{ + self.YouModSpeedToastView.alpha = 0.0; + }]; +} +%new +- (void)YouModShowSpeedToast:(CGFloat)speed isLocked:(BOOL)isLocked { + UIColor *themeTextColor = [UIColor labelColor]; + UIColor *toastBgColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) { + if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + return [UIColor colorWithWhite:0.1 alpha:0.95]; + } else { + return [UIColor colorWithWhite:0.95 alpha:0.95]; + } + }]; + + if (!self.YouModSpeedToastView) { + self.YouModSpeedToastView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 160, 44)]; + self.YouModSpeedToastView.layer.cornerRadius = 22; + self.YouModSpeedToastView.clipsToBounds = YES; + self.YouModSpeedToastView.alpha = 0.0; + self.YouModSpeedToastView.backgroundColor = toastBgColor; + + self.YouModSpeedToastLabel = [[UILabel alloc] initWithFrame:self.YouModSpeedToastView.bounds]; + self.YouModSpeedToastLabel.textAlignment = NSTextAlignmentCenter; + self.YouModSpeedToastLabel.textColor = themeTextColor; + self.YouModSpeedToastLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium]; + self.YouModSpeedToastLabel.numberOfLines = 2; + [self.YouModSpeedToastView addSubview:self.YouModSpeedToastLabel]; + + [self.playerView addSubview:self.YouModSpeedToastView]; + } + + self.YouModSpeedToastView.backgroundColor = toastBgColor; + self.YouModSpeedToastLabel.textColor = themeTextColor; + + CGRect toastFrame = self.YouModSpeedToastView.frame; + toastFrame.origin.y = 18; + toastFrame.origin.x = (self.playerView.bounds.size.width - toastFrame.size.width) / 2.0; + self.YouModSpeedToastView.frame = toastFrame; + + self.YouModSpeedToastView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; + [self.playerView bringSubviewToFront:self.YouModSpeedToastView]; + + NSTextAttachment *topAttachment = [[NSTextAttachment alloc] init]; + topAttachment.image = [[UIImage systemImageNamed:@"hare.fill"] imageWithTintColor:themeTextColor]; + topAttachment.bounds = CGRectMake(0, -2, 14, 14); + + NSTextAttachment *lockAttachment = nil; + if (IS_ENABLED(LockSpeed)) { + lockAttachment = [[NSTextAttachment alloc] init]; + NSString *lockIconName = isLocked ? @"lock.fill" : @"lock.open.fill"; + lockAttachment.image = [[UIImage systemImageNamed:lockIconName] imageWithTintColor:themeTextColor]; + lockAttachment.bounds = CGRectMake(0, -1, 12, 12); + } + + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@\n", LOC(@"PLAYBACK_SPEED")]]; + if (topAttachment.image) { + NSAttributedString *topIconString = [NSAttributedString attributedStringWithAttachment:topAttachment]; + [attrString insertAttributedString:topIconString atIndex:0]; + } + + if (lockAttachment && lockAttachment.image) { + NSAttributedString *lockIconString = [NSAttributedString attributedStringWithAttachment:lockAttachment]; + [attrString appendAttributedString:lockIconString]; + [attrString appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; + } + + NSAttributedString *speedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%gx", speed]]; + [attrString appendAttributedString:speedText]; + + self.YouModSpeedToastLabel.attributedText = attrString; + + [UIView animateWithDuration:0.2 animations:^{ + self.YouModSpeedToastView.alpha = 1.0; + }]; +} + +%new +- (void)YouModHoldToSpeed:(UILongPressGestureRecognizer *)gesture { + if (self.playerState != 3) return; + + NSInteger speedIndex = INTFORVAL(HoldToSpeedIndex); + CGFloat speed = YouModSpeedForHoldIndex(speedIndex); + + static CGPoint startLocation; + static BOOL initialLockState; + static BOOL isPendingToggle; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + if (gesture.state == UIGestureRecognizerStateBegan) { + YTMainAppVideoPlayerOverlayViewController *con = [self activeVideoPlayerOverlay]; + CGFloat currentRate = [con currentPlaybackRate]; + CGFloat savedNormal = FLOAT_FOR_KEY(GlobalSpeedLocked); + + if (savedNormal <= 0) { + savedNormal = (currentRate > 0) ? currentRate : 1.0; + [defaults setFloat:savedNormal forKey:GlobalSavedNormalRate]; + [defaults setBool:NO forKey:GlobalSpeedLocked]; + } + + initialLockState = IS_ENABLED(GlobalSpeedLocked); + isPendingToggle = NO; + startLocation = [gesture locationInView:self.playerView]; + + if (!initialLockState) { + if (currentRate != speed) { + savedNormal = (currentRate > 0) ? currentRate : 1.0; + [defaults setFloat:savedNormal forKey:GlobalSavedNormalRate]; + } + [self setPlaybackRate:speed]; + [self YouModShowSpeedToast:speed isLocked:NO]; + } else { + [self setPlaybackRate:speed]; + [self YouModShowSpeedToast:speed isLocked:YES]; + } + } else if (gesture.state == UIGestureRecognizerStateChanged) { + if (!IS_ENABLED(LockSpeed)) return; + + CGPoint currentLocation = [gesture locationInView:self.playerView]; + CGFloat dragDistanceY = currentLocation.y - startLocation.y; + + BOOL stateChanged = NO; + + if (!isPendingToggle && dragDistanceY > 50.0) { + isPendingToggle = YES; + stateChanged = YES; + } else if (isPendingToggle && dragDistanceY < 30.0) { + isPendingToggle = NO; + stateChanged = YES; + } + + if (stateChanged) { + UIImpactFeedbackStyle feedbackStyle = isPendingToggle ? UIImpactFeedbackStyleMedium : UIImpactFeedbackStyleLight; + UIImpactFeedbackGenerator *feedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:feedbackStyle]; + [feedback impactOccurred]; + + BOOL previewLockState = initialLockState ? !isPendingToggle : isPendingToggle; + + if (previewLockState) { + [self YouModShowSpeedToast:speed isLocked:YES]; + } else { + CGFloat toastSpeed; + if (initialLockState) { + CGFloat savedNormal = [defaults floatForKey:GlobalSavedNormalRate]; + toastSpeed = (savedNormal >= 0.25) ? savedNormal : 1.0; + } else { + toastSpeed = speed; + } + [self YouModShowSpeedToast:toastSpeed isLocked:NO]; + } + } + } else if (gesture.state == UIGestureRecognizerStateEnded || + gesture.state == UIGestureRecognizerStateCancelled || + gesture.state == UIGestureRecognizerStateFailed) { + + BOOL finalLockState = initialLockState; + if (gesture.state == UIGestureRecognizerStateEnded) { + if (IS_ENABLED(LockSpeed) && isPendingToggle) { + finalLockState = !initialLockState; + [defaults setBool:finalLockState forKey:GlobalSpeedLocked]; + } + } + if (finalLockState) { + [self setPlaybackRate:speed]; + } else { + CGFloat savedNormal = [defaults floatForKey:GlobalSavedNormalRate]; + CGFloat targetRate = (savedNormal >= 0.25) ? savedNormal : 1.0; + [self setPlaybackRate:targetRate]; + } + isPendingToggle = NO; + [self YouModHideSpeedToast]; + } +} %end %ctor { %init; + YouModConfigureRemoteSkipCommands(); if (IS_ENABLED(OldQualityPicker)) { %init(OldVideoQuality); } - if (IS_ENABLED(ExtraSpeed) || IS_ENABLED(GestureControls)) { + if (IS_ENABLED(ExtraSpeed) || IS_ENABLED(GestureControls) || INTFORVAL(HoldToSpeedIndex) >= 9 || INTFORVAL(AutoSpeedIndex) >= 9) { %init(Speed); } - if (IS_ENABLED(HidePaidPromoOverlay)) { - %init(PaidPromoOverlay); - } - if (IS_ENABLED(GestureControls)) { - %init(Gestures); - } if (IS_ENABLED(ForceMiniPlayer)) { %init(ForceMiniPlayer); } diff --git a/Files/Settings.x b/Files/Settings.x index f2967084..dbc55386 100644 --- a/Files/Settings.x +++ b/Files/Settings.x @@ -4,29 +4,31 @@ #define TweakName @"YouMod" -#define LOC(x) [tweakBundle localizedStringForKey:x value:nil table:nil] +#define YMLOC(x) [tweakBundle localizedStringForKey:x value:nil table:nil] #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) static const NSInteger TweakSection = 'ytmo'; +@class YMSettingsItem; +extern void YMPushSubSettings(NSString *title, NSArray *items, id settingsVC, id parentResponder); +extern YMSettingsItem *YMToggle(NSString *title, NSString *subtitle, NSString *key); +extern YMSettingsItem *YMSlider(NSString *title, NSString *subtitle, NSString *key, float min, float max, float step, float defaultValue); +extern YMSettingsItem *YMPicker(NSString *title, NSString *subtitle, NSString *key, NSArray *options, NSInteger defaultValue); +extern YMSettingsItem *YMAction(NSString *title, NSString *subtitle, void (^action)(UIViewController *vc)); +extern YMSettingsItem *YMHeader(NSString *title); +extern YMSettingsItem *YMSegment(NSString *title, NSString *key, NSArray *icons, NSInteger defaultValue); +extern YMSettingsItem *YMTextSegment(NSString *title, NSString *key, NSArray *labels, NSInteger defaultValue); +extern YMSettingsItem *YMImageSegment(NSString *title, NSString *key, NSArray *images, NSInteger defaultValue); +extern void YMPushTabOrder(id settingsVC, id parentResponder); +extern void YMRegisterSettingsGroup(NSString *title, NSArray *items); +extern void YMPushSettingsSearch(id settingsVC, id parentResponder); + @interface YTSettingsSectionItemManager (YouMod) - (void)updateYouModSectionWithEntry:(id)entry; +- (void)updateSponsorBlockSectionWithEntry:(id)entry; @end -static NSBundle *YouModBundle() { - static NSBundle *bundle = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *tweakBundlePath = [[NSBundle mainBundle] pathForResource:TweakName ofType:@"bundle"]; - if (tweakBundlePath) - bundle = [NSBundle bundleWithPath:tweakBundlePath]; - else - bundle = [NSBundle bundleWithPath:[NSString stringWithFormat:PS_ROOT_PATH_NS(@"/Library/Application Support/%@.bundle"), TweakName]]; - }); - return bundle; -} - static NSString *GetCacheSize() { // YTLite - @dayanch96 NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:cachePath error:nil]; @@ -41,34 +43,14 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 return [formatter stringFromByteCount:folderSize]; } -// Basic switch item - YTLitePlus -#define BASIC_SWITCH(title, description, key) \ - [YTSettingsSectionItemClass switchItemWithTitle:title \ - titleDescription:description \ - accessibilityIdentifier:nil \ - switchOn:IS_ENABLED(key) \ - switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { \ - [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:key]; \ - return YES; \ - } \ - settingItemId:0] - -// Settings header -#define SETTINGS_HEADER \ - [YTSettingsSectionItemClass itemWithTitle:nil \ - titleDescription:LOC(@"SETTINGS") \ - accessibilityIdentifier:nil \ - detailTextBlock:nil \ - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { \ - return NO; \ - }] - %hook YTSettingsGroupData - (NSArray *)orderedCategories { - if (self.type != 1 || class_getClassMethod(objc_getClass("YTSettingsGroupData"), @selector(tweaks))) + if (self.type != 1 || class_getClassMethod(objc_getClass("YTSettingsGroupData"), @selector(tweaks))) { return %orig; - NSMutableArray *mutableCategories = %orig.mutableCopy; + } + NSArray *temp = %orig; + NSMutableArray *mutableCategories = temp.mutableCopy; [mutableCategories insertObject:@(TweakSection) atIndex:0]; return mutableCategories.copy; } @@ -101,7 +83,7 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 // Tweak Version (at the top) // Thanks to the original codes from YTweaks by fosterbarnes - https://github.com/fosterbarnes/YTweaks/blob/e921591a89b87256a2b37c4788bd99282f70d9c2/Settings.x - YTSettingsSectionItem *tweakVersion = [YTSettingsSectionItemClass itemWithTitle:@"YouMod v1.3.0" + YTSettingsSectionItem *tweakVersion = [YTSettingsSectionItemClass itemWithTitle:@"YouMod v2.0.0" titleDescription:nil accessibilityIdentifier:nil detailTextBlock:nil @@ -110,16 +92,6 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 }]; [sectionItems addObject:tweakVersion]; - // Note - YTSettingsSectionItem *note = [YTSettingsSectionItemClass itemWithTitle:LOC(@"NOTE") - titleDescription:nil - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - return NO; - }]; - [sectionItems addObject:note]; - // Section 0 // Github YTSettingsSectionItem *github = [YTSettingsSectionItemClass itemWithTitle:nil @@ -132,8 +104,8 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 [sectionItems addObject:github]; // Issues - YTSettingsSectionItem *issues = [YTSettingsSectionItemClass itemWithTitle:LOC(@"NEW_ISSUES") - titleDescription:LOC(@"NEW_ISSUES_DESC") // Found bug or Feature request -> Report Issues + YTSettingsSectionItem *issues = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"NEW_ISSUES") + titleDescription:YMLOC(@"NEW_ISSUES_DESC") // Found bug or Feature request -> Report Issues accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { @@ -143,8 +115,8 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 [sectionItems addObject:issues]; // Sources codes - YTSettingsSectionItem *sourceCodes = [YTSettingsSectionItemClass itemWithTitle:LOC(@"SOURCE_CODES") - titleDescription:LOC(@"SOURCE_CODES_DESC") // Take a look + YTSettingsSectionItem *sourceCodes = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"SOURCE_CODES") + titleDescription:YMLOC(@"SOURCE_CODES_DESC") // Take a look accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { @@ -153,23 +125,34 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 ]; [sectionItems addObject:sourceCodes]; - /* - // Center YT logo - YTSettingsSectionItem *centerytlogo = [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"CENTER_YT_LOGO") - titleDescription:LOC(@"CENTER_YT_LOGO_DESC") // Set center logo + // ? + YTSettingsSectionItem *blank = [YTSettingsSectionItemClass itemWithTitle:nil + titleDescription:YMLOC(@"EXTRA") accessibilityIdentifier:nil - switchOn:IS_ENABLED(CenterYTLogo) + detailTextBlock:nil + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + return NO; + }]; + [sectionItems addObject:blank]; + + // Fix playback issues + YTSettingsSectionItem *fixPlaybackissues = [YTSettingsSectionItemClass switchItemWithTitle:YMLOC(@"FIX_PLAYBACK_ISSUES") + titleDescription:YMLOC(@"FIX_PLAYBACK_ISSUES_DESC") + accessibilityIdentifier:nil + switchOn:IS_ENABLED(FixPlaybackIssues) switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { - [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:CenterYTLogo]; + [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:FixPlaybackIssues]; return YES; } settingItemId:0]; - [sectionItems addObject:centerytlogo]; - */ + [sectionItems addObject:fixPlaybackissues]; + + // TODO: Center YT logo (not yet implemented) + // [sectionItems addObject: YMToggle(YMLOC(@"CENTER_YT_LOGO"), YMLOC(@"CENTER_YT_LOGO_DESC"), CenterYTLogo)]; // Settings YTSettingsSectionItem *settings = [YTSettingsSectionItemClass itemWithTitle:nil - titleDescription:LOC(@"SETTINGS") + titleDescription:YMLOC(@"SETTINGS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { @@ -177,17 +160,37 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 }]; [sectionItems addObject:settings]; + // Search — opens the global settings search page. The top-level list is YouTube's + // native settings collection view, which we can't attach a live search bar to + // without triggering YouTube's own search mode (it hijacks the pane), so global + // search lives on a pushed page whose own search bar is focused on appear. + // No settingIcon: YouTube already uses the magnifier (YT_SEARCH) for the Navbar + // row, so an icon here would duplicate it. The row's title carries the meaning. + YTSettingsSectionItem *search = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"SEARCH") + titleDescription:YMLOC(@"SEARCH_DESC") + accessibilityIdentifier:nil + detailTextBlock:nil + selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSettingsSearch(settingsViewController, [self parentResponder]); + return YES; + }]; + [sectionItems addObject:search]; + // Section 1 // Downloading - YTSettingsSectionItem *downloadinggroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"DOWNLOADING") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - SETTINGS_HEADER, - BASIC_SWITCH(LOC(@"DOWNLOAD_MANAGER"), LOC(@"DOWNLOAD_MANAGER_DESC"), DownloadManager), - BASIC_SWITCH(LOC(@"DOWNLOAD_SAVE_PHOTOS"), LOC(@"DOWNLOAD_SAVE_PHOTOS_DESC"), DownloadSaveToPhotos), - BASIC_SWITCH(LOC(@"DOWNLOAD_DRC_AUDIO"), LOC(@"DOWNLOAD_DRC_AUDIO"), DownloadPreferDRCAudio), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"DOWNLOADING") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + NSArray *downloadingItems = @[ + YMToggle(YMLOC(@"DOWNLOAD_MANAGER"), YMLOC(@"DOWNLOAD_MANAGER_DESC"), DownloadManager), + YMToggle(YMLOC(@"DOWNLOAD_SAVE_PHOTOS"), YMLOC(@"DOWNLOAD_SAVE_PHOTOS_DESC"), DownloadSaveToPhotos), + YMTextSegment(YMLOC(@"AUDIO_TRACK"), AudioPreferIndex, (@[YMLOC(@"SHOW_OPTIONS"), YMLOC(@"ORIGINAL"), YMLOC(@"ENGLISH")]), 0), + YMToggle(YMLOC(@"ADD_SHORTS_DOWNLOAD"), YMLOC(@"ADD_SHORTS_DOWNLOAD_DESC"), AddDownloadToShorts), + YMToggle(YMLOC(@"FIX_DOWNLOAD"), YMLOC(@"FIX_DOWNLOAD_DESC"), DownloadFix), + YMPicker(YMLOC(@"DOWNLOAD_SERVER"), YMLOC(@"CHOOSE_DOWNLOAD_SERVER"), DownloadServerIndex, (@[YMLOC(@"SERVER_EUROPRE1"), YMLOC(@"SERVER_ASIA1")]), 0), + YMToggle(YMLOC(@"DOWNLOAD_COMMENT"), YMLOC(@"DOWNLOAD_COMMENT_DESC"), DownloadComment), + YMToggle(YMLOC(@"DOWNLOAD_POST"), YMLOC(@"DOWNLOAD_POST_DESC"), DownloadPost), + ]; + YMRegisterSettingsGroup(YMLOC(@"DOWNLOADING"), downloadingItems); + YTSettingsSectionItem *downloadinggroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"DOWNLOADING") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSubSettings(YMLOC(@"DOWNLOADING"), downloadingItems, settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *downloadIcon = [%c(YTIIcon) new]; @@ -197,14 +200,13 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 // Section 2 // Appearance - YTSettingsSectionItem *appergroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"APPEARANCE") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - SETTINGS_HEADER, - BASIC_SWITCH(LOC(@"OLED_THEME"), LOC(@"OLED_THEME_DESC"), OLEDTheme), - BASIC_SWITCH(LOC(@"OLED_KEYBOARD"), LOC(@"OLED_KEYBOARD_DESC"), OLEDKeyboard), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"APPEARANCE") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + NSArray *appearanceItems = @[ + YMToggle(YMLOC(@"OLED_THEME"), YMLOC(@"OLED_THEME_DESC"), OLEDTheme), + YMToggle(YMLOC(@"OLED_KEYBOARD"), YMLOC(@"OLED_KEYBOARD_DESC"), OLEDKeyboard), + ]; + YMRegisterSettingsGroup(YMLOC(@"APPEARANCE"), appearanceItems); + YTSettingsSectionItem *appergroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"APPEARANCE") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSubSettings(YMLOC(@"APPEARANCE"), appearanceItems, settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *icon0 = [%c(YTIIcon) new]; @@ -214,18 +216,18 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 // Section 3 // Navigation bar - YTSettingsSectionItem *navbargroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"NAVBAR") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - SETTINGS_HEADER, - BASIC_SWITCH(LOC(@"HIDE_YT_LOGO"), LOC(@"HIDE_YT_LOGO_DESC"), HideYTLogo), - BASIC_SWITCH(LOC(@"PREMIUM_LOGO"), LOC(@"PREMIUM_LOGO_DESC"), YTPremiumLogo), - BASIC_SWITCH(LOC(@"HIDE_NOTIFICATION_BUTTON"), LOC(@"HIDE_NOTIFICATION_BUTTON_DESC"), HideNoti), - BASIC_SWITCH(LOC(@"HIDE_SEARCH_BUTTON"), LOC(@"HIDE_SEARCH_BUTTON_DESC"), HideSearch), - BASIC_SWITCH(LOC(@"HIDE_VOICE_SEARCH_BUTTON"), LOC(@"HIDE_VOICE_SEARCH_BUTTON_DESC"), HideVoiceSearch), - BASIC_SWITCH(LOC(@"HIDE_CAST_BUTTON_NAVBAR"), LOC(@"HIDE_CAST_BUTTON_NAVBAR_DESC"), HideCastButtonNav), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"NAVBAR") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + NSArray *navbarItems = @[ + YMToggle(YMLOC(@"STICKY_NAVBAR"), YMLOC(@"STICKY_NAVBAR_DESC"), StickyNavBar), + YMToggle(YMLOC(@"HIDE_YT_LOGO"), YMLOC(@"HIDE_YT_LOGO_DESC"), HideYTLogo), + YMToggle(YMLOC(@"PREMIUM_LOGO"), YMLOC(@"PREMIUM_LOGO_DESC"), YTPremiumLogo), + YMToggle(YMLOC(@"HIDE_NOTIFICATION_BUTTON"), YMLOC(@"HIDE_NOTIFICATION_BUTTON_DESC"), HideNoti), + YMToggle(YMLOC(@"HIDE_SEARCH_BUTTON"), YMLOC(@"HIDE_SEARCH_BUTTON_DESC"), HideSearch), + YMToggle(YMLOC(@"HIDE_VOICE_SEARCH_BUTTON"), YMLOC(@"HIDE_VOICE_SEARCH_BUTTON_DESC"), HideVoiceSearch), + YMToggle(YMLOC(@"HIDE_CAST_BUTTON_NAVBAR"), YMLOC(@"HIDE_CAST_BUTTON_NAVBAR_DESC"), HideCastButtonNav), + ]; + YMRegisterSettingsGroup(YMLOC(@"NAVBAR"), navbarItems); + YTSettingsSectionItem *navbargroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"NAVBAR") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSubSettings(YMLOC(@"NAVBAR"), navbarItems, settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *icon1 = [%c(YTIIcon) new]; @@ -235,20 +237,20 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 // Section 4 // Feed - YTSettingsSectionItem *feedgroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"FEED") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - SETTINGS_HEADER, - BASIC_SWITCH(LOC(@"HIDE_SUBBAR"), LOC(@"HIDE_SUBBAR_DESC"), HideSubbar), - BASIC_SWITCH(LOC(@"HIDE_MUSIC_PLAYLISTS"), LOC(@"HIDE_MUSIC_PLAYLISTS_DESC"), HideGenMusicShelf), - BASIC_SWITCH(LOC(@"HIDE_FEED_POST"), LOC(@"HIDE_FEED_POST_DESC"), HideFeedPost), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_SHELF"), LOC(@"HIDE_SHORTS_SHELF_DESC"), HideShortsShelf), - BASIC_SWITCH(LOC(@"HIDE_SEARCH_HISTORY"), LOC(@"HIDE_SEARCH_HISTORY_DESC"), HideSearchHis), - BASIC_SWITCH(LOC(@"HIDE_SUB_BUTTON"), LOC(@"HIDE_SUB_BUTTON_DESC"), HideSubButton), - BASIC_SWITCH(LOC(@"HIDE_SHOP_BUTTON"), LOC(@"HIDE_SHOP_BUTTON_DESC"), HideShoppingButton), - BASIC_SWITCH(LOC(@"HIDE_MEMBER_BUTTON"), LOC(@"HIDE_MEMBER_BUTTON_DESC"), HideMemberButton), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"FEED") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + NSArray *feedItems = @[ + YMToggle(YMLOC(@"HIDE_SUBBAR"), YMLOC(@"HIDE_SUBBAR_DESC"), HideSubbar), + YMToggle(YMLOC(@"HIDE_HORI_SHELF"), YMLOC(@"HIDE_HORI_SHELF_DESC"), HideHoriShelf), + YMToggle(YMLOC(@"HIDE_MUSIC_PLAYLISTS"), YMLOC(@"HIDE_MUSIC_PLAYLISTS_DESC"), HideGenMusicShelf), + YMToggle(YMLOC(@"HIDE_SURVEYS"), YMLOC(@"HIDE_SURVEYS_DESC"), HideSurveys), + YMToggle(YMLOC(@"HIDE_FEED_POST"), YMLOC(@"HIDE_FEED_POST_DESC"), HideFeedPost), + YMToggle(YMLOC(@"HIDE_PLAYABLES"), YMLOC(@"HIDE_PLAYABLES_DESC"), HidePlayables), + YMToggle(YMLOC(@"HIDE_SHORTS_SHELF"), YMLOC(@"HIDE_SHORTS_SHELF_DESC"), HideShortsShelf), + YMToggle(YMLOC(@"KEEP_SHORTS_SUBSCRIPT"), YMLOC(@"KEEP_SHORTS_SUBSCRIPT_DESC"), KeepShortsSubscript), + YMToggle(YMLOC(@"HIDE_SEARCH_HISTORY"), YMLOC(@"HIDE_SEARCH_HISTORY_DESC"), HideSearchHis), + ]; + YMRegisterSettingsGroup(YMLOC(@"FEED"), feedItems); + YTSettingsSectionItem *feedgroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"FEED") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSubSettings(YMLOC(@"FEED"), feedItems, settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *icon2 = [%c(YTIIcon) new]; @@ -258,152 +260,86 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 // Section 5 // Player - YTSettingsSectionItem *playergroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"PLAYER") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - SETTINGS_HEADER, - BASIC_SWITCH(LOC(@"HIDE_AUTOPLAY"), LOC(@"HIDE_AUTOPLAY_DESC"), HideAutoPlayToggle), - BASIC_SWITCH(LOC(@"HIDE_CAPTIONS_BUTTON"), LOC(@"HIDE_CAPTIONS_BUTTON_DESC"), HideCaptionsButton), - BASIC_SWITCH(LOC(@"HIDE_CAST_BUTTON_PLAYER"), LOC(@"HIDE_CAST_BUTTON_PLAYER_DESC"), HideCastButtonPlayer), - BASIC_SWITCH(LOC(@"HIDE_PREV_BUTTON"), LOC(@"HIDE_PREV_BUTTON_DESC"), HidePrevButton), - BASIC_SWITCH(LOC(@"HIDE_NEXT_BUTTON"), LOC(@"HIDE_NEXT_BUTTON_DESC"), HideNextButton), - BASIC_SWITCH(LOC(@"REPLACE_PREVNEXT_BUTTONS"), LOC(@"REPLACE_PREVNEXT_BUTTONS_DESC"), ReplacePrevNextButtons), - BASIC_SWITCH(LOC(@"REMOVE_DARK_OVERLAY"), LOC(@"REMOVE_DARK_OVERLAY_DESC"), RemoveDarkOverlay), - BASIC_SWITCH(LOC(@"HIDE_END_SCREEN"), LOC(@"HIDE_END_SCREEN_DESC"), HideEndScreenCards), - BASIC_SWITCH(LOC(@"REMOVE_AMBIANT"), LOC(@"REMOVE_AMBIANT_DESC"), RemoveAmbiant), - BASIC_SWITCH(LOC(@"HIDE_SUGGESTED_VIDEO"), LOC(@"HIDE_SUGGESTED_VIDEO_DESC"), HideSuggestedVideo), - BASIC_SWITCH(LOC(@"HIDE_PAID_OVERLAY"), LOC(@"HIDE_PAID_OVERLAY_DESC"), HidePaidPromoOverlay), - BASIC_SWITCH(LOC(@"HIDE_WATERMARK"), LOC(@"HIDE_WATERMARK_DESC"), HideWaterMark), - BASIC_SWITCH(LOC(@"GESTURES"), LOC(@"GESTURES_DESC"), GestureControls), - [YTSettingsSectionItemClass itemWithTitle:LOC(@"GESTURE_AREA") - titleDescription:LOC(@"GESTURE_AREA_DESC") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - int selectedIndex = 1; - int currentVal = INTFORVAL(GestureActivationArea); - if (currentVal == 0) selectedIndex = 0; - else if (currentVal == 1) selectedIndex = 1; - else if (currentVal == 2) selectedIndex = 2; - else if (currentVal == 3) selectedIndex = 3; - else if (currentVal == 4) selectedIndex = 4; - else if (currentVal == 5) selectedIndex = 5; - else if (currentVal == 6) selectedIndex = 6; - else if (currentVal == 7) selectedIndex = 7; - else if (currentVal == 8) selectedIndex = 8; - - NSArray *rows = @[ - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"10%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"15%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"20%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"25%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"30%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:4 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"35%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:5 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"40%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:6 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"45%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:7 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:@"50%" titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:8 forKey:GestureActivationArea]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }] - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"GESTURE_AREA") pickerSectionTitle:nil rows:rows selectedItemIndex:selectedIndex parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; - return YES; - } - ], - [YTSettingsSectionItemClass itemWithTitle:LOC(@"LEFT_SIDE_GESTURE") - titleDescription:nil - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - int currentVal = [[NSUserDefaults standardUserDefaults] objectForKey:LeftSideGesture] ? INTFORVAL(LeftSideGesture) : 1; - NSArray *rows = @[ - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GESTURE_NONE") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:LeftSideGesture]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GESTURE_BRIGHTNESS") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:LeftSideGesture]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GESTURE_VOLUME") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:LeftSideGesture]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GESTURE_SPEED") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:LeftSideGesture]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }] - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"LEFT_SIDE_GESTURE") pickerSectionTitle:nil rows:rows selectedItemIndex:currentVal parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; - return YES; - } - ], - [YTSettingsSectionItemClass itemWithTitle:LOC(@"RIGHT_SIDE_GESTURE") - titleDescription:nil - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - int currentVal = [[NSUserDefaults standardUserDefaults] objectForKey:RightSideGesture] ? INTFORVAL(RightSideGesture) : 2; - NSArray *rows = @[ - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GESTURE_NONE") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:RightSideGesture]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GESTURE_BRIGHTNESS") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:RightSideGesture]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GESTURE_VOLUME") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:RightSideGesture]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GESTURE_SPEED") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:RightSideGesture]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }] - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"RIGHT_SIDE_GESTURE") pickerSectionTitle:nil rows:rows selectedItemIndex:currentVal parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; - return YES; - } - ], - BASIC_SWITCH(LOC(@"GESTURE_HUD"), LOC(@"GESTURE_HUD_DESC"), GestureHUD), - [YTSettingsSectionItemClass itemWithTitle:LOC(@"GESTURE_HUD_SIZE") - titleDescription:LOC(@"GESTURE_HUD_SIZE_DESC") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - int currentVal = [[NSUserDefaults standardUserDefaults] objectForKey:@"GestureHUDSize"] ? (int)[[NSUserDefaults standardUserDefaults] integerForKey:@"GestureHUDSize"] : 1; - NSArray *rows = @[ - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"SMALL") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"GestureHUDSize"]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"NORMAL") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"GestureHUDSize"]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"LARGE") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:@"GestureHUDSize"]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"EXTRALARGE") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:@"GestureHUDSize"]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"MAX") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:4 forKey:@"GestureHUDSize"]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }] - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"GESTURE_HUD_SIZE") pickerSectionTitle:nil rows:rows selectedItemIndex:currentVal parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; - return YES; - } - ], - [YTSettingsSectionItemClass itemWithTitle:LOC(@"GESTURE_HUD_POSITION") - titleDescription:LOC(@"GESTURE_HUD_POSITION_DESC") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - int currentVal = [[NSUserDefaults standardUserDefaults] objectForKey:@"GestureHUDPosition"] ? (int)[[NSUserDefaults standardUserDefaults] integerForKey:@"GestureHUDPosition"] : 0; - NSArray *rows = @[ - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"TOP") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"GestureHUDPosition"]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"MIDDLE") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"GestureHUDPosition"]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"BOTTOM") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:@"GestureHUDPosition"]; [[NSUserDefaults standardUserDefaults] synchronize]; [settingsViewController reloadData]; return YES; }] - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"GESTURE_HUD_POSITION") pickerSectionTitle:nil rows:rows selectedItemIndex:currentVal parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; - return YES; - } - ], - BASIC_SWITCH(LOC(@"DISABLES_DOUBLE_TAP"), LOC(@"DISABLES_DOUBLE_TAP_DESC"), DisablesDoubleTap), - BASIC_SWITCH(LOC(@"DISABLES_LONG_HOLD"), LOC(@"DISABLES_LONG_HOLD_DESC"), DisablesLongHold), - BASIC_SWITCH(LOC(@"AUTO_EXIT_FULLSCREEN"), LOC(@"AUTO_EXIT_FULLSCREEN_DESC"), AutoExitFullScreen), - BASIC_SWITCH(LOC(@"AUTO_DISABLES_CAPTION"), LOC(@"AUTO_DISABLES_CAPTION_DESC"), DisablesCaptions), - BASIC_SWITCH(LOC(@"DISABLES_SHOW_REMAINING"), LOC(@"DISABLES_SHOW_REMAINING_DESC"), DisablesShowRemaining), - BASIC_SWITCH(LOC(@"ALWAYS_SHOW_REMAINING"), LOC(@"ALWAYS_SHOW_REMAINING_DESC"), AlwaysShowRemaining), - // BASIC_SWITCH(LOC(@"SHOW_REMAINING_EXTRA"), LOC(@"SHOW_REMAINING_EXTRA_DESC"), ShowExtraTimeRemaining), - BASIC_SWITCH(LOC(@"HIDE_FULLSCREEN_ACTIONS"), LOC(@"HIDE_FULLSCREEN_ACTIONS_DESC"), HideFullAction), - BASIC_SWITCH(LOC(@"HIDE_FULL_VID_TITLE"), LOC(@"HIDE_FULL_VID_TITLE_DESC"), HideFullvidTitle), - BASIC_SWITCH(LOC(@"STOP_AUTOPLAY_VIDEO"), LOC(@"STOP_AUTOPLAY_VIDEO_DESC"), StopAutoplayVideo), - BASIC_SWITCH(LOC(@"HIDE_CONTENT_WARNING"), LOC(@"HIDE_CONTENT_WARNING_DESC"), HideContentWarning), - BASIC_SWITCH(LOC(@"AUTO_FULLSCREEN"), LOC(@"AUTO_FULLSCREEN_DESC"), AutoFullScreen), - BASIC_SWITCH(LOC(@"PORTRAIT_FULLSCREEN"), LOC(@"PORTRAIT_FULLSCREEN_DESC"), PortFull), - BASIC_SWITCH(LOC(@"OLD_QUALITY_PICKER"), LOC(@"OLD_QUALITY_PICKER_DESC"), OldQualityPicker), - BASIC_SWITCH(LOC(@"EXTRA_SPEED"), LOC(@"EXTRA_SPEED_DESC"), ExtraSpeed), - BASIC_SWITCH(LOC(@"DISABLE_HINTS"), LOC(@"DISABLE_HINTS_DESC"), DisableHints), - BASIC_SWITCH(LOC(@"FORCE_MINIPLAYER"), LOC(@"FORCE_MINIPLAYER_DESC"), ForceMiniPlayer), - BASIC_SWITCH(LOC(@"FORCE_SEEKBAR"), LOC(@"FORCE_SEEKBAR_DESC"), AlwaysShowSeekbar), - BASIC_SWITCH(LOC(@"HIDE_LIKE_BUTTON"), LOC(@"HIDE_LIKE_BUTTON_DESC"), HideLikeButton), - BASIC_SWITCH(LOC(@"HIDE_DISLIKE_BUTTON"), LOC(@"HIDE_DISLIKE_BUTTON_DESC"), HideDisLikeButton), - BASIC_SWITCH(LOC(@"HIDE_SHARE_BUTTON"), LOC(@"HIDE_SHARE_BUTTON_DESC"), HideShareButton), - BASIC_SWITCH(LOC(@"HIDE_DOWNLOAD_BUTTON"), LOC(@"HIDE_DOWNLOAD_BUTTON_DESC"), HideDownloadButton), - BASIC_SWITCH(LOC(@"HIDE_CLIP_BUTTON"), LOC(@"HIDE_CLIP_BUTTON_DESC"), HideClipButton), - BASIC_SWITCH(LOC(@"HIDE_REMIX_BUTTON"), LOC(@"HIDE_REMIX_BUTTON_DESC"), HideRemixButton), - BASIC_SWITCH(LOC(@"HIDE_SAVE_BUTTON"), LOC(@"HIDE_SAVE_BUTTON_DESC"), HideSaveButton), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"PLAYER") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + NSArray *playerItems = @[ + YMPicker(YMLOC(@"QUALITY_WIFI"), YMLOC(@"QUALITY_WIFI_DESC"), WifiQualityIndex, (@[YMLOC(@"DEFAULT"), YMLOC(@"BEST"), @"2160p60", @"2160p", @"1440p60", @"1440p", @"1080p60", @"1080p", @"720p60", @"720p", @"480p", @"360p", @"240p", @"144p"]), 0), + YMPicker(YMLOC(@"QUALITY_CELLULAR"), YMLOC(@"QUALITY_CELLULAR_DESC"), CellQualityIndex, (@[YMLOC(@"DEFAULT"), YMLOC(@"BEST"), @"2160p60", @"2160p", @"1440p60", @"1440p", @"1080p60", @"1080p", @"720p60", @"720p", @"480p", @"360p", @"240p", @"144p"]), 0), + YMPicker(YMLOC(@"QUALITY_LOW_POWER"), YMLOC(@"QUALITY_LOW_POWER_DESC"), LowPowerQualityIndex, (@[YMLOC(@"DEFAULT"), YMLOC(@"BEST"), @"2160p60", @"2160p", @"1440p60", @"1440p", @"1080p60", @"1080p", @"720p60", @"720p", @"480p", @"360p", @"240p", @"144p"]), 0), + YMTextSegment(YMLOC(@"AUDIO_TRACK"), AudioTrack, (@[YMLOC(@"DEFAULT"), YMLOC(@"ORIGINAL"), YMLOC(@"SELECT_MANUALLY")]), 0), + YMPicker(YMLOC(@"AUDIO_TRACK_SELECT"), YMLOC(@"AUDIO_TRACK_SELECT_DESC"), AudioTrackLangIndex, getAllSystemLanguageTitles(), 0), + YMToggle(YMLOC(@"NO_AUTO_DUBBED"), YMLOC(@"NO_AUTO_DUBBED_DESC"), NoDubbedAudioTrack), + YMTextSegment(YMLOC(@"CAPTION_TRACK"), CaptionTrack, (@[YMLOC(@"DEFAULT"), YMLOC(@"DISABLED"), YMLOC(@"SELECT_MANUALLY")]), 0), + YMPicker(YMLOC(@"CAPTION_TRACK_SELECT"), YMLOC(@"CAPTION_TRACK_SELECT_DESC"), CaptionTrackLangIndex, getAllSystemLanguageTitles(), 0), + YMToggle(YMLOC(@"DISABLES_CAPTION_TRACK"), YMLOC(@"DISABLES_CAPTION_TRACK_DESC"), DisablesCaptionTrack), + YMHeader(@""), + YMPicker(YMLOC(@"HOLD_TO_SPEED"), YMLOC(@"HOLD_TO_SPEED_DESC"), HoldToSpeedIndex, (@[YMLOC(@"DEFAULT"), @"0.25x", @"0.5x", @"0.75x", @"1x", @"1.25x", @"1.5x", @"1.75x", @"2x", @"3x", @"4x", @"5x"]), 0), + YMToggle(YMLOC(@"LOCK_SPEED"), YMLOC(@"LOCK_SPEED_DESC"), LockSpeed), + YMHeader(YMLOC(@"INTERFACE")), + YMToggle(YMLOC(@"MUTE_BUTTON"), YMLOC(@"MUTE_BUTTON_DESC"), MuteButton), + YMToggle(YMLOC(@"SPEED_BUTTON"), YMLOC(@"SPEED_BUTTON_DESC"), SpeedButton), + YMToggle(YMLOC(@"QUALITY_BUTTON"), YMLOC(@"QUALITY_BUTTON_DESC"), QualityButton), + YMToggle(YMLOC(@"SHARE_BUTTON"), YMLOC(@"SHARE_BUTTON_DESC"), ShareButton), + YMToggle(YMLOC(@"LOOP_BUTTON"), YMLOC(@"LOOP_BUTTON_DESC"), LoopButton), + YMToggle(YMLOC(@"CAPTION_BUTTON"), YMLOC(@"CAPTION_BUTTON_DESC"), CaptionButton), + YMToggle(YMLOC(@"HIDE_AUTOPLAY"), YMLOC(@"HIDE_AUTOPLAY_DESC"), HideAutoPlayToggle), + YMToggle(YMLOC(@"HIDE_FULL_VID_TITLE"), YMLOC(@"HIDE_FULL_VID_TITLE_DESC"), HideFullvidTitle), + YMToggle(YMLOC(@"HIDE_CAPTIONS_BUTTON"), YMLOC(@"HIDE_CAPTIONS_BUTTON_DESC"), HideCaptionsButton), + YMToggle(YMLOC(@"HIDE_CAST_BUTTON_PLAYER"), YMLOC(@"HIDE_CAST_BUTTON_PLAYER_DESC"), HideCastButtonPlayer), + YMToggle(YMLOC(@"HIDE_NEXT_AND_PREV_BUTTON"), YMLOC(@"HIDE_NEXT_AND_PREV_BUTTON_DESC"), HideNextAndPrevButtons), + YMToggle(YMLOC(@"REPLACE_PREVNEXT_BUTTONS"), YMLOC(@"REPLACE_PREVNEXT_BUTTONS_DESC"), ReplacePrevNextButtons), + YMToggle(YMLOC(@"REMOVE_AMBIANT"), YMLOC(@"REMOVE_AMBIANT_DESC"), RemoveAmbiant), + YMToggle(YMLOC(@"REMOVE_DARK_OVERLAY"), YMLOC(@"REMOVE_DARK_OVERLAY_DESC"), RemoveDarkOverlay), + YMToggle(YMLOC(@"HIDE_END_SCREEN"), YMLOC(@"HIDE_END_SCREEN_DESC"), HideEndScreenCards), + YMToggle(YMLOC(@"HIDE_SUGGESTED_VIDEO"), YMLOC(@"HIDE_SUGGESTED_VIDEO_DESC"), HideSuggestedVideo), + YMToggle(YMLOC(@"HIDE_PAID_OVERLAY"), YMLOC(@"HIDE_PAID_OVERLAY_DESC"), HidePaidPromoOverlay), + YMToggle(YMLOC(@"HIDE_WATERMARK"), YMLOC(@"HIDE_WATERMARK_DESC"), HideWaterMark), + YMToggle(YMLOC(@"HIDE_FULLSCREEN_ACTIONS"), YMLOC(@"HIDE_FULLSCREEN_ACTIONS_DESC"), HideFullAction), + YMToggle(YMLOC(@"FORCE_SEEKBAR"), YMLOC(@"FORCE_SEEKBAR_DESC"), AlwaysShowSeekbar), + YMToggle(YMLOC(@"DISABLES_SHOW_REMAINING"), YMLOC(@"DISABLES_SHOW_REMAINING_DESC"), DisablesShowRemaining), + YMToggle(YMLOC(@"ALWAYS_SHOW_REMAINING"), YMLOC(@"ALWAYS_SHOW_REMAINING_DESC"), AlwaysShowRemaining), + YMToggle(YMLOC(@"SHOW_REMAINING_EXTRA"), YMLOC(@"SHOW_REMAINING_EXTRA_DESC"), ShowExtraTimeRemaining), + YMToggle(YMLOC(@"USES_24_HOURS_TIME"), YMLOC(@"USES_24_HOURS_TIME_DESC"), Uses24HoursTime), + YMToggle(YMLOC(@"OLD_QUALITY_PICKER"), YMLOC(@"OLD_QUALITY_PICKER_DESC"), OldQualityPicker), + YMToggle(YMLOC(@"EXTRA_SPEED"), YMLOC(@"EXTRA_SPEED_DESC"), ExtraSpeed), + // YMToggle(YMLOC(@"USE_ANOTHER_MINIPLAYER"), YMLOC(@"USE_ANOTHER_MINIPLAYER_DESC"), UseAnotherMiniplayer), + YMToggle(YMLOC(@"PORTRAIT_FULLSCREEN"), YMLOC(@"PORTRAIT_FULLSCREEN_DESC"), PortFull), + YMToggle(YMLOC(@"HIDE_RELATED_VIDEOS"), YMLOC(@"HIDE_RELATED_VIDEOS_DESC"), HideRelatedVideos), + YMToggle(YMLOC(@"HIDE_COMMENTS_SECTION"), YMLOC(@"HIDE_COMMENTS_SECTION_DESC"), HideCommentsSection), + YMToggle(YMLOC(@"HIDE_COMMENTS_PREVIEW"), YMLOC(@"HIDE_COMMENTS_PREVIEW_DESC"), HideCommentsPreview), + YMHeader(YMLOC(@"CONTROL_CENTER")), + YMToggle(YMLOC(@"SKIP_BACKWARD"), YMLOC(@"SKIP_BACKWARD_DESC"), SkipBackwardEnabled), + YMSlider(YMLOC(@"REWIND_SECONDS"), nil, RewindSeconds, 5, 60, 5, 10), + YMToggle(YMLOC(@"SKIP_FORWARD"), YMLOC(@"SKIP_FORWARD_DESC"), SkipForwardEnabled), + YMSlider(YMLOC(@"FORWARD_SECONDS"), nil, ForwardSeconds, 5, 60, 5, 10), + YMHeader(YMLOC(@"PLAYER_ACTIONS")), + YMPicker(YMLOC(@"DEFAULT_SPEED"), YMLOC(@"DEFAULT_SPEED_DESC"), AutoSpeedIndex, (@[YMLOC(@"DISABLED"), @"0.25x", @"0.5x", @"0.75x", @"1x", @"1.25x", @"1.5x", @"1.75x", @"2x", @"3x", @"4x", @"5x"]), 0), + YMToggle(YMLOC(@"FORCE_MINIPLAYER"), YMLOC(@"FORCE_MINIPLAYER_DESC"), ForceMiniPlayer), + YMToggle(YMLOC(@"HIDE_CONTENT_WARNING"), YMLOC(@"HIDE_CONTENT_WARNING_DESC"), HideContentWarning), + YMToggle(YMLOC(@"STOP_AUTOPLAY_VIDEO"), YMLOC(@"STOP_AUTOPLAY_VIDEO_DESC"), StopAutoplayVideo), + YMToggle(YMLOC(@"AUTO_FULLSCREEN"), YMLOC(@"AUTO_FULLSCREEN_DESC"), AutoFullScreen), + YMToggle(YMLOC(@"AUTO_EXIT_FULLSCREEN"), YMLOC(@"AUTO_EXIT_FULLSCREEN_DESC"), AutoExitFullScreen), + YMHeader(YMLOC(@"GESTURE_HEADER")), + YMToggle(YMLOC(@"GESTURES"), YMLOC(@"GESTURES_DESC"), GestureControls), + YMPicker(YMLOC(@"GESTURE_AREA"), YMLOC(@"GESTURE_AREA_DESC"), GestureActivationArea, (@[@"10%", @"15%", @"20%", @"25%", @"30%", @"35%", @"40%", @"45%", @"50%"]), 1), + YMPicker(YMLOC(@"LEFT_SIDE_GESTURE"), nil, LeftSideGesture, (@[YMLOC(@"GESTURE_NONE"), YMLOC(@"GESTURE_BRIGHTNESS"), YMLOC(@"GESTURE_VOLUME"), YMLOC(@"GESTURE_SPEED")]), 1), + YMPicker(YMLOC(@"RIGHT_SIDE_GESTURE"), nil, RightSideGesture, (@[YMLOC(@"GESTURE_NONE"), YMLOC(@"GESTURE_BRIGHTNESS"), YMLOC(@"GESTURE_VOLUME"), YMLOC(@"GESTURE_SPEED")]), 2), + YMToggle(YMLOC(@"GESTURE_HUD"), YMLOC(@"GESTURE_HUD_DESC"), GestureHUD), + YMPicker(YMLOC(@"GESTURE_HUD_SIZE"), YMLOC(@"GESTURE_HUD_SIZE_DESC"), GestureHUDSize, (@[YMLOC(@"SMALL"), YMLOC(@"NORMAL"), YMLOC(@"LARGE"), YMLOC(@"EXTRALARGE"), YMLOC(@"MAX")]), 1), + YMPicker(YMLOC(@"GESTURE_HUD_POSITION"), YMLOC(@"GESTURE_HUD_POSITION_DESC"), GestureHUDPosition, (@[YMLOC(@"TOP"), YMLOC(@"MIDDLE"), YMLOC(@"BOTTOM")]), 0), + YMHeader(@""), + YMToggle(YMLOC(@"TAP_TO_SEEK"), YMLOC(@"TAP_TO_SEEK_DESC"), TapToSeek), + YMToggle(YMLOC(@"SEEK_ON_OVERLAY"), YMLOC(@"SEEK_ON_OVERLAY_DESC"), SeekOnOverlay), + YMToggle(YMLOC(@"PAUSE_TWO_FINGERS"), YMLOC(@"PAUSE_TWO_FINGERS_DESC"), PauseTwoFingers), + YMToggle(YMLOC(@"PAUSE_ON_OVERLAY"), YMLOC(@"PAUSE_ON_OVERLAY_DESC"), PauseOnOverlay), + YMToggle(YMLOC(@"COPY_TIMESTAMP_ON_PAUSE"), YMLOC(@"COPY_TIMESTAMP_ON_PAUSE_DESC"), CopyWithTimestampOnPause), + YMToggle(YMLOC(@"DISABLES_DOUBLE_TAP"), YMLOC(@"DISABLES_DOUBLE_TAP_DESC"), DisablesDoubleTap), + YMToggle(YMLOC(@"DISABLES_LONG_HOLD"), YMLOC(@"DISABLES_LONG_HOLD_DESC"), DisablesLongHold), + YMToggle(YMLOC(@"DISABLES_ZOOM"), YMLOC(@"DISABLES_ZOOM_DESC"), DisablesFreeZoom), + YMToggle(YMLOC(@"DISABLES_SNAP_TO_CHAPTER"), YMLOC(@"DISABLES_SNAP_TO_CHAPTER_DESC"), DontSnapToChapter), + YMToggle(YMLOC(@"DISABLES_ENGAGE_PANEL"), YMLOC(@"DISABLES_ENGAGE_PANEL_DESC"), DisablesEngagementPanel), + ]; + YMRegisterSettingsGroup(YMLOC(@"PLAYER"), playerItems); + YTSettingsSectionItem *playergroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"PLAYER") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSubSettings(YMLOC(@"PLAYER"), playerItems, settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *icon3 = [%c(YTIIcon) new]; @@ -413,28 +349,21 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 // Section 6 // Shorts - YTSettingsSectionItem *shortsgroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"SHORTS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - SETTINGS_HEADER, - BASIC_SWITCH(LOC(@"HIDE_SHORTS_LIKE_BUTTON"), LOC(@"HIDE_SHORTS_LIKE_BUTTON_DESC"), HideShortsLikeButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_DISLIKE_BUTTON"), LOC(@"HIDE_SHORTS_DISLIKE_BUTTON_DESC"), HideShortsDisLikeButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_COMMENT_BUTTON"), LOC(@"HIDE_SHORTS_COMMENT_BUTTON_DESC"), HideShortsCommentButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_SHARE_BUTTON"), LOC(@"HIDE_SHORTS_SHARE_BUTTON_DESC"), HideShortsShareButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_REMIX_BUTTON"), LOC(@"HIDE_SHORTS_REMIX_BUTTON_DESC"), HideShortsRemixButton), - BASIC_SWITCH(LOC(@"HIDE_METADATA_BUTTON"), LOC(@"HIDE_METADATA_BUTTON_DESC"), HideShortsMetaButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_PRODUCT"), LOC(@"HIDE_SHORTS_PRODUCT_DESC"), HideShortsProducts), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_RECBAR"), LOC(@"HIDE_SHORTS_RECBAR_DESC"), HideShortsRecbar), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_COMMIT"), LOC(@"HIDE_SHORTS_COMMIT_DESC"), HideShortsCommit), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_SUBSCRIPT_BUTTON"), LOC(@"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC"), HideShortsSubscriptButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_LIVE_BUTTON"), LOC(@"HIDE_SHORTS_LIVE_BUTTON_DESC"), HideShortsLiveButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_LENS_BUTTON"), LOC(@"HIDE_SHORTS_LENS_BUTTON_DESC"), HideShortsLensButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_TRENDS_BUTTON"), LOC(@"HIDE_SHORTS_TRENDS_BUTTON_DESC"), HideShortsTrendsButton), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_TO_VIDEO"), LOC(@"HIDE_SHORTS_TO_VIDEO_DESC"), HideShortsToVideo), - BASIC_SWITCH(LOC(@"ENABLES_SHORTS_QUALITY"), LOC(@"ENABLES_SHORTS_QUALITY_DESC"), EnablesShortsQuality), - BASIC_SWITCH(LOC(@"SHOW_SHORTS_SEEKBAR"), LOC(@"SHOW_SHORTS_SEEKBAR_DESC"), ShowShortsSeekbar), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"SHORTS") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + NSArray *shortsItems = @[ + YMTextSegment(YMLOC(@"SHORTS_ACTION"), ShortsActionIndex, (@[YMLOC(@"LOOP"), YMLOC(@"SKIP_TO_NEXT_SHORTS"), YMLOC(@"PAUSE_SHORTS")]), 0), + YMToggle(YMLOC(@"ENABLES_SHORTS_QUALITY"), YMLOC(@"ENABLES_SHORTS_QUALITY_DESC"), EnablesShortsQuality), + YMToggle(YMLOC(@"SHOW_SHORTS_SEEKBAR"), YMLOC(@"SHOW_SHORTS_SEEKBAR_DESC"), ShowShortsSeekbar), + YMToggle(YMLOC(@"SHORTS_ONLY"), YMLOC(@"SHORTS_ONLY_DESC"), ShortsOnly), + YMToggle(YMLOC(@"SHORTS_FULLSCREEN"), YMLOC(@"SHORTS_FULLSCREEN_DESC"), FullScreenShorts), + YMToggle(YMLOC(@"REMOVE_LIVE_SHORTS"), YMLOC(@"REMOVE_LIVE_SHORTS_DESC"), RemoveShortsLive), + YMToggle(YMLOC(@"REMOVE_POSTS_SHORTS"), YMLOC(@"REMOVE_POSTS_SHORTS_DESC"), RemoveShortsPosts), + YMHeader(YMLOC(@"INTERFACE")), + YMToggle(YMLOC(@"HIDE_SHORTS_PRODUCT"), YMLOC(@"HIDE_SHORTS_PRODUCT_DESC"), HideShortsProducts), + YMToggle(YMLOC(@"HIDE_SHORTS_RECBAR"), YMLOC(@"HIDE_SHORTS_RECBAR_DESC"), HideShortsRecbar), + ]; + YMRegisterSettingsGroup(YMLOC(@"SHORTS"), shortsItems); + YTSettingsSectionItem *shortsgroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"SHORTS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSubSettings(YMLOC(@"SHORTS"), shortsItems, settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *icon4 = [%c(YTIIcon) new]; @@ -444,49 +373,54 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 // Section 7 // Tab bar - YTSettingsSectionItem *tabgroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"TABBAR") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - SETTINGS_HEADER, - [YTSettingsSectionItemClass itemWithTitle:LOC(@"DEFAULT_TAB") - titleDescription:LOC(@"DEFAULT_TAB_DESC") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"HOME_NAME") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:DefaultTab]; - [settingsViewController reloadData]; - return YES; - }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"Shorts") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:DefaultTab]; - [settingsViewController reloadData]; - return YES; - }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"SUBSCRIPT_NAME") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:DefaultTab]; - [settingsViewController reloadData]; - return YES; - }], - [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"LIB_NAME") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:DefaultTab]; - [settingsViewController reloadData]; - return YES; - }] - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"DEFAULT_TAB") pickerSectionTitle:nil rows:rows selectedItemIndex:INTFORVAL(DefaultTab) parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; - return YES; - }], - BASIC_SWITCH(LOC(@"HIDE_TAB_INDI"), LOC(@"HIDE_TAB_INDI_DESC"), HideTabIndi), - BASIC_SWITCH(LOC(@"HIDE_TAB_LABELS"), LOC(@"HIDE_TAB_LABELS_DESC"), HideTabLabels), - BASIC_SWITCH(LOC(@"HIDE_HOME_TAB"), LOC(@"HIDE_HOME_TAB_DESC"), HideHomeTab), - BASIC_SWITCH(LOC(@"HIDE_SHORTS_TAB"), LOC(@"HIDE_SHORTS_TAB_DESC"), HideShortsTab), - BASIC_SWITCH(LOC(@"HIDE_CREATE_BUTTON"), LOC(@"HIDE_CREATE_BUTTON_DESC"), HideCreateButton), - BASIC_SWITCH(LOC(@"HIDE_SUBSCRIPT_TAB"), LOC(@"HIDE_SUBSCRIPT_TAB_DESC"), HideSubscriptTab), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"TABBAR") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + YTSettingsSectionItem *tabgroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"TABBAR") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + // Build dynamic image list from enabled tabs (standard + custom) + NSDictionary *tabYTIconMap = @{@"home": @(65), @"shorts": @(769), @"subscriptions": @(66), @"library": @(61)}; + NSDictionary *tabBundleIconMap = @{@"history": @"icons/history", @"gaming": @"icons/gaming", @"sports": @"icons/sports", @"notifications": @"icons/noti", @"news": @"icons/news", @"music": @"icons/music", @"watchlater": @"icons/watchlater", @"playlist": @"icons/playlist", @"like": @"icons/like", @"live": @"icons/live", @"post": @"icons/post", @"video": @"icons/video", @"movie": @"icons/movie", @"course": @"icons/course", @"minigame": @"icons/minigame", @"fashion": @"icons/fashion", @"learning": @"icons/learning"}; + NSBundle *ymBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"YouMod" ofType:@"bundle"]]; + YTAssetLoader *assetLoader = [[%c(YTAssetLoader) alloc] initWithBundle:ymBundle]; + + NSMutableArray *defaultTabImages = [NSMutableArray array]; + NSArray *savedOrder = [[NSUserDefaults standardUserDefaults] arrayForKey:TabOrder]; + if (savedOrder.count > 0) { + for (NSDictionary *entry in savedOrder) { + if (![entry[@"enabled"] boolValue]) continue; + NSString *tabID = entry[@"id"]; + if ([tabID isEqualToString:@"create"]) continue; + NSNumber *ytIconType = tabYTIconMap[tabID]; + if (ytIconType) { + UIImage *img = YouModYTIconImage([ytIconType intValue], YES, [UIColor whiteColor]); + if (img) [defaultTabImages addObject:img]; + } else { + NSString *bundleName = tabBundleIconMap[tabID]; + if (bundleName) { + UIImage *img = [assetLoader imageNamed:bundleName]; + if (img) { + UIImage *whiteImg = [img imageWithTintColor:[UIColor whiteColor] renderingMode:UIImageRenderingModeAlwaysOriginal]; + [defaultTabImages addObject:whiteImg]; + } + } + } + } + } + if (defaultTabImages.count == 0) { + NSArray *fallbackIcons = @[@(65), @(769), @(66), @(61)]; + for (NSNumber *iconType in fallbackIcons) { + UIImage *img = YouModYTIconImage([iconType intValue], YES, [UIColor whiteColor]); + if (img) [defaultTabImages addObject:img]; + } + } + + YMPushSubSettings(YMLOC(@"TABBAR"), @[ + YMImageSegment(YMLOC(@"DEFAULT_TAB"), DefaultTab, defaultTabImages, 0), + YMTextSegment(YMLOC(@"FORSTED_TAB_BAR"), UseFrostedTabBar, (@[YMLOC(@"DEFAULT"),YMLOC(@"ENABLED"), YMLOC(@"DISABLED")]), 0), + YMToggle(YMLOC(@"HIDE_TAB_INDI"), YMLOC(@"HIDE_TAB_INDI_DESC"), HideTabIndi), + YMToggle(YMLOC(@"HIDE_TAB_LABELS"), YMLOC(@"HIDE_TAB_LABELS_DESC"), HideTabLabels), + YMAction(YMLOC(@"MANAGE_TABS"), YMLOC(@"MANAGE_TABS_DESC"), ^(UIViewController *vc) { + (void)vc; + YMPushTabOrder(settingsViewController, [self parentResponder]); + }), + ], settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *icon5 = [%c(YTIIcon) new]; @@ -496,22 +430,48 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 // Section 8 // Miscellaneous - YTSettingsSectionItem *othergroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"MISCELLANEOUS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - SETTINGS_HEADER, - BASIC_SWITCH(LOC(@"BACKGROUND_PLAYBACK"), LOC(@"BACKGROUND_PLAYBACK_DESC"), BackgroundPlayback), - BASIC_SWITCH(LOC(@"DISABLES_SHORTS_PIP"), LOC(@"DISABLES_SHORTS_PIP_DESC"), DisablesShortsPiP), - BASIC_SWITCH(LOC(@"BLOCK_UPGRADE_DIALOGS"), LOC(@"BLOCK_UPGRADE_DIALOGS_DESC"), BlockUpgradeDialogs), - BASIC_SWITCH(LOC(@"ARE_YOU_THERE_DIALOG"), LOC(@"ARE_YOU_THERE_DIALOG_DESC"), HideAreYouThereDialog), - BASIC_SWITCH(LOC(@"FIXES_SLOW_MINIPLAYER"), LOC(@"FIXES_SLOW_MINIPLAYER_DESC"), FixesSlowMiniPlayer), - BASIC_SWITCH(LOC(@"DISABLES_NEW_MINIPLAYER"), LOC(@"DISABLES_NEW_MINIPLAYER_DESC"), DisablesNewMiniPlayer), - BASIC_SWITCH(LOC(@"DISABLES_SNACK_BAR"), LOC(@"DISABLES_SNACK_BAR_DESC"), DisablesSnackBar), - BASIC_SWITCH(LOC(@"HIDE_STARTUP_ANIMATIONS"), LOC(@"HIDE_STARTUP_ANIMATIONS_DESC"), HideStartupAni), - BASIC_SWITCH(LOC(@"HIDE_PLAY_IN_NEXT_QUEUE"), LOC(@"HIDE_PLAY_IN_NEXT_QUEUE_DESC"), HidePlayInNextQueue), - BASIC_SWITCH(LOC(@"HIDE_LIKE_DISLIKE_VOTES"), LOC(@"HIDE_LIKE_DISLIKE_VOTES_DESC"), HideLikeDislikeVotes), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"MISCELLANEOUS") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + NSArray *miscItems = @[ + YMToggle(YMLOC(@"BACKGROUND_PLAYBACK"), YMLOC(@"BACKGROUND_PLAYBACK_DESC"), BackgroundPlayback), + YMToggle(YMLOC(@"DISABLES_SHORTS_PIP"), YMLOC(@"DISABLES_SHORTS_PIP_DESC"), DisablesShortsPiP), + YMToggle(YMLOC(@"DISABLE_HINTS"), YMLOC(@"DISABLE_HINTS_DESC"), DisableHints), + YMToggle(YMLOC(@"BLOCK_UPGRADE_DIALOGS"), YMLOC(@"BLOCK_UPGRADE_DIALOGS_DESC"), BlockUpgradeDialogs), + YMToggle(YMLOC(@"ARE_YOU_THERE_DIALOG"), YMLOC(@"ARE_YOU_THERE_DIALOG_DESC"), HideAreYouThereDialog), + YMToggle(YMLOC(@"FIXES_SLOW_MINIPLAYER"), YMLOC(@"FIXES_SLOW_MINIPLAYER_DESC"), FixesSlowMiniPlayer), + YMToggle(YMLOC(@"DISABLES_NEW_MINIPLAYER"), YMLOC(@"DISABLES_NEW_MINIPLAYER_DESC"), DisablesNewMiniPlayer), + YMToggle(YMLOC(@"DISABLES_SNACK_BAR"), YMLOC(@"DISABLES_SNACK_BAR_DESC"), DisablesSnackBar), + YMToggle(YMLOC(@"HIDE_STARTUP_ANIMATIONS"), YMLOC(@"HIDE_STARTUP_ANIMATIONS_DESC"), HideStartupAni), + YMToggle(YMLOC(@"HIDE_LIKE_DISLIKE_VOTES"), YMLOC(@"HIDE_LIKE_DISLIKE_VOTES_DESC"), HideLikeDislikeVotes), + YMToggle(YMLOC(@"HIDE_COMMU_GUIDE"), YMLOC(@"HIDE_COMMU_GUIDE_DESC"), HideCommuGuide), + YMToggle(YMLOC(@"FLOATING_KEYBOARD"), YMLOC(@"FLOATING_KEYBOARD_DESC"), FloatingKeyboard), + YMToggle(YMLOC(@"DISABLES_RTL"), YMLOC(@"DISABLES_RTL_DESC"), DisablesRTL), + YMHeader(@""), + YMTextSegment(YMLOC(@"DEVICE_UI"), DeviceUIIndex, (@[YMLOC(@"DEFAULT"), YMLOC(@"iPad"), YMLOC(@"iPhone")]), 0), + YMToggle(YMLOC(@"AUTO_OPEN_LINK"), YMLOC(@"AUTO_OPEN_LINK_DESC"), AutoOpenLink), + YMHeader(YMLOC(@"FLYOUT_MENU")), + YMToggle(YMLOC(@"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION"), YMLOC(@"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC"), RemovePlayInNextQueueOption), + YMToggle(YMLOC(@"REMOVE_DOWNLOAD_OPTION"), YMLOC(@"REMOVE_DOWNLOAD_OPTION_DESC"), RemoveDownloadOption), + YMToggle(YMLOC(@"REMOVE_WATCH_LATER_OPTION"), YMLOC(@"REMOVE_WATCH_LATER_OPTION_DESC"), RemoveWatchLaterOption), + YMToggle(YMLOC(@"REMOVE_SAVE_OPTION"), YMLOC(@"REMOVE_SAVE_OPTION_DESC"), RemoveSaveOption), + YMToggle(YMLOC(@"REMOVE_REMOVE_FROM_PLAYLIST_OPTION"), YMLOC(@"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC"), RemoveRemoveFromPlaylistOption), + YMToggle(YMLOC(@"REMOVE_SHARE_OPTION"), YMLOC(@"REMOVE_SHARE_OPTION_DESC"), RemoveShareOption), + YMToggle(YMLOC(@"REMOVE_NOT_INTERESTED_OPTION"), YMLOC(@"REMOVE_NOT_INTERESTED_OPTION_DESC"), RemoveNotInterestedOption), + YMToggle(YMLOC(@"REMOVE_DONT_RECOMMEND_OPTION"), YMLOC(@"REMOVE_DONT_RECOMMEND_OPTION_DESC"), RemoveDontRecommendOption), + YMToggle(YMLOC(@"REMOVE_INFO_OPTION"), YMLOC(@"REMOVE_INFO_OPTION_DESC"), RemoveInfoOption), + YMToggle(YMLOC(@"REMOVE_FILTER_OPTION"), YMLOC(@"REMOVE_FILTER_OPTION_DESC"), RemoveFilterOption), + YMToggle(YMLOC(@"REMOVE_REPORT_OPTION"), YMLOC(@"REMOVE_REPORT_OPTION_DESC"), RemoveReportOption), + YMToggle(YMLOC(@"REMOVE_YOUTUBE_MUSIC_OPTION"), YMLOC(@"REMOVE_YOUTUBE_MUSIC_OPTION_DESC"), RemoveYouTubeMusicOption), + YMToggle(YMLOC(@"REMOVE_FEED_BACK_OPTION"), YMLOC(@"REMOVE_FEED_BACK_OPTION_DESC"), RemoveFeedBackOption), + YMToggle(YMLOC(@"REMOVE_CAST_OPTION"), YMLOC(@"REMOVE_CAST_OPTION_DESC"), RemoveCastOption), + YMToggle(YMLOC(@"REMOVE_SHUFFLE_OPTION"), YMLOC(@"REMOVE_SHUFFLE_OPTION_DESC"), RemoveShuffleOption), + YMToggle(YMLOC(@"REMOVE_UN_SUB_OPTION"), YMLOC(@"REMOVE_UN_SUB_OPTION_DESC"), RemoveUnSubOption), + YMToggle(YMLOC(@"REMOVE_HIDE_FROM_PLAYLIST_OPTION"), YMLOC(@"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC"), RemoveHideFromPlaylistOption), + YMToggle(YMLOC(@"REMOVE_HELP_OPTION"), YMLOC(@"REMOVE_HELP_OPTION_DESC"), RemoveHelpOption), + YMToggle(YMLOC(@"REMOVE_NOTIFY_OPTION"), YMLOC(@"REMOVE_NOTIFY_OPTION_DESC"), RemoveNotifyOption), + YMToggle(YMLOC(@"REMOVE_CLEARSCREEN_OPTION"), YMLOC(@"REMOVE_CLEARSCREEN_OPTION_DESC"), RemoveClearScreenOption), + ]; + YMRegisterSettingsGroup(YMLOC(@"MISCELLANEOUS"), miscItems); + YTSettingsSectionItem *othergroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"MISCELLANEOUS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSubSettings(YMLOC(@"MISCELLANEOUS"), miscItems, settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *icon6 = [%c(YTIIcon) new]; @@ -519,77 +479,99 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 othergroup.settingIcon = icon6; [sectionItems addObject:othergroup]; + // Section: SponsorBlock + YTSettingsSectionItem *sponsorblockgroup = [YTSettingsSectionItemClass itemWithTitle:@"SponsorBlock" accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + [self updateSponsorBlockSectionWithEntry:entry]; + return YES; + }]; + YTIIcon *iconSB = [%c(YTIIcon) new]; + iconSB.iconType = 610; + sponsorblockgroup.settingIcon = iconSB; + [sectionItems addObject:sponsorblockgroup]; + // Section 9 // Perferences - YTSettingsSectionItem *perfgroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"PERFER_HEADER") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - NSArray *rows = @[ - [YTSettingsSectionItemClass itemWithTitle:nil - titleDescription:LOC(@"PERFER") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - return NO; - } - ], - [YTSettingsSectionItemClass itemWithTitle:LOC(@"IMPORT") - titleDescription:LOC(@"IMPORT_DESC") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - YTAlertView *alertView = [%c(YTAlertView) confirmationDialogWithAction:^{ - [[YouModPrefsManager sharedManager] importYouModSettingsFromVC:settingsViewController]; - } actionTitle:LOC(@"YES")]; - alertView.title = LOC(@"WARNING"); - alertView.subtitle = LOC(@"OVERRIDE"); - [alertView show]; - return YES; - } - ], - [YTSettingsSectionItemClass itemWithTitle:LOC(@"EXPORT") - titleDescription:LOC(@"EXPORT_DESC") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - [[YouModPrefsManager sharedManager] exportYouModSettingsFromVC:settingsViewController]; - return YES; - } - ], - [YTSettingsSectionItemClass itemWithTitle:LOC(@"RESTORE") - titleDescription:LOC(@"RESTORE_DESC") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - [[YouModPrefsManager sharedManager] restoreYouModDefaults]; - return YES; - } - ], - [YTSettingsSectionItemClass itemWithTitle:nil - titleDescription:LOC(@"CACHE") - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - return NO; - } - ], - [YTSettingsSectionItemClass itemWithTitle:LOC(@"CLEARCACHE") - titleDescription:GetCacheSize() - accessibilityIdentifier:nil - detailTextBlock:nil - selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; - [[NSFileManager defaultManager] removeItemAtPath:cachePath error:nil]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[%c(YTToastResponderEvent) eventWithMessage:LOC(@"DONE") firstResponder:[self parentResponder]] send]; - }); + YTSettingsSectionItem *perfgroup = [YTSettingsSectionItemClass itemWithTitle:YMLOC(@"PERFER_HEADER") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) { + YMPushSubSettings(YMLOC(@"PERFER_HEADER"), @[ + YMHeader(YMLOC(@"PERFER")), + YMAction(YMLOC(@"IMPORT"), YMLOC(@"IMPORT_DESC"), ^(UIViewController *vc) { + Class alertClass = NSClassFromString(@"YTAlertView"); + YTAlertView *alertView = [alertClass confirmationDialogWithAction:^{ + [[YouModPrefsManager sharedManager] importYouModSettingsFromVC:vc]; + } actionTitle:YMLOC(@"YES")]; + alertView.title = YMLOC(@"WARNING"); + alertView.subtitle = YMLOC(@"OVERRIDE"); + [alertView show]; + }), + YMAction(YMLOC(@"EXPORT"), YMLOC(@"EXPORT_DESC"), ^(UIViewController *vc) { + [[YouModPrefsManager sharedManager] exportYouModSettingsFromVC:vc]; + }), + YMAction(YMLOC(@"RESTORE"), YMLOC(@"RESTORE_DESC"), ^(UIViewController *vc) { + [[YouModPrefsManager sharedManager] restoreYouModDefaults]; + }), + YMHeader(YMLOC(@"CACHE")), + YMAction(YMLOC(@"CLEARCACHE"), GetCacheSize(), ^(UIViewController *vc) { + __weak UIViewController *weakVC = vc; + NSString *clearTitle = YMLOC(@"CLEARCACHE"); + dispatch_async(dispatch_get_main_queue(), ^{ + __strong UIViewController *strongVC = weakVC; + if (!strongVC) return; + if ([strongVC respondsToSelector:@selector(items)] && [strongVC respondsToSelector:@selector(tableView)]) { + NSArray *items = [(id)strongVC items]; + for (id item in items) { + if ([[item title] isEqualToString:clearTitle]) { + [item setSubtitle:@""]; + break; + } + } + UITableView *tableView = [(id)strongVC tableView]; + [tableView reloadData]; + for (UITableViewCell *cell in tableView.visibleCells) { + if ([cell.textLabel.text isEqualToString:clearTitle]) { + UIActivityIndicatorView *indicator = [cell viewWithTag:0xC0FFEE]; + if (!indicator) { + indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; + indicator.tag = 0xC0FFEE; + [indicator startAnimating]; + cell.accessoryView = indicator; + } + cell.detailTextLabel.text = @""; + break; + } + } + } + }); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; + [[NSFileManager defaultManager] removeItemAtPath:cachePath error:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + __strong UIViewController *strongVC = weakVC; + if (!strongVC) return; + if ([strongVC respondsToSelector:@selector(tableView)]) { + UITableView *tableView = [(id)strongVC tableView]; + for (UITableViewCell *cell in tableView.visibleCells) { + if ([cell.textLabel.text isEqualToString:YMLOC(@"CLEARCACHE")]) { + cell.accessoryView = nil; + break; + } + } + } + if ([strongVC respondsToSelector:@selector(items)] && [strongVC respondsToSelector:@selector(tableView)]) { + NSArray *items = [(id)strongVC items]; + for (id item in items) { + if ([[item title] isEqualToString:clearTitle]) { + [item setSubtitle:@"0 KB"]; + break; + } + } + [[(id)strongVC tableView] reloadData]; + } }); - return YES; - } - ], - BASIC_SWITCH(LOC(@"AUTO_CLEARCACHE"), LOC(@"AUTO_CLEARCACHE_DESC"), AutoClearCache), - ]; - YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"PERFER_HEADER") pickerSectionTitle:nil rows:rows selectedItemIndex:0 parentResponder:[self parentResponder]]; - [settingsViewController pushViewController:picker]; + }); + }), + YMToggle(YMLOC(@"AUTO_CLEARCACHE"), YMLOC(@"AUTO_CLEARCACHE_DESC"), AutoClearCache), + ], settingsViewController, [self parentResponder]); return YES; }]; YTIIcon *icon7 = [%c(YTIIcon) new]; @@ -619,13 +601,14 @@ static NSString *GetCacheSize() { // YTLite - @dayanch96 [[NSUserDefaults standardUserDefaults] registerDefaults:@{ AutoClearCache: @YES, YTPremiumLogo: @YES, - HideCreateButton: @YES, HideCastButtonNav: @YES, HideCastButtonPlayer: @YES, BackgroundPlayback: @YES, - OldQualityPicker: @YES, DownloadManager: @YES, DownloadSaveToPhotos: @YES, + DisableHints: @YES, + RewindSeconds: @10.0, + ForwardSeconds: @10.0, }]; %init; } diff --git a/Files/Shorts.x b/Files/Shorts.x index d73cbb24..95cba106 100644 --- a/Files/Shorts.x +++ b/Files/Shorts.x @@ -33,4 +33,245 @@ %hook YTHotConfig - (BOOL)enablePlayerBarForVerticalVideoWhenControlsHiddenInFullscreen { return IS_ENABLED(ShowShortsSeekbar) ? YES : %orig; } -%end \ No newline at end of file +%end + +static void YouModMakeAShortsAction(YTReelPlayerViewController *self, YTSingleVideoController *video, YTSingleVideoTime *time) { + if (INTFORVAL(ShortsActionIndex) == 0) return; + + if (floor(time.time) >= floor(video.totalMediaTime)) { + if (INTFORVAL(ShortsActionIndex) == 1) { + [self reelContentViewRequestsAdvanceToNextVideo:nil]; + } else if (INTFORVAL(ShortsActionIndex) == 2) { + [self reelContentViewRequestsPlayPauseToggle:nil]; + } + } +} + +static BOOL isShortsOnlyOn = YES; + +%hook YTReelPlayerViewController +- (void)singleVideo:(YTSingleVideoController *)video currentVideoTimeDidChange:(YTSingleVideoTime *)time { + %orig; + YouModMakeAShortsAction(self, video, time); +} +- (void)loadPlayerBar { + %orig; + if (isShortsOnlyOn && IS_ENABLED(ShortsOnly)) [self YouModOnlyShorts]; + YTPlayerViewController *main = self.player; + if (INTFORVAL(CaptionTrack) != 0) [main performSelector:@selector(YouModAutoCaptions) withObject:nil afterDelay:0.5]; + if (INTFORVAL(AutoSpeedIndex) != 0) [main performSelector:@selector(YouModSetAutoSpeed) withObject:nil afterDelay:0.5]; + if (INTFORVAL(AudioTrack) != 0) [self performSelector:@selector(YouModAutoAudioTrack:) withObject:main afterDelay:0.5]; +} +%new +- (void)YouModOnlyShorts { + id appconmain = [self valueForKey:@"_pivotBarProvider"]; + if ([appconmain isKindOfClass:%c(YTAppViewControllerImpl)]) { + YTAppViewControllerImpl *appcon = (YTAppViewControllerImpl *)appconmain; + [appcon hidePivotBar]; + } else { + YTAppViewController *appcon = (YTAppViewController *)appconmain; + [appcon hidePivotBar]; + } +} +%new +- (void)YouModAutoAudioTrack:(YTPlayerViewController *)pv { + NSInteger selectedIndex = INTFORVAL(AudioTrackLangIndex); + NSArray *langCodes = getAllSystemLanguageValues(); + NSString *userTargetLang = langCodes[selectedIndex]; + id switchcon = self.audioTrackController; + NSArray *availableTracks = [switchcon valueForKey:@"_availableAudioTracks"]; + if (!availableTracks || availableTracks.count == 0) return; + YTIAudioTrack *matchedTrack = nil; + + if (INTFORVAL(AudioTrack) == 1) { + // Loop for all tracks + for (YTIAudioTrack *track in availableTracks) { + if ([track.id_p hasSuffix:@".4"]) { + matchedTrack = track; + break; + } + } + } else if (INTFORVAL(AudioTrack) == 2) { + // Loop for all tracks + for (YTIAudioTrack *track in availableTracks) { + if ([track.id_p hasPrefix:userTargetLang]) { + matchedTrack = track; + break; + } + } + + // Check if it's dubbed + if (matchedTrack && [matchedTrack isAutoDubbed] && IS_ENABLED(NoDubbedAudioTrack)) { + matchedTrack = nil; + return; + } + } + + // If found, change to it + if (matchedTrack) { + [pv setAudioTrack:matchedTrack source:0]; + } else if (!matchedTrack && IS_ENABLED(NoDubbedAudioTrack)) { + for (YTIAudioTrack *track in availableTracks) { + if ([track.id_p hasSuffix:@".4"]) { + [pv setAudioTrack:track source:0]; + break; + } + } + } +} +%end + +extern void YouModConfigureDownloadButton(_ASDisplayView *view); + +// _ASDisplayView filters +%hook _ASDisplayView +- (void)didMoveToWindow { + %orig; + YouModConfigureDownloadButton(self); + NSDictionary *elements = @{ + @"product_sticker.main_target": @(IS_ENABLED(HideShortsProducts)), + @"product_sticker.secondary_target": @(IS_ENABLED(HideShortsProducts)), + @"id.elements.components.suggested_action": @(IS_ENABLED(HideShortsRecbar)) + }; + if ([elements[self.accessibilityIdentifier] boolValue]) [self removeFromSuperview]; +} +%end + +static BOOL isFullscreenEnabled = NO; + +%hook YTAppDelegate +- (void)appDidBecomeActive { + %orig; + if ((isFullscreenEnabled && IS_ENABLED(FullScreenShorts)) || (isShortsOnlyOn && IS_ENABLED(ShortsOnly))) { + id appviewcon = [self valueForKey:@"_appViewController"]; + if ([appviewcon isKindOfClass:%c(YTAppViewControllerImpl)]) { + YTAppViewControllerImpl *con = (YTAppViewControllerImpl *)appviewcon; + [con hidePivotBar]; + } else { + YTAppViewController *con = (YTAppViewController *)appviewcon; + [con hidePivotBar]; + } + } +} +%end + +%hook YTReelWatchPlaybackOverlayView +%property (nonatomic, retain) UIPinchGestureRecognizer *YouModFullscreenGesture; +- (void)layoutSubviews { + %orig; + if (!IS_ENABLED(FullScreenShorts)) return; + if (!self.YouModFullscreenGesture) { + self.YouModFullscreenGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(YouModFullscrrenGestureHandler:)]; + self.YouModFullscreenGesture.delegate = (id)self; + + [self.superview addGestureRecognizer:self.YouModFullscreenGesture]; + } +} +%new +- (void)YouModFullscrrenGestureHandler:(UIPinchGestureRecognizer *)gesture { + if (gesture.state != UIGestureRecognizerStateBegan || (isShortsOnlyOn && IS_ENABLED(ShortsOnly))) return; + id appconmain = [self valueForKey:@"_pivotBarProvider"]; + if ([appconmain isKindOfClass:%c(YTAppViewControllerImpl)]) { + YTAppViewControllerImpl *appcon = (YTAppViewControllerImpl *)appconmain; + BOOL isTabBarHidden = [appcon isPivotBarHidden]; + if (gesture.scale > 1.0) { + if (!isTabBarHidden) { + [appcon hidePivotBar]; + [UIView animateWithDuration:0.3 animations:^{ + self.alpha = 0; + }]; + isFullscreenEnabled = YES; + } + } else if (gesture.scale < 1.0) { + if (isTabBarHidden) { + [appcon showPivotBar]; + [UIView animateWithDuration:0.3 animations:^{ + self.alpha = 1; + }]; + isFullscreenEnabled = NO; + } + } + } else { + YTAppViewController *appcon = (YTAppViewController *)appconmain; + BOOL isTabBarHidden = [appcon isPivotBarHidden]; + if (gesture.scale > 1.0) { + if (!isTabBarHidden) { + [appcon hidePivotBar]; + [UIView animateWithDuration:0.3 animations:^{ + self.alpha = 0; + }]; + isFullscreenEnabled = YES; + } + } else if (gesture.scale < 1.0) { + if (isTabBarHidden) { + [appcon showPivotBar]; + [UIView animateWithDuration:0.3 animations:^{ + self.alpha = 1; + }]; + isFullscreenEnabled = NO; + } + } + } +} +%new +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + if (gestureRecognizer == self.YouModFullscreenGesture) { + return YES; + } + return NO; +} +%end + +%hook YTReelContentView +%property (nonatomic, retain) UILongPressGestureRecognizer *YouModExitShortsOnlyGesture; +- (void)setPlaybackView:(id)arg1 { + %orig; + self.playbackOverlay.alpha = !isFullscreenEnabled; + if (!IS_ENABLED(ShortsOnly)) return; + if (isShortsOnlyOn) { + self.YouModExitShortsOnlyGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(YouModTurnOffShortsOnly:)]; + self.YouModExitShortsOnlyGesture.numberOfTouchesRequired = 2; + self.YouModExitShortsOnlyGesture.minimumPressDuration = 0.5; + + [self addGestureRecognizer:self.YouModExitShortsOnlyGesture]; + } +} +%new +- (void)YouModTurnOffShortsOnly:(UILongPressGestureRecognizer *)gesture { + if (gesture.state != UIGestureRecognizerStateBegan) return; + isShortsOnlyOn = NO; + UIView *parent = sbGetNotificationParent(); + [SBSkipNotificationView showSuccessInView:parent message:LOC(@"SHORTS_ONLY_DISABLED") duration:3.0]; + + YTReelContainerViewController *reelcon = [self valueForKey:@"_parentResponder"]; + YTAppReelWatchRootViewController *watchroot = [reelcon valueForKey:@"_delegate"]; + id appconmain = [watchroot valueForKey:@"_pivotBarProvider"]; + if ([appconmain isKindOfClass:%c(YTAppViewControllerImpl)]) { + YTAppViewControllerImpl *appcon = (YTAppViewControllerImpl *)appconmain; + [appcon showPivotBar]; + [UIView animateWithDuration:0.3 animations:^{ + self.playbackOverlay.alpha = 1; + }]; + } else { + YTAppViewController *appcon = (YTAppViewController *)appconmain; + [appcon showPivotBar]; + [UIView animateWithDuration:0.3 animations:^{ + self.playbackOverlay.alpha = 1; + }]; + } +} +%new +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + if (gestureRecognizer == self.YouModExitShortsOnlyGesture && [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { + return YES; + } + return NO; +} +%new +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + if (gestureRecognizer == self.YouModExitShortsOnlyGesture) { + return NO; + } + return YES; +} +%end diff --git a/Files/Sideloading.x b/Files/Sideloading.x index 9c9a2870..7e46717b 100644 --- a/Files/Sideloading.x +++ b/Files/Sideloading.x @@ -1,8 +1,6 @@ // All Codes are adapt from YTLite and uYouEnhanced + Some of my research #import "Headers.h" -extern void YouModConfigureDownloadButton(_ASDisplayView *view); - // AccessGroupID static NSString *accessGroupID() { NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: @@ -23,45 +21,6 @@ static NSString *accessGroupID() { return accessGroup; } -// _ASDisplayView filters -// This hook can hide A LOT of things -%hook _ASDisplayView - -- (void)didMoveToWindow { - %orig; - YouModConfigureDownloadButton(self); - // if (IS_ENABLED(HideShortsShelf) && [self.accessibilityIdentifier isEqualToString:@"eml.shorts-shelf"]) self.hidden = YES; - if (IS_ENABLED(HideGenMusicShelf) && [self.accessibilityIdentifier isEqualToString:@"feed_nudge.view"]) self.hidden = YES; - if (IS_ENABLED(HideFeedPost) && [self.accessibilityIdentifier isEqualToString:@"id.ui.backstage.original_post"]) self.hidden = YES; - if (IS_ENABLED(HideLikeButton) && [self.accessibilityIdentifier isEqualToString:@"id.video.like.button"]) self.hidden = YES; - if (IS_ENABLED(HideDisLikeButton) && [self.accessibilityIdentifier isEqualToString:@"id.video.dislike.button"]) self.hidden = YES; - if (IS_ENABLED(HideShareButton) && [self.accessibilityIdentifier isEqualToString:@"id.video.share.button"]) self.hidden = YES; - if (IS_ENABLED(HideDownloadButton) && [self.accessibilityIdentifier isEqualToString:@"id.ui.add_to.offline.button"]) self.hidden = YES; - if (IS_ENABLED(HideClipButton) && [self.accessibilityIdentifier isEqualToString:@"clip_button.eml"]) self.hidden = YES; - if (IS_ENABLED(HideRemixButton) && [self.accessibilityIdentifier isEqualToString:@"id.video.remix.button"]) self.hidden = YES; - if (IS_ENABLED(HideSaveButton) && [self.accessibilityIdentifier isEqualToString:@"id.video.add_to.button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsLikeButton) && [self.accessibilityIdentifier isEqualToString:@"id.reel_like_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsDisLikeButton) && [self.accessibilityIdentifier isEqualToString:@"id.reel_dislike_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsCommentButton) && [self.accessibilityIdentifier isEqualToString:@"id.reel_comment_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsShareButton) && [self.accessibilityIdentifier isEqualToString:@"id.reel_share_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsRemixButton) && [self.accessibilityIdentifier isEqualToString:@"id.reel_remix_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsMetaButton) && [self.accessibilityIdentifier isEqualToString:@"id.reel_pivot_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsProducts) && [self.accessibilityIdentifier isEqualToString:@"product_sticker.main_target"]) self.hidden = YES; - if (IS_ENABLED(HideShortsProducts) && [self.accessibilityIdentifier isEqualToString:@"product_sticker.secondary_target"]) self.hidden = YES; - if (IS_ENABLED(HideShortsRecbar) && [self.accessibilityIdentifier isEqualToString:@"id.elements.components.suggested_action"]) self.hidden = YES; - if (IS_ENABLED(HideShortsCommit) && [self.accessibilityIdentifier isEqualToString:@"eml.shorts-disclosures"]) self.hidden = YES; - if (IS_ENABLED(HideShortsSubscriptButton) && [self.accessibilityIdentifier isEqualToString:@"id.ui.shorts_paused_state.subscriptions_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsLiveButton) && [self.accessibilityIdentifier isEqualToString:@"id.ui.shorts_paused_state.live_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsLensButton) && [self.accessibilityIdentifier isEqualToString:@"id.ui.shorts_paused_state.lens_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsTrendsButton) && [self.accessibilityIdentifier isEqualToString:@"id.ui.shorts_paused_state.trends_button"]) self.hidden = YES; - if (IS_ENABLED(HideShortsToVideo) && [self.accessibilityIdentifier isEqualToString:@"id.reel_multi_format_link"]) self.hidden = YES; - if (IS_ENABLED(HideSubButton) && [self.accessibilityIdentifier isEqualToString:@"eml.animated_subscribe_button"]) self.hidden = YES; - if (IS_ENABLED(HideShoppingButton) && [self.accessibilityIdentifier isEqualToString:@"eml.header_store_button"]) self.hidden = YES; - if (IS_ENABLED(HideMemberButton) && [self.accessibilityIdentifier isEqualToString:@"id.sponsor_button"]) self.hidden = YES; -} - -%end - // IAmYouTube (https://github.com/PoomSmart/IAmYouTube) %hook YTVersionUtils + (NSString *)appName { return YT_NAME; } @@ -208,6 +167,6 @@ static NSString *accessGroupID() { NSURL *documentsURL = [paths lastObject]; return [documentsURL URLByAppendingPathComponent:@"AppGroup"]; } - return %orig(groupIdentifier); + return %orig; } -%end \ No newline at end of file +%end diff --git a/Files/SleepTimer.txt b/Files/SleepTimer.txt new file mode 100644 index 00000000..23c5423b --- /dev/null +++ b/Files/SleepTimer.txt @@ -0,0 +1,115 @@ +#import "Headers.h" + +@interface YouModSleepMenuItem : NSObject +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *subtitle; +@property (nonatomic, strong) UIImage *iconImage; +@property (nonatomic, copy) void (^handler)(void); ++ (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle handler:(void (^)(void))handler; ++ (instancetype)itemWithTitle:(NSString *)title subtitle:(NSString *)subtitle icon:(UIImage *)icon handler:(void (^)(void))handler; +@end + +static void YouModPresentMenu(NSString *title, NSString *desc, UIViewController *presenter, UIView *sender) { + presenter = YouModTopViewController(presenter); + YTDefaultSheetController *sheet = [%c(YTDefaultSheetController) sheetControllerWithParentResponder:presenter];; + for (YouModSleepMenuItem *item in items) { + YTActionSheetAction *action = [%c(YTActionSheetAction) actionWithTitle:title subtitle:desc iconImage:nil handler:^(__unused YTActionSheetAction *action) { + if (handler) item.handler(); + }]; + [sheet addAction:action]; + } + if (sender) { + [sheet presentFromView:sender animated:YES completion:nil]; + } else { + [sheet presentFromViewController:presenter animated:YES completion:nil]; + } +} + +static __weak YTPlayerViewController *YouModSleepTimerPlayer = nil; +static NSTimer *YouModSleepTimerTimer = nil; +static BOOL YouModSleepTimerAtEndOfVideo = NO; + +static void YouModCancelSleepTimer(void) { + [YouModSleepTimerTimer invalidate]; + YouModSleepTimerTimer = nil; + YouModSleepTimerAtEndOfVideo = NO; +} + +static void YouModPauseForSleepTimer(void) { + YTPlayerViewController *player = YouModSleepTimerPlayer; + [player pause]; + YouModCancelSleepTimer(); + YouModShowToast(@"Sleep timer ended", player); +} + +static void YouModStartSleepTimer(NSTimeInterval seconds, YTPlayerViewController *player) { + YouModCancelSleepTimer(); + YouModSleepTimerPlayer = player; + YouModSleepTimerTimer = [NSTimer scheduledTimerWithTimeInterval:seconds repeats:NO block:^(__unused NSTimer *timer) { + YouModPauseForSleepTimer(); + }]; + YouModShowToast([NSString stringWithFormat:@"Sleep timer: %.0f minutes", seconds / 60.0], player); +} + +static void YouModPresentSleepTimerCustomMinutes(YTPlayerViewController *player) { + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Sleep timer" message:@"Enter minutes" preferredStyle:UIAlertControllerStyleAlert]; + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.keyboardType = UIKeyboardTypeNumberPad; + textField.placeholder = @"Minutes"; + }]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Start" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { + NSInteger minutes = alert.textFields.firstObject.text.integerValue; + if (minutes <= 0) { + YouModShowToast(@"Enter a valid number of minutes", player); + return; + } + YouModStartSleepTimer((NSTimeInterval)minutes * 60.0, player); + }]]; + [YouModTopViewController() presentViewController:alert animated:YES completion:nil]; +} + +static NSDate *YouModSleepTimerDateFromClockText(NSString *text) { + NSString *trimmed = [text stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; + if (trimmed.length == 0) return nil; + + NSArray *formats = @[@"H:mm", @"HH:mm", @"h:mm a", @"h:mma"]; + NSDate *parsed = nil; + NSDateFormatter *formatter = [NSDateFormatter new]; + formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + for (NSString *format in formats) { + formatter.dateFormat = format; + parsed = [formatter dateFromString:trimmed]; + if (parsed) break; + } + if (!parsed) return nil; + + NSCalendar *calendar = NSCalendar.currentCalendar; + NSDateComponents *clock = [calendar components:NSCalendarUnitHour | NSCalendarUnitMinute fromDate:parsed]; + NSDateComponents *today = [calendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:NSDate.date]; + today.hour = clock.hour; + today.minute = clock.minute; + today.second = 0; + NSDate *target = [calendar dateFromComponents:today]; + if (target.timeIntervalSinceNow <= 5.0) + target = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:target options:0]; + return target; +} + +static void YouModPresentSleepTimerEndAtTime(YTPlayerViewController *player) { + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Sleep timer" message:@"Enter a time" preferredStyle:UIAlertControllerStyleAlert]; + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.keyboardType = UIKeyboardTypeNumbersAndPunctuation; + textField.placeholder = @"23:30 or 11:30 PM"; + }]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Start" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { + NSDate *target = YouModSleepTimerDateFromClockText(alert.textFields.firstObject.text); + if (!target) { + YouModShowToast(@"Enter a valid time", player); + return; + } + YouModStartSleepTimer(target.timeIntervalSinceNow, player); + }]]; + [YouModTopViewController() presentViewController:alert animated:YES completion:nil]; +} diff --git a/Files/SponsorBlock.x b/Files/SponsorBlock.x new file mode 100644 index 00000000..915ced20 --- /dev/null +++ b/Files/SponsorBlock.x @@ -0,0 +1,596 @@ +#import "Headers.h" +#import + +BOOL useBackwardIconForButton; + +// System sound played on skip when haptic/audio feedback is enabled. +static const SystemSoundID SBSkipHapticSoundID = 1519; + +// Skipping is suppressed within this many seconds of a segment's end, so a +// segment already almost over isn't re-triggered by a late time-change callback. +static const CGFloat SBSegmentEndGuardSeconds = 0.5; + +// The skip banner is shown after this delay so the seek completes first — +// otherwise the following time-change callback dismisses it immediately. +static const NSTimeInterval SBSkipNotificationDelaySeconds = 0.3; + +// Clamps a stored banner duration to the supported range, falling back to the +// default when unset or out of range. +static float SBClampedAlertDuration(NSString *key) { + float duration = FLOAT_FOR_KEY(key); + if (duration < SBAlertDurationMin || duration > SBAlertDurationMax) return SBAlertDurationDefault; + return duration; +} + +@interface SBPassthroughView : UIView +@end +@implementation SBPassthroughView +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + UIView *hit = [super hitTest:point withEvent:event]; + return (hit == self) ? nil : hit; +} +@end + +@interface SBPassthroughWindow : UIWindow +@end +@implementation SBPassthroughWindow +- (BOOL)_canBecomeKeyWindow { return NO; } +- (BOOL)_canAffectStatusBarAppearance { return NO; } +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + UIView *rootView = self.rootViewController.view; + if (!rootView) return nil; + CGPoint convertedPoint = [rootView convertPoint:point fromView:self]; + UIView *hitView = [rootView hitTest:convertedPoint withEvent:event]; + if (!hitView || hitView == rootView) return nil; + return hitView; +} +@end + +static SBPassthroughWindow *sbOverlayWindow = nil; + +void sbUpdateOverlayInsetForPivotBar() { + if (!sbOverlayWindow) return; + UIViewController *rootVC = sbOverlayWindow.rootViewController; + if (!rootVC) return; + + // The overlay window's frame is set only at creation; the scene can change + // size afterward (rotation, iPhone fullscreen exit) and this plain-UIViewController + // window doesn't auto-resize with it. Re-syncing to the current scene bounds keeps + // the pill safe-area math on the correct (e.g. portrait) size rather than a stale + // landscape one — otherwise pills anchor to a mid-screen bottom edge. + UIWindowScene *scene = sbOverlayWindow.windowScene; + CGRect sceneBounds = scene ? scene.coordinateSpace.bounds : sbOverlayWindow.bounds; + if (scene && !CGRectEqualToRect(sbOverlayWindow.frame, sceneBounds)) { + sbOverlayWindow.frame = sceneBounds; + } + + // Look up YouTube's root view controller in the SAME scene as our overlay + // window — on iPad multi-window the app delegate's window may belong to a + // different scene, so [delegate window] is not safe here. + UIWindow *ytWindow = nil; + for (UIWindow *win in sbOverlayWindow.windowScene.windows) { + if ([win.rootViewController isKindOfClass:NSClassFromString(@"YTAppViewController")] || [win.rootViewController isKindOfClass:NSClassFromString(@"YTAppViewControllerImpl")]) { + ytWindow = win; + break; + } + } + YTAppViewController *appVC = (YTAppViewController *)ytWindow.rootViewController; + YTAppViewControllerImpl *appVC2 = (YTAppViewControllerImpl *)ytWindow.rootViewController; + YTPivotBarViewController *pivotVC; + @try { + pivotVC = (YTPivotBarViewController *)appVC.pivotBarViewController; + } @catch (id ex) { + pivotVC = (YTPivotBarViewController *)appVC2.pivotBarViewController; + } + YTPivotBarView *pivot = (YTPivotBarView *)pivotVC.viewIfLoaded; + + // Measure the pivot bar's visible top edge in our overlay window's coords + // and convert it into the inset our pills need above the device safe area. + // This avoids reading pivot.bounds.size.height directly — that value + // includes home-indicator padding on notched devices and would over-correct + // the safe area, leaving the pill floating too high above the tabbar. + CGFloat tabH = 0.0; + if (pivot && pivot.window != nil && !pivot.hidden && pivot.alpha > 0.01) { + UIView *overlayView = rootVC.view; + CGRect pivotInOverlay = [overlayView convertRect:pivot.bounds fromView:pivot]; + CGFloat pivotTop = CGRectGetMinY(pivotInOverlay); + // The scene bounds are the authoritative overlay height. overlayView.bounds + // is avoided here because it only reflects a new window size once autoresizing + // has propagated, which may lag within the current runloop. + CGFloat overlayHeight = sceneBounds.size.height; + CGFloat deviceSafeBottom = sbOverlayWindow.safeAreaInsets.bottom; + CGFloat pivotHeight = pivot.bounds.size.height; + // Clamp to the pivot bar's own height: a legitimate inset can never exceed + // the bar it's clearing. If convertRect returns a stale value during a scene + // transition (e.g. fullscreen exit), the raw result balloons toward the full + // screen height — clamping to pivotHeight keeps the pill just above the safe + // area instead of letting it drift to the middle of the screen. + tabH = MAX(0.0, MIN(pivotHeight, overlayHeight - deviceSafeBottom - pivotTop)); + } + UIEdgeInsets current = rootVC.additionalSafeAreaInsets; + if (current.bottom != tabH) { + rootVC.additionalSafeAreaInsets = UIEdgeInsetsMake(0, 0, tabH, 0); + } +} + +static const NSTimeInterval SBOverlayRestoreFadeDuration = 0.15; + +// Hide the pill overlay instantly (non-animated): iOS captures the app-switcher +// snapshot synchronously as the app deactivates, so an animated hide wouldn't +// land in time and the pill would leak into the switcher card. +static void sbHideOverlayForSnapshot(void) { + if (sbOverlayWindow) sbOverlayWindow.hidden = YES; +} + +// Restore the overlay, fading it back in so the reappearance isn't a hard pop. +// Guarded to the hidden state so the two "became active" notifications don't +// each re-trigger the fade. +static void sbRestoreOverlayAfterSnapshot(void) { + if (!sbOverlayWindow || !sbOverlayWindow.hidden) return; + sbOverlayWindow.alpha = 0.0; + sbOverlayWindow.hidden = NO; + [UIView animateWithDuration:SBOverlayRestoreFadeDuration animations:^{ + sbOverlayWindow.alpha = 1.0; + }]; +} + +// Tracks which scene's lifecycle is currently observed. When sbOverlayWindow is +// recreated for a different scene (after the original goes Unattached), we +// re-bind observers to the new scene rather than leaving stale registrations. +static UIWindowScene *sbObservedScene = nil; +static id sbSceneDeactivateObserver = nil; +static id sbSceneBackgroundObserver = nil; +static id sbSceneActivateObserver = nil; +static id sbAppResignObserver = nil; +static id sbAppBackgroundObserver = nil; +static id sbAppActivateObserver = nil; +static id sbOrientationObserver = nil; + +static void sbRegisterOverlayLifecycleObservers(UIWindowScene *targetScene) { + if (!targetScene || sbObservedScene == targetScene) return; + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + if (sbSceneDeactivateObserver) [nc removeObserver:sbSceneDeactivateObserver]; + if (sbSceneBackgroundObserver) [nc removeObserver:sbSceneBackgroundObserver]; + if (sbSceneActivateObserver) [nc removeObserver:sbSceneActivateObserver]; + if (sbAppResignObserver) [nc removeObserver:sbAppResignObserver]; + if (sbAppBackgroundObserver) [nc removeObserver:sbAppBackgroundObserver]; + if (sbAppActivateObserver) [nc removeObserver:sbAppActivateObserver]; + if (sbOrientationObserver) [nc removeObserver:sbOrientationObserver]; + + sbObservedScene = targetScene; + + // Hide on deactivation, restore on activation. Deactivation is the trigger + // (not backgrounding) because invoking the app switcher only moves the app to + // the inactive state — a background-only observer misses that path. queue:nil + // runs the hide synchronously before the snapshot is captured. Scene + // notifications are filtered to targetScene so one scene's interruption + // doesn't hide another's overlay on iPad multi-window; the app-level + // notifications (object:nil) are an app-wide backstop. + sbSceneDeactivateObserver = [nc addObserverForName:UISceneWillDeactivateNotification object:targetScene queue:nil usingBlock:^(__unused NSNotification *note) { + sbHideOverlayForSnapshot(); + }]; + sbSceneBackgroundObserver = [nc addObserverForName:UISceneDidEnterBackgroundNotification object:targetScene queue:nil usingBlock:^(__unused NSNotification *note) { + sbHideOverlayForSnapshot(); + }]; + sbSceneActivateObserver = [nc addObserverForName:UISceneDidActivateNotification object:targetScene queue:nil usingBlock:^(__unused NSNotification *note) { + sbRestoreOverlayAfterSnapshot(); + }]; + sbAppResignObserver = [nc addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note) { + sbHideOverlayForSnapshot(); + }]; + sbAppBackgroundObserver = [nc addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note) { + sbHideOverlayForSnapshot(); + }]; + sbAppActivateObserver = [nc addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note) { + sbRestoreOverlayAfterSnapshot(); + }]; + + // Recompute pivot-bar inset on rotation / dynamic tabbar height changes. + // UIDeviceOrientationDidChangeNotification only fires when device-orientation + // generation is enabled; this call is idempotent. + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + sbOrientationObserver = [nc addObserverForName:UIDeviceOrientationDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification *note) { + sbUpdateOverlayInsetForPivotBar(); + }]; +} + +UIView *sbGetNotificationParent(void) { + if (sbOverlayWindow && sbOverlayWindow.windowScene.activationState == UISceneActivationStateUnattached) { + sbOverlayWindow = nil; + } + if (!sbOverlayWindow) { + UIWindowScene *activeScene = nil; + for (UIWindowScene *scene in [UIApplication sharedApplication].connectedScenes) { + if (scene.activationState == UISceneActivationStateForegroundActive) { + activeScene = scene; + break; + } + } + if (!activeScene) { + activeScene = (UIWindowScene *)[[[UIApplication sharedApplication].connectedScenes allObjects] firstObject]; + } + if (!activeScene) return nil; + + sbOverlayWindow = [[SBPassthroughWindow alloc] initWithWindowScene:activeScene]; + sbOverlayWindow.frame = activeScene.coordinateSpace.bounds; + sbOverlayWindow.windowLevel = UIWindowLevelAlert - 1; + sbOverlayWindow.backgroundColor = [UIColor clearColor]; + sbOverlayWindow.hidden = NO; + + UIViewController *rootVC = [[UIViewController alloc] init]; + rootVC.view = [[SBPassthroughView alloc] initWithFrame:sbOverlayWindow.bounds]; + rootVC.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + rootVC.view.backgroundColor = [UIColor clearColor]; + sbOverlayWindow.rootViewController = rootVC; + + sbRegisterOverlayLifecycleObservers(activeScene); + sbUpdateOverlayInsetForPivotBar(); + } + return sbOverlayWindow.rootViewController.view; +} + +static NSMutableDictionary *> *sbSegmentCache; + +NSArray *sbAllCategories(void) { + static NSArray *cats; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + cats = @[@"sponsor", @"intro", @"outro", @"interaction", @"selfpromo", + @"music_offtopic", @"preview", @"hook", @"poi_highlight", @"filler"]; + }); + return cats; +} + +static NSArray *sbEnabledCategories() { + NSMutableArray *enabled = [NSMutableArray array]; + for (NSString *cat in sbAllCategories()) { + NSInteger action = [[NSUserDefaults standardUserDefaults] integerForKey:SB_ACTION_KEY(cat)]; + if (action != SBSegmentActionDisable) { + [enabled addObject:cat]; + } + } + return enabled; +} + +UIColor *SBColorFromHex(NSString *hexString) { + if (!hexString || hexString.length < 7) return [UIColor whiteColor]; + unsigned int hex = 0; + NSScanner *scanner = [NSScanner scannerWithString:[hexString substringFromIndex:1]]; + [scanner scanHexInt:&hex]; + return [UIColor colorWithRed:((hex >> 16) & 0xFF) / 255.0 + green:((hex >> 8) & 0xFF) / 255.0 + blue:(hex & 0xFF) / 255.0 + alpha:1.0]; +} + +#pragma mark - SBSegment Implementation + +@implementation SBSegment + ++ (instancetype)segmentWithUUID:(NSString *)UUID category:(NSString *)category start:(float)start end:(float)end action:(NSString *)actionType { + SBSegment *seg = [[SBSegment alloc] init]; + seg.UUID = UUID; + seg.category = category; + seg.startTime = start; + seg.endTime = end; + seg.actionType = actionType; + return seg; +} + +- (SBSegmentAction)configuredAction { + return (SBSegmentAction)[[NSUserDefaults standardUserDefaults] integerForKey:SB_ACTION_KEY(self.category)]; +} + +- (UIColor *)segmentColor { + NSString *hex = [[NSUserDefaults standardUserDefaults] stringForKey:SB_COLOR_KEY(self.category)]; + return SBColorFromHex(hex); +} + +@end + +#pragma mark - SBRequest Implementation + +@implementation SBRequest + ++ (void)fetchSegmentsForVideoID:(NSString *)videoID completion:(void (^)(NSArray *))completion { + if (!videoID || videoID.length == 0) { + if (completion) completion(@[]); + return; + } + + @synchronized(sbSegmentCache) { + NSArray *cached = sbSegmentCache[videoID]; + if (cached) { + if (completion) completion(cached); + return; + } + } + + NSArray *categories = sbEnabledCategories(); + if (categories.count == 0) { + if (completion) completion(@[]); + return; + } + + NSData *catJSON = [NSJSONSerialization dataWithJSONObject:categories options:0 error:nil]; + NSString *catString = [[NSString alloc] initWithData:catJSON encoding:NSUTF8StringEncoding]; + NSString *encoded = [catString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + NSString *urlStr = [NSString stringWithFormat:@"https://sponsor.ajay.app/api/skipSegments?videoID=%@&categories=%@", videoID, encoded]; + NSURL *url = [NSURL URLWithString:urlStr]; + + NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSMutableArray *segments = [NSMutableArray array]; + + if (!error && data) { + NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response; + if (httpResp.statusCode == 200) { + NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + if ([json isKindOfClass:[NSArray class]]) { + for (NSDictionary *item in json) { + NSArray *segment = item[@"segment"]; + if (segment.count >= 2) { + SBSegment *seg = [SBSegment segmentWithUUID:item[@"UUID"] ?: @"" + category:item[@"category"] ?: @"" + start:[segment[0] floatValue] + end:[segment[1] floatValue] + action:item[@"actionType"] ?: @"skip"]; + [segments addObject:seg]; + } + } + } + } + } + + dispatch_async(dispatch_get_main_queue(), ^{ + @synchronized(sbSegmentCache) { + sbSegmentCache[videoID] = segments; + } + if (completion) completion(segments); + }); + }]; + [task resume]; +} + +@end + +#pragma mark - YTPlayerViewController Hooks + +%hook YTPlayerViewController +%property (nonatomic, strong) NSString *sbLastVideoID; +%property (nonatomic, strong) NSArray *sbSegments; +%property (nonatomic, strong) NSMutableSet *sbSkippedSegments; +%property (nonatomic, strong) SBSkipNotificationView *sbNotificationView; +%property (nonatomic, assign) BOOL sbEnabledForVideo; +- (void)playbackController:(id)playbackController didActivateVideo:(id)video withPlaybackData:(id)playbackData { + %orig; + if (!IS_ENABLED(SBEnabled) || self.isPlayingAd) return; + if ([self.parentViewController isKindOfClass:%c(YTShortsPlayerViewController)]) return; + + self.sbEnabledForVideo = YES; + self.sbSkippedSegments = [NSMutableSet set]; + self.sbSegments = nil; + + [self.sbNotificationView dismiss]; + + NSString *videoID = [self currentVideoID]; + if ([self.sbLastVideoID isEqualToString:videoID] && self.sbSegments.count > 0) return; + self.sbLastVideoID = videoID; + + __weak typeof(self) weakSelf = self; + [SBRequest fetchSegmentsForVideoID:videoID completion:^(NSArray *segments) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) return; + strongSelf.sbSegments = segments; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SBSegmentsDidLoad" + object:strongSelf + userInfo:@{@"segments": segments ?: @[]}]; + + [strongSelf sbShowHighlightBannerIfNeeded:segments]; + }]; +} + +- (void)singleVideo:(id)video currentVideoTimeDidChange:(id)time { + %orig; + [self sbCheckSegmentsAtCurrentTime]; +} + +// Time-change hook for YouTube versions that use the renamed selector. +- (void)potentiallyMutatedSingleVideo:(id)video currentVideoTimeDidChange:(id)time { + %orig; + [self sbCheckSegmentsAtCurrentTime]; +} + +// Evaluates the loaded segments against the current playback time and performs +// the configured skip / ask action for the first matching segment. Shared by +// both time-change hooks so the skip logic lives in one place. +%new +- (void)sbCheckSegmentsAtCurrentTime { + if (!IS_ENABLED(SBEnabled) || !self.sbEnabledForVideo || self.isPlayingAd) return; + if ([self.parentViewController isKindOfClass:%c(YTShortsPlayerViewController)]) return; + + CGFloat currentTime = [self currentVideoMediaTime]; + float minDuration = FLOAT_FOR_KEY(SBMinDuration); + + for (SBSegment *segment in self.sbSegments) { + SBSegmentAction action = [segment configuredAction]; + if (action == SBSegmentActionDisable || action == SBSegmentActionDisplay) continue; + if (action == SBSegmentActionSkipTo) continue; + + float duration = segment.endTime - segment.startTime; + if (duration < minDuration) continue; + + if (currentTime >= segment.startTime && currentTime < segment.endTime - SBSegmentEndGuardSeconds) { + NSString *segID = segment.UUID; + if ([self.sbSkippedSegments containsObject:segID]) continue; + + if (action == SBSegmentActionAutoSkip) { + [self sbPerformSkip:segment]; + } else if (action == SBSegmentActionAsk) { + [self sbShowAskNotification:segment]; + } + break; + } + } +} + +%new +- (void)sbPerformSkip:(SBSegment *)segment { + [self.sbSkippedSegments addObject:segment.UUID]; + [self seekToTime:(CGFloat)segment.endTime]; + + if (IS_ENABLED(SBAudioNotification)) { + AudioServicesPlaySystemSound(SBSkipHapticSoundID); + } + + if (IS_ENABLED(SBShowNotifications)) { + useBackwardIconForButton = YES; + NSBundle *bundle = YouModBundle(); + NSString *catName = [bundle localizedStringForKey:[NSString stringWithFormat:@"SB_CAT_%@", segment.category] value:segment.category table:nil]; + NSString *message = [NSString stringWithFormat:[bundle localizedStringForKey:@"SB_SKIPPED" value:@"%@ skipped" table:nil], catName]; + NSString *unskipTitle = [bundle localizedStringForKey:@"SB_UNSKIP" value:@"Unskip" table:nil]; + + float alertDuration = SBClampedAlertDuration(SBUnskipAlertDuration); + + __weak typeof(self) weakSelf = self; + // Delay notification so the seek completes before the banner is shown, + // preventing the time-change callback from dismissing it immediately. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(SBSkipNotificationDelaySeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) return; + UIView *parentView = sbGetNotificationParent(); + strongSelf.sbNotificationView = [SBSkipNotificationView showInView:parentView + message:message + buttonTitle:unskipTitle + action:^{ + __strong typeof(weakSelf) ss = weakSelf; + if (ss) [ss seekToTime:(CGFloat)segment.startTime]; + } + duration:alertDuration]; + }); + } +} + +%new +- (void)sbShowAskNotification:(SBSegment *)segment { + [self.sbSkippedSegments addObject:segment.UUID]; + + useBackwardIconForButton = NO; + NSBundle *bundle = YouModBundle(); + NSString *catName = [bundle localizedStringForKey:[NSString stringWithFormat:@"SB_CAT_%@", segment.category] value:segment.category table:nil]; + NSString *message = [NSString stringWithFormat:[bundle localizedStringForKey:@"SB_DETECTED" value:@"%@ detected" table:nil], catName]; + + float alertDuration = SBClampedAlertDuration(SBSkipAlertDuration); + + UIView *parentView = sbGetNotificationParent(); + __weak typeof(self) weakSelf = self; + self.sbNotificationView = [SBSkipNotificationView showInView:parentView + message:message + buttonTitle:[bundle localizedStringForKey:@"SB_SKIP_NOW" value:@"Skip" table:nil] + action:^{ + __strong typeof(weakSelf) ss = weakSelf; + if (ss) [ss seekToTime:(CGFloat)segment.endTime]; + } + duration:alertDuration]; +} + +%new +- (void)sbShowHighlightBannerIfNeeded:(NSArray *)segments { + for (SBSegment *seg in segments) { + if ([seg.category isEqualToString:@"poi_highlight"] && [seg configuredAction] == SBSegmentActionAsk) { + useBackwardIconForButton = NO; + NSBundle *bundle = YouModBundle(); + NSString *message = [bundle localizedStringForKey:@"SB_JUMP_TO_HIGHLIGHT" value:@"Highlight available. Jump to the point?" table:nil]; + NSString *skipTitle = [bundle localizedStringForKey:@"SB_SKIP_NOW" value:@"Skip" table:nil]; + + float alertDuration = SBClampedAlertDuration(SBSkipAlertDuration); + + UIView *parentView = sbGetNotificationParent(); + SBSkipNotificationView *pill = [SBSkipNotificationView showInView:parentView + message:message + buttonTitle:skipTitle + action:^{ [self sbSkipToHighlight]; } + duration:alertDuration]; + if (pill) { + pill.isHighlightPill = YES; + self.sbNotificationView = pill; + } + break; + } + } +} + +%new +- (void)sbSkipToHighlight { + self.sbNotificationView.isHighlightPill = NO; + + for (SBSegment *segment in self.sbSegments) { + if ([segment.category isEqualToString:@"poi_highlight"]) { + CGFloat previousTime = [self currentVideoMediaTime]; + [self seekToTime:(CGFloat)segment.startTime]; + + if (IS_ENABLED(SBShowNotifications)) { + useBackwardIconForButton = YES; + NSBundle *bundle = YouModBundle(); + NSString *message = [bundle localizedStringForKey:@"SB_JUMPED_TO_HIGHLIGHT" value:@"Jumped to highlight" table:nil]; + NSString *unskipTitle = [bundle localizedStringForKey:@"SB_UNSKIP" value:@"Unskip" table:nil]; + + float alertDuration = SBClampedAlertDuration(SBUnskipAlertDuration); + + __weak typeof(self) weakSelf = self; + SBSkipNotificationView *pill = [SBSkipNotificationView showInView:sbGetNotificationParent() + message:message + buttonTitle:unskipTitle + action:^{ + __strong typeof(weakSelf) ss = weakSelf; + if (ss) [ss seekToTime:previousTime]; + } + duration:alertDuration]; + if (pill) { + pill.isHighlightPill = YES; + self.sbNotificationView = pill; + } + } + break; + } + } +} + +%end + +// SponsorBlock's accent blue, reused for the toggle button's enabled state. +static UIColor *SBAccentColor() { + return [UIColor colorWithRed:0.4 green:0.8 blue:1.0 alpha:1.0]; +} + +%ctor { + sbSegmentCache = [NSMutableDictionary dictionary]; + %init; + + // Register the SponsorBlock toggle in the player overlay's custom button row. + // sortOrder 100 keeps it right-most (directly under YouTube's settings gear). + YMOverlayButtonSpec *toggle = [[YMOverlayButtonSpec alloc] init]; + toggle.identifier = @"sponsorblock.toggle"; + toggle.symbolName = @"shield.fill"; + toggle.tintColor = SBAccentColor(); + toggle.sortOrder = 100; + toggle.isVisible = ^BOOL(YTPlayerViewController *player) { + return IS_ENABLED(SBEnabled) && IS_ENABLED(SBShowButton); + }; + toggle.tintProvider = ^UIColor *(YTPlayerViewController *player) { + return (player && player.sbEnabledForVideo) ? SBAccentColor() : [UIColor grayColor]; + }; + toggle.onTap = ^(YTPlayerViewController *player, UIButton *button) { + if (!player) return; + BOOL newState = !player.sbEnabledForVideo; + player.sbEnabledForVideo = newState; + button.tintColor = newState ? SBAccentColor() : [UIColor grayColor]; + + NSArray *segments = newState ? (player.sbSegments ?: @[]) : @[]; + if (newState && segments.count == 0) return; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SBSegmentsDidLoad" + object:player + userInfo:@{@"segments": segments}]; + }; + YMRegisterOverlayButton(toggle); +} diff --git a/Files/SponsorBlockSettings.x b/Files/SponsorBlockSettings.x new file mode 100644 index 00000000..6800ef6e --- /dev/null +++ b/Files/SponsorBlockSettings.x @@ -0,0 +1,789 @@ +// SponsorBlockSettings.x — Custom UITableViewController matching YTLite's SponsorBlock UI +#import "Headers.h" +#import +#import + +extern UIColor *SBColorFromHex(NSString *hexString); + +// Purple accent used to tint the toggle switches and duration sliders on this +// page. (Distinct from the player overlay button's accent in SponsorBlock.x.) +static UIColor *SBControlTintColor(void) { + return [UIColor colorWithRed:0.6 green:0.2 blue:0.9 alpha:1.0]; +} + +// Tag base for a slider row's value label, offset by the row index so each +// slider can find its own label via viewWithTag:. +static const NSInteger SBSliderValueLabelTagBase = 100; + +// The localization key for a segment action's display name. Shared by the +// selected-action label and the action picker menu so the two never disagree. +static NSString *SBActionLocKey(SBSegmentAction action) { + switch (action) { + case SBSegmentActionAutoSkip: return @"SB_ACTION_AUTO_SKIP"; + case SBSegmentActionAsk: return @"SB_ACTION_ASK"; + case SBSegmentActionDisplay: return @"SB_ACTION_DISPLAY"; + case SBSegmentActionSkipTo: return @"SB_ACTION_SKIP_TO"; + default: return @"SB_ACTION_DISABLE"; + } +} + +// One toggle row: the defaults key it controls and its localized title/description +// keys. This is the single source of truth for the toggle section — the row count, +// each cell's contents, and the value written on change all read from it, so a row +// can never display one setting while toggling another. +@interface SBToggleRow : NSObject +@property (nonatomic, copy) NSString *key; +@property (nonatomic, copy) NSString *titleKey; +@property (nonatomic, copy) NSString *descKey; +@end +@implementation SBToggleRow ++ (instancetype)key:(NSString *)key title:(NSString *)titleKey desc:(NSString *)descKey { + SBToggleRow *row = [SBToggleRow new]; + row.key = key; row.titleKey = titleKey; row.descKey = descKey; + return row; +} +@end + +static NSArray *sbToggleRows() { + return @[ + [SBToggleRow key:SBEnabled title:@"SB_ENABLE" desc:@"SB_ENABLE_DESC"], + [SBToggleRow key:SBShowButton title:@"SB_SHOW_BUTTON" desc:@"SB_SHOW_BUTTON_DESC"], + [SBToggleRow key:SBShowNotifications title:@"SB_SHOW_NOTIFICATIONS" desc:@"SB_SHOW_NOTIFICATIONS_DESC"], + [SBToggleRow key:SBSegmentsInPlayer title:@"SB_SEGMENTS_IN_PLAYER" desc:@"SB_SEGMENTS_IN_PLAYER_DESC"], + [SBToggleRow key:SBSegmentsInFeed title:@"SB_SEGMENTS_IN_FEED" desc:@"SB_SEGMENTS_IN_FEED_DESC"], + [SBToggleRow key:SBSegmentsInMiniPlayer title:@"SB_SEGMENTS_IN_MINIPLAYER" desc:@"SB_SEGMENTS_IN_MINIPLAYER_DESC"], + [SBToggleRow key:SBAudioNotification title:@"SB_HAPTIC_FEEDBACK" desc:@"SB_HAPTIC_FEEDBACK_DESC"], + [SBToggleRow key:SBShowDuration title:@"SB_SHOW_DURATION" desc:@"SB_SHOW_DURATION_DESC"], + ]; +} + +static NSString *SBActionName(NSInteger action) { + return LOC(SBActionLocKey((SBSegmentAction)action)); +} + +static NSString *SBHexFromColor(UIColor *color) { + CGFloat r, g, b, a; + [color getRed:&r green:&g blue:&b alpha:&a]; + return [NSString stringWithFormat:@"#%02X%02X%02X", (int)(r * 255), (int)(g * 255), (int)(b * 255)]; +} + +#pragma mark - Color Circle View (filled center + rainbow ring) + +@interface SBColorCircleView : UIView +@property (nonatomic, strong) UIColor *fillColor; +@end + +@implementation SBColorCircleView + +- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color { + self = [super initWithFrame:frame]; + if (self) { + self.fillColor = color; + self.backgroundColor = [UIColor clearColor]; + } + return self; +} + +- (void)drawRect:(CGRect)rect { + CGContextRef ctx = UIGraphicsGetCurrentContext(); + CGFloat size = MIN(rect.size.width, rect.size.height); + CGRect square = CGRectMake((rect.size.width - size) / 2, (rect.size.height - size) / 2, size, size); + CGFloat cx = CGRectGetMidX(square), cy = CGRectGetMidY(square); + CGFloat ringWidth = 3.0; + CGFloat radius = (size - ringWidth) / 2.0; + + // Draw rainbow ring by stroking arc segments at varying hues + NSInteger segments = 64; + CGFloat anglePerSegment = (2.0 * M_PI) / segments; + for (NSInteger i = 0; i < segments; i++) { + CGFloat startAngle = i * anglePerSegment - M_PI_2; + CGFloat endAngle = startAngle + anglePerSegment + 0.02; // slight overlap to avoid gaps + CGFloat hue = (CGFloat)i / segments; + UIColor *color = [UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0]; + CGContextSetStrokeColorWithColor(ctx, color.CGColor); + CGContextSetLineWidth(ctx, ringWidth); + CGContextAddArc(ctx, cx, cy, radius, startAngle, endAngle, 0); + CGContextStrokePath(ctx); + } + + // Filled center circle + CGRect innerRect = CGRectInset(square, ringWidth + 2, ringWidth + 2); + UIBezierPath *innerPath = [UIBezierPath bezierPathWithOvalInRect:innerRect]; + [self.fillColor setFill]; + [innerPath fill]; +} + +- (void)setFillColor:(UIColor *)fillColor { + _fillColor = fillColor; + [self setNeedsDisplay]; +} + +@end + +#pragma mark - SBSettingsViewController + +@interface SBSettingsViewController : UIViewController +- (UITableView *)tableView; +- (void)setTableView:(UITableView *)tv; +- (NSString *)activeColorKey; +- (void)setActiveColorKey:(NSString *)key; +- (NSIndexPath *)activeColorIndexPath; +- (void)setActiveColorIndexPath:(NSIndexPath *)ip; +- (UIColor *)sbTextColor; +- (UIColor *)sbSecondaryTextColor; +// Cell builders, also reused by the global settings search (sbSearchRows). +- (UITableViewCell *)toggleCellForRow:(NSInteger)row tableView:(UITableView *)tableView; +- (UITableViewCell *)sliderCellForRow:(NSInteger)row tableView:(UITableView *)tableView; +- (UITableViewCell *)actionCellForCategory:(NSString *)category name:(NSString *)catName tableView:(UITableView *)tableView; +- (UITableViewCell *)colorCellForCategory:(NSString *)category name:(NSString *)catName tableView:(UITableView *)tableView; +@end + +// Flat list of all SB settings as search rows (declared before use by the per-page +// filter below; defined in the Settings Search section). +extern NSArray *sbFlatRowsWithRenderer(SBSettingsViewController *renderer); + +static const void *kSBTableViewKey = &kSBTableViewKey; +static const void *kSBColorKeyKey = &kSBColorKeyKey; +static const void *kSBColorIndexPathKey = &kSBColorIndexPathKey; +static const void *kSBPageFilterKey = &kSBPageFilterKey; +static const void *kSBAllFlatRowsKey = &kSBAllFlatRowsKey; + +@implementation SBSettingsViewController + +- (UITableView *)tableView { return objc_getAssociatedObject(self, kSBTableViewKey); } +- (void)setTableView:(UITableView *)tv { objc_setAssociatedObject(self, kSBTableViewKey, tv, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (NSString *)activeColorKey { return objc_getAssociatedObject(self, kSBColorKeyKey); } +- (void)setActiveColorKey:(NSString *)key { objc_setAssociatedObject(self, kSBColorKeyKey, key, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (NSIndexPath *)activeColorIndexPath { return objc_getAssociatedObject(self, kSBColorIndexPathKey); } +- (void)setActiveColorIndexPath:(NSIndexPath *)ip { objc_setAssociatedObject(self, kSBColorIndexPathKey, ip, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + +// Per-page search state. pageFilter is the active query (nil/empty = not filtering); +// while filtering, the page collapses to one flat section of matching search rows. +- (NSString *)pageFilter { return objc_getAssociatedObject(self, kSBPageFilterKey); } +- (void)setPageFilter:(NSString *)f { objc_setAssociatedObject(self, kSBPageFilterKey, f, OBJC_ASSOCIATION_COPY_NONATOMIC); } +- (NSArray *)allFlatRows { return objc_getAssociatedObject(self, kSBAllFlatRowsKey); } +- (void)setAllFlatRows:(NSArray *)r { objc_setAssociatedObject(self, kSBAllFlatRowsKey, r, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + +- (BOOL)isFiltering { return self.pageFilter.length > 0; } + +- (NSArray *)filteredFlatRows { + NSString *q = self.pageFilter; + if (q.length == 0) return @[]; + NSMutableArray *matches = [NSMutableArray array]; + NSStringCompareOptions opts = NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch; + for (YMSearchRow *row in self.allFlatRows) { + if ([row.searchText rangeOfString:q options:opts].location != NSNotFound) [matches addObject:row]; + } + return matches; +} + +- (void)updateSearchResultsForSearchController:(UISearchController *)searchController { + self.pageFilter = [searchController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [self.tableView reloadData]; +} + +- (void)viewDidLoad { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL))objc_msgSendSuper)(&superStruct, @selector(viewDidLoad)); + + self.title = @"SponsorBlock"; + + self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; + self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + self.tableView.estimatedRowHeight = 60; + self.tableView.rowHeight = UITableViewAutomaticDimension; + + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + self.tableView.backgroundColor = [%c(YTColor) black3]; + } else { + self.tableView.backgroundColor = [UIColor systemBackgroundColor]; + } + + [self.view addSubview:self.tableView]; + + // Pinned per-page search bar that filters this page's own rows. The flat row + // list is built once with self as the renderer (its cells write straight to + // NSUserDefaults, so reusing the live VC is safe). + self.allFlatRows = sbFlatRowsWithRenderer(self); + UISearchController *sc = [[UISearchController alloc] initWithSearchResultsController:nil]; + sc.searchResultsUpdater = self; + sc.obscuresBackgroundDuringPresentation = NO; + sc.searchBar.placeholder = LOC(@"SEARCH"); + sc.searchBar.tintColor = SBControlTintColor(); + self.navigationItem.searchController = sc; + self.navigationItem.hidesSearchBarWhenScrolling = NO; + self.definesPresentationContext = YES; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle) { + self.tableView.backgroundColor = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) + ? [%c(YTColor) black3] + : [UIColor systemBackgroundColor]; + [self.tableView reloadData]; + } +} + +- (void)viewWillAppear:(BOOL)animated { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL, BOOL))objc_msgSendSuper)(&superStruct, @selector(viewWillAppear:), animated); +} + +- (void)viewDidLayoutSubviews { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL))objc_msgSendSuper)(&superStruct, @selector(viewDidLayoutSubviews)); + YTQTMButton *backButton = [self valueForKey:@"_backButton"]; + + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + backButton.tintColor = [UIColor whiteColor]; + } else { + backButton.tintColor = [UIColor blackColor]; + } +} + +- (UIColor *)navBarForegroundColor { + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + return [UIColor whiteColor]; + } + // Light mode: return a concrete colour rather than nil. nil lets YouTube tint the + // nav-bar foreground (back chevron + title) with its default appearance, which is + // system blue in light mode. labelColor keeps it black in light / white in dark. + return [UIColor labelColor]; +} + +- (UIColor *)sbTextColor { + return (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) + ? [UIColor whiteColor] : [UIColor labelColor]; +} + +- (UIColor *)sbSecondaryTextColor { + return [UIColor colorWithWhite:0.55 alpha:1.0]; +} + +#pragma mark - Sections: 0=Main, 1=Sliders, 2=Segments + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return self.isFiltering ? 1 : 3; // one flat section of matches while searching +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (self.isFiltering) return self.filteredFlatRows.count; + if (section == 0) return sbToggleRows().count; // toggles + if (section == 1) return 2; // sliders + return sbAllCategories().count * 2; // action + color per category +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + if (self.isFiltering) return nil; // headers dropped in the flat filtered list + NSString *title = nil; + if (section == 0) title = LOC(@"SB_SECTION_MAIN"); + else if (section == 2) title = LOC(@"SB_CATEGORIES_HEADER"); + if (!title) return nil; + + UIView *header = [[UIView alloc] init]; + UILabel *label = [[UILabel alloc] init]; + label.text = title; + // Match the main YouMod settings section headers (ymSecondaryColor / size 14). + label.textColor = [UIColor colorWithWhite:0.55 alpha:1.0]; + label.font = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular]; + label.translatesAutoresizingMaskIntoConstraints = NO; + [header addSubview:label]; + [NSLayoutConstraint activateConstraints:@[ + [label.leadingAnchor constraintEqualToAnchor:header.leadingAnchor constant:16], + [label.bottomAnchor constraintEqualToAnchor:header.bottomAnchor constant:-6], + ]]; + return header; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + if (self.isFiltering) return 0; + if (section == 1) return 16; + return 36; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + if (self.isFiltering) { + CGFloat h = self.filteredFlatRows[indexPath.row].cellHeight; + return h > 0 ? h : UITableViewAutomaticDimension; + } + if (indexPath.section == 1) return 70; + if (indexPath.section == 2) { + BOOL isActionRow = (indexPath.row % 2 == 0); + NSInteger catIndex = indexPath.row / 2; + if (isActionRow && catIndex > 0) return 64; // extra top spacing between groups + return 48; + } + return UITableViewAutomaticDimension; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if (self.isFiltering) return self.filteredFlatRows[indexPath.row].makeCell(tableView); + if (indexPath.section == 0) return [self toggleCellForRow:indexPath.row tableView:tableView]; + if (indexPath.section == 1) return [self sliderCellForRow:indexPath.row tableView:tableView]; + return [self segmentCellForRow:indexPath.row tableView:tableView]; +} + +#pragma mark - Toggle Cells (Section 0) + +- (UITableViewCell *)toggleCellForRow:(NSInteger)row tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.textLabel.textColor = [self sbTextColor]; + cell.textLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + cell.detailTextLabel.textColor = [self sbSecondaryTextColor]; + cell.detailTextLabel.font = [UIFont systemFontOfSize:13]; + cell.detailTextLabel.numberOfLines = 0; + + SBToggleRow *def = sbToggleRows()[row]; + + cell.textLabel.text = LOC(def.titleKey); + cell.detailTextLabel.text = LOC(def.descKey); + + UISwitch *sw = [[UISwitch alloc] init]; + sw.on = [[NSUserDefaults standardUserDefaults] boolForKey:def.key]; + sw.onTintColor = SBControlTintColor(); + sw.tag = row; + [sw addTarget:self action:@selector(toggleChanged:) forControlEvents:UIControlEventValueChanged]; + cell.accessoryView = sw; + + return cell; +} + +- (void)toggleChanged:(UISwitch *)sender { + NSArray *rows = sbToggleRows(); + if (sender.tag < 0 || sender.tag >= (NSInteger)rows.count) return; + NSString *key = rows[sender.tag].key; + [[NSUserDefaults standardUserDefaults] setBool:sender.on forKey:key]; +} + +#pragma mark - Slider Cells (Section 1) + +- (UITableViewCell *)sliderCellForRow:(NSInteger)row tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + NSString *title = (row == 0) ? LOC(@"SB_SKIP_ALERT_DURATION") : LOC(@"SB_UNSKIP_ALERT_DURATION"); + NSString *key = (row == 0) ? SBSkipAlertDuration : SBUnskipAlertDuration; + float currentVal = [[NSUserDefaults standardUserDefaults] floatForKey:key]; + if (currentVal <= 0) currentVal = SBAlertDurationDefault; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.text = title; + titleLabel.textColor = [self sbSecondaryTextColor]; + titleLabel.font = [UIFont systemFontOfSize:13]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + + UISlider *slider = [[UISlider alloc] init]; + slider.minimumValue = SBAlertDurationMin; + slider.maximumValue = SBAlertDurationMax; + slider.value = currentVal; + slider.minimumTrackTintColor = SBControlTintColor(); + slider.maximumTrackTintColor = [UIColor colorWithWhite:0.3 alpha:1.0]; + slider.translatesAutoresizingMaskIntoConstraints = NO; + slider.tag = row; + [slider addTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged]; + + UILabel *valueLabel = [[UILabel alloc] init]; + NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; + formatter.allowedUnits = NSCalendarUnitSecond; + formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated; + + valueLabel.text = [formatter stringFromTimeInterval:currentVal]; + valueLabel.textColor = [self sbSecondaryTextColor]; + valueLabel.font = [UIFont systemFontOfSize:13]; + valueLabel.textAlignment = NSTextAlignmentRight; + valueLabel.translatesAutoresizingMaskIntoConstraints = NO; + valueLabel.tag = SBSliderValueLabelTagBase + row; + + [cell.contentView addSubview:titleLabel]; + [cell.contentView addSubview:slider]; + [cell.contentView addSubview:valueLabel]; + + [NSLayoutConstraint activateConstraints:@[ + [titleLabel.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor constant:8], + [titleLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + + [slider.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:8], + [slider.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [slider.trailingAnchor constraintEqualToAnchor:valueLabel.leadingAnchor constant:-8], + [slider.bottomAnchor constraintEqualToAnchor:cell.contentView.bottomAnchor constant:-8], + + [valueLabel.centerYAnchor constraintEqualToAnchor:slider.centerYAnchor], + [valueLabel.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-16], + [valueLabel.widthAnchor constraintEqualToConstant:50], + ]]; + + return cell; +} + +- (void)sliderChanged:(UISlider *)sender { + NSString *key = (sender.tag == 0) ? SBSkipAlertDuration : SBUnskipAlertDuration; + int rounded = (int)roundf(sender.value); + sender.value = rounded; + [[NSUserDefaults standardUserDefaults] setFloat:(float)rounded forKey:key]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + UILabel *valueLabel = (UILabel *)[sender.superview viewWithTag:SBSliderValueLabelTagBase + sender.tag]; + NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; + formatter.allowedUnits = NSCalendarUnitSecond; + formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated; + + valueLabel.text = [formatter stringFromTimeInterval:rounded]; +} + +#pragma mark - Segment Cells (Section 2) + +- (UITableViewCell *)segmentCellForRow:(NSInteger)row tableView:(UITableView *)tableView { + NSInteger catIndex = row / 2; + BOOL isColorRow = (row % 2 == 1); + NSString *category = sbAllCategories()[catIndex]; + NSBundle *bundle = YouModBundle(); + NSString *catLocKey = [NSString stringWithFormat:@"SB_CAT_%@", category]; + NSString *catName = [bundle localizedStringForKey:catLocKey value:category table:nil]; + + if (isColorRow) { + return [self colorCellForCategory:category name:catName tableView:tableView]; + } else { + return [self actionCellForCategory:category name:catName tableView:tableView]; + } +} + +- (UITableViewCell *)actionCellForCategory:(NSString *)category name:(NSString *)catName tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.textLabel.text = catName; + cell.textLabel.textColor = [self sbTextColor]; + cell.textLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + + BOOL isHighlight = [category isEqualToString:@"poi_highlight"]; + NSString *actionKey = SB_ACTION_KEY(category); + + UIButton *menuButton = [UIButton buttonWithType:UIButtonTypeSystem]; + NSInteger currentAction = [[NSUserDefaults standardUserDefaults] integerForKey:actionKey]; + [menuButton setTitle:SBActionName(currentAction) forState:UIControlStateNormal]; + [menuButton setTitleColor:[self sbSecondaryTextColor] forState:UIControlStateNormal]; + menuButton.titleLabel.font = [UIFont systemFontOfSize:15]; + [menuButton setImage:[UIImage systemImageNamed:@"chevron.up.chevron.down"] forState:UIControlStateNormal]; + menuButton.tintColor = [self sbSecondaryTextColor]; + + NSMutableArray *menuActions = [NSMutableArray array]; + // Which actions a category may take differs by kind: a highlight can only be + // disabled, skipped-to, or displayed, while a regular segment offers + // auto-skip / ask instead of skip-to. These are separate option sets, not + // duplication — each action's label is resolved through SBActionLocKey. + NSArray *actionOptions; + if (isHighlight) { + actionOptions = @[@(SBSegmentActionDisable), @(SBSegmentActionAsk), @(SBSegmentActionDisplay)]; + } else { + actionOptions = @[@(SBSegmentActionDisable), @(SBSegmentActionAutoSkip), @(SBSegmentActionAsk), @(SBSegmentActionDisplay)]; + } + + NSBundle *bundle = YouModBundle(); + for (NSNumber *option in actionOptions) { + NSInteger actionVal = [option integerValue]; + NSString *actionTitle = [bundle localizedStringForKey:SBActionLocKey((SBSegmentAction)actionVal) value:nil table:nil]; + + UIAction *action = [UIAction actionWithTitle:actionTitle image:nil identifier:nil handler:^(__kindof UIAction *a) { + [[NSUserDefaults standardUserDefaults] setInteger:actionVal forKey:actionKey]; + [self.tableView reloadData]; + }]; + if (actionVal == currentAction) action.state = UIMenuElementStateOn; + [menuActions addObject:action]; + } + + menuButton.menu = [UIMenu menuWithTitle:catName children:menuActions]; + menuButton.showsMenuAsPrimaryAction = YES; + [menuButton sizeToFit]; + cell.accessoryView = menuButton; + + return cell; +} + +- (UITableViewCell *)colorCellForCategory:(NSString *)category name:(NSString *)catName tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", catName, LOC(@"SB_SEGMENT_COLOR_SUFFIX")]; + cell.textLabel.textColor = [self sbTextColor]; + cell.textLabel.font = [UIFont systemFontOfSize:15]; + + NSString *colorKey = SB_COLOR_KEY(category); + NSString *hex = [[NSUserDefaults standardUserDefaults] stringForKey:colorKey]; + UIColor *color = SBColorFromHex(hex); + + SBColorCircleView *circle = [[SBColorCircleView alloc] initWithFrame:CGRectMake(0, 0, 34, 34) color:color]; + cell.accessoryView = circle; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (self.isFiltering) { + // In the flat filtered list the row carries its own tap handler (colour rows + // open the picker); route through it with a reload that refreshes results. + YMSearchRow *row = self.filteredFlatRows[indexPath.row]; + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + __weak typeof(self) weakSelf = self; + if (row.onSelect) row.onSelect(self, ^{ [weakSelf.tableView reloadData]; }); + return; + } + if (indexPath.section != 2) return; + if (indexPath.row % 2 != 1) return; // only color rows are tappable + + NSInteger catIndex = indexPath.row / 2; + NSString *category = sbAllCategories()[catIndex]; + NSString *colorKey = SB_COLOR_KEY(category); + + self.activeColorKey = colorKey; + self.activeColorIndexPath = indexPath; + + UIColorPickerViewController *picker = [[UIColorPickerViewController alloc] init]; + NSString *catName = [YouModBundle() localizedStringForKey:[NSString stringWithFormat:@"SB_CAT_%@", category] value:category table:nil]; + picker.title = [NSString stringWithFormat:@"%@ %@", catName, LOC(@"SB_SEGMENT_COLOR_SUFFIX")]; + NSString *currentHex = [[NSUserDefaults standardUserDefaults] stringForKey:colorKey]; + if (currentHex) picker.selectedColor = SBColorFromHex(currentHex); + picker.supportsAlpha = NO; + picker.delegate = self; + + [self presentViewController:picker animated:YES completion:nil]; +} + +#pragma mark - UIColorPickerViewControllerDelegate + +- (void)colorPickerViewControllerDidFinish:(UIColorPickerViewController *)viewController { + UIColor *color = viewController.selectedColor; + NSString *hex = SBHexFromColor(color); + [[NSUserDefaults standardUserDefaults] setObject:hex forKey:self.activeColorKey]; + [self.tableView reloadRowsAtIndexPaths:@[self.activeColorIndexPath] withRowAnimation:UITableViewRowAnimationNone]; +} + +- (void)colorPickerViewController:(UIColorPickerViewController *)viewController didSelectColor:(UIColor *)color continuously:(BOOL)continuously { + if (!continuously) { + NSString *hex = SBHexFromColor(color); + [[NSUserDefaults standardUserDefaults] setObject:hex forKey:self.activeColorKey]; + [self.tableView reloadRowsAtIndexPaths:@[self.activeColorIndexPath] withRowAnimation:UITableViewRowAnimationNone]; + } +} + +#pragma mark - Footer spacing between category groups + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return (section == 2) ? 0 : 16; +} + +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { + return [[UIView alloc] init]; +} + +@end + +#pragma mark - Settings Search integration + +// Retained delegate for a colour picker opened from a search result. The stock +// SBSettingsViewController colour flow reloads a specific SB-table index path, +// which doesn't exist in the search results table; this dedicated delegate writes +// the chosen colour and calls the search table's own reload instead. It is +// associated with the presented picker so it lives exactly as long as the picker. +@interface SBSearchColorDelegate : NSObject +@property (nonatomic, copy) NSString *colorKey; +@property (nonatomic, copy) void (^reload)(void); +@end +@implementation SBSearchColorDelegate +- (void)applyColor:(UIColor *)color { + [[NSUserDefaults standardUserDefaults] setObject:SBHexFromColor(color) forKey:self.colorKey]; + if (self.reload) self.reload(); +} +- (void)colorPickerViewControllerDidFinish:(UIColorPickerViewController *)vc { [self applyColor:vc.selectedColor]; } +- (void)colorPickerViewController:(UIColorPickerViewController *)vc didSelectColor:(UIColor *)color continuously:(BOOL)continuously { + if (!continuously) [self applyColor:color]; +} +@end + +static const void *kSBSearchColorDelegateKey = &kSBSearchColorDelegateKey; + +// Build the flat list of every SponsorBlock setting as YMSearchRows, each rendered +// by the given SBSettingsViewController's own cell builders. Shared by the global +// settings search (via sbSearchRows) and SponsorBlock's own per-page search filter. +NSArray *sbFlatRowsWithRenderer(SBSettingsViewController *renderer) { + NSMutableArray *rows = [NSMutableArray array]; + // Weak so a row's makeCell block can't retain the renderer: on the SponsorBlock + // page the renderer IS the presented VC, which also retains this row list, so a + // strong capture would cycle and leak the VC on every visit. The renderer always + // outlives cell rendering (it's the live VC or a retained child), so weak is safe. + __weak SBSettingsViewController *renderer_w = renderer; + + // Section 0 — toggles. + NSArray *toggles = sbToggleRows(); + for (NSInteger i = 0; i < (NSInteger)toggles.count; i++) { + SBToggleRow *def = toggles[i]; + YMSearchRow *row = [YMSearchRow new]; + row.searchText = [NSString stringWithFormat:@"%@ %@", LOC(def.titleKey), LOC(def.descKey)]; + row.makeCell = ^UITableViewCell *(UITableView *tv) { return [renderer_w toggleCellForRow:i tableView:tv]; }; + [rows addObject:row]; + } + + // Section 1 — the two alert-duration sliders. + for (NSInteger i = 0; i < 2; i++) { + YMSearchRow *row = [YMSearchRow new]; + row.searchText = (i == 0) ? LOC(@"SB_SKIP_ALERT_DURATION") : LOC(@"SB_UNSKIP_ALERT_DURATION"); + row.cellHeight = 70; + row.makeCell = ^UITableViewCell *(UITableView *tv) { return [renderer_w sliderCellForRow:i tableView:tv]; }; + [rows addObject:row]; + } + + // Section 2 — per category: an action picker and a colour circle. + NSBundle *bundle = YouModBundle(); + for (NSString *category in sbAllCategories()) { + NSString *catName = [bundle localizedStringForKey:[NSString stringWithFormat:@"SB_CAT_%@", category] value:category table:nil]; + + YMSearchRow *actionRow = [YMSearchRow new]; + actionRow.searchText = catName; + actionRow.cellHeight = 48; + actionRow.makeCell = ^UITableViewCell *(UITableView *tv) { return [renderer_w actionCellForCategory:category name:catName tableView:tv]; }; + [rows addObject:actionRow]; + + YMSearchRow *colorRow = [YMSearchRow new]; + colorRow.searchText = [NSString stringWithFormat:@"%@ %@", catName, LOC(@"SB_SEGMENT_COLOR_SUFFIX")]; + colorRow.cellHeight = 48; + colorRow.makeCell = ^UITableViewCell *(UITableView *tv) { return [renderer_w colorCellForCategory:category name:catName tableView:tv]; }; + colorRow.onSelect = ^(UIViewController *presenter, void (^reload)(void)) { + NSString *colorKey = SB_COLOR_KEY(category); + UIColorPickerViewController *picker = [[UIColorPickerViewController alloc] init]; + picker.title = [NSString stringWithFormat:@"%@ %@", catName, LOC(@"SB_SEGMENT_COLOR_SUFFIX")]; + NSString *currentHex = [[NSUserDefaults standardUserDefaults] stringForKey:colorKey]; + if (currentHex) picker.selectedColor = SBColorFromHex(currentHex); + picker.supportsAlpha = NO; + + SBSearchColorDelegate *delegate = [SBSearchColorDelegate new]; + delegate.colorKey = colorKey; + delegate.reload = reload; + picker.delegate = delegate; + objc_setAssociatedObject(picker, kSBSearchColorDelegateKey, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [presenter presentViewController:picker animated:YES completion:nil]; + }; + [rows addObject:colorRow]; + } + + return rows; +} + +NSArray *sbSearchRows(UIViewController *host) { + // One renderer builds every SB result cell. It's added as a child VC so the + // cells inherit the host's trait collection (light/dark), and its tableView is + // pointed at the host's (search results) table so SB's own cell handlers — + // e.g. the action picker's [self.tableView reloadData] — refresh the visible + // results rather than a table the renderer never presents. + SBSettingsViewController *renderer = [[SBSettingsViewController alloc] init]; + [host addChildViewController:renderer]; + [renderer didMoveToParentViewController:host]; + if ([host respondsToSelector:@selector(tableView)]) { + renderer.tableView = ((UITableView *(*)(id, SEL))objc_msgSend)(host, @selector(tableView)); + } + return sbFlatRowsWithRenderer(renderer); +} + +#pragma mark - Hook entry point + +@interface YTSettingsSectionItemManager (SponsorBlock) +- (void)updateSponsorBlockSectionWithEntry:(id)entry; +@end + +%hook YTSettingsSectionItemManager + +%new(v@:@) +- (void)updateSponsorBlockSectionWithEntry:(id)entry { + YTSettingsViewController *settingsVC = [self valueForKey:@"_settingsViewControllerDelegate"]; + // Use runtime-registered subclass of YTStyledViewController for YouTube's nav styling + Class sbClass = objc_getClass("SBSettingsViewControllerStyled"); + if (!sbClass) sbClass = [SBSettingsViewController class]; + // initWithParentResponder: sets up YouTube's DI container (gimme) for nav bar theming + id allocated = [sbClass alloc]; + SBSettingsViewController *sbVC = (SBSettingsViewController *)((id (*)(id, SEL, id))objc_msgSend)(allocated, @selector(initWithParentResponder:), settingsVC); + [settingsVC pushViewController:sbVC]; +} + +%end + +%ctor { + // Register SBSettingsViewControllerStyled as a runtime subclass of YTStyledViewController + // with all methods from SBSettingsViewController — gives us YouTube's nav bar styling + Class ytStyled = %c(YTStyledViewController); + if (ytStyled) { + Class sbStyled = objc_allocateClassPair(ytStyled, "SBSettingsViewControllerStyled", 0); + if (sbStyled) { + // Copy all instance methods from our compiled SBSettingsViewController + unsigned int count = 0; + Method *methods = class_copyMethodList([SBSettingsViewController class], &count); + for (unsigned int i = 0; i < count; i++) { + SEL sel = method_getName(methods[i]); + IMP imp = method_getImplementation(methods[i]); + const char *types = method_getTypeEncoding(methods[i]); + class_addMethod(sbStyled, sel, imp, types); + } + free(methods); + + // Copy properties (for @synthesize ivars) + unsigned int propCount = 0; + objc_property_t *props = class_copyPropertyList([SBSettingsViewController class], &propCount); + for (unsigned int i = 0; i < propCount; i++) { + unsigned int attrCount = 0; + objc_property_attribute_t *attrs = property_copyAttributeList(props[i], &attrCount); + class_addProperty(sbStyled, property_getName(props[i]), attrs, attrCount); + free(attrs); + } + free(props); + + // Copy ivars won't work after registration, but properties use associated objects + objc_registerClassPair(sbStyled); + } + } + + // Per-category default action and seek-bar color. Sponsor is the only + // category that auto-skips out of the box; the rest are disabled until the + // user opts in. The action/color default entries are generated from + // sbAllCategories() below so this table stays the sole per-category source. + NSDictionary *categoryDefaults = @{ + @"sponsor": @[@(SBSegmentActionAutoSkip), @"#00D400"], + @"intro": @[@(SBSegmentActionDisable), @"#00FFFF"], + @"outro": @[@(SBSegmentActionDisable), @"#0202ED"], + @"interaction": @[@(SBSegmentActionDisable), @"#FF00F7"], + @"selfpromo": @[@(SBSegmentActionDisable), @"#FFFF00"], + @"music_offtopic": @[@(SBSegmentActionDisable), @"#FF9900"], + @"preview": @[@(SBSegmentActionDisable), @"#0084D6"], + @"hook": @[@(SBSegmentActionDisable), @"#395699"], + @"poi_highlight": @[@(SBSegmentActionDisable), @"#FF006A"], + @"filler": @[@(SBSegmentActionDisable), @"#7300FF"], + }; + + NSMutableDictionary *defaults = [@{ + SBEnabled: @YES, + SBShowButton: @YES, + SBShowNotifications: @YES, + SBSegmentsInPlayer: @YES, + SBSegmentsInFeed: @YES, + SBSegmentsInMiniPlayer: @YES, + SBSkipAlertDuration: @(SBAlertDurationDefault), + SBUnskipAlertDuration: @(SBAlertDurationDefault), + } mutableCopy]; + + for (NSString *category in sbAllCategories()) { + NSArray *def = categoryDefaults[category]; + if (!def) continue; + defaults[SB_ACTION_KEY(category)] = def[0]; + defaults[SB_COLOR_KEY(category)] = def[1]; + } + + [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; + %init; +} diff --git a/Files/SponsorBlockUI.x b/Files/SponsorBlockUI.x new file mode 100644 index 00000000..ce36cbee --- /dev/null +++ b/Files/SponsorBlockUI.x @@ -0,0 +1,827 @@ +#import "Headers.h" +#import + +extern BOOL useBackwardIconForButton; + +// Range segments render as a filled bar at least this wide so very short segments +// stay visible. Point segments (poi_highlight) have no duration, so they render as +// a fixed-width tick centered on their position via the x-offset. +static const CGFloat SBMarkerMinWidth = 2.0; +static const CGFloat SBPoiMarkerWidth = 3.0; +static const CGFloat SBPoiMarkerXOffset = 1.5; + +#pragma mark - SBSkipNotificationView Implementation + +@implementation SBSkipNotificationView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(appDidEnterBackground) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(appWillEnterForeground) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)appDidEnterBackground { + if (!self.isPaused) { + [self pauseProgress]; + self.backgroundDate = [NSDate date]; + } +} + +- (void)appWillEnterForeground { + if (self.backgroundDate) { + NSTimeInterval timeInBackground = [[NSDate date] timeIntervalSinceDate:self.backgroundDate]; + self.remainingDuration -= timeInBackground; + self.backgroundDate = nil; + + if (self.remainingDuration <= 0) { + [self removeFromSuperview]; + return; + } else { + CGFloat newScaleX = self.remainingDuration / self.totalDuration; + newScaleX = MAX(0.001, MIN(newScaleX, 1.0)); + self.progressOverlay.transform = CGAffineTransformMakeScale(newScaleX, 1.0); + self.progressOverlay.alpha = newScaleX; + } + } + [self resumeProgress]; +} + ++ (instancetype)showInView:(UIView *)parentView message:(NSString *)message buttonTitle:(NSString *)buttonTitle action:(void (^)(void))action duration:(NSTimeInterval)duration { + if (!parentView) return nil; + + for (UIView *sub in [parentView.subviews copy]) { + if ([sub isKindOfClass:[SBSkipNotificationView class]]) { + SBSkipNotificationView *existing = (SBSkipNotificationView *)sub; + if (existing.isHighlightPill) return nil; + [existing dismiss]; + } + } + + SBSkipNotificationView *view = [[SBSkipNotificationView alloc] initWithFrame:CGRectZero]; + view.translatesAutoresizingMaskIntoConstraints = NO; + view.clipsToBounds = YES; + view.layer.cornerRadius = 22.0; + view.onAction = action; + view.totalDuration = duration; + view.remainingDuration = duration; + view.isPaused = NO; + + // Base layer (revealed as progress depletes) + view.backgroundColor = [UIColor colorWithWhite:0.08 alpha:1.0]; + + // Progress overlay (shrinks from right to left) + UIView *progressOverlay = [[UIView alloc] initWithFrame:CGRectZero]; + progressOverlay.translatesAutoresizingMaskIntoConstraints = YES; + progressOverlay.backgroundColor = [UIColor colorWithWhite:0.18 alpha:1.0]; + progressOverlay.userInteractionEnabled = NO; + progressOverlay.layer.anchorPoint = CGPointMake(0, 0.5); + progressOverlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + view.progressOverlay = progressOverlay; + [view addSubview:progressOverlay]; + + // Message label + UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.text = message; + label.textColor = [UIColor whiteColor]; + label.font = [UIFont systemFontOfSize:14.0 weight:UIFontWeightMedium]; + label.numberOfLines = 2; + label.lineBreakMode = NSLineBreakByTruncatingTail; + view.messageLabel = label; + [view addSubview:label]; + + // Icon button (right side) + BOOL showButton = (buttonTitle != nil || action != nil); + UIButton *button = nil; + + if (showButton) { + button = [UIButton buttonWithType:UIButtonTypeCustom]; + button.translatesAutoresizingMaskIntoConstraints = NO; + + NSString *iconName = useBackwardIconForButton ? @"backward.fill" : @"forward.end.fill"; + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:14 weight:UIImageSymbolWeightMedium]; + UIImage *icon = [UIImage systemImageNamed:iconName withConfiguration:config]; + [button setImage:icon forState:UIControlStateNormal]; + button.tintColor = [UIColor whiteColor]; + button.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.15]; + button.layer.cornerRadius = 16.0; + button.clipsToBounds = YES; + [button addTarget:view action:@selector(actionButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + view.actionButton = button; + [view addSubview:button]; + } + + [parentView addSubview:view]; + + // Layout: centered horizontally, anchored above tab bar via safe area + NSLayoutConstraint *maxWidth = [view.widthAnchor constraintLessThanOrEqualToAnchor:parentView.widthAnchor multiplier:0.85]; + [NSLayoutConstraint activateConstraints:@[ + [view.centerXAnchor constraintEqualToAnchor:parentView.centerXAnchor], + [view.bottomAnchor constraintEqualToAnchor:parentView.safeAreaLayoutGuide.bottomAnchor constant:-60.0], + [view.heightAnchor constraintEqualToConstant:44.0], + maxWidth + ]]; + + // Internal layout + if (showButton) { + [NSLayoutConstraint activateConstraints:@[ + [label.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:16.0], + [label.centerYAnchor constraintEqualToAnchor:view.centerYAnchor], + [label.trailingAnchor constraintEqualToAnchor:button.leadingAnchor constant:-10.0], + + [button.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-8.0], + [button.centerYAnchor constraintEqualToAnchor:view.centerYAnchor], + [button.widthAnchor constraintEqualToConstant:32.0], + [button.heightAnchor constraintEqualToConstant:32.0] + ]]; + } else { + [NSLayoutConstraint activateConstraints:@[ + [label.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:16.0], + [label.centerYAnchor constraintEqualToAnchor:view.centerYAnchor], + [label.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-40.0], + ]]; + } + + // Pan gesture for interactive dismissal + UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:view action:@selector(handlePan:)]; + [view addGestureRecognizer:pan]; + + // Slide up from below + view.transform = CGAffineTransformMakeTranslation(0, 60); + view.alpha = 0.0; + [UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.85 initialSpringVelocity:0.5 options:UIViewAnimationOptionCurveEaseOut animations:^{ + view.alpha = 1.0; + view.transform = CGAffineTransformIdentity; + } completion:^(BOOL finished) { + if (finished && duration > 0) { + [view startProgressAnimation]; + } + }]; + + return view; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + if (self.progressOverlay.layer.animationKeys.count == 0 || self.isPaused) { + self.progressOverlay.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height); + } +} + +- (void)startProgressAnimation { + if (self.remainingDuration <= 0) return; + + self.progressOverlay.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height); + + [UIView animateWithDuration:self.remainingDuration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ + self.progressOverlay.transform = CGAffineTransformMakeScale(0.001, 1.0); + self.progressOverlay.alpha = 0.0; + } completion:^(BOOL finished) { + if (finished && !self.isPaused && self.superview) { + [self dismiss]; + } + }]; +} + +- (void)pauseProgress { + if (self.isPaused) return; + self.isPaused = YES; + + CALayer *presentationLayer = self.progressOverlay.layer.presentationLayer; + CGFloat currentScaleX = 1.0; + if (presentationLayer) { + CATransform3D t = presentationLayer.transform; + currentScaleX = t.m11; + } + + [self.progressOverlay.layer removeAllAnimations]; + currentScaleX = MAX(0.001, MIN(currentScaleX, 1.0)); + self.progressOverlay.transform = CGAffineTransformMakeScale(currentScaleX, 1.0); + self.progressOverlay.alpha = currentScaleX; + self.remainingDuration = self.totalDuration * currentScaleX; +} + +- (void)resumeProgress { + if (!self.isPaused) return; + self.isPaused = NO; + + if (self.remainingDuration <= 0) { + [self dismiss]; + return; + } + + [UIView animateWithDuration:self.remainingDuration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ + self.progressOverlay.transform = CGAffineTransformMakeScale(0.001, 1.0); + self.progressOverlay.alpha = 0.0; + } completion:^(BOOL finished) { + if (finished && !self.isPaused && self.superview) { + [self dismiss]; + } + }]; +} + +- (void)handlePan:(UIPanGestureRecognizer *)gesture { + if (self.alpha < 1.0) { + gesture.enabled = NO; + gesture.enabled = YES; + return; + } + + CGPoint translation = [gesture translationInView:self.superview]; + CGPoint velocity = [gesture velocityInView:self.superview]; + + switch (gesture.state) { + case UIGestureRecognizerStateBegan: + [self pauseProgress]; + break; + + case UIGestureRecognizerStateChanged: + self.transform = CGAffineTransformMakeTranslation(0, translation.y); + break; + + case UIGestureRecognizerStateEnded: + case UIGestureRecognizerStateCancelled: { + CGFloat distanceThreshold = 30.0; + CGFloat velocityThreshold = 500.0; + BOOL shouldDismiss = (fabs(translation.y) > distanceThreshold) || (fabs(velocity.y) > velocityThreshold); + + if (shouldDismiss) { + CGFloat direction = (translation.y < 0) ? -1.0 : 1.0; + [self dismissInDirection:direction velocity:fabs(velocity.y)]; + } else { + // Snap back + [UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:0.5 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + self.transform = CGAffineTransformIdentity; + } completion:^(BOOL finished) { + [self resumeProgress]; + }]; + } + break; + } + default: + break; + } +} + +- (void)dismissInDirection:(CGFloat)direction velocity:(CGFloat)velocity { + CGFloat offscreenY = direction < 0 ? -(self.frame.size.height + 80) : (self.frame.size.height + 80); + CGFloat animDuration = velocity > 500 ? 0.2 : 0.35; + + [UIView animateWithDuration:animDuration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ + self.transform = CGAffineTransformMakeTranslation(0, offscreenY); + self.alpha = 0.0; + } completion:^(BOOL finished) { + [self removeFromSuperview]; + }]; +} + +- (void)actionButtonTapped { + if (self.onAction) { + self.onAction(); + } + [self dismiss]; +} + +- (void)dismiss { + [self.progressOverlay.layer removeAllAnimations]; + [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ + self.transform = CGAffineTransformMakeTranslation(0, 60); + self.alpha = 0.0; + } completion:^(BOOL finished) { + [self removeFromSuperview]; + }]; +} + ++ (instancetype)showSuccessInView:(UIView *)parentView message:(NSString *)message duration:(NSTimeInterval)duration { + SBSkipNotificationView *view = [self showInView:parentView message:message buttonTitle:nil action:nil duration:duration]; + if (view) { + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:16 weight:UIImageSymbolWeightMedium]; + UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"checkmark.circle.fill" withConfiguration:config]]; + iconView.tintColor = [UIColor systemGreenColor]; + iconView.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:iconView]; + [NSLayoutConstraint activateConstraints:@[ + [iconView.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-12.0], + [iconView.centerYAnchor constraintEqualToAnchor:view.centerYAnchor], + ]]; + } + return view; +} + ++ (instancetype)showErrorInView:(UIView *)parentView message:(NSString *)message duration:(NSTimeInterval)duration { + SBSkipNotificationView *view = [self showInView:parentView message:message buttonTitle:nil action:nil duration:duration]; + if (view) { + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:16 weight:UIImageSymbolWeightMedium]; + UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"xmark.circle.fill" withConfiguration:config]]; + iconView.tintColor = [UIColor systemRedColor]; + iconView.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:iconView]; + [NSLayoutConstraint activateConstraints:@[ + [iconView.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-12.0], + [iconView.centerYAnchor constraintEqualToAnchor:view.centerYAnchor], + ]]; + } + return view; +} + +@end + +#pragma mark - YMDownloadProgressView + +@implementation YMDownloadProgressView + ++ (instancetype)showInView:(UIView *)parentView message:(NSString *)message cancelAction:(void (^)(void))cancelAction { + if (!parentView) return nil; + + // Dismiss any existing download progress pill + for (UIView *sub in [parentView.subviews copy]) { + if ([sub isKindOfClass:[YMDownloadProgressView class]]) { + [(YMDownloadProgressView *)sub dismiss]; + } + } + + YMDownloadProgressView *view = [[YMDownloadProgressView alloc] init]; + view.onCancel = cancelAction; + view.translatesAutoresizingMaskIntoConstraints = NO; + view.backgroundColor = [UIColor colorWithWhite:0.12 alpha:1.0]; + view.layer.cornerRadius = 16.0; + view.clipsToBounds = YES; + view.layer.borderWidth = 0.5; + view.layer.borderColor = [UIColor colorWithWhite:0.25 alpha:1.0].CGColor; + + // Title label + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.text = message; + titleLabel.textColor = [UIColor whiteColor]; + titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + view.titleLabel = titleLabel; + [view addSubview:titleLabel]; + + // Subtitle label (speed + size) + UILabel *subtitleLabel = [[UILabel alloc] init]; + subtitleLabel.text = @""; + subtitleLabel.textColor = [UIColor colorWithWhite:0.55 alpha:1.0]; + subtitleLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightRegular]; + subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + view.subtitleLabel = subtitleLabel; + [view addSubview:subtitleLabel]; + + // Progress bar + UIProgressView *progressBar = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + progressBar.progress = 0.0; + progressBar.trackTintColor = [UIColor colorWithWhite:0.22 alpha:1.0]; + progressBar.progressTintColor = [UIColor colorWithRed:0.6 green:0.2 blue:0.9 alpha:1.0]; + progressBar.translatesAutoresizingMaskIntoConstraints = NO; + progressBar.layer.cornerRadius = 3.0; + progressBar.clipsToBounds = YES; + view.progressBar = progressBar; + [view addSubview:progressBar]; + + // Cancel button + UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem]; + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:18 weight:UIImageSymbolWeightMedium]; + [cancelButton setImage:[UIImage systemImageNamed:@"xmark.circle.fill" withConfiguration:config] forState:UIControlStateNormal]; + cancelButton.tintColor = [UIColor colorWithWhite:0.45 alpha:1.0]; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + [cancelButton addTarget:view action:@selector(cancelButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + view.cancelButton = cancelButton; + [view addSubview:cancelButton]; + + // Layout + [NSLayoutConstraint activateConstraints:@[ + [titleLabel.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:16], + [titleLabel.topAnchor constraintEqualToAnchor:view.topAnchor constant:12], + [titleLabel.trailingAnchor constraintEqualToAnchor:cancelButton.leadingAnchor constant:-10], + + [subtitleLabel.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:16], + [subtitleLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:3], + [subtitleLabel.trailingAnchor constraintEqualToAnchor:cancelButton.leadingAnchor constant:-10], + + [progressBar.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:16], + [progressBar.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-16], + [progressBar.topAnchor constraintEqualToAnchor:subtitleLabel.bottomAnchor constant:10], + [progressBar.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-14], + [progressBar.heightAnchor constraintEqualToConstant:6], + + [cancelButton.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-14], + [cancelButton.centerYAnchor constraintEqualToAnchor:titleLabel.centerYAnchor], + [cancelButton.widthAnchor constraintEqualToConstant:32], + [cancelButton.heightAnchor constraintEqualToConstant:32], + ]]; + + [parentView addSubview:view]; + + // Center horizontally with max width + NSLayoutConstraint *centerX = [view.centerXAnchor constraintEqualToAnchor:parentView.centerXAnchor]; + NSLayoutConstraint *maxWidth = [view.widthAnchor constraintLessThanOrEqualToConstant:360]; + NSLayoutConstraint *leadingFallback = [view.leadingAnchor constraintGreaterThanOrEqualToAnchor:parentView.leadingAnchor constant:16]; + NSLayoutConstraint *trailingFallback = [view.trailingAnchor constraintLessThanOrEqualToAnchor:parentView.trailingAnchor constant:-16]; + NSLayoutConstraint *preferredWidth = [view.widthAnchor constraintEqualToAnchor:parentView.widthAnchor constant:-32]; + preferredWidth.priority = UILayoutPriorityDefaultHigh; + + [NSLayoutConstraint activateConstraints:@[ + centerX, maxWidth, leadingFallback, trailingFallback, preferredWidth, + [view.bottomAnchor constraintEqualToAnchor:parentView.safeAreaLayoutGuide.bottomAnchor constant:-12], + ]]; + + // Slide-up animation + view.transform = CGAffineTransformMakeTranslation(0, 80); + view.alpha = 0; + [UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:0.75 initialSpringVelocity:0.5 options:UIViewAnimationOptionCurveEaseOut animations:^{ + view.transform = CGAffineTransformIdentity; + view.alpha = 1.0; + } completion:nil]; + + return view; +} + +- (void)updateProgress:(float)progress title:(NSString *)title subtitle:(NSString *)subtitle { + self.titleLabel.text = title; + self.subtitleLabel.text = subtitle; + [self.progressBar setProgress:progress animated:YES]; +} + +- (void)cancelButtonTapped { + if (self.onCancel) { + self.onCancel(); + [self dismiss]; + } +} + +- (void)dismiss { + if (!self.superview) return; + [UIView animateWithDuration:0.25 animations:^{ + self.transform = CGAffineTransformMakeTranslation(0, 80); + self.alpha = 0; + } completion:^(BOOL finished) { + [self removeFromSuperview]; + }]; +} + +@end + +#pragma mark - Marker Repositioning Hooks + +// YTModularPlayerBarView - normal player +%hook YTModularPlayerBarView + +- (void)layoutSubviews { + %orig; + CGFloat barWidth = self.bounds.size.width; + if (barWidth <= 0) return; + + // Find reference view for Y + UIView *referenceView = nil; + for (UIView *sub in self.subviews) { + if ([sub isKindOfClass:%c(YTPlayerBarRectangleDecorationView)] || + [sub isKindOfClass:%c(YTPlayerBarProgressDecorationView)]) { + referenceView = sub; + break; + } + } + + for (UIView *sub in self.subviews) { + if (sub.tag != SBSegmentMarkerTag) continue; + NSArray *data = objc_getAssociatedObject(sub, @selector(sbSegmentData)); + if (!data || data.count < 3) continue; + + CGFloat startFrac = [data[0] floatValue]; + CGFloat endFrac = [data[1] floatValue]; + BOOL isPoi = [data[2] boolValue]; + + CGFloat x = startFrac * barWidth; + CGFloat w = (endFrac - startFrac) * barWidth; + if (isPoi) { w = SBPoiMarkerWidth; x = MAX(0, x - SBPoiMarkerXOffset); } + else if (w < SBMarkerMinWidth) w = SBMarkerMinWidth; + + sub.frame = CGRectMake(x, referenceView.frame.origin.y, w, referenceView.frame.size.height); + } + + // Keep the segment markers above YouTube's decoration views, and the scrubber + // dot above the markers, so the stack stays track < markers < dot. YouTube + // rebuilds its own decorations on top of ours on each layout (splitting the + // progress view per chapter), which would otherwise bury the markers. + NSArray *subs = self.subviews; + NSMutableArray *markers = [NSMutableArray array]; + UIView *scrubberDot = nil; + @try { + scrubberDot = [self valueForKey:@"_scrubberCircle"]; + } @catch (id ex) {} + for (UIView *sub in subs) { + if (sub.tag == SBSegmentMarkerTag) { + [markers addObject:sub]; + } else if ([sub isKindOfClass:%c(YTPlayerBarScrubberDotDecorationView)]) { + if (!scrubberDot) scrubberDot = sub; + } + } + if (markers.count == 0) return; + + // Desired top group, front-most last: the markers followed by the dot. Reorder + // only when the subview tail doesn't already match it, so a settled layout is a + // no-op and doesn't trigger a fresh layout pass on every runloop cycle. + NSMutableArray *desiredTail = [markers mutableCopy]; + if (scrubberDot) [desiredTail addObject:scrubberDot]; + BOOL settled = subs.count >= desiredTail.count; + for (NSUInteger i = 0; settled && i < desiredTail.count; i++) { + if (subs[subs.count - desiredTail.count + i] != desiredTail[i]) settled = NO; + } + if (settled) return; + + for (UIView *marker in markers) [self bringSubviewToFront:marker]; + if (scrubberDot) [self bringSubviewToFront:scrubberDot]; +} + +%end + +// YTWatchFloatingMiniplayerProgressBarView - miniplayer +%hook YTWatchFloatingMiniplayerProgressBarView +- (void)layoutSubviews { + %orig; + CGFloat barWidth = self.bounds.size.width; + + for (UIView *sub in self.superview.subviews) { + if (sub.tag != SBSegmentMarkerTag) continue; + NSArray *data = objc_getAssociatedObject(sub, @selector(sbSegmentData)); + if (!data || data.count < 3) continue; + + CGFloat startFrac = [data[0] floatValue]; + CGFloat endFrac = [data[1] floatValue]; + BOOL isPoi = [data[2] boolValue]; + + CGFloat x = startFrac * barWidth; + CGFloat w = (endFrac - startFrac) * barWidth; + if (isPoi) { w = SBPoiMarkerWidth; x = MAX(0, x - SBPoiMarkerXOffset); } + else if (w < SBMarkerMinWidth) w = SBMarkerMinWidth; + + sub.frame = CGRectMake(x, self.frame.origin.y, w, self.bounds.size.height); + } +} +%end + +#pragma mark - YTPlayerViewController Hook (Notification Observer) + +%group SBObserver +%hook YTPlayerViewController + +- (void)viewDidLoad { + %orig; + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SBSegmentsDidLoad" object:self]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sbSegmentsDidLoad:) + name:@"SBSegmentsDidLoad" + object:self]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SBSegmentsDidLoad" object:self]; + %orig; +} + +%new +- (void)sbSegmentsDidLoad:(NSNotification *)notification { + [self sbRefreshMarkers:notification.userInfo[@"segments"]]; +} + +// Re-resolves the current player bar view fresh and re-creates segment markers +// on it. Called whenever the player layout might have changed (initial load, +// fullscreen enter/exit, viewport resize) so markers always live on the +// currently-visible bar instead of an old detached one. +%new +- (void)sbRefreshMarkers:(NSArray *)segments { + if (!IS_ENABLED(SBSegmentsInPlayer) && !IS_ENABLED(SBSegmentsInMiniPlayer) && !IS_ENABLED(SBSegmentsInFeed)) return; + if (!segments) segments = self.sbSegments; + + CGFloat totalTime = [self currentVideoTotalMediaTime]; + if (totalTime <= 0) return; + CGFloat barWidth; + CGFloat h; + CGFloat y; + // Explicitly nil so the surface branches can rely on "unassigned == nil" + // when deciding marker ordering, without depending on ARC zero-init. + UIView *mainView = nil; + UIView *scrubberDot = nil; + UIView *referenceView = nil; + + if ([self.parentViewController isKindOfClass:%c(YTWatchFloatingMiniplayerViewController)] && IS_ENABLED(SBSegmentsInMiniPlayer)) { + YTWatchFloatingMiniplayerViewController *miniplayercontroller = (YTWatchFloatingMiniplayerViewController *)self.parentViewController; + YTWatchFloatingMiniplayerWithPersistentControlsView *controlsview = (YTWatchFloatingMiniplayerWithPersistentControlsView *)miniplayercontroller.view; + + for (UIView *sub in controlsview.subviews) { + for (UIView *sub2 in sub.subviews) { + if ([sub2 isKindOfClass:%c(YTWatchFloatingMiniplayerProgressBarView)]) { + referenceView = sub2; + break; + } + } + if (referenceView) break; + } + + mainView = referenceView.superview; + + // Remove old markers + for (UIView *sub in [mainView.subviews copy]) { + if (sub.tag == SBSegmentMarkerTag) [sub removeFromSuperview]; + } + if (!segments || segments.count == 0) return; + + barWidth = referenceView.bounds.size.width; + h = referenceView.bounds.size.height; + y = referenceView.frame.origin.y; + } else if ([[self activeVideoPlayerOverlay] isKindOfClass:%c(YTMainAppVideoPlayerOverlayViewController)] && IS_ENABLED(SBSegmentsInPlayer)) { + YTMainAppVideoPlayerOverlayViewController *overlay = [self activeVideoPlayerOverlay]; + YTPlayerBarController *barController = [overlay playerBarController]; + YTInlinePlayerBarContainerView *containerView = barController.playerBar; + UIView *playerBar; + + for (UIView *subview in containerView.subviews) { + if ([subview isKindOfClass:%c(YTModularPlayerBarView)]) { + playerBar = subview; + mainView = subview; + break; + } + } + if (!playerBar) return; + + // Remove old markers + for (UIView *sub in [playerBar.subviews copy]) { + if (sub.tag == SBSegmentMarkerTag) [sub removeFromSuperview]; + } + + if (!segments || segments.count == 0) return; + + barWidth = playerBar.bounds.size.width; + if (barWidth <= 0) return; + + @try { + scrubberDot = [playerBar valueForKey:@"_scrubberCircle"]; + } @catch (id ex) {} + // Find reference track view for Y position and height + for (UIView *sub in playerBar.subviews) { + if ([sub isKindOfClass:%c(YTPlayerBarRectangleDecorationView)]) { + if (!referenceView) referenceView = sub; + } else if ([sub isKindOfClass:%c(YTPlayerBarProgressDecorationView)]) { + if (!referenceView) referenceView = sub; + } else if ([sub isKindOfClass:%c(YTPlayerBarScrubberDotDecorationView)]) { + if (!scrubberDot) scrubberDot = sub; + } + if (referenceView && scrubberDot) break; + } + h = referenceView.bounds.size.height; + y = referenceView.frame.origin.y; + } else if ([[self activeVideoPlayerOverlay] isKindOfClass:%c(YTInlineMutedPlaybackPlayerOverlayViewController)] && IS_ENABLED(SBSegmentsInFeed)) { + YTInlineMutedPlaybackPlayerOverlayViewController *viewcon = [self activeVideoPlayerOverlay]; + YTInlineMutedPlaybackPlayerOverlayView *view = (YTInlineMutedPlaybackPlayerOverlayView *)viewcon.view; + UIView *scrub; + UIView *playerBar; + for (UIView *sub in view.subviews) { + if ([sub isKindOfClass:%c(YTInlineMutedPlaybackScrubberView)]) { + scrub = sub; + mainView = sub; + break; + } + } + + if (!segments || segments.count == 0) return; + + for (UIView *sub in scrub.subviews) { + if ([sub isKindOfClass:%c(YTPlayerBarMarkerView)] && sub.frame.origin.y != 0) { + playerBar = sub; + } else if ([sub isKindOfClass:%c(YTModularPlayerBarView)] && sub.frame.origin.y != 0) { + playerBar = sub; + mainView = sub; + } else if ([sub isKindOfClass:%c(YTInlineMutedPlaybackScrubbingSlider)]) { + if ([sub.accessibilityIdentifier isEqualToString:@"id.player.scrubber.slider"]) { + scrubberDot = sub; + } + } + if (playerBar && scrubberDot) break; + } + + if (!playerBar) return; + + // Remove old markers + for (UIView *sub in [mainView.subviews copy]) { + if (sub.tag == SBSegmentMarkerTag) [sub removeFromSuperview]; + } + + if ([mainView isKindOfClass:%c(YTModularPlayerBarView)]) { + @try { + scrubberDot = [mainView valueForKey:@"_scrubberCircle"]; + } @catch (id ex) {} + // Find reference track view for Y position and height + for (UIView *sub in mainView.subviews) { + if ([sub isKindOfClass:%c(YTPlayerBarRectangleDecorationView)]) { + if (!referenceView) referenceView = sub; + } else if ([sub isKindOfClass:%c(YTPlayerBarProgressDecorationView)]) { + if (!referenceView) referenceView = sub; + } else if ([sub isKindOfClass:%c(YTPlayerBarScrubberDotDecorationView)]) { + if (!scrubberDot) scrubberDot = sub; + } + if (referenceView && scrubberDot) break; + } + barWidth = playerBar.bounds.size.width; + h = referenceView.bounds.size.height; + y = referenceView.frame.origin.y; + } else { + barWidth = playerBar.bounds.size.width; + h = playerBar.bounds.size.height; + y = playerBar.frame.origin.y; + } + } else { + return; + } + + if (!self.sbEnabledForVideo) return; + + for (SBSegment *segment in segments) { + SBSegmentAction action = [segment configuredAction]; + if (action == SBSegmentActionDisable) continue; + + CGFloat startFrac = segment.startTime / totalTime; + CGFloat endFrac; + if (segment.endTime > totalTime) { + endFrac = 1.0; + } else { + endFrac = segment.endTime / totalTime; + } + CGFloat x = startFrac * barWidth; + CGFloat w = (endFrac - startFrac) * barWidth; + + // poi_highlight is a point, not a range — give it fixed width + BOOL isPoi = [segment.category isEqualToString:@"poi_highlight"]; + if (isPoi) { + w = SBPoiMarkerWidth; + x = MAX(0, x - SBPoiMarkerXOffset); + } else { + if (w < SBMarkerMinWidth) w = SBMarkerMinWidth; + } + + UIView *marker = [[UIView alloc] initWithFrame:CGRectMake(x, y, w, h)]; + marker.backgroundColor = [segment segmentColor]; + marker.userInteractionEnabled = NO; + marker.tag = SBSegmentMarkerTag; + objc_setAssociatedObject(marker, @selector(sbSegmentData), @[@(startFrac), @(endFrac), @(isPoi)], OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Insert above the track (main player bar) so the marker paints on it; + // the dot is re-fronted after the loop. Miniplayer/feed keep dot-relative + // or top ordering. + if (referenceView && referenceView.superview == mainView) { + [mainView insertSubview:marker aboveSubview:referenceView]; + } else if (scrubberDot && scrubberDot.superview == mainView) { + [mainView insertSubview:marker belowSubview:scrubberDot]; + } else { + [mainView addSubview:marker]; + [mainView bringSubviewToFront:marker]; + } + } + if (scrubberDot) { + [mainView bringSubviewToFront:scrubberDot]; + } +} + +// On fullscreen enter/exit and other layout transitions, YouTube swaps the +// player bar instance. Re-render markers on the current bar (matches +// iSponsorBlock's approach). Deferred to the next runloop so YouTube's own +// layout pass finishes first — otherwise the new bar's bounds.size.width can +// still be 0 and the refresh early-returns without inserting markers. +- (void)setPlayerViewLayout:(NSInteger)layout { + %orig; + __weak typeof(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf sbRefreshMarkers:nil]; + sbUpdateOverlayInsetForPivotBar(); + }); +} + +- (void)updateViewportSizeProvider { + %orig; + __weak typeof(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf sbRefreshMarkers:nil]; + sbUpdateOverlayInsetForPivotBar(); + }); +} + +%end +%end + +#pragma mark - Constructor + +%ctor { + %init; + %init(SBObserver); +} \ No newline at end of file diff --git a/Files/Tabbar.x b/Files/Tabbar.x index c12a9b49..61f7ed5f 100644 --- a/Files/Tabbar.x +++ b/Files/Tabbar.x @@ -1,40 +1,239 @@ #import "Headers.h" +#import + +// Tab icons +%hook YTAppPivotBarItemStyle +- (UIImage *)pivotBarItemIconImageWithIconType:(int)type color:(UIColor *)color useNewIcons:(BOOL)isNew selected:(BOOL)isSelected { + if (type == 1 || type == 2 || type == 3 || type == 4 || type == 5 || type == 6 || type == 7 || type == 8 || type == 9 || type == 10 || type == 11 || type == 12 || type == 13 || type == 14 || type == 15 || type == 16 || type == 17) { + NSString *imageName; + if (type == 1) imageName = isSelected ? @"icons/history_selected" : @"icons/history"; + else if (type == 2) imageName = isSelected ? @"icons/gaming_selected" : @"icons/gaming"; + else if (type == 3) imageName = isSelected ? @"icons/sports_selected" : @"icons/sports"; + else if (type == 4) imageName = isSelected ? @"icons/noti_selected" : @"icons/noti"; + else if (type == 5) imageName = isSelected ? @"icons/news_selected" : @"icons/news"; + else if (type == 6) imageName = isSelected ? @"icons/music_selected" : @"icons/music"; + else if (type == 7) imageName = isSelected ? @"icons/watchlater_selected" : @"icons/watchlater"; + else if (type == 8) imageName = isSelected ? @"icons/playlist_selected" : @"icons/playlist"; + else if (type == 9) imageName = isSelected ? @"icons/like_selected" : @"icons/like"; + else if (type == 10) imageName = isSelected ? @"icons/live_selected" : @"icons/live"; + else if (type == 11) imageName = isSelected ? @"icons/post_selected" : @"icons/post"; + else if (type == 12) imageName = isSelected ? @"icons/video_selected" : @"icons/video"; + else if (type == 13) imageName = isSelected ? @"icons/movie_selected" : @"icons/movie"; + else if (type == 14) imageName = isSelected ? @"icons/course_selected" : @"icons/course"; + else if (type == 15) imageName = isSelected ? @"icons/minigame_selected" : @"icons/minigame"; + else if (type == 16) imageName = isSelected ? @"icons/fashion_selected" : @"icons/fashion"; + else if (type == 17) imageName = isSelected ? @"icons/learning_selected" : @"icons/learning"; + YTAssetLoader *al = [[%c(YTAssetLoader) alloc] initWithBundle:YouModBundle()]; + return [al imageNamed:imageName]; + } + return %orig; +} +%end + +static NSString *ymPivotIDForTabID(NSString *tabID) { + if ([tabID isEqualToString:@"home"]) return @"FEwhat_to_watch"; + if ([tabID isEqualToString:@"shorts"]) return @"FEshorts"; + if ([tabID isEqualToString:@"create"]) return @"FEuploads"; + if ([tabID isEqualToString:@"subscriptions"]) return @"FEsubscriptions"; + if ([tabID isEqualToString:@"library"]) return @"FElibrary"; + if ([tabID isEqualToString:@"history"]) return [%c(YTIBrowseRequest) browseIDForHistory]; + if ([tabID isEqualToString:@"gaming"]) return [%c(YTIBrowseRequest) browseIDForGamingDestination]; + if ([tabID isEqualToString:@"sports"]) return [%c(YTIBrowseRequest) browseIDForSportsDestination]; + if ([tabID isEqualToString:@"notifications"]) return [%c(YTIBrowseRequest) browseIDForNotificationsInbox]; + if ([tabID isEqualToString:@"news"]) return @"UCYfdidRxbB8Qhf0Nx7ioOYw"; // FEnews_destination + if ([tabID isEqualToString:@"music"]) return @"UC-9-kyTW8ZkZNDHQJ6FgpwQ"; + if ([tabID isEqualToString:@"watchlater"]) return @"VLWL"; + if ([tabID isEqualToString:@"playlist"]) return @"FEplaylist_aggregation"; + if ([tabID isEqualToString:@"like"]) return @"VLLL"; + if ([tabID isEqualToString:@"live"]) return @"UC4R8DWoMoI7CAwX8_LjQHig"; + if ([tabID isEqualToString:@"post"]) return @"FEpost_home"; + if ([tabID isEqualToString:@"video"]) return @"UC3qapbGAd2-S75NkBY3XWww"; + if ([tabID isEqualToString:@"movie"]) return @"FEstorefront"; + if ([tabID isEqualToString:@"course"]) return @"FEcourses"; + if ([tabID isEqualToString:@"minigame"]) return @"FEmini_app_destination"; + if ([tabID isEqualToString:@"fashion"]) return @"UCrpQ4p1Ql_hG8rKXIKM1MOQ"; + if ([tabID isEqualToString:@"learning"]) return @"UCtFRv9O2AHqOZjjynzrv-xg"; + return nil; +} + +static NSInteger ymIconTypeForTabID(NSString *tabID) { + if ([tabID isEqualToString:@"history"]) return 1; + if ([tabID isEqualToString:@"gaming"]) return 2; + if ([tabID isEqualToString:@"sports"]) return 3; + if ([tabID isEqualToString:@"notifications"]) return 4; + if ([tabID isEqualToString:@"news"]) return 5; + if ([tabID isEqualToString:@"music"]) return 6; + if ([tabID isEqualToString:@"watchlater"]) return 7; + if ([tabID isEqualToString:@"playlist"]) return 8; + if ([tabID isEqualToString:@"like"]) return 9; + if ([tabID isEqualToString:@"live"]) return 10; + if ([tabID isEqualToString:@"post"]) return 11; + if ([tabID isEqualToString:@"video"]) return 12; + if ([tabID isEqualToString:@"movie"]) return 13; + if ([tabID isEqualToString:@"course"]) return 14; + if ([tabID isEqualToString:@"minigame"]) return 15; + if ([tabID isEqualToString:@"fashion"]) return 16; + if ([tabID isEqualToString:@"learning"]) return 17; + return 0; +} + +static NSString *ymTitleForTabID(NSString *tabID) { + if ([tabID isEqualToString:@"history"]) return LOC(@"HISTORY_TAB"); + if ([tabID isEqualToString:@"gaming"]) return LOC(@"GAMING_TAB"); + if ([tabID isEqualToString:@"sports"]) return LOC(@"SPORTS_TAB"); + if ([tabID isEqualToString:@"notifications"]) return LOC(@"NOTI_TAB"); + if ([tabID isEqualToString:@"news"]) return LOC(@"NEWS_TAB"); + if ([tabID isEqualToString:@"music"]) return LOC(@"MUSIC_TAB"); + if ([tabID isEqualToString:@"watchlater"]) return LOC(@"WATCH_LATER_TAB"); + if ([tabID isEqualToString:@"playlist"]) return LOC(@"PLAYLIST_TAB"); + if ([tabID isEqualToString:@"like"]) return LOC(@"LIKE_TAB"); + if ([tabID isEqualToString:@"live"]) return LOC(@"LIVE_TAB"); + if ([tabID isEqualToString:@"post"]) return LOC(@"POST_TAB"); + if ([tabID isEqualToString:@"video"]) return LOC(@"VIDEO_TAB"); + if ([tabID isEqualToString:@"movie"]) return LOC(@"MOVIE_TAB"); + if ([tabID isEqualToString:@"course"]) return LOC(@"COURSE_TAB"); + if ([tabID isEqualToString:@"minigame"]) return LOC(@"MINIGAME_TAB"); + if ([tabID isEqualToString:@"fashion"]) return LOC(@"FASHION_TAB"); + if ([tabID isEqualToString:@"learning"]) return LOC(@"LEARNING_TAB"); + return nil; +} %hook YTPivotBarView - (void)setRenderer:(YTIPivotBarRenderer *)renderer { - NSMutableArray *items = [renderer itemsArray]; - NSMutableIndexSet *indicesToRemove = [NSMutableIndexSet indexSet]; - // Loop through every item in the bar - for (NSUInteger i = 0; i < items.count; i++) { - YTIPivotBarSupportedRenderers *item = items[i]; - NSString *pID = [[item pivotBarItemRenderer] pivotIdentifier]; - NSString *pID2 = [[item pivotBarIconOnlyItemRenderer] pivotIdentifier]; - if ([pID isEqualToString:@"FEwhat_to_watch"] && IS_ENABLED(HideHomeTab)) { - [indicesToRemove addIndex:i]; - } - if ([pID isEqualToString:@"FEshorts"] && IS_ENABLED(HideShortsTab)) { - [indicesToRemove addIndex:i]; - } - if ([pID2 isEqualToString:@"FEuploads"] && IS_ENABLED(HideCreateButton)) { - [indicesToRemove addIndex:i]; + NSArray *savedOrder = [[NSUserDefaults standardUserDefaults] arrayForKey:TabOrder]; + if (savedOrder.count > 0) { + NSMutableArray *items = [renderer itemsArray]; + + // Build lookup: pivotIdentifier -> renderer item + NSMutableDictionary *lookup = [NSMutableDictionary dictionary]; + for (YTIPivotBarSupportedRenderers *item in items) { + NSString *pID = [[item pivotBarItemRenderer] pivotIdentifier]; + NSString *pID2 = [[item pivotBarIconOnlyItemRenderer] pivotIdentifier]; + if (pID) lookup[pID] = item; + if (pID2) lookup[pID2] = item; } - if ([pID isEqualToString:@"FEsubscriptions"] && IS_ENABLED(HideSubscriptTab)) { - [indicesToRemove addIndex:i]; + + // Build ordered array from saved data + NSMutableArray *ordered = [NSMutableArray array]; + for (NSDictionary *entry in savedOrder) { + NSString *tabID = entry[@"id"]; + BOOL enabled = [entry[@"enabled"] boolValue]; + if (!enabled) continue; + + NSString *pivotID = ymPivotIDForTabID(tabID); + if (!pivotID) continue; + + YTIPivotBarSupportedRenderers *existing = lookup[pivotID]; + if (existing) { + [ordered addObject:existing]; + } else { + // Custom tab not in YouTube's default items — create it + NSInteger iconType = ymIconTypeForTabID(tabID); + NSString *title = ymTitleForTabID(tabID); + if (iconType > 0 && title) { + YTIPivotBarSupportedRenderers *newTab = [%c(YTIPivotBarRenderer) pivotSupportedRenderersWithBrowseId:pivotID title:title iconType:iconType]; + if (newTab) [ordered addObject:newTab]; + } + } } + // Replace items with ordered set + [items removeAllObjects]; + [items addObjectsFromArray:ordered]; } - // Remove them all at once so the layout doesn't break - [items removeObjectsAtIndexes:indicesToRemove]; %orig(renderer); } %end // Hide Tab Bar Indicators %hook YTPivotBarIndicatorView -- (void)setFillColor:(id)arg1 { IS_ENABLED(HideTabIndi) ? %orig([UIColor clearColor]) : %orig; } -- (void)setBorderColor:(id)arg1 { IS_ENABLED(HideTabIndi) ? %orig([UIColor clearColor]) : %orig; } +- (void)setFillColor:(UIColor *)arg1 { + UIColor *temp = IS_ENABLED(HideTabIndi) ? [UIColor clearColor] : arg1; + %orig(temp); +} +- (void)setBorderColor:(UIColor *)arg1 { + UIColor *temp = IS_ENABLED(HideTabIndi) ? [UIColor clearColor] : arg1; + %orig(temp); +} %end -// Hide Tab Labels +static NSString *YMExtractYouTubeVideoID(NSString *urlString) { + if (!urlString || urlString.length == 0) return nil; + + NSString *cleanString = [urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if (cleanString.length == 11 && ![cleanString containsString:@"/"] && ![cleanString containsString:@"?"]) { + return cleanString; + } + + NSString *extractedID = nil; + NSURL *url = [NSURL URLWithString:cleanString]; + + if (url) { + if ([url.host containsString:@"youtu.be"]) { + NSString *path = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""]; + if (path.length >= 11) { + extractedID = [path substringToIndex:11]; + } + } else if ([url.host containsString:@"youtube.com"]) { + if ([url.path containsString:@"/shorts/"] || [url.path containsString:@"/live/"] || [url.path containsString:@"/clip/"]) { + NSString *lastPath = [url.path lastPathComponent]; + if ([lastPath containsString:@"?"]) { + lastPath = [[lastPath componentsSeparatedByString:@"?"] firstObject]; + } + if (lastPath.length >= 11) { + extractedID = [lastPath substringToIndex:11]; + } + } else { + NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + for (NSURLQueryItem *item in components.queryItems) { + if ([item.name isEqualToString:@"v"] && item.value.length >= 11) { + extractedID = [item.value substringToIndex:11]; + break; + } + } + } + } + } + + if (!extractedID) { + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?:v=|\\/(?:shorts|live|clip)\\Timer|youtu\\.be\\/)([a-zA-Z0-9_-]{11})" + options:NSRegularExpressionCaseInsensitive + error:&error]; + NSTextCheckingResult *match = [regex firstMatchInString:cleanString options:0 range:NSMakeRange(0, cleanString.length)]; + if (match && match.numberOfRanges > 1) { + extractedID = [cleanString substringWithRange:[match rangeAtIndex:1]]; + } + } + + return (extractedID && extractedID.length == 11) ? extractedID : nil; +} + +static NSString *gLastOpenedVideoID = nil; + +static void YMOpenLinkFromClipboard(UIViewController *presentingVC, BOOL isRuntime) { + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + + if (![pasteboard hasStrings]) return; + + NSString *rawString = [pasteboard.string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *videoID = YMExtractYouTubeVideoID(rawString); + + if (!videoID || videoID.length == 0) return; + + if (gLastOpenedVideoID && [gLastOpenedVideoID isEqualToString:videoID] && IS_ENABLED(AutoOpenLink) && !isRuntime) return; + + NSString *schemeURLString = [NSString stringWithFormat:@"youtube://%@", videoID]; + NSURL *targetURL = [NSURL URLWithString:schemeURLString]; + + if ([[UIApplication sharedApplication] canOpenURL:targetURL]) { + gLastOpenedVideoID = [videoID copy]; + [[UIApplication sharedApplication] openURL:targetURL options:@{} completionHandler:nil]; + } +} + +static BOOL isGestureRegistered = NO; +// Hide Tab Labels + long-press on the first tab to open Manage Tabs %hook YTPivotBarItemView - (void)setRenderer:(YTIPivotBarRenderer *)renderer { %orig; @@ -42,18 +241,116 @@ [self.navigationButton setTitle:@"" forState:UIControlStateNormal]; [self.navigationButton setSizeWithPaddingAndInsets:NO]; } + // Attach long-press gesture once per view; the action handler checks the + // current pivotIdentifier at fire time, so cell reuse / pivot bar refresh + // can rebind the same view to a different tab safely. + static const void *kYMContextMenuKey = &kYMContextMenuKey; + if (!objc_getAssociatedObject(self, kYMContextMenuKey) && !isGestureRegistered) { + UIContextMenuInteraction *interaction = [[UIContextMenuInteraction alloc] initWithDelegate:(id)self]; + [self addInteraction:interaction]; + objc_setAssociatedObject(self, kYMContextMenuKey, interaction, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + isGestureRegistered = YES; + } +} +%new +- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location { + return [UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:nil actionProvider:^UIMenu * _Nullable(NSArray * _Nonnull suggestedActions) { + UIAction *tabBarAction = [UIAction actionWithTitle:LOC(@"MANAGE_TABS") + image:[UIImage systemImageNamed:@"dock.rectangle"] + identifier:nil + handler:^(__kindof UIAction * _Nonnull action) { + YMPresentTabOrderModally(nil); + }]; + UIAction *openLinkAction = [UIAction actionWithTitle:LOC(@"OPEN_LINK") + image:[UIImage systemImageNamed:@"link"] + identifier:nil + handler:^(__kindof UIAction * _Nonnull action) { + UIViewController *topVC = YouModTopViewController(nil); + YMOpenLinkFromClipboard(topVC, YES); + }]; + return [UIMenu menuWithTitle:@"" children:@[tabBarAction, openLinkAction]]; + }]; } %end // Startup Tab -BOOL isTabSelected = NO; +static BOOL isTabSelected = NO; %hook YTPivotBarViewController - (void)viewDidAppear:(BOOL)animated { %orig; + sbUpdateOverlayInsetForPivotBar(); + if (IS_ENABLED(ShortsOnly) && !isTabSelected) { + [self selectItemWithPivotIdentifier:@"FEshorts"]; + isTabSelected = YES; + return; + } if (!isTabSelected) { - NSArray *pivotIdentifiers = @[@"FEwhat_to_watch", @"FEshorts", @"FEsubscriptions", @"FElibrary"]; - [self selectItemWithPivotIdentifier:pivotIdentifiers[INTFORVAL(DefaultTab)]]; // Set int here + // Build pivot identifiers from enabled tabs (skip Create — matches Settings.x segment logic) + NSMutableArray *pivotIdentifiers = [NSMutableArray array]; + NSArray *savedOrder = [[NSUserDefaults standardUserDefaults] arrayForKey:TabOrder]; + if (savedOrder.count > 0) { + for (NSDictionary *entry in savedOrder) { + if (![entry[@"enabled"] boolValue]) continue; + NSString *tabID = entry[@"id"]; + if ([tabID isEqualToString:@"create"]) continue; + NSString *pivot = ymPivotIDForTabID(tabID); + if (pivot) [pivotIdentifiers addObject:pivot]; + } + } + if (pivotIdentifiers.count == 0) { + pivotIdentifiers = [@[@"FEwhat_to_watch", @"FEshorts", @"FEsubscriptions", @"FElibrary"] mutableCopy]; + } + + NSInteger tabIndex = INTFORVAL(DefaultTab); + if (tabIndex < 0) tabIndex = 0; + if (tabIndex >= (NSInteger)pivotIdentifiers.count) tabIndex = MAX(0, (NSInteger)pivotIdentifiers.count - 1); + [self selectItemWithPivotIdentifier:pivotIdentifiers[tabIndex]]; isTabSelected = YES; } } -%end \ No newline at end of file +// Translucent tab bar +- (BOOL)isFrostedPivotBarPermitted { + if (INTFORVAL(UseFrostedTabBar) == 1) { + return YES; + } else if (INTFORVAL(UseFrostedTabBar) == 2) { + return NO; + } + return %orig; +} +%end + +%hook YTAppDelegate +- (void)appDidBecomeActive { + %orig; + if (IS_ENABLED(AutoOpenLink)) { + UIViewController *topVC = YouModTopViewController(nil); + YMOpenLinkFromClipboard(topVC, NO); + } +} +%end + +// Recompute SB overlay safe-area inset whenever YouTube shows or hides the pivot bar +// (e.g. entering/exiting fullscreen player). This keeps the SponsorBlock skip pill +// and download progress pill anchored above the tabbar when visible, and at the +// device safe-area bottom when the tabbar is hidden. +%hook YTAppViewController +- (void)hidePivotBar { + %orig; + sbUpdateOverlayInsetForPivotBar(); +} +- (void)showPivotBar { + %orig; + sbUpdateOverlayInsetForPivotBar(); +} +%end + +%hook YTAppViewControllerImpl +- (void)hidePivotBar { + %orig; + sbUpdateOverlayInsetForPivotBar(); +} +- (void)showPivotBar { + %orig; + sbUpdateOverlayInsetForPivotBar(); +} +%end diff --git a/Files/ThumbnailPreview.x b/Files/ThumbnailPreview.x new file mode 100644 index 00000000..c7824c60 --- /dev/null +++ b/Files/ThumbnailPreview.x @@ -0,0 +1,213 @@ +#import +#import "Headers.h" + +static BOOL isPad() { + return UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad; +} + +@implementation YouModThumbnailViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + UIView *bgContainer = [[UIView alloc] init]; + bgContainer.translatesAutoresizingMaskIntoConstraints = NO; + bgContainer.clipsToBounds = YES; + bgContainer.backgroundColor = [UIColor clearColor]; + [self.view addSubview:bgContainer]; + + UIImageView *bgImageView = [[UIImageView alloc] initWithImage:self.thumbnailImage]; + bgImageView.contentMode = UIViewContentModeScaleAspectFill; + bgImageView.translatesAutoresizingMaskIntoConstraints = NO; + [bgContainer addSubview:bgImageView]; + + UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemMaterial]; + UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; + blurView.translatesAutoresizingMaskIntoConstraints = NO; + [bgContainer addSubview:blurView]; + + self.scrollView = [[UIScrollView alloc] init]; + self.scrollView.delegate = self; + self.scrollView.clipsToBounds = YES; + self.scrollView.minimumZoomScale = 1.0; + self.scrollView.maximumZoomScale = 4.0; + self.scrollView.showsVerticalScrollIndicator = NO; + self.scrollView.showsHorizontalScrollIndicator = NO; + self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; + [bgContainer addSubview:self.scrollView]; + + self.imageView = [[UIImageView alloc] initWithImage:self.thumbnailImage]; + self.imageView.contentMode = UIViewContentModeScaleAspectFit; + self.imageView.translatesAutoresizingMaskIntoConstraints = NO; + [self.scrollView addSubview:self.imageView]; + + UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)]; + doubleTap.numberOfTapsRequired = 2; + [self.scrollView addGestureRecognizer:doubleTap]; + + UIImageSymbolConfiguration *iconConfig = [UIImageSymbolConfiguration configurationWithPointSize:22 weight:UIImageSymbolWeightSemibold]; + UIButton *closeBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [closeBtn setImage:[UIImage systemImageNamed:@"xmark" withConfiguration:iconConfig] forState:UIControlStateNormal]; + closeBtn.tintColor = [UIColor whiteColor]; + closeBtn.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.4]; + closeBtn.layer.cornerRadius = 25; + closeBtn.translatesAutoresizingMaskIntoConstraints = NO; + [closeBtn addTarget:self action:@selector(closeTapped) forControlEvents:UIControlEventTouchUpInside]; + [bgContainer addSubview:closeBtn]; + + UIButton *moreBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [moreBtn setImage:[UIImage systemImageNamed:@"ellipsis" withConfiguration:iconConfig] forState:UIControlStateNormal]; + moreBtn.tintColor = [UIColor whiteColor]; + moreBtn.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.4]; + moreBtn.layer.cornerRadius = 25; + moreBtn.translatesAutoresizingMaskIntoConstraints = NO; + [moreBtn addTarget:self action:@selector(moreTapped:) forControlEvents:UIControlEventTouchUpInside]; + [bgContainer addSubview:moreBtn]; + + NSMutableArray *constraints = [NSMutableArray array]; + + if (isPad()) { + self.view.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5]; + bgContainer.layer.cornerRadius = 16; + + [constraints addObject:[bgContainer.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor]]; + [constraints addObject:[bgContainer.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor]]; + [constraints addObject:[bgContainer.widthAnchor constraintEqualToAnchor:bgContainer.heightAnchor]]; + [constraints addObject:[bgContainer.widthAnchor constraintLessThanOrEqualToAnchor:self.view.safeAreaLayoutGuide.widthAnchor multiplier:0.9]]; + [constraints addObject:[bgContainer.heightAnchor constraintLessThanOrEqualToAnchor:self.view.safeAreaLayoutGuide.heightAnchor multiplier:0.9]]; + + NSLayoutConstraint *expandBox = [bgContainer.widthAnchor constraintEqualToConstant:2000]; + expandBox.priority = UILayoutPriorityDefaultHigh; + [constraints addObject:expandBox]; + + [constraints addObjectsFromArray:@[ + [closeBtn.topAnchor constraintEqualToAnchor:bgContainer.topAnchor constant:12], + [closeBtn.leadingAnchor constraintEqualToAnchor:bgContainer.leadingAnchor constant:12], + [moreBtn.topAnchor constraintEqualToAnchor:bgContainer.topAnchor constant:12], + [moreBtn.trailingAnchor constraintEqualToAnchor:bgContainer.trailingAnchor constant:-12], + ]]; + } else { + self.view.backgroundColor = [UIColor clearColor]; + bgContainer.layer.cornerRadius = 0; + + [constraints addObjectsFromArray:@[ + [bgContainer.topAnchor constraintEqualToAnchor:self.view.topAnchor], + [bgContainer.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], + [bgContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], + [bgContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor] + ]]; + + [constraints addObjectsFromArray:@[ + [closeBtn.topAnchor constraintEqualToAnchor:bgContainer.safeAreaLayoutGuide.topAnchor constant:12], + [closeBtn.leadingAnchor constraintEqualToAnchor:bgContainer.safeAreaLayoutGuide.leadingAnchor constant:12], + [moreBtn.topAnchor constraintEqualToAnchor:bgContainer.safeAreaLayoutGuide.topAnchor constant:12], + [moreBtn.trailingAnchor constraintEqualToAnchor:bgContainer.safeAreaLayoutGuide.trailingAnchor constant:-12], + ]]; + } + + [constraints addObjectsFromArray:@[ + [bgImageView.topAnchor constraintEqualToAnchor:bgContainer.topAnchor], + [bgImageView.bottomAnchor constraintEqualToAnchor:bgContainer.bottomAnchor], + [bgImageView.leadingAnchor constraintEqualToAnchor:bgContainer.leadingAnchor], + [bgImageView.trailingAnchor constraintEqualToAnchor:bgContainer.trailingAnchor], + [blurView.topAnchor constraintEqualToAnchor:bgContainer.topAnchor], + [blurView.bottomAnchor constraintEqualToAnchor:bgContainer.bottomAnchor], + [blurView.leadingAnchor constraintEqualToAnchor:bgContainer.leadingAnchor], + [blurView.trailingAnchor constraintEqualToAnchor:bgContainer.trailingAnchor] + ]]; + + [constraints addObjectsFromArray:@[ + [self.scrollView.topAnchor constraintEqualToAnchor:bgContainer.topAnchor], + [self.scrollView.bottomAnchor constraintEqualToAnchor:bgContainer.bottomAnchor], + [self.scrollView.leadingAnchor constraintEqualToAnchor:bgContainer.leadingAnchor], + [self.scrollView.trailingAnchor constraintEqualToAnchor:bgContainer.trailingAnchor] + ]]; + + [constraints addObjectsFromArray:@[ + [self.imageView.topAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.topAnchor], + [self.imageView.bottomAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.bottomAnchor], + [self.imageView.leadingAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.leadingAnchor], + [self.imageView.trailingAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.trailingAnchor], + [self.imageView.widthAnchor constraintEqualToAnchor:self.scrollView.frameLayoutGuide.widthAnchor], + [self.imageView.heightAnchor constraintEqualToAnchor:self.scrollView.frameLayoutGuide.heightAnchor] + ]]; + + [constraints addObjectsFromArray:@[ + [closeBtn.widthAnchor constraintEqualToConstant:50], + [closeBtn.heightAnchor constraintEqualToConstant:50], + [moreBtn.widthAnchor constraintEqualToConstant:50], + [moreBtn.heightAnchor constraintEqualToConstant:50], + ]]; + + [NSLayoutConstraint activateConstraints:constraints]; + + UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; + panGesture.delegate = self; + [self.view addGestureRecognizer:panGesture]; +} + +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { + return self.imageView; +} + +- (void)handleDoubleTap:(UITapGestureRecognizer *)gesture { + if (self.scrollView.zoomScale > self.scrollView.minimumZoomScale) { + [self.scrollView setZoomScale:self.scrollView.minimumZoomScale animated:YES]; + } else { + CGFloat targetZoomScale = 3.0; + CGPoint touchPoint = [gesture locationInView:self.imageView]; + CGFloat zoomWidth = self.scrollView.bounds.size.width / targetZoomScale; + CGFloat zoomHeight = self.scrollView.bounds.size.height / targetZoomScale; + CGFloat zoomX = touchPoint.x - (zoomWidth / 2.0); + CGFloat zoomY = touchPoint.y - (zoomHeight / 2.0); + [self.scrollView zoomToRect:CGRectMake(zoomX, zoomY, zoomWidth, zoomHeight) animated:YES]; + } +} + +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { + if (self.scrollView.zoomScale > 1.0) return NO; + } + return YES; +} + +- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture { + CGPoint translation = [gesture translationInView:self.view]; + CGPoint velocity = [gesture velocityInView:self.view]; + + if (gesture.state == UIGestureRecognizerStateChanged) { + if (translation.y > 0) { + self.view.transform = CGAffineTransformMakeTranslation(0, translation.y); + if (isPad()) { + CGFloat alpha = MAX(0.0, 0.5 - (translation.y / self.view.bounds.size.height)); + self.view.backgroundColor = [UIColor colorWithWhite:0.0 alpha:alpha]; + } + } else { + self.view.transform = CGAffineTransformMakeTranslation(0, translation.y * 0.1); + } + } else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) { + if (translation.y > 150 || velocity.y > 1000) { + [self dismissViewControllerAnimated:YES completion:nil]; + } else { + [UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:velocity.y / 1000.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.view.transform = CGAffineTransformIdentity; + if (isPad()) self.view.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5]; + } completion:nil]; + } + } +} + +- (void)closeTapped { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)moreTapped:(UIButton *)sender { + UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[self.thumbnailImage] applicationActivities:nil]; + if (isPad()) { + activityVC.popoverPresentationController.sourceView = sender; + activityVC.popoverPresentationController.sourceRect = sender.bounds; + } + [self presentViewController:activityVC animated:YES completion:nil]; +} + +@end \ No newline at end of file diff --git a/Files/TranslateText.x b/Files/TranslateText.x new file mode 100644 index 00000000..4deecf4f --- /dev/null +++ b/Files/TranslateText.x @@ -0,0 +1,434 @@ +#import "Headers.h" + +static void YouModTranslateText(NSString *text, NSString *targetLang, void (^completion)(NSString *translatedText, NSError *error)) { + if (!text || text.length == 0) { + if (completion) completion(@"", nil); + return; + } + + NSString *encodedText = [text stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + NSString *urlString = [NSString stringWithFormat:@"https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=%@&dt=t&q=%@", targetLang ?: @"en", encodedText]; + NSURL *url = [NSURL URLWithString:urlString]; + + NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error || !data) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) completion(nil, error); + }); + return; + } + + @try { + id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + if ([json isKindOfClass:[NSArray class]] && [json count] > 0) { + NSArray *sentences = json[0]; + NSMutableString *result = [NSMutableString string]; + for (id sentence in sentences) { + if ([sentence isKindOfClass:[NSArray class]] && [sentence count] > 0) { + [result appendString:sentence[0]]; + } + } + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) completion(result, nil); + }); + return; + } + } @catch (NSException *e) {} + + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) completion(nil, [NSError errorWithDomain:@"YouModTranslate" code:-1 userInfo:nil]); + }); + }]; + [task resume]; +} + +@implementation YouModLanguagePickerViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; + + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissPicker)]; + tap.delegate = self; + [self.view addGestureRecognizer:tap]; + + self.containerView = [[UIView alloc] init]; + self.containerView.backgroundColor = [UIColor systemBackgroundColor]; + self.containerView.layer.cornerRadius = 20.0; + self.containerView.clipsToBounds = YES; + self.containerView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:self.containerView]; + + UILabel *headerLabel = [[UILabel alloc] init]; + headerLabel.text = LOC(@"LANGUAGE"); + headerLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium]; + headerLabel.textColor = [UIColor systemGrayColor]; + headerLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.containerView addSubview:headerLabel]; + + UIView *headerDivider = [[UIView alloc] init]; + headerDivider.backgroundColor = [UIColor separatorColor]; + headerDivider.translatesAutoresizingMaskIntoConstraints = NO; + [self.containerView addSubview:headerDivider]; + + self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.backgroundColor = [UIColor clearColor]; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; + self.tableView.separatorColor = [UIColor separatorColor]; + self.tableView.translatesAutoresizingMaskIntoConstraints = NO; + [self.containerView addSubview:self.tableView]; + + UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [cancelButton setTitle:LOC(@"CANCEL") forState:UIControlStateNormal]; + [cancelButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + cancelButton.backgroundColor = [UIColor systemPurpleColor]; + cancelButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightBold]; + cancelButton.layer.cornerRadius = 14.0; + cancelButton.clipsToBounds = YES; + cancelButton.translatesAutoresizingMaskIntoConstraints = NO; + [cancelButton addTarget:self action:@selector(dismissPicker) forControlEvents:UIControlEventTouchUpInside]; + [self.containerView addSubview:cancelButton]; + + NSLayoutConstraint *widthConstraint = [self.containerView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor multiplier:0.8]; + widthConstraint.priority = UILayoutPriorityDefaultHigh; + + NSLayoutConstraint *heightConstraint = [self.containerView.heightAnchor constraintEqualToAnchor:self.view.heightAnchor multiplier:0.65]; + heightConstraint.priority = UILayoutPriorityDefaultHigh; + + [NSLayoutConstraint activateConstraints:@[ + [self.containerView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], + [self.containerView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + + widthConstraint, + [self.containerView.widthAnchor constraintLessThanOrEqualToConstant:320.0], + + heightConstraint, + [self.containerView.heightAnchor constraintLessThanOrEqualToConstant:500.0], + + [headerLabel.topAnchor constraintEqualToAnchor:self.containerView.topAnchor constant:14], + [headerLabel.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:20], + [headerLabel.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor constant:-20], + + [headerDivider.topAnchor constraintEqualToAnchor:headerLabel.bottomAnchor constant:12], + [headerDivider.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor], + [headerDivider.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor], + [headerDivider.heightAnchor constraintEqualToConstant:0.5], + + [self.tableView.topAnchor constraintEqualToAnchor:headerDivider.bottomAnchor], + [self.tableView.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor], + [self.tableView.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor], + [self.tableView.bottomAnchor constraintEqualToAnchor:cancelButton.topAnchor constant:-10], + + [cancelButton.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:16], + [cancelButton.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor constant:-16], + [cancelButton.bottomAnchor constraintEqualToAnchor:self.containerView.bottomAnchor constant:-14], + [cancelButton.heightAnchor constraintEqualToConstant:44] + ]]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + NSUInteger selectedIndex = [self.codes indexOfObject:self.selectedLangCode]; + if (selectedIndex != NSNotFound && selectedIndex < self.titles.count) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0]; + [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO]; + } +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { + if ([touch.view isDescendantOfView:self.containerView]) { + return NO; + } + return YES; +} + +- (void)dismissPicker { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - TableView Delegate & DataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.titles.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *cellID = @"LangCell"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; + cell.backgroundColor = [UIColor clearColor]; + } + + NSString *name = self.titles[indexPath.row]; + NSString *code = self.codes[indexPath.row]; + cell.textLabel.text = name; + + if ([code isEqualToString:self.selectedLangCode]) { + cell.textLabel.textColor = [UIColor systemPurpleColor]; + cell.textLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightBold]; + + UIImage *checkImg = [UIImage systemImageNamed:@"checkmark"]; + cell.imageView.image = [checkImg imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + cell.imageView.tintColor = [UIColor systemPurpleColor]; + } else { + cell.textLabel.textColor = [UIColor labelColor]; + cell.textLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular]; + + static UIImage *emptyImage = nil; + if (!emptyImage) { + UIGraphicsBeginImageContextWithOptions(CGSizeMake(20, 20), NO, 0.0); + emptyImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + cell.imageView.image = emptyImage; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + if (self.onSelectLanguage) { + self.onSelectLanguage(self.codes[indexPath.row], self.titles[indexPath.row]); + } + [self dismissPicker]; +} + +@end + +@implementation YouModTranslationViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor systemBackgroundColor]; + self.title = LOC(@"TRANSLATION"); + + if (self.navigationController) { + UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; + [appearance configureWithOpaqueBackground]; + appearance.backgroundColor = [UIColor systemBackgroundColor]; + appearance.titleTextAttributes = @{ + NSForegroundColorAttributeName: [UIColor labelColor], + NSFontAttributeName: [UIFont boldSystemFontOfSize:17] + }; + self.navigationController.navigationBar.standardAppearance = appearance; + self.navigationController.navigationBar.scrollEdgeAppearance = appearance; + } + + UIBarButtonItem *closeItem = [self createEqualBarButtonWithSymbol:@"xmark" action:@selector(closeTapped)]; + UIBarButtonItem *copyItem = [self createEqualBarButtonWithSymbol:@"doc.on.doc" action:@selector(copyTapped)]; + UIBarButtonItem *shareItem = [self createEqualBarButtonWithSymbol:@"square.and.arrow.up" action:@selector(shareTapped:)]; + + self.navigationItem.rightBarButtonItems = @[closeItem, copyItem, shareItem]; + + self.languageTitles = getAllSystemLanguageTitles(); + self.languageCodes = getAllSystemLanguageValues(); + + self.selectedLangCode = @"en"; + self.selectedLangName = @"English"; + + NSUInteger defaultIndex = [self.languageCodes indexOfObject:self.selectedLangCode]; + if (defaultIndex != NSNotFound && defaultIndex < self.languageTitles.count) { + self.selectedLangName = self.languageTitles[defaultIndex]; + } else if (self.languageTitles.count > 0 && self.languageCodes.count > 0) { + self.selectedLangCode = self.languageCodes.firstObject; + self.selectedLangName = self.languageTitles.firstObject; + } + + UIView *langRowView = [[UIView alloc] init]; + langRowView.translatesAutoresizingMaskIntoConstraints = NO; + langRowView.backgroundColor = [UIColor secondarySystemBackgroundColor]; + langRowView.layer.cornerRadius = 12; + [self.view addSubview:langRowView]; + + UILabel *langTitleLabel = [[UILabel alloc] init]; + langTitleLabel.translatesAutoresizingMaskIntoConstraints = NO; + langTitleLabel.text = LOC(@"LANGUAGE"); + langTitleLabel.textColor = [UIColor labelColor]; + langTitleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium]; + [langRowView addSubview:langTitleLabel]; + + self.langValueLabel = [[UILabel alloc] init]; + self.langValueLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.langValueLabel.userInteractionEnabled = YES; + + UITapGestureRecognizer *langTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(selectLanguageTapped:)]; + [self.langValueLabel addGestureRecognizer:langTap]; + [langRowView addSubview:self.langValueLabel]; + [self updateLanguageLabelText]; + + self.reloadButton = [UIButton buttonWithType:UIButtonTypeSystem]; + self.reloadButton.translatesAutoresizingMaskIntoConstraints = NO; + UIImageSymbolConfiguration *reloadConfig = [UIImageSymbolConfiguration configurationWithPointSize:15 weight:UIFontWeightMedium]; + UIImage *reloadImage = [UIImage systemImageNamed:@"arrow.clockwise" withConfiguration:reloadConfig]; + [self.reloadButton setImage:reloadImage forState:UIControlStateNormal]; + self.reloadButton.tintColor = [UIColor systemRedColor]; + self.reloadButton.hidden = YES; + [self.reloadButton addTarget:self action:@selector(performTranslation) forControlEvents:UIControlEventTouchUpInside]; + [langRowView addSubview:self.reloadButton]; + + self.resultTextView = [[UITextView alloc] init]; + self.resultTextView.translatesAutoresizingMaskIntoConstraints = NO; + self.resultTextView.backgroundColor = [UIColor secondarySystemBackgroundColor]; + self.resultTextView.layer.cornerRadius = 12; + self.resultTextView.textColor = [UIColor labelColor]; + self.resultTextView.font = [UIFont systemFontOfSize:16]; + self.resultTextView.editable = NO; + self.resultTextView.textContainerInset = UIEdgeInsetsMake(12, 12, 12, 12); + [self.view addSubview:self.resultTextView]; + + UILayoutGuide *guide = self.view.safeAreaLayoutGuide; + + [NSLayoutConstraint activateConstraints:@[ + [langRowView.topAnchor constraintEqualToAnchor:guide.topAnchor constant:12], + [langRowView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor constant:16], + [langRowView.trailingAnchor constraintEqualToAnchor:guide.trailingAnchor constant:-16], + [langRowView.heightAnchor constraintEqualToConstant:48], + + [langTitleLabel.leadingAnchor constraintEqualToAnchor:langRowView.leadingAnchor constant:16], + [langTitleLabel.centerYAnchor constraintEqualToAnchor:langRowView.centerYAnchor], + + [self.langValueLabel.trailingAnchor constraintEqualToAnchor:langRowView.trailingAnchor constant:-16], + [self.langValueLabel.centerYAnchor constraintEqualToAnchor:langRowView.centerYAnchor], + + [self.reloadButton.trailingAnchor constraintEqualToAnchor:self.langValueLabel.leadingAnchor constant:-8], + [self.reloadButton.centerYAnchor constraintEqualToAnchor:langRowView.centerYAnchor], + [self.reloadButton.widthAnchor constraintEqualToConstant:28], + [self.reloadButton.heightAnchor constraintEqualToConstant:28], + + [self.resultTextView.topAnchor constraintEqualToAnchor:langRowView.bottomAnchor constant:12], + [self.resultTextView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor constant:16], + [self.resultTextView.trailingAnchor constraintEqualToAnchor:guide.trailingAnchor constant:-16], + [self.resultTextView.bottomAnchor constraintEqualToAnchor:guide.bottomAnchor constant:-16] + ]]; + + [self performTranslation]; +} + +- (UIBarButtonItem *)createEqualBarButtonWithSymbol:(NSString *)symbolName action:(SEL)action { + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:15 weight:UIFontWeightMedium]; + UIImage *image = [UIImage systemImageNamed:symbolName withConfiguration:config]; + [button setImage:image forState:UIControlStateNormal]; + button.tintColor = [UIColor labelColor]; + + button.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [button.widthAnchor constraintEqualToConstant:32], + [button.heightAnchor constraintEqualToConstant:32] + ]]; + + [button addTarget:self action:action forControlEvents:UIControlEventTouchUpInside]; + return [[UIBarButtonItem alloc] initWithCustomView:button]; +} + +- (void)updateLanguageLabelText { + NSString *langName = self.selectedLangName ?: @""; + UIColor *purpleColor = [UIColor systemPurpleColor]; + UIFont *labelFont = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; + + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ ", langName] attributes:@{ + NSFontAttributeName: labelFont, + NSForegroundColorAttributeName: purpleColor + }]; + + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithFont:labelFont]; + UIImage *symbol = [UIImage systemImageNamed:@"chevron.up.chevron.down" withConfiguration:config]; + + if (symbol) { + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + attachment.image = [symbol imageWithTintColor:purpleColor renderingMode:UIImageRenderingModeAlwaysOriginal]; + + CGFloat fontCapHeight = labelFont.capHeight; + CGFloat iconHeight = symbol.size.height; + attachment.bounds = CGRectMake(0, (fontCapHeight - iconHeight) / 2.0, symbol.size.width, iconHeight); + + [attrString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; + } + + self.langValueLabel.attributedText = attrString; +} + +- (void)closeTapped { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)copyTapped { + if (self.resultTextView.text.length > 0 && ![self.resultTextView.text isEqualToString:LOC(@"TRANSLATING")] && ![self.resultTextView.text isEqualToString:LOC(@"TRANSLATE_FAILED")]) { + [UIPasteboard generalPasteboard].string = self.resultTextView.text; + + UIImpactFeedbackGenerator *feedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; + [feedback impactOccurred]; + + UIView *parent = sbGetNotificationParent(); + [SBSkipNotificationView showSuccessInView:parent message:LOC(@"COPIED_TO_CLIPBOARD") duration:3.0]; + } +} + +- (void)shareTapped:(id)sender { + if (self.resultTextView.text.length == 0 || [self.resultTextView.text isEqualToString:LOC(@"TRANSLATING")] || [self.resultTextView.text isEqualToString:LOC(@"TRANSLATE_FAILED")]) return; + + UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[self.resultTextView.text] applicationActivities:nil]; + if (activityVC.popoverPresentationController) { + if ([sender isKindOfClass:[UIBarButtonItem class]]) { + activityVC.popoverPresentationController.barButtonItem = (UIBarButtonItem *)sender; + } else if ([sender isKindOfClass:[UIView class]]) { + activityVC.popoverPresentationController.sourceView = (UIView *)sender; + activityVC.popoverPresentationController.sourceRect = ((UIView *)sender).bounds; + } + } + [self presentViewController:activityVC animated:YES completion:nil]; +} + +- (void)selectLanguageTapped:(UITapGestureRecognizer *)gesture { + if (!self.languageTitles || self.languageTitles.count == 0) return; + + YouModLanguagePickerViewController *pickerVC = [[YouModLanguagePickerViewController alloc] init]; + pickerVC.titles = self.languageTitles; + pickerVC.codes = self.languageCodes; + pickerVC.selectedLangCode = self.selectedLangCode; + + __weak typeof(self) weakSelf = self; + pickerVC.onSelectLanguage = ^(NSString *code, NSString *title) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) return; + strongSelf.selectedLangCode = code; + strongSelf.selectedLangName = title; + [strongSelf updateLanguageLabelText]; + [strongSelf performTranslation]; + }; + + pickerVC.modalPresentationStyle = UIModalPresentationOverFullScreen; + pickerVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + [self presentViewController:pickerVC animated:YES completion:nil]; +} + +- (void)performTranslation { + self.reloadButton.hidden = YES; + self.resultTextView.text = LOC(@"TRANSLATING"); + self.resultTextView.textColor = [UIColor systemPurpleColor]; + + __weak typeof(self) weakSelf = self; + YouModTranslateText(self.originalText, self.selectedLangCode, ^(NSString *translatedText, NSError *error) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) return; + + if (translatedText && translatedText.length > 0) { + strongSelf.resultTextView.text = translatedText; + strongSelf.resultTextView.textColor = [UIColor labelColor]; + strongSelf.reloadButton.hidden = YES; + } else { + strongSelf.resultTextView.text = LOC(@"TRANSLATE_FAILED"); + strongSelf.resultTextView.textColor = [UIColor systemRedColor]; + strongSelf.reloadButton.hidden = NO; + } + }); +} + +@end \ No newline at end of file diff --git a/Files/YouModPerferences.x b/Files/YouModPerferences.x index 361ce66c..fc3a4944 100644 --- a/Files/YouModPerferences.x +++ b/Files/YouModPerferences.x @@ -3,21 +3,6 @@ #define Prefix @"YouMod" -static NSBundle *YouModBundle() { - static NSBundle *bundle = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *tweakBundlePath = [[NSBundle mainBundle] pathForResource:Prefix ofType:@"bundle"]; - if (tweakBundlePath) - bundle = [NSBundle bundleWithPath:tweakBundlePath]; - else - bundle = [NSBundle bundleWithPath:[NSString stringWithFormat:PS_ROOT_PATH_NS(@"/Library/Application Support/%@.bundle"), Prefix]]; - }); - return bundle; -} - -#define LOC(x) [YouModBundle() localizedStringForKey:x value:nil table:nil] - @implementation YouModPrefsManager + (instancetype)sharedManager { @@ -57,6 +42,7 @@ static NSBundle *YouModBundle() { YTAlertView *alertView = [%c(YTAlertView) infoDialog]; alertView.title = LOC(@"ERROR"); alertView.subtitle = LOC(@"ERROR_INVALID_FILE"); + alertView.shouldDismissOnBackgroundTap = YES; [alertView show]; return; } @@ -81,16 +67,21 @@ static NSBundle *YouModBundle() { YTAlertView *alertView = [%c(YTAlertView) infoDialog]; alertView.title = LOC(@"ERROR"); alertView.subtitle = LOC(@"ERROR_NO_KEYS_IMPORT"); + alertView.shouldDismissOnBackgroundTap = YES; [alertView show]; return; } [defaults synchronize]; // Success Alert with Restart YTAlertView *alertView = [%c(YTAlertView) confirmationDialogWithAction:^{ - exit(0); + [[UIApplication sharedApplication] performSelector:@selector(suspend)]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + exit(0); + }); } actionTitle:LOC(@"YES")]; alertView.title = LOC(@"IMPORT_DONE"); alertView.subtitle = LOC(@"APPLY_DESC"); // "Restart required" + alertView.shouldDismissOnBackgroundTap = YES; [alertView show]; } @@ -107,6 +98,7 @@ static NSBundle *YouModBundle() { YTAlertView *alertView = [%c(YTAlertView) infoDialog]; alertView.title = LOC(@"ERROR"); alertView.subtitle = LOC(@"ERROR_NO_KEYS_EXPORT"); + alertView.shouldDismissOnBackgroundTap = YES; [alertView show]; return; } @@ -134,18 +126,23 @@ static NSBundle *YouModBundle() { } [defaults setBool:YES forKey:AutoClearCache]; [defaults setBool:YES forKey:YTPremiumLogo]; - [defaults setBool:YES forKey:HideCreateButton]; [defaults setBool:YES forKey:HideCastButtonNav]; [defaults setBool:YES forKey:HideCastButtonPlayer]; [defaults setBool:YES forKey:BackgroundPlayback]; - [defaults setBool:YES forKey:OldQualityPicker]; - [defaults setBool:YES forKey:DownloadManager]; - [defaults setBool:YES forKey:DownloadSaveToPhotos]; + [defaults setBool:YES forKey:DisableHints]; + [defaults setInteger:1 forKey:GestureActivationArea]; + [defaults setInteger:1 forKey:LeftSideGesture]; + [defaults setInteger:2 forKey:RightSideGesture]; + [defaults setInteger:1 forKey:GestureHUDSize]; [defaults synchronize]; - exit(0); + [[UIApplication sharedApplication] performSelector:@selector(suspend)]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + exit(0); + }); } actionTitle:LOC(@"YES")]; alertView.title = LOC(@"WARNING"); alertView.subtitle = LOC(@"RESETDEFAULT"); + alertView.shouldDismissOnBackgroundTap = YES; [alertView show]; } diff --git a/Files/YouModSettings.x b/Files/YouModSettings.x new file mode 100644 index 00000000..a4ca8448 --- /dev/null +++ b/Files/YouModSettings.x @@ -0,0 +1,1472 @@ +// YouModSettings.x — Reusable UIKit-based sub-page for YouMod settings sections +#import "Headers.h" +#import +#import + +#pragma mark - Data Model + +typedef NS_ENUM(NSInteger, YMRowType) { + YMRowTypeToggle = 0, + YMRowTypePicker, + YMRowTypeAction, + YMRowTypeHeader, + YMRowTypeSegment, + YMRowTypeTextSegment, + YMRowTypeImageSegment, + YMRowTypeSlider +}; + +@interface YMSettingsItem : NSObject +@property (nonatomic, assign) YMRowType type; +@property (nonatomic, strong) NSString *title; +@property (nonatomic, strong) NSString *subtitle; +@property (nonatomic, strong) NSString *key; +@property (nonatomic, strong) NSArray *pickerOptions; +@property (nonatomic, assign) NSInteger pickerDefault; +@property (nonatomic, copy) void (^action)(UIViewController *vc); +@property (nonatomic, strong) NSArray *segmentIcons; +@property (nonatomic, strong) NSArray *segmentLabels; +@property (nonatomic, strong) NSArray *segmentImages; +@property (nonatomic, assign) float sliderMin; +@property (nonatomic, assign) float sliderMax; +@property (nonatomic, assign) float sliderStep; +@property (nonatomic, assign) float sliderDefault; ++ (instancetype)toggleWithTitle:(NSString *)title subtitle:(NSString *)subtitle key:(NSString *)key; ++ (instancetype)sliderWithTitle:(NSString *)title subtitle:(NSString *)subtitle key:(NSString *)key min:(float)min max:(float)max step:(float)step defaultValue:(float)defaultValue; ++ (instancetype)pickerWithTitle:(NSString *)title subtitle:(NSString *)subtitle key:(NSString *)key options:(NSArray *)options defaultValue:(NSInteger)defaultValue; ++ (instancetype)actionWithTitle:(NSString *)title subtitle:(NSString *)subtitle action:(void (^)(UIViewController *vc))action; ++ (instancetype)headerWithTitle:(NSString *)title; ++ (instancetype)segmentWithTitle:(NSString *)title key:(NSString *)key icons:(NSArray *)icons defaultValue:(NSInteger)defaultValue; ++ (instancetype)textSegmentWithTitle:(NSString *)title key:(NSString *)key labels:(NSArray *)labels defaultValue:(NSInteger)defaultValue; ++ (instancetype)imageSegmentWithTitle:(NSString *)title key:(NSString *)key images:(NSArray *)images defaultValue:(NSInteger)defaultValue; +@end + +@implementation YMSettingsItem + ++ (instancetype)toggleWithTitle:(NSString *)title subtitle:(NSString *)subtitle key:(NSString *)key { + YMSettingsItem *item = [[YMSettingsItem alloc] init]; + item.type = YMRowTypeToggle; + item.title = title; + item.subtitle = subtitle; + item.key = key; + return item; +} + ++ (instancetype)sliderWithTitle:(NSString *)title subtitle:(NSString *)subtitle key:(NSString *)key min:(float)min max:(float)max step:(float)step defaultValue:(float)defaultValue { + YMSettingsItem *item = [[YMSettingsItem alloc] init]; + item.type = YMRowTypeSlider; + item.title = title; + item.subtitle = subtitle; + item.key = key; + item.sliderMin = min; + item.sliderMax = max; + item.sliderStep = step; + item.sliderDefault = defaultValue; + return item; +} + ++ (instancetype)pickerWithTitle:(NSString *)title subtitle:(NSString *)subtitle key:(NSString *)key options:(NSArray *)options defaultValue:(NSInteger)defaultValue { + YMSettingsItem *item = [[YMSettingsItem alloc] init]; + item.type = YMRowTypePicker; + item.title = title; + item.subtitle = subtitle; + item.key = key; + item.pickerOptions = options; + item.pickerDefault = defaultValue; + return item; +} + ++ (instancetype)actionWithTitle:(NSString *)title subtitle:(NSString *)subtitle action:(void (^)(UIViewController *vc))action { + YMSettingsItem *item = [[YMSettingsItem alloc] init]; + item.type = YMRowTypeAction; + item.title = title; + item.subtitle = subtitle; + item.action = action; + return item; +} + ++ (instancetype)headerWithTitle:(NSString *)title { + YMSettingsItem *item = [[YMSettingsItem alloc] init]; + item.type = YMRowTypeHeader; + item.title = title; + return item; +} + ++ (instancetype)segmentWithTitle:(NSString *)title key:(NSString *)key icons:(NSArray *)icons defaultValue:(NSInteger)defaultValue { + YMSettingsItem *item = [[YMSettingsItem alloc] init]; + item.type = YMRowTypeSegment; + item.title = title; + item.key = key; + item.segmentIcons = icons; + item.pickerDefault = defaultValue; + return item; +} + ++ (instancetype)textSegmentWithTitle:(NSString *)title key:(NSString *)key labels:(NSArray *)labels defaultValue:(NSInteger)defaultValue { + YMSettingsItem *item = [[YMSettingsItem alloc] init]; + item.type = YMRowTypeTextSegment; + item.title = title; + item.key = key; + item.segmentLabels = labels; + item.pickerDefault = defaultValue; + return item; +} + ++ (instancetype)imageSegmentWithTitle:(NSString *)title key:(NSString *)key images:(NSArray *)images defaultValue:(NSInteger)defaultValue { + YMSettingsItem *item = [[YMSettingsItem alloc] init]; + item.type = YMRowTypeImageSegment; + item.title = title; + item.key = key; + item.segmentImages = images; + item.pickerDefault = defaultValue; + return item; +} + +@end + +#pragma mark - YMSubSettingsViewController + +@interface YMSubSettingsViewController : UIViewController +- (UITableView *)tableView; +- (void)setTableView:(UITableView *)tv; +- (NSString *)navTitle; +- (void)setNavTitle:(NSString *)t; +- (NSArray *)items; +- (void)setItems:(NSArray *)items; +- (UIColor *)ymTextColor; +- (UIColor *)ymSecondaryColor; +- (UITableViewCell *)cellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView; +// Whether this page installs a per-page search bar that filters its own rows. +// The global-search subclass overrides this to NO (it hosts its own search). +- (BOOL)usesPageSearch; +@end + +static const void *kYMTableViewKey = &kYMTableViewKey; +static const void *kYMNavTitleKey = &kYMNavTitleKey; +static const void *kYMItemsKey = &kYMItemsKey; +static const void *kYMSwitchKeyAssoc = &kYMSwitchKeyAssoc; +static const void *kYMSliderKeyAssoc = &kYMSliderKeyAssoc; +static const void *kYMSliderStepAssoc = &kYMSliderStepAssoc; +static const void *kYMSliderLabelAssoc = &kYMSliderLabelAssoc; +static const void *kYMPageFilterKey = &kYMPageFilterKey; + +@implementation YMSubSettingsViewController + +- (UITableView *)tableView { return objc_getAssociatedObject(self, kYMTableViewKey); } +- (void)setTableView:(UITableView *)tv { objc_setAssociatedObject(self, kYMTableViewKey, tv, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (NSString *)navTitle { return objc_getAssociatedObject(self, kYMNavTitleKey); } +- (void)setNavTitle:(NSString *)t { objc_setAssociatedObject(self, kYMNavTitleKey, t, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (NSArray *)items { return objc_getAssociatedObject(self, kYMItemsKey); } +- (void)setItems:(NSArray *)items { objc_setAssociatedObject(self, kYMItemsKey, items, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + +- (BOOL)usesPageSearch { return YES; } + +// The active per-page filter text (nil/empty = not filtering). +- (NSString *)pageFilter { return objc_getAssociatedObject(self, kYMPageFilterKey); } +- (void)setPageFilter:(NSString *)f { objc_setAssociatedObject(self, kYMPageFilterKey, f, OBJC_ASSOCIATION_COPY_NONATOMIC); } + +// The rows to display: all items, or — while a page search is active — only those +// whose title/subtitle match, with headers dropped (a filtered list has no sections). +- (NSArray *)displayedItems { + NSString *q = self.pageFilter; + if (q.length == 0) return self.items; + NSMutableArray *matches = [NSMutableArray array]; + NSStringCompareOptions opts = NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch; + for (YMSettingsItem *item in self.items) { + if (item.type == YMRowTypeHeader) continue; + NSString *hay = [NSString stringWithFormat:@"%@ %@", item.title ?: @"", item.subtitle ?: @""]; + if ([hay rangeOfString:q options:opts].location != NSNotFound) [matches addObject:item]; + } + return matches; +} + +// Install a pinned search bar that filters this page's own rows in place. +- (void)installPageSearchBar { + UISearchController *sc = [[UISearchController alloc] initWithSearchResultsController:nil]; + sc.searchResultsUpdater = self; + sc.obscuresBackgroundDuringPresentation = NO; + sc.searchBar.placeholder = LOC(@"SEARCH"); + sc.searchBar.tintColor = [UIColor colorWithRed:0.6 green:0.2 blue:0.9 alpha:1.0]; + self.navigationItem.searchController = sc; + self.navigationItem.hidesSearchBarWhenScrolling = NO; + self.definesPresentationContext = YES; +} + +- (void)updateSearchResultsForSearchController:(UISearchController *)searchController { + self.pageFilter = [searchController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [self.tableView reloadData]; +} + +- (void)viewDidLoad { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL))objc_msgSendSuper)(&superStruct, @selector(viewDidLoad)); + + self.title = self.navTitle; + + UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; + [appearance configureWithDefaultBackground]; + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + appearance.backgroundColor = [%c(YTColor) black3]; + } else { + appearance.backgroundColor = [UIColor systemBackgroundColor]; + } + self.navigationController.navigationBar.standardAppearance = appearance; + self.navigationController.navigationBar.scrollEdgeAppearance = appearance; + + self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; + self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + self.tableView.estimatedRowHeight = 60; + self.tableView.rowHeight = UITableViewAutomaticDimension; + + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + self.tableView.backgroundColor = [%c(YTColor) black3]; + } else { + self.tableView.backgroundColor = [UIColor systemBackgroundColor]; + } + + [self.view addSubview:self.tableView]; + + if (self.usesPageSearch) [self installPageSearchBar]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle) { + self.tableView.backgroundColor = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) + ? [%c(YTColor) black3] + : [UIColor systemBackgroundColor]; + UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; + [appearance configureWithDefaultBackground]; + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + appearance.backgroundColor = [%c(YTColor) black3]; + } else { + appearance.backgroundColor = [UIColor systemBackgroundColor]; + } + self.navigationController.navigationBar.standardAppearance = appearance; + self.navigationController.navigationBar.scrollEdgeAppearance = appearance; + [self.tableView reloadData]; + } +} + +- (void)viewWillAppear:(BOOL)animated { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL, BOOL))objc_msgSendSuper)(&superStruct, @selector(viewWillAppear:), animated); +} + +- (void)viewDidLayoutSubviews { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL))objc_msgSendSuper)(&superStruct, @selector(viewDidLayoutSubviews)); + YTQTMButton *backButton = [self valueForKey:@"_backButton"]; + + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + backButton.tintColor = [UIColor whiteColor]; + } else { + backButton.tintColor = [UIColor blackColor]; + } +} + +- (UIColor *)navBarForegroundColor { + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + return [UIColor whiteColor]; + } + // Light mode: return a concrete colour rather than nil. nil lets YouTube tint the + // nav-bar foreground (back chevron + title) with its default appearance, which is + // system blue in light mode. labelColor keeps it black in light / white in dark. + return [UIColor labelColor]; +} + +- (UIColor *)ymTextColor { + return (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) + ? [UIColor whiteColor] : [UIColor labelColor]; +} + +- (UIColor *)ymSecondaryColor { + return [UIColor colorWithWhite:0.55 alpha:1.0]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.displayedItems.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + return [self cellForItem:self.displayedItems[indexPath.row] tableView:tableView]; +} + +// Builds the cell for a single item, dispatching on its row type. Shared by this +// page's data source and the global settings-search results table, so both render +// the same live, editable controls from one type dispatch. +- (UITableViewCell *)cellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + if (item.type == YMRowTypeToggle) { + return [self toggleCellForItem:item tableView:tableView]; + } else if (item.type == YMRowTypeAction) { + return [self actionCellForItem:item tableView:tableView]; + } else if (item.type == YMRowTypeHeader) { + return [self headerCellForItem:item tableView:tableView]; + } else if (item.type == YMRowTypeSegment) { + return [self segmentCellForItem:item tableView:tableView]; + } else if (item.type == YMRowTypeTextSegment) { + return [self textSegmentCellForItem:item tableView:tableView]; + } else if (item.type == YMRowTypeImageSegment) { + return [self imageSegmentCellForItem:item tableView:tableView]; + } else if (item.type == YMRowTypeSlider) { + return [self sliderCellForItem:item tableView:tableView]; + } + return [self pickerCellForItem:item tableView:tableView]; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + YMSettingsItem *item = self.displayedItems[indexPath.row]; + if (item.type == YMRowTypeAction && item.action) { + item.action(self); + } +} + +#pragma mark - Toggle Cell + +- (UITableViewCell *)toggleCellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.textLabel.text = item.title; + cell.textLabel.textColor = [self ymTextColor]; + cell.textLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + + if (item.subtitle.length > 0) { + cell.detailTextLabel.text = item.subtitle; + cell.detailTextLabel.textColor = [self ymSecondaryColor]; + cell.detailTextLabel.font = [UIFont systemFontOfSize:13]; + cell.detailTextLabel.numberOfLines = 0; + } + + UISwitch *sw = [[UISwitch alloc] init]; + sw.on = IS_ENABLED(item.key); + sw.onTintColor = [UIColor colorWithRed:0.6 green:0.2 blue:0.9 alpha:1.0]; + objc_setAssociatedObject(sw, kYMSwitchKeyAssoc, item.key, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [sw addTarget:self action:@selector(toggleChanged:) forControlEvents:UIControlEventValueChanged]; + cell.accessoryView = sw; + + return cell; +} + +- (void)toggleChanged:(UISwitch *)sender { + NSString *key = objc_getAssociatedObject(sender, kYMSwitchKeyAssoc); + if (key) { + [[NSUserDefaults standardUserDefaults] setBool:sender.on forKey:key]; + } +} + +- (UITableViewCell *)sliderCellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + // A stored value of zero is treated as "no value yet" and shown as the + // default. Slider ranges are therefore expected to be positive. + float stored = [[NSUserDefaults standardUserDefaults] floatForKey:item.key]; + if (stored <= 0) stored = item.sliderDefault; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + titleLabel.text = item.title; + titleLabel.textColor = [self ymTextColor]; + titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + [cell.contentView addSubview:titleLabel]; + + UILabel *valueLabel = [[UILabel alloc] init]; + valueLabel.translatesAutoresizingMaskIntoConstraints = NO; + valueLabel.textColor = [self ymSecondaryColor]; + valueLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; + valueLabel.textAlignment = NSTextAlignmentRight; + NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; + formatter.allowedUnits = NSCalendarUnitSecond; + formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated; + + valueLabel.text = [formatter stringFromTimeInterval:stored]; + [cell.contentView addSubview:valueLabel]; + + UISlider *slider = [[UISlider alloc] init]; + slider.translatesAutoresizingMaskIntoConstraints = NO; + slider.minimumValue = item.sliderMin; + slider.maximumValue = item.sliderMax; + slider.value = stored; + slider.minimumTrackTintColor = [UIColor colorWithRed:0.6 green:0.2 blue:0.9 alpha:1.0]; + slider.maximumTrackTintColor = [UIColor colorWithWhite:0.3 alpha:1.0]; + objc_setAssociatedObject(slider, kYMSliderKeyAssoc, item.key, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(slider, kYMSliderStepAssoc, @(item.sliderStep), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(slider, kYMSliderLabelAssoc, valueLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [slider addTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged]; + [cell.contentView addSubview:slider]; + + [NSLayoutConstraint activateConstraints:@[ + [titleLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [titleLabel.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor constant:12], + [valueLabel.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-16], + [valueLabel.centerYAnchor constraintEqualToAnchor:titleLabel.centerYAnchor], + [valueLabel.widthAnchor constraintGreaterThanOrEqualToConstant:60], + [slider.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [slider.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-16], + [slider.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:8], + [slider.bottomAnchor constraintEqualToAnchor:cell.contentView.bottomAnchor constant:-12], + ]]; + + return cell; +} + +- (void)sliderChanged:(UISlider *)sender { + NSString *key = objc_getAssociatedObject(sender, kYMSliderKeyAssoc); + if (!key) return; + float step = [objc_getAssociatedObject(sender, kYMSliderStepAssoc) floatValue]; + if (step <= 0) step = 1; + float snapped = roundf(sender.value / step) * step; + sender.value = snapped; + [[NSUserDefaults standardUserDefaults] setFloat:snapped forKey:key]; + UILabel *valueLabel = objc_getAssociatedObject(sender, kYMSliderLabelAssoc); + NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; + formatter.allowedUnits = NSCalendarUnitSecond; + formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated; + + valueLabel.text = [formatter stringFromTimeInterval:snapped]; +} + +#pragma mark - Action Cell + +- (UITableViewCell *)actionCellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleDefault; + cell.textLabel.text = item.title; + cell.textLabel.textColor = [self ymTextColor]; + cell.textLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + + if (item.subtitle.length > 0) { + cell.detailTextLabel.text = item.subtitle; + cell.detailTextLabel.textColor = [self ymSecondaryColor]; + cell.detailTextLabel.font = [UIFont systemFontOfSize:13]; + cell.detailTextLabel.numberOfLines = 0; + } + + return cell; +} + +#pragma mark - Header Cell + +- (UITableViewCell *)headerCellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.textLabel.text = item.title; + cell.textLabel.textColor = [self ymSecondaryColor]; + cell.textLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular]; + return cell; +} + +#pragma mark - Segment Cell + +- (UITableViewCell *)segmentCellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.text = item.title; + titleLabel.textColor = [self ymTextColor]; + titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + [cell.contentView addSubview:titleLabel]; + + NSMutableArray *items = [NSMutableArray array]; + for (NSUInteger i = 0; i < item.segmentIcons.count; i++) { + [items addObject:@""]; + } + UISegmentedControl *segment = [[UISegmentedControl alloc] initWithItems:items]; + + for (NSInteger i = 0; i < (NSInteger)item.segmentIcons.count; i++) { + YTIIcon *ytIcon = [%c(YTIIcon) new]; + if (ytIcon) { + ((void (*)(id, SEL, int))objc_msgSend)(ytIcon, @selector(setIconType:), [item.segmentIcons[i] intValue]); + UIImage *iconImage = nil; + if ([ytIcon respondsToSelector:@selector(iconImageWithColor:)]) { + iconImage = [ytIcon iconImageWithColor:[UIColor whiteColor]]; + } else if ([ytIcon respondsToSelector:@selector(iconImageWithSelected:)]) { + iconImage = [ytIcon iconImageWithSelected:NO]; + } + if (iconImage) { + [segment setImage:iconImage forSegmentAtIndex:i]; + } + } + } + + id storedSegVal = [[NSUserDefaults standardUserDefaults] objectForKey:item.key]; + NSInteger segIdx = storedSegVal ? [storedSegVal integerValue] : item.pickerDefault; + segment.selectedSegmentIndex = MAX(0, MIN(segIdx, segment.numberOfSegments - 1)); + segment.backgroundColor = [UIColor colorWithRed:0.13 green:0.13 blue:0.13 alpha:1.0]; + segment.selectedSegmentTintColor = [UIColor colorWithRed:0.25 green:0.25 blue:0.25 alpha:1.0]; + segment.layer.cornerRadius = 8.0; + segment.clipsToBounds = YES; + + objc_setAssociatedObject(segment, kYMSwitchKeyAssoc, item.key, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [segment addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged]; + + segment.translatesAutoresizingMaskIntoConstraints = NO; + [cell.contentView addSubview:segment]; + + [NSLayoutConstraint activateConstraints:@[ + [titleLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [titleLabel.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor constant:12], + + [segment.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [segment.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-16], + [segment.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:10], + [segment.bottomAnchor constraintEqualToAnchor:cell.contentView.bottomAnchor constant:-12], + [segment.heightAnchor constraintEqualToConstant:36] + ]]; + + return cell; +} + +- (void)segmentChanged:(UISegmentedControl *)sender { + NSString *key = objc_getAssociatedObject(sender, kYMSwitchKeyAssoc); + if (key) { + [[NSUserDefaults standardUserDefaults] setInteger:sender.selectedSegmentIndex forKey:key]; + } +} + +#pragma mark - Text Segment Cell + +- (UITableViewCell *)textSegmentCellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.text = item.title; + titleLabel.textColor = [self ymTextColor]; + titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + [cell.contentView addSubview:titleLabel]; + + UISegmentedControl *segment = [[UISegmentedControl alloc] initWithItems:item.segmentLabels]; + + id storedVal = [[NSUserDefaults standardUserDefaults] objectForKey:item.key]; + NSInteger txtSegIdx = storedVal ? [storedVal integerValue] : item.pickerDefault; + segment.selectedSegmentIndex = MAX(0, MIN(txtSegIdx, segment.numberOfSegments - 1)); + segment.backgroundColor = [UIColor colorWithRed:0.13 green:0.13 blue:0.13 alpha:1.0]; + segment.selectedSegmentTintColor = [UIColor colorWithRed:0.25 green:0.25 blue:0.25 alpha:1.0]; + segment.layer.cornerRadius = 8.0; + segment.clipsToBounds = YES; + + NSDictionary *textAttrs = @{NSForegroundColorAttributeName: [UIColor whiteColor], NSFontAttributeName: [UIFont systemFontOfSize:13 weight:UIFontWeightMedium]}; + [segment setTitleTextAttributes:textAttrs forState:UIControlStateNormal]; + [segment setTitleTextAttributes:textAttrs forState:UIControlStateSelected]; + + objc_setAssociatedObject(segment, kYMSwitchKeyAssoc, item.key, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [segment addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged]; + + segment.translatesAutoresizingMaskIntoConstraints = NO; + [cell.contentView addSubview:segment]; + + [NSLayoutConstraint activateConstraints:@[ + [titleLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [titleLabel.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor constant:12], + + [segment.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [segment.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-16], + [segment.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:10], + [segment.bottomAnchor constraintEqualToAnchor:cell.contentView.bottomAnchor constant:-12], + [segment.heightAnchor constraintEqualToConstant:36] + ]]; + + return cell; +} + +#pragma mark - Image Segment Cell + +- (UITableViewCell *)imageSegmentCellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + UILabel *titleLabel = [[UILabel alloc] init]; + titleLabel.text = item.title; + titleLabel.textColor = [self ymTextColor]; + titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + [cell.contentView addSubview:titleLabel]; + + NSMutableArray *segItems = [NSMutableArray array]; + for (NSUInteger i = 0; i < item.segmentImages.count; i++) { + [segItems addObject:@""]; + } + UISegmentedControl *segment = [[UISegmentedControl alloc] initWithItems:segItems]; + + for (NSInteger i = 0; i < (NSInteger)item.segmentImages.count; i++) { + UIImage *img = item.segmentImages[i]; + if (img) [segment setImage:img forSegmentAtIndex:i]; + } + + id storedVal = [[NSUserDefaults standardUserDefaults] objectForKey:item.key]; + NSInteger idx = storedVal ? [storedVal integerValue] : item.pickerDefault; + segment.selectedSegmentIndex = MAX(0, MIN(idx, segment.numberOfSegments - 1)); + segment.backgroundColor = [UIColor colorWithRed:0.13 green:0.13 blue:0.13 alpha:1.0]; + segment.selectedSegmentTintColor = [UIColor colorWithRed:0.25 green:0.25 blue:0.25 alpha:1.0]; + segment.layer.cornerRadius = 8.0; + segment.clipsToBounds = YES; + + objc_setAssociatedObject(segment, kYMSwitchKeyAssoc, item.key, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [segment addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged]; + + segment.translatesAutoresizingMaskIntoConstraints = NO; + [cell.contentView addSubview:segment]; + + [NSLayoutConstraint activateConstraints:@[ + [titleLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [titleLabel.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor constant:12], + + [segment.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:16], + [segment.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-16], + [segment.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:10], + [segment.bottomAnchor constraintEqualToAnchor:cell.contentView.bottomAnchor constant:-12], + [segment.heightAnchor constraintEqualToConstant:36] + ]]; + + return cell; +} + +#pragma mark - Picker Cell + +- (UITableViewCell *)pickerCellForItem:(YMSettingsItem *)item tableView:(UITableView *)tableView { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.textLabel.text = item.title; + cell.textLabel.textColor = [self ymTextColor]; + cell.textLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + + if (item.subtitle.length > 0) { + cell.detailTextLabel.text = item.subtitle; + cell.detailTextLabel.textColor = [self ymSecondaryColor]; + cell.detailTextLabel.font = [UIFont systemFontOfSize:13]; + cell.detailTextLabel.numberOfLines = 0; + } + + NSInteger safeDefault = (item.pickerDefault >= 0 && item.pickerDefault < (NSInteger)item.pickerOptions.count) + ? item.pickerDefault : 0; + id storedValue = [[NSUserDefaults standardUserDefaults] objectForKey:item.key]; + NSInteger currentValue = storedValue ? [storedValue integerValue] : safeDefault; + NSString *currentTitle = (currentValue >= 0 && currentValue < (NSInteger)item.pickerOptions.count) + ? item.pickerOptions[currentValue] + : item.pickerOptions[safeDefault]; + + UIButton *menuButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [menuButton setTitle:currentTitle forState:UIControlStateNormal]; + [menuButton setTitleColor:[self ymSecondaryColor] forState:UIControlStateNormal]; + menuButton.titleLabel.font = [UIFont systemFontOfSize:15]; + [menuButton setImage:[UIImage systemImageNamed:@"chevron.up.chevron.down"] forState:UIControlStateNormal]; + menuButton.tintColor = [self ymSecondaryColor]; + + NSMutableArray *menuActions = [NSMutableArray array]; + __weak typeof(self) weakSelf = self; + for (NSInteger i = 0; i < (NSInteger)item.pickerOptions.count; i++) { + NSString *optionTitle = item.pickerOptions[i]; + NSString *itemKey = item.key; + UIAction *action = [UIAction actionWithTitle:optionTitle image:nil identifier:nil handler:^(__kindof UIAction *a) { + [[NSUserDefaults standardUserDefaults] setInteger:i forKey:itemKey]; + [weakSelf.tableView reloadData]; + }]; + if (i == currentValue) { + action.state = UIMenuElementStateOn; + } + [menuActions addObject:action]; + } + + menuButton.menu = [UIMenu menuWithTitle:item.title children:menuActions]; + menuButton.showsMenuAsPrimaryAction = YES; + [menuButton sizeToFit]; + cell.accessoryView = menuButton; + + return cell; +} + +#pragma mark - Table View Footer + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return 0; +} + +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { + return [[UIView alloc] init]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 16; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + return [[UIView alloc] init]; +} + +@end + +#pragma mark - Settings Search + +// A searchable group of YouMod (YMSettingsItem-based) settings: the group's +// display name plus its items. Registered by Settings.x so the search VC can build +// a flat index without knowing how each group's items are constructed. +@interface YMSettingsGroup : NSObject +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSArray *items; +@end +@implementation YMSettingsGroup +@end + +@implementation YMSearchRow +@end + +// The registered YouMod setting groups (excludes SponsorBlock, which contributes +// its own rows via sbSearchRows). Settings.x re-registers these each time the +// settings screen is built; registration dedupes by title (see below). +static NSMutableArray *gYMSearchGroups = nil; + +void YMRegisterSettingsGroup(NSString *title, NSArray *items) { + if (!gYMSearchGroups) gYMSearchGroups = [NSMutableArray array]; + // Replace any prior registration for this title (idempotent) so re-opening the + // settings screen — which rebuilds the section — doesn't duplicate index rows. + for (YMSettingsGroup *existing in [gYMSearchGroups copy]) { + if ([existing.title isEqualToString:title]) [gYMSearchGroups removeObject:existing]; + } + YMSettingsGroup *group = [YMSettingsGroup new]; + group.title = title; + group.items = items; + [gYMSearchGroups addObject:group]; +} + +// Global settings search. Subclasses YMSubSettingsViewController to inherit every +// YMSettingsItem cell builder (toggle/slider/picker/segment) and its change +// handlers, so results are the live, editable controls. Rows that don't originate +// from a YMSettingsItem (SponsorBlock's action pickers and colour circles) are +// supplied as YMSearchRow objects that render themselves. +@interface YMSettingsSearchViewController : YMSubSettingsViewController +@end + +static const void *kYMSearchAllRowsKey = &kYMSearchAllRowsKey; +static const void *kYMSearchFilteredRowsKey = &kYMSearchFilteredRowsKey; +static const void *kYMSearchControllerKey = &kYMSearchControllerKey; + +@implementation YMSettingsSearchViewController + +- (NSArray *)allRows { return objc_getAssociatedObject(self, kYMSearchAllRowsKey); } +- (void)setAllRows:(NSArray *)r { objc_setAssociatedObject(self, kYMSearchAllRowsKey, r, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (NSArray *)filteredRows { return objc_getAssociatedObject(self, kYMSearchFilteredRowsKey); } +- (void)setFilteredRows:(NSArray *)r { objc_setAssociatedObject(self, kYMSearchFilteredRowsKey, r, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (UISearchController *)searchController { return objc_getAssociatedObject(self, kYMSearchControllerKey); } +- (void)setSearchController:(UISearchController *)c { objc_setAssociatedObject(self, kYMSearchControllerKey, c, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + +// This VC hosts its own search controller (below), so it must not also install the +// inherited per-page search bar. +- (BOOL)usesPageSearch { return NO; } + +// Wrap a YMSettingsItem as a search row rendered through the inherited cell builder. +- (YMSearchRow *)searchRowForItem:(YMSettingsItem *)item groupTitle:(NSString *)groupTitle { + __weak typeof(self) weakSelf = self; + YMSearchRow *row = [YMSearchRow new]; + NSString *sub = item.subtitle.length ? item.subtitle : @""; + row.searchText = [NSString stringWithFormat:@"%@ %@ %@", item.title ?: @"", sub, groupTitle ?: @""]; + row.makeCell = ^UITableViewCell *(UITableView *tv) { return [weakSelf cellForItem:item tableView:tv]; }; + return row; +} + +- (void)buildIndex { + NSMutableArray *all = [NSMutableArray array]; + for (YMSettingsGroup *group in gYMSearchGroups) { + for (YMSettingsItem *item in group.items) { + // Headers and action rows (which navigate elsewhere) aren't settings to find. + if (item.type == YMRowTypeHeader || item.type == YMRowTypeAction) continue; + if (item.title.length == 0) continue; + [all addObject:[self searchRowForItem:item groupTitle:group.title]]; + } + } + [all addObjectsFromArray:sbSearchRows(self)]; + self.allRows = all; + self.filteredRows = @[]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + [self buildIndex]; + + UISearchController *sc = [[UISearchController alloc] initWithSearchResultsController:nil]; + sc.searchResultsUpdater = self; + sc.obscuresBackgroundDuringPresentation = NO; + sc.searchBar.placeholder = LOC(@"SEARCH"); + sc.searchBar.tintColor = [UIColor colorWithRed:0.6 green:0.2 blue:0.9 alpha:1.0]; + self.searchController = sc; + + self.navigationItem.searchController = sc; + self.navigationItem.hidesSearchBarWhenScrolling = NO; + self.definesPresentationContext = YES; +} + +// Focus our own search bar once the page is actually on screen (becomeFirstResponder +// from viewDidLoad is unreliable — the bar isn't in the window yet), so the keyboard +// is up as soon as the search page appears. +- (void)viewDidAppear:(BOOL)animated { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL, BOOL))objc_msgSendSuper)(&superStruct, @selector(viewDidAppear:), animated); + + // Focus the search field so the keyboard is up on arrival. A nav-bar + // UISearchController only accepts first responder once it's active AND its bar is + // in the window, which lags the appearance transition — so activate it, then + // become first responder on the next runloop. + UISearchController *sc = self.searchController; + if (sc.isActive && sc.searchBar.isFirstResponder) return; + sc.active = YES; + dispatch_async(dispatch_get_main_queue(), ^{ + [sc.searchBar becomeFirstResponder]; + }); +} + +- (void)updateSearchResultsForSearchController:(UISearchController *)searchController { + NSString *query = searchController.searchBar.text ?: @""; + query = [query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if (query.length == 0) { + self.filteredRows = @[]; + } else { + NSMutableArray *matches = [NSMutableArray array]; + NSStringCompareOptions opts = NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch; + for (YMSearchRow *row in self.allRows) { + if ([row.searchText rangeOfString:query options:opts].location != NSNotFound) { + [matches addObject:row]; + } + } + self.filteredRows = matches; + } + [self.tableView reloadData]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.filteredRows.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + return self.filteredRows[indexPath.row].makeCell(tableView); +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + CGFloat h = self.filteredRows[indexPath.row].cellHeight; + return h > 0 ? h : UITableViewAutomaticDimension; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + YMSearchRow *row = self.filteredRows[indexPath.row]; + if (!row.onSelect) return; + __weak typeof(self) weakSelf = self; + row.onSelect(self, ^{ [weakSelf.tableView reloadData]; }); +} + +@end + +// Push the global settings search page onto the native settings nav stack. +void YMPushSettingsSearch(id settingsVC, id parentResponder) { + Class styledClass = objc_getClass("YMSettingsSearchViewControllerStyled"); + if (!styledClass) styledClass = [YMSettingsSearchViewController class]; + + YMSettingsSearchViewController *vc = (YMSettingsSearchViewController *)((id (*)(id, SEL, id))objc_msgSend)([styledClass alloc], @selector(initWithParentResponder:), parentResponder); + if (!vc) vc = [[styledClass alloc] init]; + vc.navTitle = LOC(@"SEARCH"); + [settingsVC pushViewController:vc]; +} + +#pragma mark - Convenience Factory Functions + +YMSettingsItem *YMToggle(NSString *title, NSString *subtitle, NSString *key) { + return [YMSettingsItem toggleWithTitle:title subtitle:subtitle key:key]; +} + +YMSettingsItem *YMSlider(NSString *title, NSString *subtitle, NSString *key, float min, float max, float step, float defaultValue) { + return [YMSettingsItem sliderWithTitle:title subtitle:subtitle key:key min:min max:max step:step defaultValue:defaultValue]; +} + +YMSettingsItem *YMPicker(NSString *title, NSString *subtitle, NSString *key, NSArray *options, NSInteger defaultValue) { + return [YMSettingsItem pickerWithTitle:title subtitle:subtitle key:key options:options defaultValue:defaultValue]; +} + +YMSettingsItem *YMAction(NSString *title, NSString *subtitle, void (^action)(UIViewController *vc)) { + return [YMSettingsItem actionWithTitle:title subtitle:subtitle action:action]; +} + +YMSettingsItem *YMHeader(NSString *title) { + return [YMSettingsItem headerWithTitle:title]; +} + +YMSettingsItem *YMSegment(NSString *title, NSString *key, NSArray *icons, NSInteger defaultValue) { + return [YMSettingsItem segmentWithTitle:title key:key icons:icons defaultValue:defaultValue]; +} + +YMSettingsItem *YMTextSegment(NSString *title, NSString *key, NSArray *labels, NSInteger defaultValue) { + return [YMSettingsItem textSegmentWithTitle:title key:key labels:labels defaultValue:defaultValue]; +} + +YMSettingsItem *YMImageSegment(NSString *title, NSString *key, NSArray *images, NSInteger defaultValue) { + return [YMSettingsItem imageSegmentWithTitle:title key:key images:images defaultValue:defaultValue]; +} + +#pragma mark - YMTabOrderViewController + +static NSString * const kYMTabIDs[] = { + @"home", @"shorts", @"create", @"subscriptions", @"library", @"history", @"gaming", @"sports", @"notifications", @"news", @"music", @"watchlater", @"playlist", @"like", @"live", @"post", @"video", @"movie", @"course", @"minigame", @"fashion", @"learning" +}; +static const NSInteger kYMTabCount = 22; +static const NSInteger kYMTabMaxEnabled = 6; +static const NSInteger kYMTabMinEnabled = 1; + +@interface YMTabOrderViewController : UIViewController +- (UITableView *)tableView; +- (void)setTableView:(UITableView *)tv; +- (NSMutableArray *)tabData; +- (void)setTabData:(NSMutableArray *)data; +- (NSArray *)initialSnapshot; +- (void)setInitialSnapshot:(NSArray *)snap; +@end + +static const void *kYMTabTableViewKey = &kYMTabTableViewKey; +static const void *kYMTabDataKey = &kYMTabDataKey; +static const void *kYMTabSnapshotKey = &kYMTabSnapshotKey; +// Saved copies of the shared navigation bar's appearance, restored when this +// screen is dismissed. This screen installs its own opaque navigation bar +// appearance; because that appearance is shared, leaving it in place would repaint +// the previous screen's back button with the appearance's default (blue) tint. +static const void *kYMTabSavedStdAppearanceKey = &kYMTabSavedStdAppearanceKey; +static const void *kYMTabSavedScrollEdgeAppearanceKey = &kYMTabSavedScrollEdgeAppearanceKey; + +@implementation YMTabOrderViewController + +- (UITableView *)tableView { return objc_getAssociatedObject(self, kYMTabTableViewKey); } +- (void)setTableView:(UITableView *)tv { objc_setAssociatedObject(self, kYMTabTableViewKey, tv, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (NSMutableArray *)tabData { return objc_getAssociatedObject(self, kYMTabDataKey); } +- (void)setTabData:(NSMutableArray *)data { objc_setAssociatedObject(self, kYMTabDataKey, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (NSArray *)initialSnapshot { return objc_getAssociatedObject(self, kYMTabSnapshotKey); } +- (void)setInitialSnapshot:(NSArray *)snap { objc_setAssociatedObject(self, kYMTabSnapshotKey, snap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + +- (NSString *)localizedNameForTabID:(NSString *)tabID { + if ([tabID isEqualToString:@"home"]) return LOC(@"HOME_TAB"); + if ([tabID isEqualToString:@"shorts"]) return LOC(@"SHORTS_TAB"); + if ([tabID isEqualToString:@"create"]) return LOC(@"CREATE_TAB"); + if ([tabID isEqualToString:@"subscriptions"]) return LOC(@"SUBSCRIPTIONS_TAB"); + if ([tabID isEqualToString:@"library"]) return LOC(@"LIBRARY_TAB"); + if ([tabID isEqualToString:@"history"]) return LOC(@"HISTORY_TAB"); + if ([tabID isEqualToString:@"gaming"]) return LOC(@"GAMING_TAB"); + if ([tabID isEqualToString:@"sports"]) return LOC(@"SPORTS_TAB"); + if ([tabID isEqualToString:@"notifications"]) return LOC(@"NOTI_TAB"); + if ([tabID isEqualToString:@"news"]) return LOC(@"NEWS_TAB"); + if ([tabID isEqualToString:@"music"]) return LOC(@"MUSIC_TAB"); + if ([tabID isEqualToString:@"watchlater"]) return LOC(@"WATCH_LATER_TAB"); + if ([tabID isEqualToString:@"playlist"]) return LOC(@"PLAYLIST_TAB"); + if ([tabID isEqualToString:@"like"]) return LOC(@"LIKE_TAB"); + if ([tabID isEqualToString:@"live"]) return LOC(@"LIVE_TAB"); + if ([tabID isEqualToString:@"post"]) return LOC(@"POST_TAB"); + if ([tabID isEqualToString:@"video"]) return LOC(@"VIDEO_TAB"); + if ([tabID isEqualToString:@"movie"]) return LOC(@"MOVIE_TAB"); + if ([tabID isEqualToString:@"course"]) return LOC(@"COURSE_TAB"); + if ([tabID isEqualToString:@"minigame"]) return LOC(@"MINIGAME_TAB"); + if ([tabID isEqualToString:@"fashion"]) return LOC(@"FASHION_TAB"); + if ([tabID isEqualToString:@"learning"]) return LOC(@"LEARNING_TAB"); + return tabID; +} + +- (UIImage *)iconForTabID:(NSString *)tabID { + static YTAssetLoader *cachedLoader = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + cachedLoader = [[%c(YTAssetLoader) alloc] initWithBundle:YouModBundle()]; + }); + + if ([tabID isEqualToString:@"create"]) { + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:18 weight:UIImageSymbolWeightMedium]; + return [[UIImage systemImageNamed:@"plus" withConfiguration:config] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } + NSDictionary *ytIconTypes = @{@"home": @(65), @"shorts": @(769), @"subscriptions": @(66), @"library": @(61)}; + NSDictionary *bundleIcons = @{@"history": @"icons/history", @"gaming": @"icons/gaming", @"sports": @"icons/sports", @"notifications": @"icons/noti", @"news": @"icons/news", @"music": @"icons/music", @"watchlater": @"icons/watchlater", @"playlist": @"icons/playlist", @"like": @"icons/like", @"live": @"icons/live", @"post": @"icons/post", @"video": @"icons/video", @"movie": @"icons/movie", @"course": @"icons/course", @"minigame": @"icons/minigame", @"fashion": @"icons/fashion", @"learning": @"icons/learning"}; + + NSNumber *iconType = ytIconTypes[tabID]; + if (iconType) { + YTIIcon *icon = [%c(YTIIcon) new]; + if (icon) { + ((void (*)(id, SEL, int))objc_msgSend)(icon, @selector(setIconType:), [iconType intValue]); + if ([icon respondsToSelector:@selector(iconImageWithColor:)]) { + return [[icon iconImageWithColor:[UIColor whiteColor]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } + } + } + + NSString *bundleName = bundleIcons[tabID]; + if (bundleName && cachedLoader) { + UIImage *img = [cachedLoader imageNamed:bundleName]; + if (img) return [img imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } + + return nil; +} + +- (void)viewDidLoad { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL))objc_msgSendSuper)(&superStruct, @selector(viewDidLoad)); + + self.title = LOC(@"MANAGE_TABS"); + [self loadTabData]; + [self takeSnapshot]; + + // The previous screen's navigation bar appearance is saved before this screen + // installs its own opaque appearance, and restored on dismissal so that screen + // keeps its own back-button tint. + UINavigationBar *sharedNavBar = self.navigationController.navigationBar; + objc_setAssociatedObject(self, kYMTabSavedStdAppearanceKey, sharedNavBar.standardAppearance, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kYMTabSavedScrollEdgeAppearanceKey, sharedNavBar.scrollEdgeAppearance, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Configure navigation bar appearance with solid color + UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; + [appearance configureWithDefaultBackground]; + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + appearance.backgroundColor = [%c(YTColor) black3]; + } else { + appearance.backgroundColor = [UIColor systemBackgroundColor]; + } + self.navigationController.navigationBar.standardAppearance = appearance; + self.navigationController.navigationBar.scrollEdgeAppearance = appearance; + + self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; + self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.editing = YES; + self.tableView.allowsSelectionDuringEditing = NO; + self.tableView.estimatedRowHeight = 56; + self.tableView.rowHeight = UITableViewAutomaticDimension; + + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + self.tableView.backgroundColor = [%c(YTColor) black3]; + } else { + self.tableView.backgroundColor = [UIColor systemBackgroundColor]; + } + + [self.view addSubview:self.tableView]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle) { + self.tableView.backgroundColor = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) + ? [%c(YTColor) black3] + : [UIColor systemBackgroundColor]; + + // Update navigation bar appearance for dark/light mode + UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; + [appearance configureWithDefaultBackground]; + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + appearance.backgroundColor = [%c(YTColor) black3]; + } else { + appearance.backgroundColor = [UIColor systemBackgroundColor]; + } + self.navigationController.navigationBar.standardAppearance = appearance; + self.navigationController.navigationBar.scrollEdgeAppearance = appearance; + + [self.tableView reloadData]; + } +} + +- (void)viewWillAppear:(BOOL)animated { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL, BOOL))objc_msgSendSuper)(&superStruct, @selector(viewWillAppear:), animated); +} + +- (void)viewWillDisappear:(BOOL)animated { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL, BOOL))objc_msgSendSuper)(&superStruct, @selector(viewWillDisappear:), animated); + + // Restore the previous screen's saved navigation bar appearance so it keeps + // its own back-button tint rather than this screen's opaque appearance. + UINavigationBar *navBar = self.navigationController.navigationBar; + navBar.standardAppearance = objc_getAssociatedObject(self, kYMTabSavedStdAppearanceKey); + navBar.scrollEdgeAppearance = objc_getAssociatedObject(self, kYMTabSavedScrollEdgeAppearanceKey); +} + +- (void)viewDidLayoutSubviews { + Class ytStyled = objc_getClass("YTStyledViewController"); + struct objc_super superStruct = { self, ytStyled ?: [UIViewController class] }; + ((void (*)(struct objc_super *, SEL))objc_msgSendSuper)(&superStruct, @selector(viewDidLayoutSubviews)); + YTQTMButton *backButton = [self valueForKey:@"_backButton"]; + + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + backButton.tintColor = [UIColor whiteColor]; + } else { + backButton.tintColor = [UIColor blackColor]; + } +} + +- (void)loadTabData { + NSArray *savedOrder = [[NSUserDefaults standardUserDefaults] arrayForKey:TabOrder]; + NSMutableArray *data = [NSMutableArray array]; + + if (savedOrder.count > 0) { + for (NSDictionary *entry in savedOrder) { + NSString *tabID = entry[@"id"]; + BOOL enabled = [entry[@"enabled"] boolValue]; + if (tabID) { + [data addObject:[@{@"id": tabID, @"enabled": @(enabled)} mutableCopy]]; + } + } + // Add any new tabs not in saved data + for (NSInteger i = 0; i < kYMTabCount; i++) { + NSString *tabID = kYMTabIDs[i]; + BOOL found = NO; + for (NSDictionary *d in data) { + if ([d[@"id"] isEqualToString:tabID]) { found = YES; break; } + } + if (!found) { + [data addObject:[@{@"id": tabID, @"enabled": @NO} mutableCopy]]; + } + } + } else { + // Default: Home, Shorts, Create, Subscriptions, Library enabled + for (NSInteger i = 0; i < kYMTabCount; i++) { + BOOL defaultEnabled = i < 5; + [data addObject:[@{@"id": kYMTabIDs[i], @"enabled": @(defaultEnabled)} mutableCopy]]; + } + } + + self.tabData = data; +} + +- (void)saveTabData { + NSMutableArray *toSave = [NSMutableArray array]; + for (NSMutableDictionary *entry in self.tabData) { + [toSave addObject:@{@"id": entry[@"id"], @"enabled": entry[@"enabled"]}]; + } + [[NSUserDefaults standardUserDefaults] setObject:toSave forKey:TabOrder]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"YouModUpdateTabBar" object:nil]; +} + +- (void)takeSnapshot { + NSMutableArray *snap = [NSMutableArray array]; + for (NSDictionary *entry in self.tabData) { + [snap addObject:@{@"id": entry[@"id"], @"enabled": entry[@"enabled"]}]; + } + self.initialSnapshot = [snap copy]; +} + +- (NSInteger)enabledCount { + NSInteger count = 0; + for (NSDictionary *entry in self.tabData) { + if ([entry[@"enabled"] boolValue]) count++; + } + return count; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.tabData.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *cellID = @"YMTabCell"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; + UISwitch *sw; + + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + sw = [[UISwitch alloc] init]; + sw.onTintColor = [UIColor colorWithRed:0.6 green:0.2 blue:0.9 alpha:1.0]; + [sw addTarget:self action:@selector(tabToggleChanged:) forControlEvents:UIControlEventValueChanged]; + sw.translatesAutoresizingMaskIntoConstraints = NO; + sw.tag = 999; + [cell.contentView addSubview:sw]; + + [NSLayoutConstraint activateConstraints:@[ + [sw.centerYAnchor constraintEqualToAnchor:cell.contentView.centerYAnchor], + [sw.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-16] + ]]; + } else { + sw = [cell.contentView viewWithTag:999]; + } + + NSMutableDictionary *entry = self.tabData[indexPath.row]; + NSString *tabID = entry[@"id"]; + BOOL enabled = [entry[@"enabled"] boolValue]; + + cell.textLabel.text = [self localizedNameForTabID:tabID]; + cell.textLabel.textColor = [UIColor labelColor]; + cell.textLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + + UIImage *tabIcon = [self iconForTabID:tabID]; + cell.imageView.image = tabIcon; + cell.imageView.tintColor = [UIColor labelColor]; + + sw.on = enabled; + objc_setAssociatedObject(sw, kYMSwitchKeyAssoc, tabID, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + return cell; +} + +- (void)tabToggleChanged:(UISwitch *)sender { + NSString *tabID = objc_getAssociatedObject(sender, kYMSwitchKeyAssoc); + if (!tabID) return; + + NSMutableDictionary *entry = nil; + for (NSMutableDictionary *d in self.tabData) { + if ([d[@"id"] isEqualToString:tabID]) { entry = d; break; } + } + if (!entry) return; + + BOOL wantsEnabled = sender.on; + + if (wantsEnabled && [self enabledCount] >= kYMTabMaxEnabled) { + sender.on = NO; + YTAlertView *alert = [%c(YTAlertView) infoDialog]; + alert.title = LOC(@"TAB_LIMIT"); + alert.subtitle = LOC(@"TAB_LIMIT_DESC"); + [alert show]; + return; + } else if (!wantsEnabled && [self enabledCount] <= kYMTabMinEnabled) { + sender.on = YES; + YTAlertView *alert = [%c(YTAlertView) infoDialog]; + alert.title = LOC(@"WARNING"); + alert.subtitle = LOC(@"ZERO_TAB_DESC"); + [alert show]; + return; + } + + entry[@"enabled"] = @(wantsEnabled); + [self saveTabData]; +} + +#pragma mark - Reordering + +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } + +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)from toIndexPath:(NSIndexPath *)to { + NSMutableDictionary *item = self.tabData[from.row]; + [self.tabData removeObjectAtIndex:from.row]; + [self.tabData insertObject:item atIndex:to.row]; + [self saveTabData]; +} + +- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { + return UITableViewCellEditingStyleNone; +} + +- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath { + return NO; +} + +#pragma mark - Section Header/Footer + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return UITableViewAutomaticDimension; +} + +- (UIColor *)ymSecondaryColor { + return [UIColor colorWithWhite:0.55 alpha:1.0]; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UIView *headerView = [[UIView alloc] init]; + headerView.backgroundColor = [UIColor clearColor]; + + UILabel *hintLabel = [[UILabel alloc] init]; + hintLabel.text = LOC(@"TAB_REORDER_HINT"); + hintLabel.textColor = [self ymSecondaryColor]; + hintLabel.font = [UIFont systemFontOfSize:13]; + hintLabel.numberOfLines = 0; + hintLabel.translatesAutoresizingMaskIntoConstraints = NO; + [headerView addSubview:hintLabel]; + + [NSLayoutConstraint activateConstraints:@[ + [hintLabel.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor constant:16], + [hintLabel.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor constant:-16], + [hintLabel.topAnchor constraintEqualToAnchor:headerView.topAnchor constant:12], + [hintLabel.bottomAnchor constraintEqualToAnchor:headerView.bottomAnchor constant:-12] + ]]; + + return headerView; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { return 0; } +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { return [[UIView alloc] init]; } + +@end + +void YMPushTabOrder(id settingsVC, id parentResponder) { + Class styledClass = objc_getClass("YMTabOrderViewControllerStyled"); + if (!styledClass) styledClass = [YMTabOrderViewController class]; + + YMTabOrderViewController *vc = (YMTabOrderViewController *)((id (*)(id, SEL, id))objc_msgSend)([styledClass alloc], @selector(initWithParentResponder:), parentResponder); + if (!vc) vc = [[styledClass alloc] init]; + [settingsVC pushViewController:vc]; +} + +// Modal entry point for opening Manage Tabs without a YTSettingsViewController nav stack +// (used by the long-press gesture on the Home tab). Wraps the standard tab-order VC in +// a UINavigationController with a Done button and presents from the topmost VC. +void YMPresentTabOrderModally(id parentResponder) { + Class styledClass = objc_getClass("YMTabOrderViewControllerStyled"); + if (!styledClass) styledClass = [YMTabOrderViewController class]; + + YMTabOrderViewController *vc = (YMTabOrderViewController *)((id (*)(id, SEL, id))objc_msgSend)([styledClass alloc], @selector(initWithParentResponder:), parentResponder); + if (!vc) vc = [[styledClass alloc] init]; + + UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; + nav.modalPresentationStyle = UIModalPresentationFormSheet; + + UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; + [appearance configureWithDefaultBackground]; + appearance.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) { + if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + return [%c(YTColor) black3]; + } else { + return [UIColor systemBackgroundColor]; + } + }]; + + nav.navigationBar.standardAppearance = appearance; + nav.navigationBar.scrollEdgeAppearance = appearance; + nav.navigationBar.compactAppearance = appearance; + + __weak UINavigationController *weakNav = nav; + UIAction *doneAction = [UIAction actionWithTitle:@"" image:nil identifier:nil handler:^(__unused UIAction *action) { + [weakNav dismissViewControllerAnimated:YES completion:nil]; + }]; + vc.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemDone + primaryAction:doneAction]; + + UIViewController *presenter = YouModTopViewController(nil); + if (!presenter) return; + [presenter presentViewController:nav animated:YES completion:nil]; +} + +#pragma mark - Entry Point + +void YMPushSubSettings(NSString *title, NSArray *items, id settingsVC, id parentResponder) { + Class styledClass = objc_getClass("YMSubSettingsViewControllerStyled"); + if (!styledClass) styledClass = [YMSubSettingsViewController class]; + + YMSubSettingsViewController *vc = (YMSubSettingsViewController *)((id (*)(id, SEL, id))objc_msgSend)([styledClass alloc], @selector(initWithParentResponder:), parentResponder); + if (!vc) vc = [[styledClass alloc] init]; + vc.navTitle = title; + vc.items = items; + [settingsVC pushViewController:vc]; +} + +#pragma mark - Runtime Class Registration + +static void ymRegisterStyledSubclass(Class sourceClass, const char *name) { + Class ytStyled = %c(YTStyledViewController); + Class newClass = objc_allocateClassPair(ytStyled, name, 0); + if (!newClass) return; + + // Copy methods and properties from sourceClass up through its own class chain, + // stopping before UIViewController/YTStyledViewController. Walking the chain + // (not just sourceClass) is required when sourceClass itself subclasses another + // YouMod controller — e.g. YMSettingsSearchViewController inherits its cell + // builders from YMSubSettingsViewController; a single-level copy would drop + // them. Subclass levels are copied first so an override wins over its parent + // (class_addMethod does not replace an already-added selector). + for (Class cls = sourceClass; cls && cls != [UIViewController class]; cls = class_getSuperclass(cls)) { + unsigned int count = 0; + Method *methods = class_copyMethodList(cls, &count); + for (unsigned int i = 0; i < count; i++) { + class_addMethod(newClass, method_getName(methods[i]), method_getImplementation(methods[i]), method_getTypeEncoding(methods[i])); + } + free(methods); + + unsigned int propCount = 0; + objc_property_t *props = class_copyPropertyList(cls, &propCount); + for (unsigned int i = 0; i < propCount; i++) { + unsigned int attrCount = 0; + objc_property_attribute_t *attrs = property_copyAttributeList(props[i], &attrCount); + class_addProperty(newClass, property_getName(props[i]), attrs, attrCount); + free(attrs); + } + free(props); + } + + objc_registerClassPair(newClass); +} + +%hook YTQTMButton +- (void)layoutSubviews { + %orig; + if ([self.accessibilityIdentifier isEqualToString:@"id.ui.title.tab.button"]) { + UIColor *customTitle = [self valueForKey:@"_desiredCustomTitleColor"]; + + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + self.titleLabel.textColor = [UIColor whiteColor]; + if (customTitle) { + [self setValue:[UIColor whiteColor] forKey:@"_desiredCustomTitleColor"]; + } + } else { + self.titleLabel.textColor = [UIColor blackColor]; + if (customTitle) { + [self setValue:[UIColor blackColor] forKey:@"_desiredCustomTitleColor"]; + } + } + } else if ([self.accessibilityIdentifier isEqualToString:@"id.ui.browse.back.button"]) { + if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + self.tintColor = [UIColor whiteColor]; + } else { + self.tintColor = [UIColor blackColor]; + } + } +} +%end + +%hook YTPivotBarViewController +- (void)viewDidLoad { + %orig; + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"YouModUpdateTabBar" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(YouModReloadTabBar:) + name:@"YouModUpdateTabBar" + object:nil]; +} +%new +- (void)YouModReloadTabBar:(id)arg { + if ([self.parentViewController isKindOfClass:%c(YTAppViewControllerImpl)]) { + YTAppViewControllerImpl *appcon = (YTAppViewControllerImpl *)self.parentViewController; + [appcon refreshPivotBarWithTriggedByNotification:YES]; + } else { + YTAppViewController *appcon = (YTAppViewController *)self.parentViewController; + [appcon refreshPivotBarWithTriggedByNotification:YES]; + } +} +%end + +%ctor { + ymRegisterStyledSubclass([YMSubSettingsViewController class], "YMSubSettingsViewControllerStyled"); + ymRegisterStyledSubclass([YMTabOrderViewController class], "YMTabOrderViewControllerStyled"); + ymRegisterStyledSubclass([YMSettingsSearchViewController class], "YMSettingsSearchViewControllerStyled"); +} diff --git a/Makefile b/Makefile index 2718ca0a..c93851ce 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ # Original Makefile from YTLite +DEBUG = 0 FINALPACKAGE = 1 ARCHS = arm64 TARGET := iphone:clang:latest:14.0 @@ -6,7 +7,7 @@ TARGET := iphone:clang:latest:14.0 include $(THEOS)/makefiles/common.mk TWEAK_NAME = YouMod -$(TWEAK_NAME)_FRAMEWORKS = UIKit Foundation +$(TWEAK_NAME)_FRAMEWORKS = UIKit Foundation AudioToolbox MediaPlayer $(TWEAK_NAME)_CFLAGS = -fobjc-arc $(TWEAK_NAME)_FILES = $(wildcard Files/*.x) diff --git a/README.md b/README.md index 0736c3bb..af6e12c1 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,37 @@ # YouMod + +Badge +Badge +Badge + A tweak that's try to recreate features like in YTLite as much as possible. Contributions are welcome! ^^ -Thanks for 200 stars and forks! This might be the best project I've done. +Thanks for 350 stars and 650 forks! This might be the best project I've done. If you want to contribute this project, scroll down to `How to contribute this project?` part. + ## Why I created this project? The reason is because of YTLite no longer free. So I'll try my best to recreate all the features as much as I can, plus alternative tweaks combined. + ## Features This tweak add a lot of QoL feature and can hide elements in YouTube. Such as: -- Downloading (video, audio, select quality) -- Feed modifications (hide shorts, hide post) -- Appearance modification (OLED theme, keyboard, add/remove buttons) -- Navigation bar (hide buttons) -- Player (add/remove buttons and elements) -- Shorts (add/remove buttons and elements) -- Tab bar (reorder, add/remove buttons) - +- Downloading (Video, Audio, Captions, Thumbnail, Video datails, etc. Currently supports up to 1080p60) +- Appearance (OLED theme and keyboard) +- Navigation bar (Hiding buttons) +- Player (Hiding elements, buttons, and Add custom actions, etc.) +- Shorts (Hiding elements and Add custom actions) +- Tab bar (Reorder tabs, Hiding things, etc.) +- Built-in SponsorBlock - And much more! ## Supported YouTube version -Up to the latest, this is the goal of this tweak. Any versions starting from 19.x - latest. +Up to the latest, this is the goal of this tweak. Any versions starting from 19.x - latest and running at least iOS 14. ## How to build the IPA? 1. Click on **Sync fork**, and if your branch is out-of-date, click on **Update branch**. 2. Navigate to **Actions** in your forked repository and select **Build IPA with tweaks**. 3. Click the **Run workflow** button located on the right side. -4. Get a decrypted .ipa file (I cannot provide this due to legal reasons.), then upload it to a file provider (e.g., filebin.net,filemail.com,catbox.moe or Dropbox is recommended). Paste the URL of the decrypted IPA file in the provided field. You can also change the BundleID and Display Name if desired. +4. Get a decrypted .ipa file (I cannot provide this due to legal reasons.), then upload it to a file provider (e.g., filebin.net,filemail.com,catbox.moe, Mega, or Dropbox is recommended). Paste the URL of the decrypted IPA file in the provided field. You can also change the BundleID and Display Name if desired. **NOTE:** Make sure to provide a direct download link to the file, not a link to a webpage. Otherwise, the process will fail. @@ -46,11 +52,16 @@ This project uses GPLv3 license. See [LICENSE](https://github.com/Tonwalter888/Y ## Credits - [YTLite](https://github.com/dayanch96/YTLite) - dayanch96 +- [YTMusicUltimate](https://github.com/dayanch96/YTMusicUltimate) - dayanch96 - [OLEDKeyboard](https://github.com/dayanch96/OLEDKeyboard) - dayanch96 +- [YTKACE](https://github.com/Epic0001/YTKACE) - itzzace - [YTLitePlus](https://github.com/YTLitePlus/YTLitePlus) - [uYouEnhanced](https://github.com/arichornlover/uYouEnhanced) - arichornlover - [YTweaks](https://github.com/fosterbarnes/YTweaks) - fosterbarnes -- [daisuke1227](https://github.com/daisuke1227) - Implementing download features +- [jefemagril](https://github.com/jefemagril) - Implemented rewind/fast forward buttons to system Control Center +- [grohit1810](https://github.com/grohit1810) - Implemented built-in SponsorBlock, whole new settings logic, and others +- [SponsorBlock](https://sponsor.ajay.app) - SponsorBlock segments API +- [Google Fonts](https://fonts.google.com/icons) - Icons for custom tabs - And [PoomSmart](https://github.com/PoomSmart)'s tweaks -Also thanks to all the contributors in the past! +Also thanks to the other contributors in the past! \ No newline at end of file diff --git a/control b/control index 579c5b7a..87d7e1f6 100644 --- a/control +++ b/control @@ -1,9 +1,9 @@ Package: dev.water888.youmod Name: YouMod -Version: 1.3.0 +Version: 2.0.0 Architecture: iphoneos-arm Description: A lightweight YouTube enhancer -Maintainer: Tonwalter888 +Maintainer: Tonwalter888, G Rohit Author: Tonwalter888 Section: Tweaks Depends: mobilesubstrate (>= 0.9.5000), firmware (>= 14.0) \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/Info.plist b/layout/Library/Application Support/YouMod.bundle/Info.plist index fe35029e..53ba69a4 100644 --- a/layout/Library/Application Support/YouMod.bundle/Info.plist +++ b/layout/Library/Application Support/YouMod.bundle/Info.plist @@ -13,11 +13,11 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.2.0 + 1.3.0 CFBundleSignature ???? CFBundleVersion - 1.2.0 + 1.3.0 NSPrincipalClass YouMod diff --git a/layout/Library/Application Support/YouMod.bundle/ar.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/ar.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/ar.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/ar.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/cs.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/cs.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/cs.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/cs.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/da.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/da.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/da.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/da.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/de.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/de.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/de.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/de.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/el.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/el.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/el.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/el.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/en.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/en.lproj/Localizable.strings index bccaf550..b12eb622 100644 --- a/layout/Library/Application Support/YouMod.bundle/en.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/en.lproj/Localizable.strings @@ -1,11 +1,21 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"SEARCH" = "Search"; +"SEARCH_DESC" = "Search all YouMod settings."; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +34,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +43,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +139,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +157,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +245,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +285,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +299,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +407,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/es.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/es.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/es.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/es.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/fi.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/fi.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/fi.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/fi.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/fr.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/fr.lproj/Localizable.strings index 3e68e72a..692fd72c 100644 --- a/layout/Library/Application Support/YouMod.bundle/fr.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/fr.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "REMARQUE : Certaines fonctionnalités de masquage peuvent laisser une case vide."; "SETTINGS" = "Paramètres"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Signaler des problèmes ou demander de nouvelles fonctionnalités"; "NEW_ISSUES_DESC" = "Des bugs ou des demandes de nouvelles fonctionnalités ? Appuie ici."; "SOURCE_CODES" = "Code source"; "SOURCE_CODES_DESC" = "Jette un œil au code !"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "Réglages YouMod"; "PERFER" = "Réglages"; "IMPORT" = "Importer les réglages"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "Aucun réglages à importer"; "ERROR_NO_KEYS_EXPORT" = "Aucun réglages à exporter"; "OVERRIDE" = "Cela va remplacer les réglages actuels. Souhaite-tu continuer?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache"; "CLEARCACHE" = "Vider le cache"; @@ -36,8 +44,90 @@ "DOWNLOAD_MANAGER_DESC" = "Remplace le téléchargeur vidéo de YouTube par celui de YouMod. Avec la possibilité de télécharger des vidéos, des audios, des sous-titres et des vignettes"; "DOWNLOAD_SAVE_PHOTOS" = "Enregistrer automatiquement dans Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Enregistre automatiquement les vidéos et images téléchargées directement dans votre application Photos."; -"DOWNLOAD_DRC_AUDIO" = "Préférer l'audio DRC"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Préfère les flux audio avec Dynamic Range Compression (DRC) pour un niveau de volume plus consistant."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Apparence"; "OLED_THEME" = "Thème OLED"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Applique un effet noir (OLED) au clavier pour le thème sombre."; "NAVBAR" = "Bar de navigation"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Cacher le logo YouTube"; "HIDE_YT_LOGO_DESC" = "Cache le logo YouTube dans la bar de navigation."; "PREMIUM_LOGO" = "Utiliser le logo YouTube Premium"; @@ -62,32 +154,80 @@ "FEED" = "Fil YouTube"; "HIDE_SUBBAR" = "Cacher la barre d'abonnements"; "HIDE_SUBBAR_DESC" = "Cache la barre d'abonnements en haut du fil YouTube."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Cacher le générateur de playlists musicales"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Cache le générateur de playlists musicales (Sélection musicale en trois titres) dans le fil YouTube."; "HIDE_FEED_POST" = "Cacher les posts"; "HIDE_FEED_POST_DESC" = "Cacher les posts dans le fil YouTube."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Cacher les Shorts"; "HIDE_SHORTS_SHELF_DESC" = "Cache les Shorts dans le fil YouTube, y compris dans l'onglet Abonnements."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Cacher l'historique de recherche"; "HIDE_SEARCH_HISTORY_DESC" = "Cache l'historique de recherche et les suggestions lors de l'utilisation de la barre de recherche.\nNOTE: Ton historique de recherche restera accessible sur tes autres clients YouTube."; -"HIDE_SUB_BUTTON" = "Cacher le bouton S'abonner"; -"HIDE_SUB_BUTTON_DESC" = "Cache le bouton s'abonner dans les chaînes et les descriptions de vidéos."; -"HIDE_SHOP_BUTTON" = "Cacher le bouton boutique"; -"HIDE_SHOP_BUTTON_DESC" = "Cache le bouton boutique/magasin dans les descriptions de chaînes."; -"HIDE_MEMBER_BUTTON" = "Cacher le bouton rejoindre"; -"HIDE_MEMBER_BUTTON_DESC" = "Cache le bouton 'Rejoindre' dans les chaînes et les descriptions de vidéos."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Lecteur"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "HIDE_AUTOPLAY" = "Cacher le bouton lecture automatique"; "HIDE_AUTOPLAY_DESC" = "Cache le bouton de lecture automatique dans l'interface vidéo."; "HIDE_CAPTIONS_BUTTON" = "Cacher le bouton des sous-titres"; "HIDE_CAPTIONS_BUTTON_DESC" = "Cache le bouton des sous-titres dans l'interface vidéo."; "HIDE_CAST_BUTTON_PLAYER" = "Cacher le bouton de diffusion"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Cache le bouton de diffusion dans l'interface vidéo."; -"HIDE_PREV_BUTTON" = "Cacher le bouton précédent/retour"; -"HIDE_PREV_BUTTON_DESC" = "Cache le bouton précédent/retour dans l'interface vidéo."; -"HIDE_NEXT_BUTTON" = "Cacher le bouton suivant/passer"; -"HIDE_NEXT_BUTTON_DESC" = "Cache le bouton suivant/passer dans l'interface vidéo."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Remplacer les boutons Précédent/Suivant"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Remplace les boutons Précédent et Suivant du lecteur par des boutons Reculer et Avance rapide."; "REMOVE_DARK_OVERLAY" = "Supprimer l'overlay sombre"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Cacher 'Inclus du contenu sponsorisé' dans l'interface vidéo."; "HIDE_WATERMARK" = "Cacher le filigrane"; "HIDE_WATERMARK_DESC" = "Cache le filigrane de la chaîne en bas de l'interface vidéo."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Activer le contrôle par gestes"; "GESTURES_DESC" = "Faites glisser vers le haut ou le bas sur les bords gauche/droit de l'écran vidéo pour régler la luminosité, le volume ou la vitesse de lecture."; "GESTURE_AREA" = "Zone d'activation des gestes"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Désactive la fonctionnalité qui affiche le temps restant dans une vidéo en appuyant sur l'horodatage."; "ALWAYS_SHOW_REMAINING" = "Toujours afficher le temps restant"; "ALWAYS_SHOW_REMAINING_DESC" = "Affiche en permanence le temps restant par défaut dans l'interface du lecteur."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Afficher le temps restant supplémentaire"; "SHOW_REMAINING_EXTRA_DESC" = "Ajoute l'heure de fin estimée au libellé dans l'interface du lecteur."; "HIDE_FULLSCREEN_ACTIONS" = "Cacher les actions en plein écran"; @@ -148,93 +296,105 @@ "HIDE_CONTENT_WARNING_DESC" = "Ignore automatiquement l'alerte d'avertissement pour contenu sensible dans certaines vidéos."; "AUTO_FULLSCREEN" = "Plein écran automatique"; "AUTO_FULLSCREEN_DESC" = "Passe automatiquement en mode plein écran au démarrage de la lecture."; -"AUTO_DISABLES_CAPTION" = "Désactivation automatique des sous-titres"; -"AUTO_DISABLES_CAPTION_DESC" = "Désactive automatiquement les sous-titres (CC) à chaque nouvelle lecture de vidéo."; "PORTRAIT_FULLSCREEN" = "Plein écran en mode portrait"; "PORTRAIT_FULLSCREEN_DESC" = "Active le support du mode plein écran en portrait."; "OLD_QUALITY_PICKER" = "Utiliser l'ancien sélecteur de qualité vidéo"; "OLD_QUALITY_PICKER_DESC" = "Rétablit le menu de sélection de qualité vidéo en version classique (affiche immédiatement toutes les résolutions)."; "EXTRA_SPEED" = "Options de vitesse supplémentaires"; "EXTRA_SPEED_DESC" = "Ajoute plus d'options de vitesse de lecture jusqu'à 10x."; -"DISABLE_HINTS" = "Désactiver les conseils"; -"DISABLE_HINTS_DESC" = "Désactive les conseils et les pop-ups interactifs."; "FORCE_MINIPLAYER" = "Forcer le minilecteur"; "FORCE_MINIPLAYER_DESC" = "Permet à toutes les vidéos, y compris celles destinées aux enfants, de se lire en minilecteur lorsqu'on ferme le lecteur."; "FORCE_SEEKBAR" = "Toujours afficher la barre de progression"; "FORCE_SEEKBAR_DESC" = "Assure que la barre de progression reste visible en permanence en mode plein écran."; -"HIDE_LIKE_BUTTON" = "Cacher le bouton J'aime"; -"HIDE_LIKE_BUTTON_DESC" = "Cache le bouton J'aime dans la barre d'actions sous l'interface vidéo."; -"HIDE_DISLIKE_BUTTON" = "Cacher le bouton Je n'aime pas"; -"HIDE_DISLIKE_BUTTON_DESC" = "Cache le bouton Je n'aime pas dans la barre d'actions sous l'interface vidéo."; -"HIDE_SHARE_BUTTON" = "Cacher le bouton Partager"; -"HIDE_SHARE_BUTTON_DESC" = "Cache le bouton Partager dans la barre d'actions sous l'interface vidéo."; -"HIDE_DOWNLOAD_BUTTON" = "Cacher le bouton Télécharger"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Cache le bouton Télécharger dans la barre d'actions sous l'interface vidéo."; -"HIDE_CLIP_BUTTON" = "Cacher le bouton Clip"; -"HIDE_CLIP_BUTTON_DESC" = "Cache le bouton Clip dans la barre d'actions sous l'interface vidéo."; -"HIDE_REMIX_BUTTON" = "Cacher le bouton Remix"; -"HIDE_REMIX_BUTTON_DESC" = "Cache le bouton Remix dans la barre d'actions sous l'interface vidéo."; -"HIDE_SAVE_BUTTON" = "Cacher le bouton Enregistrer"; -"HIDE_SAVE_BUTTON_DESC" = "Cache le bouton Enregistrer dans la barre d'actions sous l'interface vidéo."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_ACTION_BAR" = "Hide actions bar"; +"HIDE_ACTION_BAR_DESC" = "Removes the bar containing the like, dislike, share buttons etc. below the video player."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Cacher le bouton J'aime"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Cache le bouton J'aime dans le lecteur Shorts."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Cacher le bouton Je n'aime pas"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Cache le bouton Je n'aime pas dans le lecteur Shorts."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Cacher le bouton Commentaires"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Cache le bouton Commentaires dans le lecteur Shorts."; -"HIDE_SHORTS_SHARE_BUTTON" = "Cacher le bouton Partager"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Cache le bouton Partager dans le lecteur Shorts."; -"HIDE_SHORTS_REMIX_BUTTON" = "Cacher le bouton Remix"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Cache le bouton Remix dans le lecteur Shorts."; -"HIDE_METADATA_BUTTON" = "Cacher le bouton de métadonnées audio"; -"HIDE_METADATA_BUTTON_DESC" = "Cache le bouton affichant la source audio ou les métadonnées musicales dans le lecteur Shorts."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Cacher les produits"; "HIDE_SHORTS_PRODUCT_DESC" = "Cache les produits tagués et les liens de shopping dans l'interface Shorts."; "HIDE_SHORTS_RECBAR" = "Cacher la barre de recommandations"; "HIDE_SHORTS_RECBAR_DESC" = "Cache la barre d'actions suggérées dans le lecteur Shorts (ex. Essayer ce son, Shopping)."; -"HIDE_SHORTS_COMMIT" = "Cacher la barre de commissions"; -"HIDE_SHORTS_COMMIT_DESC" = "Cache la barre indiquant que la vidéo est sponsorisée dans le lecteur Shorts."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Cacher le bouton Abonnements"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Cache le bouton Abonnements en haut du lecteur Shorts."; -"HIDE_SHORTS_LIVE_BUTTON" = "Cacher le bouton En direct"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Cache le bouton En direct en haut du lecteur Shorts."; -"HIDE_SHORTS_LENS_BUTTON" = "Cacher le bouton Lens"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Cache le bouton Lens en haut du lecteur Shorts."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Cacher le bouton Tendances"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Cache le bouton Tendances en haut du lecteur Shorts."; -"HIDE_SHORTS_TO_VIDEO" = "Cacher le lien vers la vidéo complète"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Cache le lien qui redirige depuis un Short vers la vidéo complète dans le lecteur Shorts."; "ENABLES_SHORTS_QUALITY" = "Activer le sélecteur de qualité vidéo des Shorts"; "ENABLES_SHORTS_QUALITY_DESC" = "Force l'activation du menu de sélection de qualité vidéo dans les Shorts."; "SHOW_SHORTS_SEEKBAR" = "Toujours afficher la barre de progression"; "SHOW_SHORTS_SEEKBAR_DESC" = "Affiche en permanence la barre de progression en bas du lecteur Shorts."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; + +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Barre d'onglets"; "DEFAULT_TAB" = "Onglet de démarrage par défaut"; -"DEFAULT_TAB_DESC" = "Tu peux sélectionner l'onglet à ouvrir au lancement de l'application."; -"HOME_NAME" = "Accueil"; -"SUBSCRIPT_NAME" = "Abonnements"; -"LIB_NAME" = "Bibliothèque (Vous)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Cacher les indicateurs d'onglets"; "HIDE_TAB_INDI_DESC" = "Cache tous les indicateurs d'onglets dans la barre d'onglets."; "HIDE_TAB_LABELS" = "Cacher les libellés d'onglets"; "HIDE_TAB_LABELS_DESC" = "Cache tous les libellés d'onglets dans la barre d'onglets."; -"HIDE_HOME_TAB" = "Cacher l'onglet Accueil"; -"HIDE_HOME_TAB_DESC" = "Cache l'onglet Accueil dans la barre d'onglets."; -"HIDE_SHORTS_TAB" = "Cacher l'onglet Shorts"; -"HIDE_SHORTS_TAB_DESC" = "Cache l'onglet Shorts dans la barre d'onglets."; -"HIDE_CREATE_BUTTON" = "Cacher le bouton Créer"; -"HIDE_CREATE_BUTTON_DESC" = "Cache le bouton Créer dans la barre d'onglets."; -"HIDE_SUBSCRIPT_TAB" = "Cacher l'onglet Abonnements"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Cache l'onglet Abonnements dans la barre d'onglets."; +"HOME_TAB" = "Accueil"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Abonnements"; +"LIBRARY_TAB" = "Bibliothèque (Vous)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Autres"; "BACKGROUND_PLAYBACK" = "Lecture en arrière-plan"; "BACKGROUND_PLAYBACK_DESC" = "Active la lecture en arrière-plan pour les vidéos."; "DISABLES_SHORTS_PIP" = "Désactiver le PiP des Shorts"; "DISABLES_SHORTS_PIP_DESC" = "Tente de désactiver le PiP des Shorts, cela peut ne pas fonctionner à 100%."; +"DISABLE_HINTS" = "Désactiver les conseils"; +"DISABLE_HINTS_DESC" = "Désactive les conseils et les pop-ups interactifs."; "BLOCK_UPGRADE_DIALOGS" = "Bloquer les dialogues de mise à jour"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Bloque les dialogues YouTube qui te demandent de mettre à jour l'application."; "ARE_YOU_THERE_DIALOG" = "Cacher la boîte de dialogue 'Êtes-vous là ?'"; @@ -247,7 +407,114 @@ "DISABLES_SNACK_BAR_DESC" = "Désactive la barre de notification qui apparaît en appuyant sur certains boutons ou en effectuant certaines actions dans l'application."; "HIDE_STARTUP_ANIMATIONS" = "Cacher les animations de démarrage de YouTube"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Cache les animations de démarrage de YouTube à l'ouverture de l'application."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Cacher 'Lire ensuite dans la file'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Cache l'option 'Lire ensuite dans la file' dans le menu contextuel."; "HIDE_LIKE_DISLIKE_VOTES" = "Cacher les votes J'aime/Je n'aime pas"; "HIDE_LIKE_DISLIKE_VOTES_DESC" = "Cache les messages qui apparaissent lorsque tu aimes ou n'aimes pas des vidéos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Remove guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Removes pinned guidelines from the comments section."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/he.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/he.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/he.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/he.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/hi.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/hi.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/hi.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/hi.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/hu.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/hu.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/hu.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/hu.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/icons/course@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/course@3x.png new file mode 100644 index 00000000..959cd054 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/course@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/course_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/course_selected@3x.png new file mode 100644 index 00000000..21b2a15a Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/course_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/fashion@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/fashion@3x.png new file mode 100644 index 00000000..66f5994a Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/fashion@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/fashion_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/fashion_selected@3x.png new file mode 100644 index 00000000..cc0559e2 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/fashion_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/gaming@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/gaming@3x.png new file mode 100644 index 00000000..02d77fd8 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/gaming@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/gaming_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/gaming_selected@3x.png new file mode 100644 index 00000000..e1850716 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/gaming_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/history@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/history@3x.png new file mode 100644 index 00000000..784c003f Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/history@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/history_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/history_selected@3x.png new file mode 100644 index 00000000..89cf4162 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/history_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/learning@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/learning@3x.png new file mode 100644 index 00000000..17db08e2 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/learning@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/learning_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/learning_selected@3x.png new file mode 100644 index 00000000..066aaf37 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/learning_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/like@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/like@3x.png new file mode 100644 index 00000000..c11c1aed Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/like@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/like_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/like_selected@3x.png new file mode 100644 index 00000000..ee22dc57 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/like_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/live@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/live@3x.png new file mode 100644 index 00000000..3843e196 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/live@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/live_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/live_selected@3x.png new file mode 100644 index 00000000..621cc75c Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/live_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/minigame@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/minigame@3x.png new file mode 100644 index 00000000..db197904 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/minigame@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/minigame_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/minigame_selected@3x.png new file mode 100644 index 00000000..9b728c72 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/minigame_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/movie@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/movie@3x.png new file mode 100644 index 00000000..dc9c7dbf Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/movie@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/movie_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/movie_selected@3x.png new file mode 100644 index 00000000..5141b4d7 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/movie_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/music@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/music@3x.png new file mode 100644 index 00000000..8de5a904 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/music@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/music_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/music_selected@3x.png new file mode 100644 index 00000000..16209212 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/music_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/news@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/news@3x.png new file mode 100644 index 00000000..fd63821e Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/news@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/news_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/news_selected@3x.png new file mode 100644 index 00000000..75cb8584 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/news_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/noti@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/noti@3x.png new file mode 100644 index 00000000..272fe733 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/noti@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/noti_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/noti_selected@3x.png new file mode 100644 index 00000000..d184fd9c Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/noti_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/playlist@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/playlist@3x.png new file mode 100644 index 00000000..a48a2374 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/playlist@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/playlist_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/playlist_selected@3x.png new file mode 100644 index 00000000..7afd5edc Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/playlist_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/post@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/post@3x.png new file mode 100644 index 00000000..b63ea72b Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/post@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/post_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/post_selected@3x.png new file mode 100644 index 00000000..f1952f9e Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/post_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/sports@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/sports@3x.png new file mode 100644 index 00000000..3cc508fa Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/sports@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/sports_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/sports_selected@3x.png new file mode 100644 index 00000000..d3384c66 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/sports_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/video@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/video@3x.png new file mode 100644 index 00000000..9d516472 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/video@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/video_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/video_selected@3x.png new file mode 100644 index 00000000..9f19fa27 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/video_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/watchlater@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/watchlater@3x.png new file mode 100644 index 00000000..fc35003c Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/watchlater@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/icons/watchlater_selected@3x.png b/layout/Library/Application Support/YouMod.bundle/icons/watchlater_selected@3x.png new file mode 100644 index 00000000..82b0f6f4 Binary files /dev/null and b/layout/Library/Application Support/YouMod.bundle/icons/watchlater_selected@3x.png differ diff --git a/layout/Library/Application Support/YouMod.bundle/id.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/id.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/id.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/id.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/it.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/it.lproj/Localizable.strings index 7bf6685a..98ced292 100644 --- a/layout/Library/Application Support/YouMod.bundle/it.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/it.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTA: Alcune funzioni potrebbero lasciare un riquadro vuoto."; "SETTINGS" = "Impostazioni"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Segnala un problema o richiedi una funzione"; "NEW_ISSUES_DESC" = "Hai trovato un bug o vuoi una nuova funzione? Clicca qui!"; "SOURCE_CODES" = "Codice sorgente"; "SOURCE_CODES_DESC" = "Dai un'occhiata al codice!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "Opzioni YouMod"; "PERFER" = "Opzioni"; "IMPORT" = "Importa impostazioni"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "Non ci sono chiavi valide da importare in questo file"; "ERROR_NO_KEYS_EXPORT" = "Non ci sono chiavi valide da esportare in questo file"; "OVERRIDE" = "Questo sovrascriverà le tue impostazioni attuali. Vuoi continuare?"; +"DISABLED" = "Disabled"; "CACHE" = "Gestione cache"; "CLEARCACHE" = "Pulisci cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Pulisce automaticamente la cache all'avvio."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Aspetto"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "La tastiera usa un nero puro (OLED) per la modalità scura."; "NAVBAR" = "Barra di navigazione"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Nascondi logo YouTube"; "HIDE_YT_LOGO_DESC" = "Nasconde il logo YouTube nella barra di navigazione."; "PREMIUM_LOGO" = "Usa il logo YouTube Premium"; @@ -58,36 +150,84 @@ "HIDE_VOICE_SEARCH_BUTTON_DESC" = "Nasconde il pulsante ricerca vocale nella barra di navigazione."; "HIDE_CAST_BUTTON_NAVBAR" = "Nascondi pulsante cast"; "HIDE_CAST_BUTTON_NAVBAR_DESC" = "Nasconde il pulsante cast nella barra di navigazione."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; "FEED" = "Feed"; "HIDE_SUBBAR" = "Nascondi barra superiore"; "HIDE_SUBBAR_DESC" = "Nasconde la barra in alto al feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Nascondi il generatore di playlist musicali"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Nasconde il generatore di playlist musicali (Scegli brani musicali tra tre) nel feed."; "HIDE_FEED_POST" = "Nascondi post"; "HIDE_FEED_POST_DESC" = "Nasconde i post nel feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Nascondi il riquadro degli Shorts"; "HIDE_SHORTS_SHELF_DESC" = "Nasconde il riquadro degli Shorts nel feed, anche nella scheda Iscrizioni."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Nascondi cronologia ricerche"; "HIDE_SEARCH_HISTORY_DESC" = "Nasconde la cronologia delle ricerche precedenti e i suggerimenti quando si usa la barra di ricerca.\nNOTA: La tua cronologia delle ricerche sarà ancora accessibile negli altri client YouTube."; -"HIDE_SUB_BUTTON" = "Nascondi pulsante Iscriviti"; -"HIDE_SUB_BUTTON_DESC" = "Nasconde il pulsante iscriviti nei canali e nelle descrizioni dei video."; -"HIDE_SHOP_BUTTON" = "Nascondi pulsante Shopping"; -"HIDE_SHOP_BUTTON_DESC" = "Nasconde il pulsante shopping/negozio nelle descrizioni dei canali."; -"HIDE_MEMBER_BUTTON" = "Nascondi pulsante Abbonati"; -"HIDE_MEMBER_BUTTON_DESC" = "Nasconde il pulsante 'Abbonati' nei canali e nelle descrizioni dei video."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically Disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Nascondi interruttore riproduzione automatica"; "HIDE_AUTOPLAY_DESC" = "Nasconde l'interruttore della riproduzione automatica nel player."; "HIDE_CAPTIONS_BUTTON" = "Nascondi pulsante sottotitoli"; "HIDE_CAPTIONS_BUTTON_DESC" = "Nasconde il pulsante dei sottotitoli nel player."; "HIDE_CAST_BUTTON_PLAYER" = "Nascondi pulsante cast"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Nasconde il pulsante cast nel player."; -"HIDE_PREV_BUTTON" = "Nascondi pulsante precedente/indietro"; -"HIDE_PREV_BUTTON_DESC" = "Nasconde il pulsante precedente/indietro nel player."; -"HIDE_NEXT_BUTTON" = "Nascondi pulsante successivo/avanti"; -"HIDE_NEXT_BUTTON_DESC" = "Nasconde il pulsante successivo/avanti nel player."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; "REMOVE_DARK_OVERLAY" = "Rimuovi overlay scuro"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Nasconde l'avviso 'Include promozioni a pagamento' nel player."; "HIDE_WATERMARK" = "Nascondi filigrana"; "HIDE_WATERMARK_DESC" = "Nasconde la filigrana del canale in basso nel player."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Abilita gestures"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disabilita la funzione che mostra quanto tempo manca alla fine del video toccando il timestamp."; "ALWAYS_SHOW_REMAINING" = "Mostra sempre il tempo rimanente"; "ALWAYS_SHOW_REMAINING_DESC" = "Assicura che il tempo rimanente sia sempre visualizzato per impostazione predefinita nel player."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Nascondi azioni a schermo intero"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Salta automaticamente l'avviso per contenuti sensibili in alcuni video."; "AUTO_FULLSCREEN" = "Schermo intero automatico"; "AUTO_FULLSCREEN_DESC" = "Entra automaticamente in modalità schermo intero all'avvio della riproduzione."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Modalità schermo intero verticale"; "PORTRAIT_FULLSCREEN_DESC" = "Abilita il supporto alla modalità schermo intero verticale."; "OLD_QUALITY_PICKER" = "Usa vecchio selettore qualità"; "OLD_QUALITY_PICKER_DESC" = "Ripristina il menu di selezione della qualità video alla versione classica (elenca subito tutte le risoluzioni)."; "EXTRA_SPEED" = "Opzioni di velocità extra"; "EXTRA_SPEED_DESC" = "Aggiunge più opzioni di velocità di riproduzione video fino a 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Ensures the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Nascondi pulsante Mi piace"; -"HIDE_LIKE_BUTTON_DESC" = "Nasconde il pulsante Mi piace nella barra delle azioni sotto il video."; -"HIDE_DISLIKE_BUTTON" = "Nascondi pulsante Non mi piace"; -"HIDE_DISLIKE_BUTTON_DESC" = "Nasconde il pulsante Non mi piace nella barra delle azioni sotto il video."; -"HIDE_SHARE_BUTTON" = "Nascondi pulsante Condividi"; -"HIDE_SHARE_BUTTON_DESC" = "Nasconde il pulsante Condividi nella barra delle azioni sotto il video."; -"HIDE_DOWNLOAD_BUTTON" = "Nascondi pulsante Scarica"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Nasconde il pulsante Scarica nella barra delle azioni sotto il video."; -"HIDE_CLIP_BUTTON" = "Nascondi pulsante Clip"; -"HIDE_CLIP_BUTTON_DESC" = "Nasconde il pulsante Clip nella barra delle azioni sotto il video."; -"HIDE_REMIX_BUTTON" = "Nascondi pulsante Remix"; -"HIDE_REMIX_BUTTON_DESC" = "Nasconde il pulsante Remix nella barra delle azioni sotto il video."; -"HIDE_SAVE_BUTTON" = "Nascondi pulsante Salva"; -"HIDE_SAVE_BUTTON_DESC" = "Nasconde il pulsante Salva nella barra delle azioni sotto il video."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_ACTION_BAR" = "Hide actions bar"; +"HIDE_ACTION_BAR_DESC" = "Removes the bar containing the like, dislike, share buttons etc. below the video player."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Nascondi pulsante Mi piace"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Nasconde il pulsante Mi piace nel player degli Shorts."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Nascondi pulsante Non mi piace"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Nasconde il pulsante Non mi piace nel player degli Shorts."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Nascondi pulsante Commenti"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Nasconde il pulsante dei commenti nel player degli Shorts."; -"HIDE_SHORTS_SHARE_BUTTON" = "Nascondi pulsante Condividi"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Nasconde il pulsante Condividi nel player degli Shorts."; -"HIDE_SHORTS_REMIX_BUTTON" = "Nascondi pulsante Remix"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Nasconde il pulsante Remix nel player degli Shorts."; -"HIDE_METADATA_BUTTON" = "Nascondi pulsante metadati audio"; -"HIDE_METADATA_BUTTON_DESC" = "Nasconde il pulsante che mostra la sorgente audio o i metadati musicali nel player degli Shorts."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Nascondi prodotti"; "HIDE_SHORTS_PRODUCT_DESC" = "Nasconde i prodotti taggati e i link per lo shopping negli Shorts."; "HIDE_SHORTS_RECBAR" = "Nascondi barra dei suggerimenti"; "HIDE_SHORTS_RECBAR_DESC" = "Nasconde la barra delle azioni suggerite nel player degli Shorts (es. Prova questo audio, Shopping, Anteprima commento)."; -"HIDE_SHORTS_COMMIT" = "Nascondi barra commissioni"; -"HIDE_SHORTS_COMMIT_DESC" = "Nasconde la barra che indica che il video ha commissioni nel player degli Shorts."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Nascondi pulsante iscrizioni"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Nasconde il pulsante Iscrizioni in alto nel player degli Shorts."; -"HIDE_SHORTS_LIVE_BUTTON" = "Nascondi pulsante live"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Nasconde il pulsante Live in alto nel player degli Shorts."; -"HIDE_SHORTS_LENS_BUTTON" = "Nascondi pulsante lens"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Nasconde il pulsante Lens in alto nel player degli Shorts."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Nascondi pulsante tendenze"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Nasconde il pulsante Tendenze in alto nel player degli Shorts."; -"HIDE_SHORTS_TO_VIDEO" = "Nascondi link al video completo"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Nasconde il link che reindirizza da uno Short al video completo."; "ENABLES_SHORTS_QUALITY" = "Abilita selettore qualità negli Shorts"; "ENABLES_SHORTS_QUALITY_DESC" = "Forza l'abilitazione del menu di selezione della qualità video negli Shorts."; "SHOW_SHORTS_SEEKBAR" = "Mostra sempre barra di scorrimento"; "SHOW_SHORTS_SEEKBAR_DESC" = "Assicura che la barra di avanzamento sia sempre visibile in basso nel player degli Shorts."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Barra delle schede"; "DEFAULT_TAB" = "Scheda di avvio predefinita"; -"DEFAULT_TAB_DESC" = "Puoi selezionare la scheda che desideri visualizzare all'apertura dell'app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Iscrizioni"; -"LIB_NAME" = "Raccolta (Tu)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Nascondi indicatori schede"; "HIDE_TAB_INDI_DESC" = "Nasconde tutti gli indicatori delle schede nella barra delle schede."; "HIDE_TAB_LABELS" = "Nascondi etichette schede"; "HIDE_TAB_LABELS_DESC" = "Nasconde tutte le etichette delle schede nella barra delle schede."; -"HIDE_HOME_TAB" = "Nascondi scheda Home"; -"HIDE_HOME_TAB_DESC" = "Nasconde la scheda Home nella barra delle schede."; -"HIDE_SHORTS_TAB" = "Nascondi scheda Shorts"; -"HIDE_SHORTS_TAB_DESC" = "Nasconde la scheda Shorts nella barra delle schede."; -"HIDE_CREATE_BUTTON" = "Nascondi pulsante Crea"; -"HIDE_CREATE_BUTTON_DESC" = "Nasconde il pulsante Crea nella barra delle schede."; -"HIDE_SUBSCRIPT_TAB" = "Nascondi scheda Iscrizioni"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Nasconde la scheda Iscrizioni nella barra delle schede."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Iscrizioni"; +"LIBRARY_TAB" = "Raccolta (Tu)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Varie"; "BACKGROUND_PLAYBACK" = "Riproduzione in background"; "BACKGROUND_PLAYBACK_DESC" = "Abilita la riproduzione in background per i video."; "DISABLES_SHORTS_PIP" = "Disabilita PiP negli Shorts"; "DISABLES_SHORTS_PIP_DESC" = "Tenta di disabilitare il PiP negli Shorts, potrebbe non funzionare al 100%."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Blocca avvisi di aggiornamento"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Blocca gli avvisi di YouTube che chiedono di aggiornare l'app."; "ARE_YOU_THERE_DIALOG" = "Nascondi avviso 'Ci sei ancora?'"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disabilita la barra di notifica (snack bar) che appare premendo alcuni pulsanti o eseguendo azioni nell'app."; "HIDE_STARTUP_ANIMATIONS" = "Nascondi animazioni di avvio di YouTube"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Nasconde le animazioni di avvio di YouTube all'apertura dell'app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Nascondi 'Riproduci come successivo nella coda'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Nasconde l'opzione 'Riproduci come successivo nella coda' nel menu a comparsa."; "HIDE_LIKE_DISLIKE_VOTES" = "Nascondi voti mi piace/non mi piace"; "HIDE_LIKE_DISLIKE_VOTES_DESC" = "Nasconde i messaggi che appaiono quando metti mi piace/non mi piace ai video."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"HIDE_COMMU_GUIDE" = "Remove guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Removes pinned guidelines from the comments section."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/ja.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/ja.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/ja.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/ja.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/ko.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/ko.lproj/Localizable.strings index 422c60fc..478d5e8d 100644 --- a/layout/Library/Application Support/YouMod.bundle/ko.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/ko.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "참고: 일부 숨기기 기능을 사용하면 화면에 빈 공간이 남을 수 있습니다."; "SETTINGS" = "설정"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "버그 신고 및 새 기능 요청"; "NEW_ISSUES_DESC" = "버그를 발견했거나 새로운 기능을 원하시나요? 여기를 눌러주세요."; "SOURCE_CODES" = "소스 코드"; "SOURCE_CODES_DESC" = "앱의 소스 코드를 확인해 보세요!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod 환경설정"; "PERFER" = "환경설정"; "IMPORT" = "설정 불러오기"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "불러올 수 있는 설정값이 없습니다."; "ERROR_NO_KEYS_EXPORT" = "내보낼 수 있는 설정값이 없습니다."; "OVERRIDE" = "기존 설정에 덮어쓰게 됩니다. 계속하시겠습니까?"; +"DISABLED" = "Disabled"; "CACHE" = "캐시 (임시 데이터)"; "CLEARCACHE" = "캐시 지우기"; @@ -36,8 +44,90 @@ "DOWNLOAD_MANAGER_DESC" = "유튜브 기본 다운로더 대신 YouMod 전용 다운로더를 사용합니다. 동영상, 오디오, 자막, 썸네일을 다운로드할 수 있습니다."; "DOWNLOAD_SAVE_PHOTOS" = "사진 앱에 자동 저장"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "다운로드한 동영상과 이미지를 기기의 사진(갤러리) 앱에 바로 저장합니다."; -"DOWNLOAD_DRC_AUDIO" = "DRC 오디오 우선 적용"; -"DOWNLOAD_DRC_AUDIO_DESC" = "볼륨을 일정하게 유지해 주는 DRC(동적 범위 압축) 오디오 트랙을 우선적으로 다운로드합니다."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "화면 및 테마"; "OLED_THEME" = "OLED 다크 모드"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "다크 모드 사용 시 키보드 배경을 완전한 검은색(OLED 블랙)으로 바꿉니다."; "NAVBAR" = "상단 메뉴 바"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "유튜브 로고 숨기기"; "HIDE_YT_LOGO_DESC" = "화면 맨 위 상단 바에서 유튜브 로고를 보이지 않게 숨깁니다."; "PREMIUM_LOGO" = "유튜브 프리미엄 로고 사용"; @@ -62,32 +154,80 @@ "FEED" = "피드 (홈 화면)"; "HIDE_SUBBAR" = "서브 바(카테고리) 숨기기"; "HIDE_SUBBAR_DESC" = "홈 화면 맨 위에 있는 주제별 필터(게임, 음악 등)를 숨깁니다."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "음악 재생목록 자동생성 숨기기"; "HIDE_MUSIC_PLAYLISTS_DESC" = "피드 중간에 뜨는 유튜브 자동 생성 음악 믹스 추천을 숨깁니다."; "HIDE_FEED_POST" = "게시물 숨기기"; "HIDE_FEED_POST_DESC" = "피드에서 텍스트나 이미지로 된 커뮤니티 게시물을 숨깁니다."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "쇼츠(Shorts) 선반 숨기기"; "HIDE_SHORTS_SHELF_DESC" = "홈 화면과 구독 탭에서 여러 개가 가로로 나열된 쇼츠 영상 모음을 안 보이게 합니다."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "검색 기록 숨기기"; "HIDE_SEARCH_HISTORY_DESC" = "검색창을 누를 때 이전 검색 기록이나 추천 검색어가 안 보이게 숨깁니다.\n참고: 이 앱에서만 안 보일 뿐, 다른 기기의 유튜브에는 기록이 남아있습니다."; -"HIDE_SUB_BUTTON" = "구독 버튼 숨기기"; -"HIDE_SUB_BUTTON_DESC" = "채널 홈이나 동영상 설명창에 있는 구독 버튼을 숨깁니다."; -"HIDE_SHOP_BUTTON" = "쇼핑 버튼 숨기기"; -"HIDE_SHOP_BUTTON_DESC" = "채널 설명창에 있는 스토어(쇼핑) 버튼을 숨깁니다."; -"HIDE_MEMBER_BUTTON" = "멤버십 가입 버튼 숨기기"; -"HIDE_MEMBER_BUTTON_DESC" = "채널 홈이나 영상 설명창에서 멤버십 '가입' 버튼을 숨깁니다."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "동영상 플레이어"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "HIDE_AUTOPLAY" = "자동재생 스위치 숨기기"; "HIDE_AUTOPLAY_DESC" = "영상 화면 위쪽에 있는 자동재생 켜기/끄기 스위치를 숨깁니다."; "HIDE_CAPTIONS_BUTTON" = "자막 버튼 숨기기"; "HIDE_CAPTIONS_BUTTON_DESC" = "영상 화면 위쪽에 있는 CC(자막) 버튼을 숨깁니다."; "HIDE_CAST_BUTTON_PLAYER" = "화면 전송(캐스트) 버튼 숨기기"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "영상 화면에 있는 TV 모양 캐스트 버튼을 숨깁니다."; -"HIDE_PREV_BUTTON" = "이전 영상 버튼 숨기기"; -"HIDE_PREV_BUTTON_DESC" = "재생바 옆에 있는 이전 영상으로 가기 버튼을 숨깁니다."; -"HIDE_NEXT_BUTTON" = "다음 영상 버튼 숨기기"; -"HIDE_NEXT_BUTTON_DESC" = "재생바 옆에 있는 다음 영상으로 가기 버튼을 숨깁니다."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "이전/다음 버튼 교체하기"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "플레이어의 이전 영상, 다음 영상 버튼을 뒤로 감기, 빨리 감기 버튼으로 바꿉니다."; "REMOVE_DARK_OVERLAY" = "어두운 음영 효과 제거"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "영상 화면에 뜨는 '유료 광고 포함' 안내 배너를 숨깁니다."; "HIDE_WATERMARK" = "채널 워터마크 숨기기"; "HIDE_WATERMARK_DESC" = "영상 오른쪽 아래 구석에 작게 떠 있는 채널 로고(워터마크)를 숨깁니다."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "제스처 제어 켜기"; "GESTURES_DESC" = "화면을 스와이프하여 조작하는 제스처 기능을 켭니다.\n상단: 밝기 조절, 하단: 볼륨 조절"; "GESTURE_AREA" = "제스처 작동 영역 범위"; @@ -135,6 +281,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "시간 표시를 눌렀을 때 영상의 '남은 시간'으로 바뀌어 보여주는 기능을 끕니다."; "ALWAYS_SHOW_REMAINING" = "항상 남은 시간으로 표시"; "ALWAYS_SHOW_REMAINING_DESC" = "영상의 현재 재생 시간 대신, 끝날 때까지 남은 시간을 기본으로 띄워둡니다."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "예상 종료 시간 표시"; "SHOW_REMAINING_EXTRA_DESC" = "영상 남은 시간에 이 영상이 언제 끝나는지 예상 종료 시간도 함께 보여줍니다."; "HIDE_FULLSCREEN_ACTIONS" = "전체화면 빠른 동작 버튼 숨기기"; @@ -147,8 +295,6 @@ "HIDE_CONTENT_WARNING_DESC" = "연령 제한이나 민감한 콘텐츠를 볼 때 뜨는 경고 안내창을 묻지도 따지지도 않고 건너뜁니다."; "AUTO_FULLSCREEN" = "자동 전체화면"; "AUTO_FULLSCREEN_DESC" = "영상을 재생하자마자 자동으로 화면을 꽉 채우는 전체화면 모드로 시작합니다."; -"AUTO_DISABLES_CAPTION" = "자막 자동 끄기"; -"AUTO_DISABLES_CAPTION_DESC" = "새로운 영상을 재생할 때마다 켜져 있는 자막(CC)을 자동으로 끕니다."; "PORTRAIT_FULLSCREEN" = "세로 전체화면 모드 지원"; "PORTRAIT_FULLSCREEN_DESC" = "스마트폰을 세로로 든 상태에서도 전체화면이 꽉 차게 보이도록 지원합니다."; "OLD_QUALITY_PICKER" = "구형 화질 선택기 사용"; @@ -161,79 +307,94 @@ "FORCE_MINIPLAYER_DESC" = "아동용 동영상을 포함한 모든 영상을 아래로 내렸을 때 미니 플레이어에서 재생되도록 강제합니다."; "FORCE_SEEKBAR" = "진행 바 항상 표시"; "FORCE_SEEKBAR_DESC" = "전체화면 모드에서 영상을 볼 때 하단에 재생 진행 바를 항상 띄워둡니다."; -"HIDE_LIKE_BUTTON" = "좋아요 버튼 숨기기"; -"HIDE_LIKE_BUTTON_DESC" = "영상 아래쪽 메뉴에서 좋아요 버튼을 숨깁니다."; -"HIDE_DISLIKE_BUTTON" = "싫어요 버튼 숨기기"; -"HIDE_DISLIKE_BUTTON_DESC" = "영상 아래쪽 메뉴에서 싫어요 버튼을 숨깁니다."; -"HIDE_SHARE_BUTTON" = "공유 버튼 숨기기"; -"HIDE_SHARE_BUTTON_DESC" = "영상 아래쪽 메뉴에서 공유 버튼을 숨깁니다."; -"HIDE_DOWNLOAD_BUTTON" = "오프라인 저장 버튼 숨기기"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "영상 아래쪽 메뉴에서 다운로드(오프라인 저장) 버튼을 숨깁니다."; -"HIDE_CLIP_BUTTON" = "클립 버튼 숨기기"; -"HIDE_CLIP_BUTTON_DESC" = "영상 아래쪽 메뉴에서 짧은 구간을 잘라내는 클립(Clip) 버튼을 숨깁니다."; -"HIDE_REMIX_BUTTON" = "리믹스 버튼 숨기기"; -"HIDE_REMIX_BUTTON_DESC" = "영상 아래쪽 메뉴에서 쇼츠로 만들기(리믹스) 버튼을 숨깁니다."; -"HIDE_SAVE_BUTTON" = "저장 버튼 숨기기"; -"HIDE_SAVE_BUTTON_DESC" = "영상 아래쪽 메뉴에서 재생목록에 추가하는 저장 버튼을 숨깁니다."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_ACTION_BAR" = "Hide actions bar"; +"HIDE_ACTION_BAR_DESC" = "Removes the bar containing the like, dislike, share buttons etc. below the video player."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "쇼츠 (Shorts)"; -"HIDE_SHORTS_LIKE_BUTTON" = "좋아요 버튼 숨기기"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "쇼츠 화면에서 엄지척(좋아요) 버튼을 숨깁니다."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "싫어요 버튼 숨기기"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "쇼츠 화면에서 싫어요 버튼을 숨깁니다."; -"HIDE_SHORTS_COMMENT_BUTTON" = "댓글 버튼 숨기기"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "쇼츠 화면에서 말풍선(댓글) 버튼을 숨깁니다."; -"HIDE_SHORTS_SHARE_BUTTON" = "공유 버튼 숨기기"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "쇼츠 화면에서 화살표(공유) 버튼을 숨깁니다."; -"HIDE_SHORTS_REMIX_BUTTON" = "리믹스 버튼 숨기기"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "쇼츠 화면에서 다른 쇼츠를 만드는 리믹스 버튼을 숨깁니다."; -"HIDE_METADATA_BUTTON" = "음원 정보 버튼 숨기기"; -"HIDE_METADATA_BUTTON_DESC" = "쇼츠 우측 하단에 있는 배경음악/사운드 출처 버튼을 숨깁니다."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "상품 숨기기"; "HIDE_SHORTS_PRODUCT_DESC" = "쇼츠 화면에 뜨는 태그된 상품이나 쇼핑몰 링크들을 안 보이게 합니다."; "HIDE_SHORTS_RECBAR" = "추천 동작 바 숨기기"; "HIDE_SHORTS_RECBAR_DESC" = "쇼츠 하단에 뜨는 '이 사운드 사용해보기', '쇼핑하기' 같은 추천 행동 유도 문구를 숨깁니다."; -"HIDE_SHORTS_COMMIT" = "유료 광고 표시 숨기기"; -"HIDE_SHORTS_COMMIT_DESC" = "쇼츠에 뜨는 '유료 광고 포함(협찬/수수료)' 배너를 숨깁니다."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "구독 버튼 숨기기"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "쇼츠 화면 상단에 둥둥 떠 있는 구독 버튼을 숨깁니다."; -"HIDE_SHORTS_LIVE_BUTTON" = "라이브 버튼 숨기기"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "쇼츠 화면 상단에 뜨는 실시간 방송(라이브) 버튼을 숨깁니다."; -"HIDE_SHORTS_LENS_BUTTON" = "렌즈 버튼 숨기기"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "쇼츠 화면 상단에 있는 렌즈(검색) 버튼을 숨깁니다."; -"HIDE_SHORTS_TRENDS_BUTTON" = "트렌드 버튼 숨기기"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "쇼츠 화면 상단에 있는 트렌드(인기) 버튼을 숨깁니다."; -"HIDE_SHORTS_TO_VIDEO" = "원본 영상 링크 숨기기"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "쇼츠 영상에 걸려있는 전체 길이의 원본 동영상으로 넘어가는 링크를 숨깁니다."; "ENABLES_SHORTS_QUALITY" = "쇼츠 화질 선택 기능 켜기"; "ENABLES_SHORTS_QUALITY_DESC" = "쇼츠에서도 사용자가 직접 1080p 등 원하는 화질을 선택할 수 있도록 메뉴를 활성화합니다."; "SHOW_SHORTS_SEEKBAR" = "진행 바 항상 표시"; "SHOW_SHORTS_SEEKBAR_DESC" = "쇼츠 맨 밑에 영상이 얼마나 재생됐는지 붉은 선으로 보여주는 진행 바를 항상 띄워둡니다."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "하단 탭 바"; "DEFAULT_TAB" = "기본 시작 탭 설정"; -"DEFAULT_TAB_DESC" = "유튜브 앱을 켤 때 '홈', '구독' 등 제일 처음으로 보여줄 화면을 고를 수 있습니다."; -"HOME_NAME" = "홈"; -"SUBSCRIPT_NAME" = "구독"; -"LIB_NAME" = "보관함 (나)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "선택 탭 표시기 숨기기"; "HIDE_TAB_INDI_DESC" = "하단 탭에서 현재 내가 있는 탭을 강조해서 보여주는 효과를 없앱니다."; "HIDE_TAB_LABELS" = "탭 글자 숨기기"; "HIDE_TAB_LABELS_DESC" = "하단 탭 메뉴들에 적힌 글자(홈, Shorts 등)를 다 지우고 깔끔하게 아이콘만 남깁니다."; -"HIDE_HOME_TAB" = "홈 탭 숨기기"; -"HIDE_HOME_TAB_DESC" = "하단 탭에서 '홈' 탭을 아예 지워버립니다."; -"HIDE_SHORTS_TAB" = "쇼츠 탭 숨기기"; -"HIDE_SHORTS_TAB_DESC" = "하단 탭에서 'Shorts' 탭을 아예 지워버립니다."; -"HIDE_CREATE_BUTTON" = "만들기 버튼 숨기기"; -"HIDE_CREATE_BUTTON_DESC" = "하단 탭 한가운데에 있는 영상 업로드용 십자가(+) 버튼을 지웁니다."; -"HIDE_SUBSCRIPT_TAB" = "구독 탭 숨기기"; -"HIDE_SUBSCRIPT_TAB_DESC" = "하단 탭에서 '구독' 탭을 아예 지워버립니다."; +"HOME_TAB" = "홈"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "구독"; +"LIBRARY_TAB" = "보관함 (나)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "기타 설정"; "BACKGROUND_PLAYBACK" = "백그라운드 재생"; "BACKGROUND_PLAYBACK_DESC" = "유튜브 앱을 닫거나 스마트폰 화면을 꺼도 소리가 계속 끊기지 않고 나오게 해줍니다."; "DISABLES_SHORTS_PIP" = "Shorts PIP 모드 끄기"; "DISABLES_SHORTS_PIP_DESC" = "Shorts PIP 화면을 작은 팝업창으로 띄우는 기능을 끕니다. (완벽하게 꺼지지 않을 수도 있습니다.)"; +"DISABLE_HINTS" = "도움말 힌트 끄기"; +"DISABLE_HINTS_DESC" = "앱 사용법을 알려주는 튜토리얼 힌트나 팝업창을 안 보이게 막습니다."; "BLOCK_UPGRADE_DIALOGS" = "업데이트 강요 알림 차단"; "BLOCK_UPGRADE_DIALOGS_DESC" = "유튜브 앱을 새 버전으로 업데이트하라고 귀찮게 뜨는 팝업창을 막아줍니다."; "ARE_YOU_THERE_DIALOG" = "'계속 시청하시겠어요?' 팝업 숨기기"; @@ -246,7 +407,114 @@ "DISABLES_SNACK_BAR_DESC" = "재생목록 추가 등 앱에서 특정 행동을 할 때 화면 아래쪽에 잠깐 나타났다 사라지는 검은색 알림창을 끕니다."; "HIDE_STARTUP_ANIMATIONS" = "유튜브 로고 애니메이션 숨기기"; "HIDE_STARTUP_ANIMATIONS_DESC" = "앱을 켤 때 나오는 유튜브 로고 오프닝 애니메이션을 없애서 앱이 더 빨리 켜지게 합니다."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "'대기열 다음으로 재생' 숨기기"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "더보기 메뉴에서 '대기열 다음으로 재생(예약)' 옵션을 안 보이게 지웁니다."; "HIDE_LIKE_DISLIKE_VOTES" = "좋아요/싫어요 이펙트 끄기"; "HIDE_LIKE_DISLIKE_VOTES_DESC" = "좋아요나 싫어요 버튼을 눌렀을 때 뿅 하고 나타나는 메시지나 화려한 애니메이션 효과를 숨깁니다."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Remove guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Removes pinned guidelines from the comments section."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/ms.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/ms.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/ms.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/ms.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/nl.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/nl.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/nl.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/nl.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/no.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/no.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/no.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/no.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/pl.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/pl.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/pl.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/pl.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/pt-BR.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/pt-BR.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/pt-BR.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/pt-BR.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/pt.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/pt.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/pt.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/pt.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/ro.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/ro.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/ro.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/ro.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/ru.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/ru.lproj/Localizable.strings index 73913f4c..5a8c8302 100644 --- a/layout/Library/Application Support/YouMod.bundle/ru.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/ru.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "ПРИМЕЧАНИЕ: Некоторые функции скрытия могут оставлять пустую область."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Сообщить о проблеме или предложить новую функцию"; "NEW_ISSUES_DESC" = "Нашли ошибку или хотите предложить новую функцию? Нажмите здесь."; "SOURCE_CODES" = "Исходный код"; "SOURCE_CODES_DESC" = "Посмотреть исходный код!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Настройки"; "IMPORT" = "Импорт настроек"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "Нет доступных ключей для импорта"; "ERROR_NO_KEYS_EXPORT" = "Нет доступных ключей для экспорта"; "OVERRIDE" = "Это перезапишет текущие настройки. Продолжить?"; +"DISABLED" = "Disabled"; "CACHE" = "Кэш"; "CLEARCACHE" = "Очистить кэш"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Автоматически очищать кэш приложения при запуске."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Внешний вид"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Использовать для клавиатуры чисто чёрный цвет в тёмной теме."; "NAVBAR" = "Панель навигации"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Скрыть логотип YouTube"; "HIDE_YT_LOGO_DESC" = "Скрыть логотип YouTube на панели навигации."; "PREMIUM_LOGO" = "Использовать логотип YouTube Premium"; @@ -62,32 +154,80 @@ "FEED" = "Лента"; "HIDE_SUBBAR" = "Скрыть верхнюю панель"; "HIDE_SUBBAR_DESC" = "Скрыть верхнюю панель в ленте."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Скрыть генератор музыкальных плейлистов"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Скрыть генератор музыкальных плейлистов в ленте."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Скрыть блок Shorts"; "HIDE_SHORTS_SHELF_DESC" = "Скрыть блок Shorts в ленте, включая вкладку «Подписки»."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Скрыть историю поиска"; "HIDE_SEARCH_HISTORY_DESC" = "Скрыть предыдущие поисковые запросы и подсказки при использовании строки поиска.\nПРИМЕЧАНИЕ: История поиска по-прежнему будет доступна в других клиентах YouTube."; -"HIDE_SUB_BUTTON" = "Скрыть кнопку подписки"; -"HIDE_SUB_BUTTON_DESC" = "Скрыть кнопку подписки на каналах и в описаниях видео."; -"HIDE_SHOP_BUTTON" = "Скрыть кнопку покупок"; -"HIDE_SHOP_BUTTON_DESC" = "Скрыть кнопку покупок/магазина в описаниях каналов."; -"HIDE_MEMBER_BUTTON" = "Скрыть кнопку спонсорства"; -"HIDE_MEMBER_BUTTON_DESC" = "Скрыть кнопку «Стать спонсором» на каналах и в описаниях видео."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Плеер"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Скрыть переключатель автовоспроизведения"; "HIDE_AUTOPLAY_DESC" = "Скрыть переключатель автовоспроизведения в интерфейсе видео."; "HIDE_CAPTIONS_BUTTON" = "Скрыть кнопку субтитров"; "HIDE_CAPTIONS_BUTTON_DESC" = "Скрыть кнопку субтитров в интерфейсе видео."; "HIDE_CAST_BUTTON_PLAYER" = "Скрыть кнопку трансляции"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Скрыть кнопку трансляции в интерфейсе видео."; -"HIDE_PREV_BUTTON" = "Скрыть кнопку «Назад/Предыдущее»"; -"HIDE_PREV_BUTTON_DESC" = "Скрыть кнопку «Назад/Предыдущее» в интерфейсе видео."; -"HIDE_NEXT_BUTTON" = "Скрыть кнопку «Далее/Пропустить»"; -"HIDE_NEXT_BUTTON_DESC" = "Скрыть кнопку «Далее/Пропустить» в интерфейсе видео."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; "REMOVE_DARK_OVERLAY" = "Убрать тёмное затемнение"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Скрыть водяной знак"; "HIDE_WATERMARK_DESC" = "Скрыть водяной знак канала внизу интерфейса видео."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Отключить функцию показа оставшегося времени при нажатии на тайм-код."; "ALWAYS_SHOW_REMAINING" = "Всегда показывать оставшееся время"; "ALWAYS_SHOW_REMAINING_DESC" = "По умолчанию всегда отображать оставшееся время в интерфейсе плеера."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Скрыть действия в полноэкранном режиме"; @@ -148,93 +296,104 @@ "HIDE_CONTENT_WARNING_DESC" = "Автоматически пропускать предупреждение о деликатном содержимом в некоторых видео."; "AUTO_FULLSCREEN" = "Автополноэкранный режим"; "AUTO_FULLSCREEN_DESC" = "Автоматически переходить в полноэкранный режим при запуске воспроизведения видео."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscreen mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Использовать старый выбор качества"; "OLD_QUALITY_PICKER_DESC" = "Вернуть классическое меню выбора качества видео, в котором сразу отображаются все разрешения."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Ensures the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Скрыть кнопку «Нравится»"; -"HIDE_LIKE_BUTTON_DESC" = "Скрыть кнопку «Нравится» на панели действий под видео."; -"HIDE_DISLIKE_BUTTON" = "Скрыть кнопку «Не нравится»"; -"HIDE_DISLIKE_BUTTON_DESC" = "Скрыть кнопку «Не нравится» на панели действий под видео."; -"HIDE_SHARE_BUTTON" = "Скрыть кнопку «Поделиться»"; -"HIDE_SHARE_BUTTON_DESC" = "Скрыть кнопку «Поделиться» на панели действий под видео."; -"HIDE_DOWNLOAD_BUTTON" = "Скрыть кнопку «Скачать»"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Скрыть кнопку «Скачать» на панели действий под видео."; -"HIDE_CLIP_BUTTON" = "Скрыть кнопку «Клип»"; -"HIDE_CLIP_BUTTON_DESC" = "Скрыть кнопку «Клип» на панели действий под видео."; -"HIDE_REMIX_BUTTON" = "Скрыть кнопку Remix"; -"HIDE_REMIX_BUTTON_DESC" = "Скрыть кнопку Remix на панели действий под видео."; -"HIDE_SAVE_BUTTON" = "Скрыть кнопку «Сохранить»"; -"HIDE_SAVE_BUTTON_DESC" = "Скрыть кнопку «Сохранить» на панели действий под видео."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_ACTION_BAR" = "Hide actions bar"; +"HIDE_ACTION_BAR_DESC" = "Removes the bar containing the like, dislike, share buttons etc. below the video player."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Скрыть кнопку «Нравится»"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Скрыть кнопку «Нравится» в плеере Shorts."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Скрыть кнопку «Не нравится»"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Скрыть кнопку «Не нравится» в плеере Shorts."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Скрыть кнопку комментариев"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Скрыть кнопку комментариев в плеере Shorts."; -"HIDE_SHORTS_SHARE_BUTTON" = "Скрыть кнопку «Поделиться»"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Скрыть кнопку «Поделиться» в плеере Shorts."; -"HIDE_SHORTS_REMIX_BUTTON" = "Скрыть кнопку Remix"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Скрыть кнопку Remix в плеере Shorts."; -"HIDE_METADATA_BUTTON" = "Скрыть кнопку метаданных звука"; -"HIDE_METADATA_BUTTON_DESC" = "Скрыть кнопку, которая показывает источник звука или музыкальные метаданные в плеере Shorts."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Скрыть товары"; "HIDE_SHORTS_PRODUCT_DESC" = "Скрыть отмеченные товары и ссылки на покупки в интерфейсе Shorts."; "HIDE_SHORTS_RECBAR" = "Скрыть панель рекомендаций"; "HIDE_SHORTS_RECBAR_DESC" = "Скрыть панель предлагаемых действий в плеере Shorts (например, «Попробовать этот звук», «Покупки»)."; -"HIDE_SHORTS_COMMIT" = "Скрыть панель комиссий"; -"HIDE_SHORTS_COMMIT_DESC" = "Скрыть панель, которая показывает, что видео приносит комиссию, в плеере Shorts."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Скрыть кнопку подписок"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Скрыть кнопку «Подписки» в верхней части плеера Shorts."; -"HIDE_SHORTS_LIVE_BUTTON" = "Скрыть кнопку прямого эфира"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Скрыть кнопку прямого эфира в верхней части плеера Shorts."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Скрыть ссылку на полное видео"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Скрыть ссылку, которая ведёт из Shorts к полному видео."; "ENABLES_SHORTS_QUALITY" = "Включить выбор качества в Shorts"; "ENABLES_SHORTS_QUALITY_DESC" = "Принудительно включить меню выбора качества видео в Shorts."; "SHOW_SHORTS_SEEKBAR" = "Всегда показывать полосу перемотки"; "SHOW_SHORTS_SEEKBAR_DESC" = "Сделать полосу прогресса/перемотки внизу плеера Shorts всегда видимой."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Панель вкладок"; "DEFAULT_TAB" = "Вкладка по умолчанию при запуске"; -"DEFAULT_TAB_DESC" = "Вы можете выбрать вкладку, которая будет открываться при запуске приложения."; -"HOME_NAME" = "Главная"; -"SUBSCRIPT_NAME" = "Подписки"; -"LIB_NAME" = "Библиотека (Вы)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Скрыть индикаторы вкладок"; "HIDE_TAB_INDI_DESC" = "Скрыть все индикаторы вкладок на панели вкладок."; "HIDE_TAB_LABELS" = "Скрыть подписи вкладок"; "HIDE_TAB_LABELS_DESC" = "Скрыть все подписи вкладок на панели вкладок."; -"HIDE_HOME_TAB" = "Скрыть вкладку «Главная»"; -"HIDE_HOME_TAB_DESC" = "Скрыть вкладку «Главная» на панели вкладок."; -"HIDE_SHORTS_TAB" = "Скрыть вкладку Shorts"; -"HIDE_SHORTS_TAB_DESC" = "Скрыть вкладку Shorts на панели вкладок."; -"HIDE_CREATE_BUTTON" = "Скрыть кнопку «Создать»"; -"HIDE_CREATE_BUTTON_DESC" = "Скрыть кнопку «Создать» на панели вкладок."; -"HIDE_SUBSCRIPT_TAB" = "Скрыть вкладку «Подписки»"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Скрыть вкладку «Подписки» на панели вкладок."; +"HOME_TAB" = "Главная"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Подписки"; +"LIBRARY_TAB" = "Библиотека (Вы)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Разное"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Отключить PiP для Shorts"; "DISABLES_SHORTS_PIP_DESC" = "Попробовать отключить режим «картинка в картинке» для Shorts; это может сработать не во всех случаях."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Блокировать диалоги обновления"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Блокировать диалоги YouTube с предложением обновить приложение."; "ARE_YOU_THERE_DIALOG" = "Скрыть диалог «Вы ещё здесь?»"; @@ -247,7 +406,114 @@ "DISABLES_SNACK_BAR_DESC" = "Отключить всплывающие уведомления при нажатии некоторых кнопок или выполнении действий в приложении."; "HIDE_STARTUP_ANIMATIONS" = "Скрыть анимации запуска YouTube"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Скрыть анимации запуска YouTube при открытии приложения."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Скрыть «Воспроизвести следующим в очереди»"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Скрыть пункт «Воспроизвести следующим в очереди» во всплывающем меню."; "HIDE_LIKE_DISLIKE_VOTES" = "Скрыть уведомления о лайках/дизлайках"; "HIDE_LIKE_DISLIKE_VOTES_DESC" = "Скрыть сообщения, которые появляются, когда вы ставите лайк или дизлайк видео."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Remove guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Removes pinned guidelines from the comments section."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/sk.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/sk.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/sk.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/sk.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/sv.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/sv.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/sv.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/sv.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/th.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/th.lproj/Localizable.strings index 37eebe70..b11eabdc 100644 --- a/layout/Library/Application Support/YouMod.bundle/th.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/th.lproj/Localizable.strings @@ -1,11 +1,27 @@ -"NOTE" = "หมายเหตุ: ฟีเจอร์การซ่อนบางอย่างอาจทำให้เกิดช่องว่างเปล่า"; "SETTINGS" = "การตั้งค่า"; +"INTERFACE" = "ลักษณะ"; + +"URL_COPIED" = "คัดลอกลิงก์วิดีโอแล้ว"; +"COPY_URL" = "คัดลอกลิงก์วิดีโอ"; +"COPY_URL_TIMESTAMP" = "คัดลอกลิงก์วิดีโอพร้อมเวลา"; +"URL_TIMESTAMP_COPIED" = "คัดลอกลิงก์วิดีโอพร้อมเวลาแล้ว"; +"CANCEL" = "ยกเลิก"; +"ERROR" = "เกิดข้อผิดผลาด"; +"ERROR_VIDEOID" = "ไม่พบ ID วิดีโอ"; +"ERROR_ADS" = "ไม่สามารถคัดลอกลิงก์วิดีโอขณะกำลังเล่นโฆษณาอยู่ได้"; + +"LOOP_DISABLED" = "ปิดใช้งานการวนซ้ำแล้ว"; +"LOOP_ENABLED" = "เปิดใช้งานการวนซ้ำแล้ว"; "NEW_ISSUES" = "รายงานปัญหาหรือขอฟีเจอร์ใหม่"; "NEW_ISSUES_DESC" = "พบบัคหรืออยากได้ฟีเจอร์ใหม่? แตะที่นี่"; "SOURCE_CODES" = "ซอร์สโค้ด"; "SOURCE_CODES_DESC" = "ลองอ่านโค้ดดูสิ!"; +"EXTRA" = "เพิ่มเติม"; +"FIX_PLAYBACK_ISSUES" = "แก้ไขปัญหาการเล่นวิดีโอ (ชั่วคราว)"; +"FIX_PLAYBACK_ISSUES_DESC" = "แก้ไขปัญหาการเล่นวิดีโอสำหรับผู้ใช้บางราย เมื่อเปิดใช้งานตัวเลือกนี้เครื่องเล่นจะรีโหลดอัตโนมัติเมื่อเกิดข้อผิดพลาดในการเล่นวิดีโอ"; + "PERFER_HEADER" = "การตั้งค่า YouMod"; "PERFER" = "การตั้งค่า"; "IMPORT" = "นำเข้าการตั้งค่า"; @@ -24,6 +40,7 @@ "ERROR_NO_KEYS_IMPORT" = "ไม่มีคีย์สำหรับนำเข้า"; "ERROR_NO_KEYS_EXPORT" = "ไม่มีคีย์สำหรับส่งออก"; "OVERRIDE" = "การกระทำนี้จะเขียนทับการตั้งค่าปัจจุบัน ดำเนินการต่อหรือไม่?"; +"DISABLED" = "ปิดใช้งาน"; "CACHE" = "การจัดการแคช"; "CLEARCACHE" = "ล้างแคช"; @@ -36,8 +53,85 @@ "DOWNLOAD_MANAGER_DESC" = "แทนที่ตัวดาวน์โหลดวิดีโอของ YouTube ด้วยของ YouMod รองรับการดาวน์โหลดวิดีโอ เสียง คำบรรยาย และปกคลิป"; "DOWNLOAD_SAVE_PHOTOS" = "บันทึกลงรูปภาพอัตโนมัติ"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "บันทึกวิดีโอและรูปภาพที่ดาวน์โหลดไปยังแอปรูปภาพของคุณโดยอัตโนมัติ"; -"DOWNLOAD_DRC_AUDIO" = "เลือกเสียงแบบ DRC"; -"DOWNLOAD_DRC_AUDIO_DESC" = "ให้ความสำคัญกับการดาวน์โหลดแทร็กเสียงที่มีการบีบอัดช่วงไดนามิก เพื่อให้ระดับเสียงสม่ำเสมอมากขึ้น"; +"ADD_SHORTS_DOWNLOAD" = "เพิ่มปุ่มดาวน์โหลดไปยัง Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "เพิ่มปุ่มดาวน์โหลดไปยังโอเวอร์เลย์วิดีโอ Shorts"; +"DOWNLOAD_SHORTS" = "ดาวน์โหลด Shorts"; +"COPY_VID_INFO" = "คัดลอกข้อมูลวิดีโอ"; +"COPY_TITLE" = "คัดลอกชื่อ"; +"COPY_DESCRIPTION" = "คัดลอกคำอธิบาย"; +"COPY_ALL_VID_INFO" = "คัดลอกทั้งหมด"; +"COPIED_TITLE" = "คัดลอกชื่อวิดีโอแล้ว"; +"COPIED_DESCRIPTION" = "คัดลอกคำอธิบายวิดีโอแล้ว"; +"COPY_AUTHOR" = "คัดลอกชื่อช่อง"; +"COPIED_AUTHOR" = "คัดลอกชื่อช่องวิดีโอแล้ว"; +"SAVE_THUMBNAIL" = "บันทึกปกคลิป"; +"DOWNLOAD_CAPTIONS" = "ดาวน์โหลดคำบรรยาย"; +"DOWNLOAD_AUDIO" = "ดาวน์โหลดเสียง"; +"DOWNLOAD_VIDEO" = "ดาวน์โหลดวิดีโอ"; +"OPEN_VID_BEFORE" = "กรุณาเปิดวิดีโอก่อนใช้ตัวจัดการดาวน์โหลด"; +"NO_CAPTIONS_URL" = "ไม่พบ URL ของคำบรรยายที่ถูกต้อง"; +"NO_AUDIO_URL" = "ไม่พบ URL ของเสียง"; +"NO_STREAM_URL" = "ไม่พบ URL ของสตรีม"; +"CAPTIONS_FAILED" = "ดาวน์โหลดคำบรรยายไม่สำเร็จ"; +"NO_CAPTIONS" = "ไม่มีคำบรรยายสำหรับวิดีโอนี้"; +"NO_AUDIO_STREAM_FOUND" = "ไม่พบสตรีมเสียง"; +"NO_VID_AUDIO_STREAM_FOUND" = "ไม่พบสตรีมวิดีโอ/เสียง"; +"COPIED_VID_INFO" = "คัดลอกข้อมูลวิดีโอทั้งหมดแล้ว"; +"SAVED_TO_PHOTOS" = "บันทึกลงในแอปรูปภาพแล้ว"; +"SAVE_FAILED" = "บันทึกไม่สำเร็จ"; +"PHOTO_ACCESS_DENINED" = "การเข้าถึงแอปรูปภาพถูกปฏิเสธ"; +"THUMBNAIL_FAILED" = "บันทึกปกคลิปไม่สำเร็จ"; +"DOWNLOADING_THUMBNAIL" = "กำลังดาวน์โหลดปกคลิป..."; +"DOWNLOADING_AUDIO" = "กำลังดาวน์โหลดเสียง..."; +"DOWNLOADING_VIDEO" = "กำลังดาวน์โหลดวิดีโอ..."; +"DOWNLOADING_CAPTIONS" = "กำลังดาวน์โหลดคำบรรยาย..."; +"NO_THUMBNAIL_FOUND" = "ไม่พบปกคลิป"; +"DOWNLOAD_FAILED" = "ดาวน์โหลดไม่สำเร็จ"; +"AUDIO_SAVED" = "บันทึกเสียงเรียบร้อยแล้ว"; +"DOWNLOAD_COMPLETED" = "ดาวน์โหลดเสร็จสมบูรณ์"; +"CANNOT_SAVE_TO_PHOTOS" = "ไม่สามารถบันทึกลงในแอปรูปภาพได้"; +"FINA_VIDEO" = "กำลังจัดเตรียมวิดีโอ..."; +"MERGING_VID_FALLBACK" = "กำลังรวมไฟล์วิดีโอ (โหมดสำรอง)..."; +"MERGING_VID" = "กำลังรวมไฟล์วิดีโอ..."; +"CONVERTING" = "กำลังแปลงเป็น"; +"ALREADY_DOWNLOADING" = "กำลังดาวน์โหลดอยู่แล้ว"; +"DOWNLOAD_CANCELLED" = "ยกเลิกการดาวน์โหลดแล้ว"; +"CANCEL" = "ยกเลิก"; +"TRIMMING_AUDIO" = "กำลังตัดต่อเสียง..."; +"SHOW_THUMBNAIL" = "ดูปกคลิป"; +"COPY_THUMBNAIL" = "คัดลอกปกคลิป"; +"THUMBNAIL_OPTIONS" = "ดาวน์โหลดปกคลิป"; +"COPY" = "คัดลอก"; +"COPIED_TO_CLIPBOARD" = "คัดลอกไปยังคลิปบอร์ดแล้ว"; +"CONNECTING_TO_SERVER" = "กำลังเชื่อมต่อกับเซิร์ฟเวอร์..."; +"DOWNLOADING_TO_SERVER" = "กำลังดาวน์โหลดไปยังเซิร์ฟเวอร์..."; +"COPY_COMMENT_TEXT" = "คัดลอกข้อความคอมเมนต์"; +"SAVE_COMMENT_IMAGE" = "บันทึกคอมเมนต์เป็นรูปภาพ"; +"COPY_COMMENT_IMAGE" = "คัดลอกคอมเมนต์เป็นรูปภาพ"; +"COPY_POST_TEXT" = "คัดลอกข้อความโพสต์"; +"SAVE_POST_IMAGE" = "บันทึกโพสต์เป็นรูปภาพ"; +"COPY_POST_IMAGE" = "คัดลอกโพสต์เป็นรูปภาพ"; +"FIX_DOWNLOAD" = "แก้ไขข้อผิดพลาดการดาวน์โหลด"; +"FIX_DOWNLOAD_DESC" = "ใช้เซิร์ฟเวอร์นอกในการดาวน์โหลดแทน ไม่รองรับการดาวน์โหลดแทร็กเสียงอื่นๆ"; +"DOWNLOAD_SERVER" = "เซิร์ฟเวอร์ดาวน์โหลด"; +"CHOOSE_DOWNLOAD_SERVER" = "เลือกเซิร์ฟเวอร์ดาวน์โหลด"; +"DOWNLOAD_COMMENT" = "บันทึกข้อมูลคอมเมนต์"; +"DOWNLOAD_COMMENT_DESC" = "อนุญาตให้คัดลอกข้อความคอมเมนต์และบันทึกคอมเมนต์เป็นรูปภาพโดยการแตะค้างไว้"; +"DOWNLOAD_POST" = "บันทึกข้อมูลโพสต์"; +"DOWNLOAD_POST_DESC" = "อนุญาตให้คัดลอกข้อความโพสต์และบันทึกโพสต์เป็นรูปภาพโดยการแตะค้างไว้"; +"TRANSLATE_COMMENT" = "แปลคอมเมนต์"; +"TRANSLATE_POST" = "แปลโพสต์"; +"SERVER_EUROPRE1" = "ยุโรป"; +"SERVER_ASIA1" = "ไทย - เอเชีย"; +"SHOW_OPTIONS" = "แสดงตัวเลือก"; +"ENGLISH" = "อังกฤษ"; +"TRANSLATION" = "แปลภาษา"; +"LANGUAGE" = "ภาษา"; +"TRANSLATING" = "กำลังแปล..."; +"SELECT_LANG" = "เลือกภาษา"; +"TRANSLATE_FAILED" = "แปลไม่สำเร็จ กรุณาลองใหม่อีกครั้ง"; +"SAVE_CURRENT_IMAGE" = "บันทึกรูปปัจจุบัน"; +"COPY_CURRENT_IMAGE" = "คัดลอกรูปปัจจุบัน"; "APPEARANCE" = "ลักษณะ"; "OLED_THEME" = "ธีม OLED"; @@ -46,6 +140,8 @@ "OLED_KEYBOARD_DESC" = "ใช้สีดำสนิท (OLED) กับคีย์บอร์ดในโหมดมืด"; "NAVBAR" = "แถบนำทาง"; +"STICKY_NAVBAR" = "ยึดแถบนำทาง"; +"STICKY_NAVBAR_DESC" = "ล็อกแถบนำทางให้คงเห็นอยู่เมื่อเลื่อนลง"; "HIDE_YT_LOGO" = "ซ่อนโลโก้ YouTube"; "HIDE_YT_LOGO_DESC" = "ซ่อนโลโก้ YouTube ในแถบนำทาง"; "PREMIUM_LOGO" = "ใช้โลโก้ YouTube Premium"; @@ -62,32 +158,74 @@ "FEED" = "ฟีด"; "HIDE_SUBBAR" = "ซ่อนแถบหมวดหมู่"; "HIDE_SUBBAR_DESC" = "ซ่อนแถบหมวดหมู่ในฟีด"; +"HIDE_HORI_SHELF" = "ซ่อนฟีดแนวนอน"; +"HIDE_HORI_SHELF_DESC" = "ลบฟีดแนวนอนทั้งหมด (เช่น ข่าวสาร, รับชมต่อ, โพสต์ ฯลฯ) ออกจากหน้าฟีด ยกเว้นเกม"; "HIDE_MUSIC_PLAYLISTS" = "ซ่อนเพลย์ลิสต์เพลง"; "HIDE_MUSIC_PLAYLISTS_DESC" = "ซ่อนตัวสร้างเพลย์ลิสต์ในฟีด"; "HIDE_FEED_POST" = "ซ่อนโพส"; "HIDE_FEED_POST_DESC" = "ซ่อนโพสในฟีด"; +"HIDE_SURVEYS" = "ซ่อนแบบสำรวจ"; +"HIDE_SURVEYS_DESC" = "ซ่อนแบบสำรวจวิดีโอที่ YouTube แสดงในบางครั้ง"; "HIDE_SHORTS_SHELF" = "ซ่อน Shorts"; "HIDE_SHORTS_SHELF_DESC" = "ซ่อน Shorts ในฟีด"; +"KEEP_SHORTS_SUBSCRIPT" = "แสดง Shorts ในหน้าการติดตาม"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "ป้องกันไม่ให้วิดีโอ Shorts ถูกลบออกจากแท็บการติดตาม"; "HIDE_SEARCH_HISTORY" = "ซ่อนประวัติการค้นหา"; "HIDE_SEARCH_HISTORY_DESC" = "ซ่อนประวัติและคำแนะนำการค้นหา\nหมายเหตุ: ประวัติการค้นหายังสามารถเข้าถึงได้ในอุปกรณ์อื่น"; -"HIDE_SUB_BUTTON" = "ซ่อนปุ่มติดตาม"; -"HIDE_SUB_BUTTON_DESC" = "ซ่อนปุ่มติดตามในช่องและคำอธิบายวิดีโอ"; -"HIDE_SHOP_BUTTON" = "ซ่อนปุ่มร้านค้า"; -"HIDE_SHOP_BUTTON_DESC" = "ซ่อนปุ่มร้านค้าในช่อง"; -"HIDE_MEMBER_BUTTON" = "ซ่อนปุ่ม 'สมัคร'"; -"HIDE_MEMBER_BUTTON_DESC" = "ซ่อนปุ่ม 'สมัคร' ในช่องและคำอธิบายวิดีโอ"; +"HIDE_PLAYABLES" = "ซ่อนมินิเกม"; +"HIDE_PLAYABLES_DESC" = "ซ่อนมินิเกมออกจากหน้าฟีด"; +"HIDE_RELATED_VIDEOS" = "ซ่อนวิดีโอที่เกี่ยวข้อง"; +"HIDE_RELATED_VIDEOS_DESC" = "ซ่อนวิดีโอที่เกี่ยวข้องใต้เครื่องเล่น (ไม่ใช่ในโอเวอร์เลย์)"; "PLAYER" = "เครื่องเล่น"; +"PLAYBACK_SPEED" = "ความเร็ววิดีโอ"; +"CONTROL_CENTER" = "ศูนย์ควบคุม"; +"PLAYER_ACTIONS" = "การกระทำเครื่องเล่น"; +"GESTURE_HEADER" = "ท่าทาง"; +"QUALITY_WIFI" = "คุณภาพวิดีโอเมื่อเล่นบน WiFi"; +"QUALITY_WIFI_DESC" = "เลือกคุณภาพวิดีโออัตโนมัติจากตัวเลือกเมื่อเล่นบน Wi-Fi"; +"QUALITY_CELLULAR" = "คุณภาพวิดีโอเมื่อเล่นบนเซลลูลาร์"; +"QUALITY_CELLULAR_DESC" = "เลือกคุณภาพวิดีโออัตโนมัติจากตัวเลือกเมื่อเล่นบนเซลลูลาร์"; +"QUALITY_LOW_POWER" = "คุณภาพวิดีโอในโหมดประหยังพลังงาน"; +"QUALITY_LOW_POWER_DESC" = "เลือกคุณภาพวิดีโออัตโนมัติจากตัวเลือกเมื่อเปิดโหลดประหยัดพลังงานในอุปกรณ์"; +"DEFAULT" = "ค่าเริ่มต้น"; +"BEST" = "ดีที่สุด"; +"AUDIO_TRACK" = "แทร็กเสียงที่ต้องการ"; +"ORIGINAL" = "ต้นฉบับ"; +"SELECT_MANUALLY" = "เลือกเอง"; +"AUDIO_TRACK_SELECT" = "แทร็กเสียงที่เลือก"; +"AUDIO_TRACK_SELECT_DESC" = "เลือกแทร็กเสียงที่ต้องการโดยอัตโนมัติ ถ้ามี"; +"NO_AUTO_DUBBED" = "ใช้เสียงต้นฉบับ"; +"NO_AUTO_DUBBED_DESC" = "ใช้เสียงต้นฉบับถ้าไม่พบภาษาที่ต้องการหรือเป็นเสียงแบบพากย์อัตโนมัติ"; +"CAPTION_TRACK" = "คำบรรยายที่ต้องการ"; +"CAPTION_TRACK_SELECT" = "คำบรรยายที่เลือก"; +"CAPTION_TRACK_SELECT_DESC" = "เลือกภาษาของคำบรรยายที่ต้องการให้แสดง ถ้ามี"; +"DISABLES_CAPTION_TRACK" = "ปิดใช้งานคำบรรยายอัตโนมัติ"; +"DISABLES_CAPTION_TRACK_DESC" = "ปิดใช้งานคำบรรยายอัตโนมัติถ้าภาษาที่เลือกเป็นแบบสร้างโดยอัตโนมัติหรือไม่พบ"; +"DEFAULT_SPEED" = "ความเร็วเริ่มต้น"; +"DEFAULT_SPEED_DESC" = "ตั้งค่าความเร็วเริ่มต้นเมื่อเปิดวิดีโอใหม่"; +"QUALITY_BUTTON" = "ปุ่มควบคุมคุณภาพวิดีโอ"; +"QUALITY_BUTTON_DESC" = "แสดงปุ่มควบคุมคุณภาพวิดีโอบนโอเวอร์เลย์"; +"SPEED_BUTTON" = "ปุ่มควบคุมความเร็ววิดีโอ"; +"SPEED_BUTTON_DESC" = "แสดงปุ่มควบคุมความเร็ววิดีโอบนโอเวอร์เลย์"; +"MUTE_BUTTON" = "ปุ่มปิดเสียง"; +"MUTE_BUTTON_DESC" = "แสดงปุ่มปิด/เปิดเสียงบนโอเวอร์เลย์"; +"SHARE_BUTTON" = "ปุ่มแชร์"; +"SHARE_BUTTON_DESC" = "แสดงปุ่มแชร์ลิงก์วิดีโอบนโอเวอร์เลย์"; +"LOOP_BUTTON" = "ปุ่มวนซ้ำวิดีโอ"; +"LOOP_BUTTON_DESC" = "แสดงปุ่มวนซ้ำวิดีโอบนโอเวอร์เลย์"; +"CAPTION_BUTTON" = "ปุ่มคำบรรยาย"; +"CAPTION_BUTTON_DESC" = "แสดงปุ่มคัดลอกคำบรรยายวิดีโอบนโอเวอร์เลย์"; +"HOLD_TO_SPEED" = "ความเร็วเมื่อกดค้าง"; +"HOLD_TO_SPEED_DESC" = "ใช้ความเร็วในการเล่นวิดีโอแบบกำหนดเองเมื่อกดค้างที่หน้าจอ"; "HIDE_AUTOPLAY" = "ซ่อนสวิตซ์เล่นอัตโนมัติ"; "HIDE_AUTOPLAY_DESC" = "ซ่อนสวิตซ์เล่นอัตโนมัติในโอเวอร์เลย์วิดีโอ"; "HIDE_CAPTIONS_BUTTON" = "ซ่อนปุ่มคำบรรยาย"; "HIDE_CAPTIONS_BUTTON_DESC" = "ซ่อนปุ่มคำบรรยายในโอเวอร์เลย์วิดีโอ"; "HIDE_CAST_BUTTON_PLAYER" = "ซ่อนปุ่มแคสต์"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "ซ่อนปุ่มแคสต์ในโอเวอร์เลย์วิดีโอ"; -"HIDE_PREV_BUTTON" = "ซ่อนปุ่มก่อนหน้า"; -"HIDE_PREV_BUTTON_DESC" = "ซ่อนปุ่มก่อนหน้าในโอเวอร์เลย์วิดีโอ"; -"HIDE_NEXT_BUTTON" = "ซ่อนปุ่มถัดไป"; -"HIDE_NEXT_BUTTON_DESC" = "ซ่อนปุ่มถัดไปในโอเวอร์เลย์วิดีโอ"; +"HIDE_NEXT_AND_PREV_BUTTON" = "ซ่อนปุ่มก่อนหน้าและปุ่มถัดไป"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "ซ่อนปุ่มก่อนหน้าและปุ่มถัดไปในโอเวอร์เลย์วิดีโอ"; "REPLACE_PREVNEXT_BUTTONS" = "เปลี่ยนปุ่มก่อนหน้า/ถัดไป"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "เปลี่ยนปุ่มเล่นวิดีโอก่อนหน้าและถัดไปในเครื่องเล่น เป็นปุ่มกรอกลับและปุ่มข้ามไปข้างหน้าแทน"; "REMOVE_DARK_OVERLAY" = "ลบโอเวอร์เลย์มืด"; @@ -102,6 +240,12 @@ "HIDE_PAID_OVERLAY_DESC" = "ซ่อนกล่องข้อความ 'มีโฆษณาจากสปอนเซอร์' ในโอเวอร์เลย์วิดีโอ"; "HIDE_WATERMARK" = "ซ่อนลายน้ำ"; "HIDE_WATERMARK_DESC" = "ซ่อนลายน้ำช่องในโอเวอร์เลย์วิดีโอ"; +"PAUSE_ON_OVERLAY" = "หยุดวิดีโอบนโอเวอร์เลย์"; +"PAUSE_ON_OVERLAY_DESC" = "หยุดวิดีโอเมื่อเห็นโอเวอร์เลย์"; +"DISABLES_SNAP_TO_CHAPTER" = "ปิดใช้งานการเลื่อนไปยังตอนถัดไป"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "ปิดใช้งานการแตะสองครั้งเพื่อข้ามไปยังตอนถัดไป"; +"DISABLES_ENGAGE_PANEL" = "ปิดใช้งานแผงควบคุมในโหมดเต็มจอ"; +"DISABLES_ENGAGE_PANEL_DESC" = "ป้องกันไม่ให้แผงควบคุมแสดงขึ้นมาในโหมดเต็มหน้าจอ"; "GESTURES" = "เปิดใช้งานการควบคุมด้วยท่าทาง"; "GESTURES_DESC" = "ปัดขึ้นหรือลงบริเวณขอบซ้ายหรือขวาของหน้าจอวิดีโอ เพื่อปรับความสว่าง ระดับเสียง หรือความเร็ววิดีโอ"; "GESTURE_AREA" = "พื้นที่เปิดใช้งานท่าทาง"; @@ -132,12 +276,12 @@ "DISABLES_LONG_HOLD_DESC" = "ปิดการเร่งความเร็วโดยการกดค้างในโอเวอร์เลย์วิดีโอ"; "AUTO_EXIT_FULLSCREEN" = "ออกจากเต็มจออัตโนมัติ"; "AUTO_EXIT_FULLSCREEN_DESC" = "ออกจากเต็มจออัตโนมัติเมื่อวิดีโอจบ"; -"AUTO_DISABLES_CAPTION" = "ปิดคำบรรยายอัตโนมัติ"; -"AUTO_DISABLES_CAPTION_DESC" = "ปิดคำบรรยาย (CC) โดยอัตโนมัติทุกครั้งเมื่อเริ่มเล่นวิดีโอใหม่"; "DISABLES_SHOW_REMAINING" = "ซ่อนเวลาที่เหลือ"; "DISABLES_SHOW_REMAINING_DESC" = "ซ่อนเวลาที่เหลือในโอเวอร์เลย์วิดีโอ"; "ALWAYS_SHOW_REMAINING" = "แสดงเวลาที่เหลือเสมอ"; "ALWAYS_SHOW_REMAINING_DESC" = "แสดงเวลาที่เหลือเสมอในโอเวอร์เลย์วิดีโอ"; +"COPY_TIMESTAMP_ON_PAUSE" = "คัดลอกลิงก์วิดีโอพร้อมเวลา"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "อนุญาตให้คัดลอกลิงก์วิดีโอพร้อมเวลาปัจจุบันไปยังคลิปบอร์ดเมื่อกดปุ่มหยุดเล่น"; "SHOW_REMAINING_EXTRA" = "แสดงเวลาที่เหลือ"; "SHOW_REMAINING_EXTRA_DESC" = "เพิ่มเวลาที่จะเล่นจบจริงในเวลาของวิดีโอ"; "HIDE_FULLSCREEN_ACTIONS" = "ซ่อนปุ่มในโหมดเต็มหน้าจอ"; @@ -156,85 +300,102 @@ "OLD_QUALITY_PICKER_DESC" = "ใช้ตัวเลือกคุณภาพแบบเก่าเพื่อลิสต์คุณภาพวิดีโอได้เร็วขึ้น"; "EXTRA_SPEED" = "เพิ่มตัวเลือกความเร็ววิดีโอ"; "EXTRA_SPEED_DESC" = "เพิ่มตัวเลือกความเร็ววิดีโอถึง 10x"; -"DISABLE_HINTS" = "ปิดคำแนะนำ"; -"DISABLE_HINTS_DESC" = "ปิดคำแนะนำการใช้งานต่างๆ"; -"FORCE_MINIPLAYER" = "บังคับใช้ตัวเล่นย่อส่วน"; -"FORCE_MINIPLAYER_DESC" = "อนุญาตให้เล่นวิดีโอทุกประเภทในมินิเพลเยอร์ รวมถึงวิดีโอสำหรับเด็ก"; +"FORCE_MINIPLAYER" = "บังคับใช้เครื่องเล่นขนาดเล็ก"; +"FORCE_MINIPLAYER_DESC" = "อนุญาตให้เล่นวิดีโอทุกประเภทในเครื่องเล่นขนาดเล็ก รวมถึงวิดีโอสำหรับเด็ก"; "FORCE_SEEKBAR" = "แสดงแถบสถานะการเล่นตลอดเวลา"; "FORCE_SEEKBAR_DESC" = "ทำให้แถบความคืบหน้าแสดงขึ้นตลอดเวลาในขณะที่เล่นวิดีโอแบบเต็มหน้าจอ"; -"HIDE_LIKE_BUTTON" = "ซ่อนปุ่มถูกใจ"; -"HIDE_LIKE_BUTTON_DESC" = "ซ่อนปุ่มถูกใจใต้โอเวอร์เลย์วิดีโอ"; -"HIDE_DISLIKE_BUTTON" = "ซ่อนปุ่มไม่ถูกใจ"; -"HIDE_DISLIKE_BUTTON_DESC" = "ซ่อนปุ่มไม่ถูกใจใต้โอเวอร์เลย์วิดีโอ"; -"HIDE_SHARE_BUTTON" = "ซ่อนปุ่มแชร์"; -"HIDE_SHARE_BUTTON_DESC" = "ซ่อนปุ่มแชร์ใต้โอเวอร์เลย์วิดีโอ"; -"HIDE_DOWNLOAD_BUTTON" = "ซ่อนปุ่มดาวน์โหลด"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "ซ่อนปุ่มดาวน์โหลดใต้โอเวอร์เลย์วิดีโอ"; -"HIDE_CLIP_BUTTON" = "ซ่อนปุ่มคลิป"; -"HIDE_CLIP_BUTTON_DESC" = "ซ่อนปุ่มคลิปใต้โอเวอร์เลย์วิดีโอ"; -"HIDE_REMIX_BUTTON" = "ซ่อนปุ่มรีมิกซ์"; -"HIDE_REMIX_BUTTON_DESC" = "ซ่อนปุ่มรีมิกซ์ใต้โอเวอร์เลย์วิดีโอ"; -"HIDE_SAVE_BUTTON" = "ซ่อนปุ่มบันทึก"; -"HIDE_SAVE_BUTTON_DESC" = "ซ่อนปุ่มบันทึกใต้โอเวอร์เลย์วิดีโอ"; +"TAP_TO_SEEK" = "แตะเพื่อกรอ"; +"TAP_TO_SEEK_DESC" = "กรอไปยังส่วนต่างๆของวิดีโออย่างรวดเร็วโดยแตะที่แถบคืบหน้า"; +"DISABLES_ZOOM" = "ปิดใช้งานการซูมวิดีโอ"; +"DISABLES_ZOOM_DESC" = "ปิดใช้งานการซูมวิดีโอแบบอิสระ"; +"USES_24_HOURS_TIME" = "รูปแบบเวลา 24 ชั่วโมง"; +"USES_24_HOURS_TIME_DESC" = "แสดงเวลาจบวิดีโอแบบ 24 ชั่วโมง"; +"PAUSE_TWO_FINGERS" = "หยุดวิดีโอโดยใช้ 2 นิ้ว"; +"PAUSE_TWO_FINGERS_DESC" = "หยุด/เล่น วิดีโอโดยใช้ 2 นิ้วแตะหน้าจอ"; +"SKIP_BACKWARD" = "ย้อนกลับ"; +"SKIP_BACKWARD_DESC" = "เปลี่ยนปุ่มเล่นเพลงก่อนหน้าในศูนย์ควบคุมให้เป็นปุ่มข้ามย้อนกลับตามจำนวนวินาทีที่กำหนด"; +"SKIP_FORWARD" = "ไปข้างหน้า"; +"SKIP_FORWARD_DESC" = "เปลี่ยนปุ่มเล่นเพลงถัดไปในศูนย์ควบคุมให้เป็นปุ่มข้ามไปข้างหน้าตามจำนวนวินาทีที่กำหนด"; +"REWIND_SECONDS" = "จำนวนวินาทีที่ย้อนกลับ"; +"FORWARD_SECONDS" = "จำนวนวินาทีที่ไปข้างหน้า"; +"HIDE_COMMENTS_SECTION" = "ซ่อนกล่องคอมเมนต์"; +"HIDE_COMMENTS_SECTION_DESC" = "ซ่อนกล่องคอมเมนต์ทั้งหมด"; +"HIDE_COMMENTS_PREVIEW" = "ซ่อนตัวอย่างคอมเมนต์"; +"HIDE_COMMENTS_PREVIEW_DESC" = "ซ่อนตัวอย่างคอมเมนต์ยอดนิยม แต่ยังคงสามารถกดเปิดดูคอมเมนต์ทั้งหมดได้"; +"USE_ANOTHER_MINIPLAYER" = "ใช้เครื่องเล่นขนาดเล็กอีกรูปแบบหนึ่ง"; +"USE_ANOTHER_MINIPLAYER_DESC" = "ใช้เครื่องเล่นขนาดเล็กรูปแบบที่ได้รับการออกแบบใหม่จาก YouTube เวอร์ชันเก่า"; +"LOCK_SPEED" = "ล็อคความเร็ว"; +"LOCK_SPEED_DESC" = "ล็อคตามความเร็วที่เลือกไว้โดยการเลื่อนลง"; +"SEEK_ON_OVERLAY" = "กรอวิดีโอบนโอเวอร์เลย์"; +"SEEK_ON_OVERLAY_DESC" = "อนุญาตให้ควบคุมการกรอวิดีโอเดินหน้า-ถอยหลัง โดยการปัดไปทางซ้ายและขวาบนโอเวอร์เลย์วิดีโอ"; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "ซ่อนปุ่มถูกใจ"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "ซ่อนปุ่มถูกใจใน Shorts"; -"HIDE_SHORTS_DISLIKE_BUTTON" = "ซ่อนปุ่มไม่ถูกใจ"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "ซ่อนปุ่มไม่ถูกใจใน Shorts"; -"HIDE_SHORTS_COMMENT_BUTTON" = "ซ่อนปุ่มคอมเมนต์"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "ซ่อนปุ่มคอมเมนต์ใน Shorts"; -"HIDE_SHORTS_SHARE_BUTTON" = "ซ่อนปุ่มแชร์"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "ซ่อนปุ่มแชร์ใน Shorts"; -"HIDE_SHORTS_REMIX_BUTTON" = "ซ่อนปุ่มรีมิกซ์"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "ซ่อนปุ่มรีมิกซ์ใน Shorts"; -"HIDE_METADATA_BUTTON" = "ซ่อนปุ่มข้อมูลเพลง"; -"HIDE_METADATA_BUTTON_DESC" = "ซ่อนปุ่มข้อมูลเพลงใน Shorts"; +"SHORTS_ONLY" = "โหมด Shorts เท่านั้น"; +"SHORTS_ONLY_DESC" = "จำกัดหน้าฟีดให้แสดงเฉพาะ Shorts เท่านั้น คุณสามารถปิดใช้งานโหมด Shorts เท่านั้นได้โดยการกดค้างด้วยสองนิ้วในเครื่องเล่น Shorts"; +"SHORTS_ONLY_DISABLED" = "ปิดใช้งานโหมด Shorts เท่านั้นแล้ว"; +"SHORTS_ACTION" = "การกระทำของ Shorts เมื่อจบวิดีโอ"; +"LOOP" = "ลูปวิดีโอ"; +"SKIP_TO_NEXT_SHORTS" = "ข้ามไปยังวิดีโอถัดไป"; +"PAUSE_SHORTS" = "หยุดวิดีโอ"; +"REMOVE_LIVE_SHORTS" = "ลบวิดีโอสด"; +"REMOVE_LIVE_SHORTS_DESC" = "นำวิดีโอสดออกจากฟีด"; "HIDE_SHORTS_PRODUCT" = "ซ่อนสินค้า"; "HIDE_SHORTS_PRODUCT_DESC" = "ซ่อนสินค้าและลิงค์สินค้าในโอเวอร์เลย์ Shorts"; "HIDE_SHORTS_RECBAR" = "ซ่อนแถบแนะนำ"; "HIDE_SHORTS_RECBAR_DESC" = "ซ่อนแถบแนะนำในโอเวอร์เลย์ Shorts"; -"HIDE_SHORTS_COMMIT" = "ซ่อนแถบค่าคอมมิชชั่น"; -"HIDE_SHORTS_COMMIT_DESC" = "ซ่อนแถบค่าคอมมิชชั่นในบางวิดีโอ"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "ซ่อนปุ่มการติดตาม"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "ซ่อนปุ่มการติดตามด้านบนตัววิดีโอ"; -"HIDE_SHORTS_LIVE_BUTTON" = "ซ่อนปุ่มสด"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "ซ่อนปุ่มสดด้านบนตัววิดีโอ"; -"HIDE_SHORTS_LENS_BUTTON" = "ซ่อนปุ่ม Lens"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "ซ่อนปุ่ม Lens ด้านบนตัววิดีโอ"; -"HIDE_SHORTS_TRENDS_BUTTON" = "ซ่อนปุ่มเทรนด์"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "ซ่อนปุ่มเทรนด์ด้านบนตัววิดีโอ"; -"HIDE_SHORTS_TO_VIDEO" = "ซ่อนลิงก์ไปวิดีโอเต็ม"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "ซ่อนลิงก์ไปวิดีโอเต็มใน Shorts"; "ENABLES_SHORTS_QUALITY" = "เปิดใช้งานตัวเลือกคุณภาพ Shorts"; "ENABLES_SHORTS_QUALITY_DESC" = "สามารถเลือกคุณภาพ Shorts ได้"; "SHOW_SHORTS_SEEKBAR" = "แสดงแถบเลื่อนเสมอ"; "SHOW_SHORTS_SEEKBAR_DESC" = "แสดงแถบความคืบหน้าเสมอ"; +"REMOVE_POSTS_SHORTS" = "ลบโพสต์"; +"REMOVE_POSTS_SHORTS_DESC" = "นำโพสต์ออกจากฟีด"; +"SHORTS_FULLSCREEN" = "ซูมเพื่อดูแบบเต็มหน้าจอ"; +"SHORTS_FULLSCREEN_DESC" = "ซูมเข้า-ออกเพื่อรับชม Shorts แบบเต็มหน้าจอ"; "TABBAR" = "แท็บนำทาง"; "DEFAULT_TAB" = "แท็บเริ่มต้น"; -"DEFAULT_TAB_DESC" = "คุณสามารถเลือกแท็บที่จะเปิดเมื่อเริ่มแอปได้"; -"HOME_NAME" = "หน้าแรก"; -"SUBSCRIPT_NAME" = "การติดตาม"; -"LIB_NAME" = "คลัง (คุณ)"; +"FORSTED_TAB_BAR" = "แท็บโปร่งใส"; +"ENABLED" = "เปิดใช้งาน"; +"HOME_TAB" = "หน้าแรก"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "สร้าง"; +"SUBSCRIPTIONS_TAB" = "การติดตาม"; +"LIBRARY_TAB" = "คลัง (คุณ)"; "HIDE_TAB_INDI" = "ซ่อนตัวบอกสถานะ"; "HIDE_TAB_INDI_DESC" = "ซ่อนตัวบอกสถานะในแท็บต่างๆ"; "HIDE_TAB_LABELS" = "ซ่อนชื่อแท็บ"; "HIDE_TAB_LABELS_DESC" = "ซ่อนชื่อแท็บในแท็บต่างๆ"; -"HIDE_HOME_TAB" = "ซ่อนแท็บหน้าแรก"; -"HIDE_HOME_TAB_DESC" = "ซ่อนแท็บหน้าแรกในแท็บนำทาง"; -"HIDE_SHORTS_TAB" = "ซ่อนแท็บ Shorts"; -"HIDE_SHORTS_TAB_DESC" = "ซ่อนแท็บ Shorts ในแท็บนำทาง"; -"HIDE_CREATE_BUTTON" = "ซ่อนปุ่มสร้าง"; -"HIDE_CREATE_BUTTON_DESC" = "ซ่อนปุ่มสร้างในแท็บนำทาง"; -"HIDE_SUBSCRIPT_TAB" = "ซ่อนแท็บการติดตาม"; -"HIDE_SUBSCRIPT_TAB_DESC" = "ซ่อนแท็บการติดตามในแท็บนำทาง"; +"HISTORY_TAB" = "ประวัติการเข้าชม"; +"GAMING_TAB" = "เกม"; +"SPORTS_TAB" = "กีฬา"; +"NOTI_TAB" = "การแจ้งเตือน"; +"NEWS_TAB" = "ข่าวสาร"; +"MUSIC_TAB" = "เพลง"; +"WATCH_LATER_TAB" = "ดูภายหลัง"; +"PLAYLIST_TAB" = "เพลย์ลิสต์"; +"LIKE_TAB" = "วิดีโอที่ชอบ"; +"LIVE_TAB" = "สด"; +"POST_TAB" = "โพสต์"; +"VIDEO_TAB" = "วิดีโอของคุณ"; +"MOVIE_TAB" = "ภาพยนตร์"; +"COURSE_TAB" = "หลักสูตร"; +"MINIGAME_TAB" = "เล่นเกม"; +"FASHION_TAB" = "แฟชั่นและความงาม"; +"LEARNING_TAB" = "การเรียนรู้"; +"MANAGE_TABS" = "จัดการแท็บ"; +"MANAGE_TABS_DESC" = "จัดเรียงและเปิด/ปิดใช้งานแท็บ"; +"TAB_REORDER_HINT" = "ลากเพื่อจัดเรียงใหม่ เปิด/ปิดเพื่อแสดง/ซ่อน\nนอกจากนี้ การตั้งค่าแท็บยังสามารถเข้าถึงได้โดยการแตะแท็บแรกค้างไว้"; +"TAB_LIMIT" = "จำกัดแท็บ"; +"TAB_LIMIT_DESC" = "สามารถมีแท็บได้สูงสุด 6 แท็บ"; +"ZERO_TAB_DESC" = "ต้องเปิดใช้งานอย่างน้อย 1 แท็บ"; "MISCELLANEOUS" = "อื่นๆ"; "BACKGROUND_PLAYBACK" = "เล่นภายในเบื้องหลัง"; "BACKGROUND_PLAYBACK_DESC" = "เปิดใช้งานการเล่นวิดีโอภายในเบื้องหลัง"; "DISABLES_SHORTS_PIP" = "ปิด PiP ของ Shorts"; "DISABLES_SHORTS_PIP_DESC" = "พยายามปิด PiP ของ Shorts อาจไม่สามารถปิดได้ 100%"; +"DISABLE_HINTS" = "ปิดคำแนะนำ"; +"DISABLE_HINTS_DESC" = "ปิดคำแนะนำการใช้งานต่างๆ"; "BLOCK_UPGRADE_DIALOGS" = "บล็อกกล่องแจ้งเตือนอัปเดต"; "BLOCK_UPGRADE_DIALOGS_DESC" = "บล็อกกล่องข้อความแจ้งเตือนให้อัปเดตแอป"; "ARE_YOU_THERE_DIALOG" = "ซ่อนกล่องแจ้งเตือน 'คุณยังอยู่ไหม'"; @@ -247,7 +408,114 @@ "DISABLES_SNACK_BAR_DESC" = "ปิดใข้งาน Snack bar เมื่อแตะปุ่มหรือทำอะไรในแอป"; "HIDE_STARTUP_ANIMATIONS" = "ซ่อนแอนิเมชัน"; "HIDE_STARTUP_ANIMATIONS_DESC" = "ซ่อนแอนิเมชันตอนเปิดแอป"; -"HIDE_PLAY_IN_NEXT_QUEUE" = "ซ่อน 'เล่นถัดไป'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "ซ่อน 'เล่นถัดไป' ในเมนู 3 จุด"; "HIDE_LIKE_DISLIKE_VOTES" = "ซ่อนโหวดชอบ/ไม่ชอบ"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "ซ่อนกล่องโหวดชอบ/ไม่ชอบ"; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "ซ่อนกล่องโหวดชอบ/ไม่ชอบ"; +"FLYOUT_MENU" = "เมนูป๊อปอัป"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "ลบตัวเลือก 'เล่นต่อจากวิดีโอนี้'"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "ลบตัวเลือก 'เล่นต่อจากวิดีโอนี้' ออกจากเมนูป๊อปอัป"; +"REMOVE_DOWNLOAD_OPTION" = "ลบตัวเลือก 'ดาวน์โหลดวิดีโอ'"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "ลบตัวเลือก 'ดาวน์โหลดวิดีโอ' ออกจากเมนูป๊อปอัป"; +"REMOVE_WATCH_LATER_OPTION" = "ลบตัวเลือก 'บันทึกไปยังดูภายหลัง'"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "ลบตัวเลือก 'บันทึกไปยังดูภายหลัง' ออกจากเมนูป๊อปอัป"; +"REMOVE_SAVE_OPTION" = "ลบตัวเลือก 'บันทึกไปยังเพลย์ลิสต์'"; +"REMOVE_SAVE_OPTION_DESC" = "ลบตัวเลือก 'บันทึกไปยังเพลย์ลิสต์' ออกจากเมนูป๊อปอัป"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "ลบตัวเลือก 'ลบจากเพลย์ลิสต์'"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "ลบตัวเลือก 'ลบจากเพลย์ลิสต์' ออกจากเมนูป๊อปอัป"; +"REMOVE_SHARE_OPTION" = "ลบตัวเลือก 'แชร์'"; +"REMOVE_SHARE_OPTION_DESC" = "ลบตัวเลือก 'แชร์' ออกจากเมนูป๊อปอัป"; +"REMOVE_NOT_INTERESTED_OPTION" = "ลบตัวเลือก 'ไม่สนใจ'"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "ลบตัวเลือก 'ไม่สนใจ' ออกจากเมนูป๊อปอัป"; +"REMOVE_DONT_RECOMMEND_OPTION" = "ลบตัวเลือก 'ไม่ต้องแนะนำช่อง'"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "ลบตัวเลือก 'ไม่ต้องแนะนำช่อง' ออกจากเมนูป๊อปอัป"; +"REMOVE_INFO_OPTION" = "ลบตัวเลือก 'เพราะเหตุใดฉันจึงเห็นฟีเจอร์นี้'"; +"REMOVE_INFO_OPTION_DESC" = "ลบตัวเลือก 'เพราะเหตุใดฉันจึงเห็นฟีเจอร์นี้' ออกจากเมนูป๊อปอัป"; +"REMOVE_FILTER_OPTION" = "ลบตัวเลือก 'ตัวกรองการค้นหา'"; +"REMOVE_FILTER_OPTION_DESC" = "ลบตัวเลือก 'ตัวกรองการค้นหา' ออกจากเมนูป๊อปอัป"; +"REMOVE_REPORT_OPTION" = "ลบตัวเลือก 'รายงาน'"; +"REMOVE_REPORT_OPTION_DESC" = "ลบตัวเลือก 'รายงาน' ออกจากเมนูป๊อปอัป"; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "ลบตัวเลือก 'ฟังเพลงด้วย YouTube Music'"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "ลบตัวเลือก 'ฟังเพลงด้วย YouTube Music' ออกจากเมนูป๊อปอัป"; +"REMOVE_FEED_BACK_OPTION" = "ลบตัวเลือก 'ส่งคอมเมนต์'"; +"REMOVE_FEED_BACK_OPTION_DESC" = "ลบตัวเลือก 'ส่งคอมเมนต์' ออกจากเมนูป๊อปอัป"; +"REMOVE_CAST_OPTION" = "ลบตัวเลือก 'รับชมบนทีวี'"; +"REMOVE_CAST_OPTION_DESC" = "ลบตัวเลือก 'แคสต์' ออกจากเมนูป๊อปอัป"; +"REMOVE_SHUFFLE_OPTION" = "ลบตัวเลือก 'สุ่ม'"; +"REMOVE_SHUFFLE_OPTION_DESC" = "ลบตัวเลือก 'สุ่ม' ออกจากเมนูป๊อปอัป"; +"REMOVE_UN_SUB_OPTION" = "ลบตัวเลือก 'ยกเลิกการสมัคร'"; +"REMOVE_UN_SUB_OPTION_DESC" = "ลบตัวเลือก 'ยกเลิกการสมัคร' ออกจากเมนูป๊อปอัป"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "ลบตัวเลือก 'ซ่อนวิดีโอจากเพลย์ลิสต์'"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "ลบตัวเลือก 'ซ่อนวิดีโอจากเพลย์ลิสต์' ออกจากเมนูป๊อปอัป"; +"REMOVE_HELP_OPTION" = "ลบตัวเลือก 'ความช่วยเหลือและคอมเมนต์'"; +"REMOVE_HELP_OPTION_DESC" = "ลบตัวเลือก 'ความช่วยเหลือและคอมเมนต์' ออกจากเมนูป๊อปอัป"; +"REMOVE_NOTIFY_OPTION" = "ลบตัวเลือก 'แจ้งเตือนให้ฉันทราบ'"; +"REMOVE_NOTIFY_OPTION_DESC" = "ลบตัวเลือก 'แจ้งเตือนให้ฉันทราบ' ออกจากเมนูป๊อปอัป"; +"REMOVE_CLEARSCREEN_OPTION" = "ลบตัวเลือก 'ล้างหน้าจอ'"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "ลบตัวเลือก 'ล้างหน้าจอ' ออกจากเมนูป๊อปอัป"; +"HIDE_COMMU_GUIDE" = "นำหลักเกณฑ์ชุมชนออก"; +"HIDE_COMMU_GUIDE_DESC" = "นำหลักเกณฑ์ชุมชนที่ปักหมุดไว้ออกจากคอมเมนต์"; +"DISABLES_RTL" = "ปิดใช้งานเลย์เอาต์ RTL"; +"DISABLES_RTL_DESC" = "บังคับให้แสดงผลจากซ้ายไปขวา (LTR) และปิดการจัดหน้าแบบขวาไปซ้าย (RTL)"; +"DEVICE_UI" = "รูปแบบอุปกรณ์"; +"OPEN_LINK" = "เปิดลิงก์"; +"FLOATING_KEYBOARD" = "แป้นพิมพ์แบบลอย"; +"FLOATING_KEYBOARD_DESC" = "บังคับเปิดใช้งานแป้นพิมพ์แบบลอย มีประโยชน์สำหรับ iPhone ที่ใช้การปลอมแปลงสไตล์ iPad\nหมายเหตุ: ทำงานเฉพาะเมื่อเลือกสไตล์ iPad เท่านั้น"; +"AUTO_OPEN_LINK" = "ตรวจจับคลิปบอร์ดอัตโนมัติ"; +"AUTO_OPEN_LINK_DESC" = "ตรวจสอบคลิปบอร์ดหาลิงก์ที่เกี่ยวข้องกับ YouTube ทุกครั้งที่เปิดแอป\nเพื่อให้ทำงานได้อย่างถูกต้อง ให้ไปที่การตั้งค่า iOS → YouTube → วางจากแอปอื่น แล้วเลือก 'อนุญาต'"; + +"SB_DESCRIPTION" = "ข้ามช่วงสปอนเซอร์โดยอัตโนมัติด้วยข้อมูลจากชุมชน"; +"SB_ENABLE" = "เปิดใช้งาน SponsorBlock"; +"SB_ENABLE_DESC" = "เปิดส่วนขยาย SponsorBlock เพื่อข้ามส่วนที่เป็นโฆษณาหรือสปอนเซอร์"; +"SB_SHOW_BUTTON" = "แสดงปุ่มบนหน้าจอ"; +"SB_SHOW_BUTTON_DESC" = "แสดงปุ่มเปิด-ปิด SponsorBlock บนหน้าเครื่องเล่นวิดีโอ"; +"SB_SHOW_NOTIFICATIONS" = "แสดงการแจ้งเตือน"; +"SB_SHOW_NOTIFICATIONS_DESC" = "แสดงการแจ้งเตือนเมื่อมีการข้ามช่วงวิดีโอ (การแจ้งเตือนสำหรับหมวดหมู่ที่ตั้งค่าแบบกดข้ามเองจะแสดงเสมอ)"; +"SB_HAPTIC_FEEDBACK" = "ตอบสนองด้วยการสั่น"; +"SB_HAPTIC_FEEDBACK_DESC" = "สั่นเมื่อมีการข้ามช่วงวิดีโอ"; +"SB_SEGMENTS_IN_PLAYER" = "แสดงช่วงเวลาบนเครื่องเล่นวิดีโอ"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "แสดงช่วงเวลาเป็นแถบสีต่างๆ บนแถบความคืบหน้าของเครื่องเล่นวิดีโอหลัก"; +"SB_SEGMENTS_IN_FEED" = "แสดงช่วงวิดีโอในฟีด"; +"SB_SEGMENTS_IN_FEED_DESC" = "แสดงแถบสีระบุช่วงต่างๆ บนแถบความคืบหน้าของวิดีโอในหน้าฟีด"; +"SB_SEGMENTS_IN_MINIPLAYER" = "แสดงช่วงวิดีโอในเครื่องเล่นขนาดเล็ก"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "แสดงแถบสีระบุช่วงต่างๆ บนแถบความคืบหน้าของเครื่องเล่นขนาดเล็ก"; +"SB_SHOW_DURATION" = "แสดงเวลาวิดีโอแบบไม่รวมช่วงสปอนเซอร์"; +"SB_SHOW_DURATION_DESC" = "แสดงเวลาความยาวทั้งหมดของวิดีโอโดยหักช่วงที่ข้ามได้ออก"; +"SB_CATEGORIES_HEADER" = "หมวดหมู่ช่วงวิดีโอ"; +"SB_SEGMENT_COLOR" = "สีประจำช่วง"; + +"SB_ACTION_DISABLE" = "ปิดการใช้งาน"; +"SB_ACTION_AUTO_SKIP" = "ข้ามอัตโนมัติ"; +"SB_ACTION_ASK" = "ถามก่อนข้าม"; +"SB_ACTION_DISPLAY" = "แสดงบนแถบเวลาเท่านั้น"; +"SB_ACTION_SKIP_TO" = "ข้ามไปยังช่วงนี้"; + +"SB_CAT_sponsor" = "สปอนเซอร์"; +"SB_CAT_sponsor_DESC" = "การโฆษณาสินค้าหรือบริการที่มีค่าตอบแทน ซึ่งไม่เกี่ยวข้องกับตัวผู้สร้างโดยตรง"; +"SB_CAT_intro" = "อินโทร/บทนำ"; +"SB_CAT_intro_DESC" = "ช่วงพักหรือแอนิเมชันเปิดตัวที่มักพบบ่อยในช่วงต้นวิดีโอ"; +"SB_CAT_outro" = "ท้ายคลิป/เครดิต"; +"SB_CAT_outro_DESC" = "เครดิต หรือส่วนปิดท้ายที่อยู่ช่วงท้ายของวิดีโอ"; +"SB_CAT_interaction" = "การปฏิสัมพันธ์"; +"SB_CAT_interaction_DESC" = "การเตือนให้กดไลก์ กดติดตาม หรือติดตามบนโซเชียลมีเดียต่างๆ"; +"SB_CAT_selfpromo" = "โปรโมทตัวเอง"; +"SB_CAT_selfpromo_DESC" = "การโปรโมทสินค้า สินค้าที่ระลึก หรือเนื้อหาอื่นๆ ของผู้สร้างเองโดยไม่มีค่าจ้าง"; +"SB_CAT_music_offtopic" = "ส่วนที่ไม่ใช่เพลง"; +"SB_CAT_music_offtopic_DESC" = "ส่วนที่ไม่มีเพลงในมิวสิกวิดีโอ (เช่น การพูดคุย หรือบทสนทนาเปิดเรื่อง)"; +"SB_CAT_preview" = "ตัวอย่างตอนต่อไป/สรุป"; +"SB_CAT_preview_DESC" = "การสรุปเนื้อหาเดิมหรือตัวอย่างจากวิดีโออื่นหรือตอนก่อนหน้า"; +"SB_CAT_hook" = "ฮุก/คำทักทาย"; +"SB_CAT_hook_DESC" = "คลิปเกริ่นนำนำร่องสำหรับวิดีโอถัดไป คำทักทาย และคำกล่าวลา"; +"SB_CAT_poi_highlight" = "ไฮไลท์"; +"SB_CAT_poi_highlight_DESC" = "ช่วงสำคัญหรือจุดที่เป็นไฮไลท์ของวิดีโอ ใช้คำสั่ง 'ข้ามไปยัง' เพื่อดูส่วนนี้ทันที"; +"SB_CAT_filler" = "เนื้อหาน้ำ/ส่วนเติม"; +"SB_CAT_filler_DESC" = "เนื้อหาที่ออกนอกเรื่อง มุกตลก หรือส่วนเติมที่ไม่ได้เกี่ยวข้องกับหัวข้อหลัก"; + +"SB_SKIPPED" = "ข้ามช่วง %@ แล้ว"; +"SB_DETECTED" = "ตรวจพบช่วง %@\nคุณต้องการข้ามช่วงนี้หรือไม่?"; +"SB_SKIP_NOW" = "ข้าม"; +"SB_UNSKIP" = "ยกเลิกการข้าม"; +"SB_JUMPED_TO_HIGHLIGHT" = "ข้ามไปยังไฮไลท์แล้ว"; +"SB_JUMP_TO_HIGHLIGHT" = "พบช่วงไฮไลท์ ต้องการข้ามไปดูเลยหรือไม่?"; +"SB_SEGMENT_COLOR_SUFFIX" = "สีประจำช่วง"; +"SB_SECTION_MAIN" = "หลัก"; +"SB_SKIP_ALERT_DURATION" = "ระยะเวลาแสดงการแจ้งเตือนเมื่อข้าม"; +"SB_UNSKIP_ALERT_DURATION" = "ระยะเวลาแสดงการแจ้งเตือนเมื่อยกเลิกการข้าม"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/tr.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/tr.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/tr.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/tr.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/uk.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/uk.lproj/Localizable.strings index bccaf550..6237abcf 100644 --- a/layout/Library/Application Support/YouMod.bundle/uk.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/uk.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/vi.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/vi.lproj/Localizable.strings index b6e1e6c0..31051b0a 100644 --- a/layout/Library/Application Support/YouMod.bundle/vi.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/vi.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "LƯU Ý : Một số tính năng ẩn sẽ để lại khoảng trống."; "SETTINGS" = "Cài đặt"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Báo lỗi hoặc yêu cầu tính năng mới."; "NEW_ISSUES_DESC" = "Bạn tìm thấy lỗi hoặc muốn yêu cầu thêm tính năng mới? Nhấn vào đây."; "SOURCE_CODES" = "Mã nguồn"; "SOURCE_CODES_DESC" = "Xem mã nguồn dự án!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "Tuỳ chọn YouMod"; "PERFER" = "Tùy chọn"; "IMPORT" = "Nhập cài đặt"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "Không có từ khóa nào khả dụng để nhập"; "ERROR_NO_KEYS_EXPORT" = "Không có từ khóa nào khả dụng để xuất"; "OVERRIDE" = "Thao tác này sẽ ghi đè lên các cài đặt hiện tại của bạn. Bạn có muốn tiếp tục không?"; +"DISABLED" = "Disabled"; "CACHE" = "Quản lý bộ nhớ tạm"; "CLEARCACHE" = "Xóa bộ nhớ tạm"; @@ -36,8 +44,90 @@ "DOWNLOAD_MANAGER_DESC" = "Thay thế trình tải xuống của YouTube bằng YouMod. Với khả năng tải video, âm thanh, phụ đề, và ảnh thu nhỏ"; "DOWNLOAD_SAVE_PHOTOS" = "Tự động lưu vào Thư viện ảnh"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Tự động lưu video và ảnh trực tiếp vào ứng dụng Ảnh."; -"DOWNLOAD_DRC_AUDIO" = "Ưu tiên DRC"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Ưu tiên tải các bản âm thanh có nén dải động (Dynamic Range Compression) để có mức âm lượng ổn định hơn."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Giao diện"; "OLED_THEME" = "Chế độ OLED"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Làm cho bàn phím sử dụng giao diện OLED."; "NAVBAR" = "Thanh điều hướng"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Ẩn logo YouTube"; "HIDE_YT_LOGO_DESC" = "Ẩn logo của YouTube khỏi thanh điều hướng."; "PREMIUM_LOGO" = "Sử dụng logo Youtube Premium"; @@ -62,32 +154,80 @@ "FEED" = "Bảng tin"; "HIDE_SUBBAR" = "Ẩn thanh phụ"; "HIDE_SUBBAR_DESC" = "Ẩn thanh phụ ở đầu bảng tin."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Ẩn trình tạo danh sách phát"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Ẩn trình tạo danh sách phát nhạc (Chọn ba giai điệu) trong bảng tin."; "HIDE_FEED_POST" = "Ẩn các bài đăng"; "HIDE_FEED_POST_DESC" = "Ẩn bài đăng trên bảng tin."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Ẩn Shorts trong bảng tin"; "HIDE_SHORTS_SHELF_DESC" = "Ẩn các Shorts trên bảng tin, kể cả trong tab Kênh đăng kí."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Ẩn lịch sử tìm kiếm"; "HIDE_SEARCH_HISTORY_DESC" = "Ẩn lịch sử tìm kiếm và các đề xuất trước đó khi sử dụng thanh tìm kiếm.\nGHI CHÚ: Lịch sử tìm kiếm của bạn vẫn khả dụng trên các ứng dụng YouTube khác."; -"HIDE_SUB_BUTTON" = "Ẩn nút Đăng kí"; -"HIDE_SUB_BUTTON_DESC" = "Ẩn nút Đăng kí trong các kênh và phần mô tả."; -"HIDE_SHOP_BUTTON" = "Ẩn nút Mua sắm"; -"HIDE_SHOP_BUTTON_DESC" = "Ẩn nút Mua sắm/Cửa hàng trong phần mô tả video."; -"HIDE_MEMBER_BUTTON" = "Ẩn nút Hội viên"; -"HIDE_MEMBER_BUTTON_DESC" = "Ẩn nút 'Tham gia' hội viên trong kênh và phần mô tả video."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Trình phát video"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "HIDE_AUTOPLAY" = "Ẩn công tắc Tự động phát"; "HIDE_AUTOPLAY_DESC" = "Ẩn công tắc tự động phát trong trình phát."; "HIDE_CAPTIONS_BUTTON" = "Ẩn nút Phụ đề (CC)"; "HIDE_CAPTIONS_BUTTON_DESC" = "Ẩn nút Phụ đề trong trình phát."; "HIDE_CAST_BUTTON_PLAYER" = "Ẩn nút Trình chiếu (Cast)"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Ẩn nút Trình chiếu trong trình phát."; -"HIDE_PREV_BUTTON" = "Ẩn nút Trước/Quay lại"; -"HIDE_PREV_BUTTON_DESC" = "Ẩn nút Trước/Quay lại trong trình phát."; -"HIDE_NEXT_BUTTON" = "Ẩn nút Tiếp theo/Bỏ qua"; -"HIDE_NEXT_BUTTON_DESC" = "Ẩn nút Tiếp theo/Bỏ qua trong trình phát."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Thay thế nút Trước/Tiếp theo"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Thay thế các nút video Trước và Tiếp theo trong trình phát bằng nút Tua lại và Tua nhanh."; "REMOVE_DARK_OVERLAY" = "Xóa lớp phủ tối"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Ẩn 'Chứa nội dung quảng cáo trả phí' trong trình phát video."; "HIDE_WATERMARK" = "Ẩn hình mờ (Watermark)"; "HIDE_WATERMARK_DESC" = "Ẩn hình mờ của các kênh trong trình phát video."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Bật điều khiển bằng cử chỉ"; "GESTURES_DESC" = "Vuốt lên hoặc xuống ở cạnh trái/phải của màn hình video để điều chỉnh độ sáng, âm lượng hoặc tốc độ phát."; "GESTURE_AREA" = "Khu vực cử chỉ"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Tắt tính năng hiển thị thời lượng còn lại của video khi nhấn vào mốc thời gian."; "ALWAYS_SHOW_REMAINING" = "Luôn hiển thị thời gian còn lại"; "ALWAYS_SHOW_REMAINING_DESC" = "Đảm bảo rằng thời gian còn lại luôn được hiển thị theo mặc định trong trình phát."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Hiển thị thời gian dự kiến"; "SHOW_REMAINING_EXTRA_DESC" = "Thêm thời gian kết thúc dự kiến vào nhãn trên trình phát video."; "HIDE_FULLSCREEN_ACTIONS" = "Ẩn các hành động Toàn màn hình"; @@ -148,93 +296,104 @@ "HIDE_CONTENT_WARNING_DESC" = "Tự động bỏ qua thông báo cảnh báo nội dung nhạy cảm trong một số video."; "AUTO_FULLSCREEN" = "Tự động Toàn màn hình"; "AUTO_FULLSCREEN_DESC" = "Tự động chuyển sang chế độ Toàn màn hình khi bắt đầu phát video."; -"AUTO_DISABLES_CAPTION" = "Tự tắt Phụ đề (CC)"; -"AUTO_DISABLES_CAPTION_DESC" = "Tự động tắt phụ đề (CC) mỗi khi một video mới bắt đầu phát."; "PORTRAIT_FULLSCREEN" = "Toàn màn hình dọc"; "PORTRAIT_FULLSCREEN_DESC" = "Bật hỗ trợ chế độ toàn màn hình theo chiều dọc."; "OLD_QUALITY_PICKER" = "Sử dụng menu chất lượng video kiểu cũ"; "OLD_QUALITY_PICKER_DESC" = "Đưa menu chọn chất lượng video về phiên bản cũ (liệt kê tất cả độ phân giải)."; "EXTRA_SPEED" = "Tùy chọn tốc độ bổ sung"; "EXTRA_SPEED_DESC" = "Thêm các tùy chọn tốc độ phát video lên tới 10x."; -"DISABLE_HINTS" = "Tắt gợi ý"; -"DISABLE_HINTS_DESC" = "Tắt các gợi ý hướng dẫn và cửa sổ tương tác bật lên"; "FORCE_MINIPLAYER" = "Ép dùng trình phát thu nhỏ (miniplayer)"; "FORCE_MINIPLAYER_DESC" = "Cho phép tất cả các video, bao gồm cả video dành cho trẻ em, phát trong trình phát thu nhỏ khi vuốt đóng video."; "FORCE_SEEKBAR" = "Luôn hiện thanh tiến trình"; "FORCE_SEEKBAR_DESC" = "Làm cho thanh tiến trình (thanh tua) luôn hiển thị ở mọi lúc khi ở chế độ toàn màn hình."; -"HIDE_LIKE_BUTTON" = "Ẩn nút Thích"; -"HIDE_LIKE_BUTTON_DESC" = "Ẩn nút Thích trên thanh hành động bên dưới trình phát."; -"HIDE_DISLIKE_BUTTON" = "Ẩn nút Không thích"; -"HIDE_DISLIKE_BUTTON_DESC" = "Ẩn nút Không thích trên thanh hành động bên dưới trình phát."; -"HIDE_SHARE_BUTTON" = "Ẩn nút Chia sẻ"; -"HIDE_SHARE_BUTTON_DESC" = "Ẩn nút Chia sẻ trên thanh hành động bên dưới trình phát."; -"HIDE_DOWNLOAD_BUTTON" = "Ẩn nút Tải xuống"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Ẩn nút Tải xuống trên thanh hành động bên dưới trình phát."; -"HIDE_CLIP_BUTTON" = "Ẩn nút Clip"; -"HIDE_CLIP_BUTTON_DESC" = "Ẩn nút Tạo đoạn trích (Clip) trên thanh hành động bên dưới lớp phủ video."; -"HIDE_REMIX_BUTTON" = "Ẩn nút 'Phối lại'"; -"HIDE_REMIX_BUTTON_DESC" = "Ẩn nút 'Phối lại' trên thanh hành động bên dưới trình phát."; -"HIDE_SAVE_BUTTON" = "Ẩn nút Lưu"; -"HIDE_SAVE_BUTTON_DESC" = "Ẩn nút Lưu trên thanh hành động bên dưới trình phát."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_ACTION_BAR" = "Hide actions bar"; +"HIDE_ACTION_BAR_DESC" = "Removes the bar containing the like, dislike, share buttons etc. below the video player."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Ẩn nút Thích"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Ẩn nút Thích trong trình phát Shorts."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Ẩn nút Không thích"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Ẩn nút Không thích trong trình phát Shorts."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Ẩn nút Bình luận"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Ẩn nút Bình luận trong trình phát Shorts."; -"HIDE_SHORTS_SHARE_BUTTON" = "Ẩn nút Chia sẻ"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Ẩn nút Chia sẻ trong trình phát Shorts."; -"HIDE_SHORTS_REMIX_BUTTON" = "Ẩn nút 'Phối lại'"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Ẩn nút 'Phối lại' trong trình phát Shorts."; -"HIDE_METADATA_BUTTON" = "Ẩn nút nguồn âm thanh"; -"HIDE_METADATA_BUTTON_DESC" = "Ẩn nút hiển thị nguồn âm thanh hoặc thông tin bài hát trong trình phát Shorts."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Ẩn các sản phẩm"; "HIDE_SHORTS_PRODUCT_DESC" = "Ẩn các sản phẩm được gắn thẻ và liên kết mua sắm trong trình phát Shorts."; "HIDE_SHORTS_RECBAR" = "Ẩn thanh đề xuất"; "HIDE_SHORTS_RECBAR_DESC" = "Ẩn thanh hành động đề xuất trong trình phát Shorts (ví dụ: Thử âm thanh, Mua sắm, Xem trước bình luận)."; -"HIDE_SHORTS_COMMIT" = "Ẩn thanh hoa hồng"; -"HIDE_SHORTS_COMMIT_DESC" = "Ẩn thanh thông báo video có nhận hoa hồng trong trình phát Shorts."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Ẩn nút Kênh đăng ký"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Ẩn nút Kênh đăng ký ở phía trên trình phát Shorts."; -"HIDE_SHORTS_LIVE_BUTTON" = "Ẩn nút Phát trực tiếp"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Ẩn nút Phát trực tiếp ở phía trên trình phát Shorts."; -"HIDE_SHORTS_LENS_BUTTON" = "Ẩn nút Ống kính"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Ẩn nút Ống kính ở phía trên trình phát Shorts"; -"HIDE_SHORTS_TRENDS_BUTTON" = "Ẩn nút Xu hướng"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Ẩn nút Xu hướng ở phía trên trình phát Shorts."; -"HIDE_SHORTS_TO_VIDEO" = "Ẩn liên kết tới video đầy đủ"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Ẩn liên kết chuyển hướng từ một video ngắn sang một video đầy đủ trong trình phát Shorts."; "ENABLES_SHORTS_QUALITY" = "Bật bộ chọn chất lượng Shorts"; "ENABLES_SHORTS_QUALITY_DESC" = "Ép bật menu chọn chất lượng video trong Shorts."; "SHOW_SHORTS_SEEKBAR" = "Luôn hiện thanh tiến trình"; "SHOW_SHORTS_SEEKBAR_DESC" = "Đảm bảo thanh tiến trình/thanh tua luôn hiển thị ở dưới cùng của trình phát Shorts."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Thanh Tab"; "DEFAULT_TAB" = "Tab khởi động mặc định"; -"DEFAULT_TAB_DESC" = "Bạn có thể chọn tab muốn mở khi khởi động ứng dụng."; -"HOME_NAME" = "Trang chủ"; -"SUBSCRIPT_NAME" = "Kênh đăng ký"; -"LIB_NAME" = "Thư viện (Bạn)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Ẩn chỉ báo tab"; "HIDE_TAB_INDI_DESC" = "Ẩn tất cả các chỉ báo tab trên thanh tab."; "HIDE_TAB_LABELS" = "Ẩn nhãn tab"; "HIDE_TAB_LABELS_DESC" = "Ẩn tất cả nhãn tab trên thanh tab."; -"HIDE_HOME_TAB" = "Ẩn tab Trang chủ"; -"HIDE_HOME_TAB_DESC" = "Ẩn tab Trang chủ trên thanh tab."; -"HIDE_SHORTS_TAB" = "Ẩn tab Shorts"; -"HIDE_SHORTS_TAB_DESC" = "Ẩn tab Shorts trên thanh tab."; -"HIDE_CREATE_BUTTON" = "Ẩn nút Tạo"; -"HIDE_CREATE_BUTTON_DESC" = "Ẩn nút Tạo trên thanh tab."; -"HIDE_SUBSCRIPT_TAB" = "Ẩn tab Kênh đăng ký"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Ẩn tab Kênh đăng ký trên thanh tab."; +"HOME_TAB" = "Trang chủ"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Kênh đăng ký"; +"LIBRARY_TAB" = "Thư viện (Bạn)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Khác"; "BACKGROUND_PLAYBACK" = "Phát trong nền"; "BACKGROUND_PLAYBACK_DESC" = "Cho phép video phát dưới nền"; "DISABLES_SHORTS_PIP" = "Tắt PiP của Shorts"; "DISABLES_SHORTS_PIP_DESC" = "Thử tắt tính năng Hình-trong-hình (PiP) của Shorts, có thể không hoạt động 100%."; +"DISABLE_HINTS" = "Tắt gợi ý"; +"DISABLE_HINTS_DESC" = "Tắt các gợi ý hướng dẫn và cửa sổ tương tác bật lên"; "BLOCK_UPGRADE_DIALOGS" = "Chặn hộp thoại nâng cấp"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Chặn các hộp thoại của YouTube yêu cầu bạn nâng cấp ứng dụng."; "ARE_YOU_THERE_DIALOG" = "Ẩn hộp thoại 'Bạn có ở đó không?'"; @@ -247,7 +406,114 @@ "DISABLES_SNACK_BAR_DESC" = "Tắt thanh thông báo (snack bar) khi nhấn một số nút hoặc thực hiện thao tác trong ứng dụng."; "HIDE_STARTUP_ANIMATIONS" = "Ẩn hoạt ảnh khởi động của Youtube"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Ẩn các hoạt ảnh khởi động của YouTube khi mở ứng dụng."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Ẩn 'Phát tiếp theo trong danh sách chờ'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Ẩn tùy chọn 'Phát tiếp theo trong danh sách chờ' trong menu nổi."; "HIDE_LIKE_DISLIKE_VOTES" = "Ẩn thông báo Thích/Không thích"; "HIDE_LIKE_DISLIKE_VOTES_DESC" = "Ẩn các thông báo bật lên khi bạn thích/không thích video."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Remove guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Removes pinned guidelines from the comments section."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/zh-CN.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/zh-CN.lproj/Localizable.strings index bf483a5c..2f168a42 100644 --- a/layout/Library/Application Support/YouMod.bundle/zh-CN.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/zh-CN.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "注意:部分隐藏功能可能会留下空白区域。"; "SETTINGS" = "设置"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "报告问题或请求新功能"; "NEW_ISSUES_DESC" = "发现 bug 或想请求新功能?点击此处。"; "SOURCE_CODES" = "源代码"; "SOURCE_CODES_DESC" = "来看看代码吧!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod 偏好设置"; "PERFER" = "偏好设置"; "IMPORT" = "导入设置"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "没有可导入的值"; "ERROR_NO_KEYS_EXPORT" = "没有可导出的值"; "OVERRIDE" = "这将覆盖当前设置。你想继续吗?"; +"DISABLED" = "Disabled"; "CACHE" = "缓存管理"; "CLEARCACHE" = "清除缓存"; @@ -36,8 +44,90 @@ "DOWNLOAD_MANAGER_DESC" = "用 YouMod 的下载器替换 YouTube 的视频下载器。支持下载视频、音频、字幕和缩略图"; "DOWNLOAD_SAVE_PHOTOS" = "自动保存到相册"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "自动将下载的视频和图像直接保存到你的“相册”应用。"; -"DOWNLOAD_DRC_AUDIO" = "优先 DRC 音频"; -"DOWNLOAD_DRC_AUDIO_DESC" = "优先下载带有动态范围压缩(DRC)的音轨,使音量水平更稳定一致。"; +"ADD_SHORTS_DOWNLOAD" = "Add download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Add download button to Shorts overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy Video Info"; +"COPY_ALL_VID_INFO" = "Copy All"; +"COPY_AUTHOR" = "Copy Author"; +"COPY_TITLE" = "Copy Title"; +"COPY_DESCRIPTION" = "Copy Description"; +"COPY_URL" = "Copy Video Link"; +"COPY_URL_TIMESTAMP" = "Copy Video Link with Timestamp"; +"URL_COPIED" = "Video link copied"; +"URL_TIMESTAMP_COPIED" = "Video link with timestamp copied"; +"ERROR_VIDEOID" = "Cannot get Video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Author copied"; +"COPIED_TITLE" = "Title copied"; +"COPIED_DESCRIPTION" = "Description copied"; +"SAVE_THUMBNAIL" = "Save Thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download Captions"; +"DOWNLOAD_AUDIO" = "Download Audio"; +"DOWNLOAD_VIDEO" = "Download Video"; +"OPEN_VID_BEFORE" = "Please open a video before using Download Manager"; +"NO_CAPTIONS_URL" = "No valid captions URL found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Captions download failed"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio stream found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio stream found"; +"COPIED_VID_INFO" = "All video info copied"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail processing failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Thumbnail options"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Use external server to download instead, this does not support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download Server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment info"; +"DOWNLOAD_COMMENT_DESC" = "Allow long press to copy comment text and save comment as image."; +"DOWNLOAD_POST" = "Save post info"; +"DOWNLOAD_POST_DESC" = "Allow long press to copy post text and save post as image."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "外观"; "OLED_THEME" = "OLED 深色模式"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "让键盘在深色主题下使用纯黑(OLED)颜色。"; "NAVBAR" = "导航栏"; +"STICKY_NAVBAR" = "Sticky Navigation Bar"; +"STICKY_NAVBAR_DESC" = "Pin the navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "隐藏 YouTube 图标"; "HIDE_YT_LOGO_DESC" = "隐藏导航栏中的 YouTube 图标。"; "PREMIUM_LOGO" = "使用 YouTube Premium 图标"; @@ -62,34 +154,82 @@ "FEED" = "信息流"; "HIDE_SUBBAR" = "隐藏顶部筛选栏"; "HIDE_SUBBAR_DESC" = "隐藏信息流顶部的筛选栏。"; +"HIDE_HORI_SHELF" = "Hide Horizontal shelf"; +"HIDE_HORI_SHELF_DESC" = "Remove all horizontal shelves from the feed."; "HIDE_MUSIC_PLAYLISTS" = "隐藏音乐播放列表生成器"; "HIDE_MUSIC_PLAYLISTS_DESC" = "隐藏信息流中的音乐播放列表生成器(三选一选音乐)。"; "HIDE_FEED_POST" = "隐藏帖子"; "HIDE_FEED_POST_DESC" = "隐藏信息流中的帖子。"; +"HIDE_SURVEYS" = "Hide Surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that randomly appear in the feed."; "HIDE_SHORTS_SHELF" = "隐藏 Shorts 栏"; "HIDE_SHORTS_SHELF_DESC" = "隐藏信息流中的 Shorts(短视频) 栏,包括“订阅”标签页中的。"; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevent removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "隐藏搜索历史"; "HIDE_SEARCH_HISTORY_DESC" = "使用搜索栏时隐藏之前的搜索历史和推荐。\n注意:你的搜索历史在其他 YouTube 客户端中仍然可以访问。"; -"HIDE_SUB_BUTTON" = "隐藏订阅按钮"; -"HIDE_SUB_BUTTON_DESC" = "隐藏频道和视频描述中的订阅按钮。"; -"HIDE_SHOP_BUTTON" = "隐藏购物按钮"; -"HIDE_SHOP_BUTTON_DESC" = "隐藏频道描述中的购物/商店按钮。"; -"HIDE_MEMBER_BUTTON" = "隐藏会员按钮"; -"HIDE_MEMBER_BUTTON_DESC" = "隐藏频道和视频描述中的“加入”按钮。"; +"HIDE_PLAYABLES" = "Hide Playables"; +"HIDE_PLAYABLES_DESC" = "Remove playables (Mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "播放器"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Video quality on Wi-Fi"; +"QUALITY_WIFI_DESC" = "Automatically apply the selected video quality when connected to Wi-Fi."; +"QUALITY_CELLULAR" = "Video quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically apply the selected video quality when connected to Cellular."; +"QUALITY_LOW_POWER" = "Video quality on Low Power mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically apply the selected video quality when the device is in Low Power mode."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Audio track selected"; +"AUDIO_TRACK_SELECT_DESC" = "Specify the preferred audio track language to use when playing a video."; +"NO_AUTO_DUBBED" = "Use original audio track"; +"NO_AUTO_DUBBED_DESC" = "Use the original audio track if the preferred language is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred caption track"; +"CAPTION_TRACK_SELECT" = "Caption track selected"; +"CAPTION_TRACK_SELECT_DESC" = "Specify the preferred caption track language to use when playing a video."; +"DISABLES_CAPTION_TRACK" = "Auto disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback speed"; +"DEFAULT_SPEED_DESC" = "Override the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed button"; +"SPEED_BUTTON_DESC" = "Show playback speed button on video overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Show mute/unmute button on video overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Show share button on video overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Show loop button on video overlay."; +"CAPTION_BUTTON" = "Caption button"; +"CAPTION_BUTTON_DESC" = "Show caption button on video overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Use a custom hold-to-speed playback rate."; "HIDE_AUTOPLAY" = "隐藏自动播放开关"; "HIDE_AUTOPLAY_DESC" = "隐藏视频覆盖层中的自动播放开关。"; "HIDE_CAPTIONS_BUTTON" = "隐藏字幕按钮"; "HIDE_CAPTIONS_BUTTON_DESC" = "隐藏视频覆盖层中的字幕按钮。"; "HIDE_CAST_BUTTON_PLAYER" = "隐藏投屏按钮"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "隐藏视频覆盖层中的投屏按钮。"; -"HIDE_PREV_BUTTON" = "隐藏上一个/后退按钮"; -"HIDE_PREV_BUTTON_DESC" = "隐藏视频覆盖层中的上一个/后退按钮。"; -"HIDE_NEXT_BUTTON" = "隐藏下一个/快进按钮"; -"HIDE_NEXT_BUTTON_DESC" = "隐藏视频覆盖层中的下一个/快进按钮。"; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide the previous and next video buttons from the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "替换上一个/下一个按钮"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "将播放器中的“上一个”和“下一个”视频按钮替换为“快退”和“快进”按钮。"; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replace the previous video button with a skip backward button in the control center."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replace the next video button with a skip forward button in the control center."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Forward seconds"; "REMOVE_DARK_OVERLAY" = "移除暗色覆盖层"; "REMOVE_DARK_OVERLAY_DESC" = "移除视频覆盖层中的暗色覆盖层。"; "HIDE_END_SCREEN" = "隐藏片尾卡片"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "隐藏视频覆盖层中的“包含付费推广”。"; "HIDE_WATERMARK" = "隐藏水印"; "HIDE_WATERMARK_DESC" = "隐藏视频覆盖层底部的频道水印。"; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Pause the video when the overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapter"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevent the seekbar from snapping to chapters."; +"DISABLES_ENGAGE_PANEL" = "Disable engagement panel"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent the engagement panel from showing when entering full screen."; "GESTURES" = "启用手势控制"; "GESTURES_DESC" = "在视频屏幕左/右边缘上下滑动以调节亮度、音量或播放速度。"; "GESTURE_AREA" = "手势触发区域"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "禁用点击时间戳时显示视频剩余时间的功能。"; "ALWAYS_SHOW_REMAINING" = "始终显示剩余时间"; "ALWAYS_SHOW_REMAINING_DESC" = "确保在播放器覆盖层中默认始终显示剩余时间。"; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy link with timestamp on pause"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allow copying the video link with a timestamp by pressing the pause button."; "SHOW_REMAINING_EXTRA" = "显示额外剩余时间"; "SHOW_REMAINING_EXTRA_DESC" = "在视频覆盖层的标签中添加预计完成时间。"; "HIDE_FULLSCREEN_ACTIONS" = "隐藏全屏操作按钮"; @@ -148,93 +296,103 @@ "HIDE_CONTENT_WARNING_DESC" = "自动跳过部分视频中的敏感内容警告提示。"; "AUTO_FULLSCREEN" = "自动全屏"; "AUTO_FULLSCREEN_DESC" = "开始播放视频时自动进入全屏模式。"; -"AUTO_DISABLES_CAPTION" = "自动禁用字幕"; -"AUTO_DISABLES_CAPTION_DESC" = "每当新视频开始播放时自动关闭隐藏式字幕(CC)。"; "PORTRAIT_FULLSCREEN" = "竖屏全屏模式"; "PORTRAIT_FULLSCREEN_DESC" = "启用竖屏全屏模式支持。"; "OLD_QUALITY_PICKER" = "使用旧版画质选择器"; "OLD_QUALITY_PICKER_DESC" = "将视频画质选择菜单恢复为经典版本(立即列出所有分辨率)。"; "EXTRA_SPEED" = "额外速度选项"; "EXTRA_SPEED_DESC" = "添加更多视频播放速度选项,最高可达 10 倍。"; -"DISABLE_HINTS" = "禁用提示"; -"DISABLE_HINTS_DESC" = "禁用教程提示和交互式弹窗。"; "FORCE_MINIPLAYER" = "强制使用迷你播放器"; "FORCE_MINIPLAYER_DESC" = "允许所有视频(包括儿童视频)在关闭播放器时于迷你播放器中播放。"; "FORCE_SEEKBAR" = "始终显示进度条"; "FORCE_SEEKBAR_DESC" = "使进度条在全屏模式下始终保持可见。"; -"HIDE_LIKE_BUTTON" = "隐藏点赞按钮"; -"HIDE_LIKE_BUTTON_DESC" = "隐藏视频覆盖层下方操作栏中的点赞按钮。"; -"HIDE_DISLIKE_BUTTON" = "隐藏踩按钮"; -"HIDE_DISLIKE_BUTTON_DESC" = "隐藏视频覆盖层下方操作栏中的踩按钮。"; -"HIDE_SHARE_BUTTON" = "隐藏分享按钮"; -"HIDE_SHARE_BUTTON_DESC" = "隐藏视频覆盖层下方操作栏中的分享按钮。"; -"HIDE_DOWNLOAD_BUTTON" = "隐藏下载按钮"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "隐藏视频覆盖层下方操作栏中的下载按钮。"; -"HIDE_CLIP_BUTTON" = "隐藏片段按钮"; -"HIDE_CLIP_BUTTON_DESC" = "隐藏视频覆盖层下方操作栏中的片段按钮。"; -"HIDE_REMIX_BUTTON" = "隐藏混剪按钮"; -"HIDE_REMIX_BUTTON_DESC" = "隐藏视频覆盖层下方操作栏中的混剪按钮。"; -"HIDE_SAVE_BUTTON" = "隐藏保存按钮"; -"HIDE_SAVE_BUTTON_DESC" = "隐藏视频覆盖层下方操作栏中的保存按钮。"; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to anywhere in a video by tapping on the seekbar."; +"DISABLES_ZOOM" = "Disable zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disable the pinch-to-zoom gesture in the video player."; +"USES_24_HOURS_TIME" = "24-hour time format"; +"USES_24_HOURS_TIME_DESC" = "Display the estimated end time in a 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause with two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause or resume the video by tapping on the screen with two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely remove the comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide comments preview"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Remove the top comment preview box while keeping the full comments section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Use another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Use a redesigned miniplayer style from the older version."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Lock the playback speed by swiping down on the player."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "隐藏点赞按钮"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "隐藏 Shorts 播放器中的点赞按钮。"; -"HIDE_SHORTS_DISLIKE_BUTTON" = "隐藏踩按钮"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "隐藏 Shorts 播放器中的踩按钮。"; -"HIDE_SHORTS_COMMENT_BUTTON" = "隐藏评论按钮"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "隐藏 Shorts 播放器中的评论按钮。"; -"HIDE_SHORTS_SHARE_BUTTON" = "隐藏分享按钮"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "隐藏 Shorts 播放器中的分享按钮。"; -"HIDE_SHORTS_REMIX_BUTTON" = "隐藏混剪按钮"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "隐藏 Shorts 播放器中的混剪按钮。"; -"HIDE_METADATA_BUTTON" = "隐藏声音元数据按钮"; -"HIDE_METADATA_BUTTON_DESC" = "隐藏 Shorts 播放器中显示声音来源或音乐元数据的按钮。"; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limit YouTube features to only watch Shorts. You can disable this by long-pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode disabled"; +"SHORTS_ACTION" = "Action after Shorts ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Pause video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Remove live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "隐藏商品"; "HIDE_SHORTS_PRODUCT_DESC" = "隐藏 Shorts 覆盖层中标记的商品和购物链接。"; "HIDE_SHORTS_RECBAR" = "隐藏推荐栏"; "HIDE_SHORTS_RECBAR_DESC" = "隐藏 Shorts 播放器中的推荐操作栏(例如:试用此声音、购物、预览评论)。"; -"HIDE_SHORTS_COMMIT" = "隐藏佣金栏"; -"HIDE_SHORTS_COMMIT_DESC" = "隐藏 Shorts 播放器中显示视频获得佣金的栏。"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "隐藏订阅按钮"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "隐藏 Shorts 播放器顶部的订阅按钮。"; -"HIDE_SHORTS_LIVE_BUTTON" = "隐藏直播按钮"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "隐藏 Shorts 播放器顶部的直播按钮。"; -"HIDE_SHORTS_LENS_BUTTON" = "隐藏 Lens 按钮"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "隐藏 Shorts 播放器顶部的 Lens 按钮。"; -"HIDE_SHORTS_TRENDS_BUTTON" = "隐藏趋势按钮"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "隐藏 Shorts 播放器顶部的趋势按钮。"; -"HIDE_SHORTS_TO_VIDEO" = "隐藏完整视频链接"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "隐藏 Shorts 播放器中将 Short 跳转到完整视频的链接。"; "ENABLES_SHORTS_QUALITY" = "启用 Shorts 画质选择器"; "ENABLES_SHORTS_QUALITY_DESC" = "强制启用 Shorts 中的视频画质选择菜单。"; "SHOW_SHORTS_SEEKBAR" = "始终显示进度条"; "SHOW_SHORTS_SEEKBAR_DESC" = "确保进度条在 Shorts 播放器底部始终可见。"; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manage overlay visibility using a pinch gesture to show Shorts in full-screen mode."; "TABBAR" = "标签栏"; "DEFAULT_TAB" = "默认启动标签页"; "DEFAULT_TAB_DESC" = "你可以选择打开应用时显示的标签页。"; -"HOME_NAME" = "首页"; -"SUBSCRIPT_NAME" = "订阅"; -"LIB_NAME" = "媒体库(你)"; +"FORSTED_TAB_BAR" = "Frosted Tab Bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "隐藏标签指示器"; "HIDE_TAB_INDI_DESC" = "隐藏标签栏中的所有标签指示器。"; "HIDE_TAB_LABELS" = "隐藏标签名称"; "HIDE_TAB_LABELS_DESC" = "隐藏标签栏中的所有标签名称。"; -"HIDE_HOME_TAB" = "隐藏首页标签页"; -"HIDE_HOME_TAB_DESC" = "隐藏标签栏中的首页标签页。"; -"HIDE_SHORTS_TAB" = "隐藏 Shorts 标签页"; -"HIDE_SHORTS_TAB_DESC" = "隐藏标签栏中的 Shorts(短视频) 标签页。"; -"HIDE_CREATE_BUTTON" = "隐藏创建按钮"; -"HIDE_CREATE_BUTTON_DESC" = "隐藏标签栏中的创建按钮。"; -"HIDE_SUBSCRIPT_TAB" = "隐藏订阅标签页"; -"HIDE_SUBSCRIPT_TAB_DESC" = "隐藏标签栏中的订阅标签页。"; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch Later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked Videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your Videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Mini-games"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage Tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings can also be accessed by long pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab Limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab must be enabled."; "MISCELLANEOUS" = "其他"; "BACKGROUND_PLAYBACK" = "后台播放"; "BACKGROUND_PLAYBACK_DESC" = "启用视频的后台播放功能。"; "DISABLES_SHORTS_PIP" = "禁用 Shorts 画中画"; "DISABLES_SHORTS_PIP_DESC" = "尝试禁用 Shorts 画中画,可能无法 100% 禁用。"; +"DISABLE_HINTS" = "禁用提示"; +"DISABLE_HINTS_DESC" = "禁用教程提示和交互式弹窗。"; "BLOCK_UPGRADE_DIALOGS" = "屏蔽升级弹窗"; "BLOCK_UPGRADE_DIALOGS_DESC" = "屏蔽 YouTube 提示你升级应用的弹窗。"; "ARE_YOU_THERE_DIALOG" = "隐藏“你还在吗?”弹窗"; @@ -247,7 +405,114 @@ "DISABLES_SNACK_BAR_DESC" = "禁用按下某些按钮或在应用中执行操作时出现的 Snackbar 提示。"; "HIDE_STARTUP_ANIMATIONS" = "隐藏 YouTube 启动动画"; "HIDE_STARTUP_ANIMATIONS_DESC" = "打开应用时隐藏 YouTube 的启动动画。"; -"HIDE_PLAY_IN_NEXT_QUEUE" = "隐藏“加入待播队列”"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "隐藏弹出菜单中的“加入待播队列”选项。"; "HIDE_LIKE_DISLIKE_VOTES" = "隐藏点赞/踩投票数"; "HIDE_LIKE_DISLIKE_VOTES_DESC" = "隐藏点赞/踩视频时弹出的消息提示。"; +"FLYOUT_MENU" = "Flyout Menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Remove \"Play next in queue\" option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Remove \"Play next in queue\" option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Remove \"Download video\" option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Remove \"Download video\" option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Remove \"Save to Watch Later\" option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Remove \"Save to Watch Later\" option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Remove \"Save to playlist\" option"; +"REMOVE_SAVE_OPTION_DESC" = "Remove \"Save to playlist\" option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Remove \"Remove from playlist\" option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Remove \"Remove from playlist\" option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Remove \"Share\" option"; +"REMOVE_SHARE_OPTION_DESC" = "Remove \"Share\" option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Remove \"Not interested\" option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Remove \"Not interested\" option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Remove \"Don't recommend channel\" option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Remove \"Don't recommend channel\" option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Remove \"Why this video?\" option"; +"REMOVE_INFO_OPTION_DESC" = "Remove \"Why this video?\" option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Remove \"Search filters\" option"; +"REMOVE_FILTER_OPTION_DESC" = "Remove \"Search filters\" option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Remove \"Report\" option"; +"REMOVE_REPORT_OPTION_DESC" = "Remove \"Report\" option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Remove \"Listen with YouTube Music\" option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Remove \"Listen with YouTube Music\" option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Remove \"Send feedback\" option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Remove \"Send feedback\" option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Remove \"Watch on TV\" option"; +"REMOVE_CAST_OPTION_DESC" = "Remove \"Watch on TV\" option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Remove \"Shuffle play\" option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Remove \"Shuffle play\" option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Remove \"Unsubscribe\" option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Remove \"Unsubscribe\" option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Remove \"Hide from playlist\" option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Remove \"Hide from playlist\" option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Remove \"Help & feedback\" option"; +"REMOVE_HELP_OPTION_DESC" = "Remove \"Help & feedback\" option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Remove \"Notify me\" option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Remove \"Notify me\" option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Remove \"Clear screen\" option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Remove \"Clear screen\" option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide Community Guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide the Community Guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Force text to display in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force enable the floating keyboard. Useful for iPhones with iPad style spoofing.\nNOTE: Only works when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check the clipboard for YouTube-related links every time you open the app."; + +"SB_DESCRIPTION" = "Automatically skip sponsor segments using community submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display the SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Show a notification when skipping a segment. Notifications for manual skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when skipping a segment."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Show colored segments on the main video player's progress bar."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Show colored segments on the feed player's progress bar."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in miniplayer"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Show colored segments on the miniplayer's progress bar."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding the skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment colors"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Display on progress bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion of a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro/Animation"; +"SB_CAT_intro_DESC" = "Animations, titles, or intro sequences usually found at the start of a video."; +"SB_CAT_outro" = "Outro/Endcard"; +"SB_CAT_outro_DESC" = "End cards, credits, or outro music at the end of a video."; +"SB_CAT_interaction" = "Interaction Reminder"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Unpaid/Self Promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion for the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-Music Section"; +"SB_CAT_music_offtopic_DESC" = "Sections in music videos that are not part of the actual song (e.g., talking, acting)."; +"SB_CAT_preview" = "Preview/Recap"; +"SB_CAT_preview_DESC" = "Recaps of previous episodes or previews of upcoming content."; +"SB_CAT_hook" = "Filler/Tangent"; +"SB_CAT_hook_DESC" = "Tangents, jokes, or filler content not directly related to the main topic."; +"SB_CAT_poi_highlight" = "Highlight/Point of Interest"; +"SB_CAT_poi_highlight_DESC" = "Key moments or highlights of the video. Use 'Skip to' to jump straight to these points."; +"SB_CAT_filler" = "Filler/Tangent"; +"SB_CAT_filler_DESC" = "Tangents, jokes, or filler content not directly related to the main topic."; + +"SB_SKIPPED" = "Skipped %@ segment"; +"SB_DETECTED" = "Detected %@ segment.\nDo you want to skip it?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to it?"; +"SB_SEGMENT_COLOR_SUFFIX" = "segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file diff --git a/layout/Library/Application Support/YouMod.bundle/zh-TW.lproj/Localizable.strings b/layout/Library/Application Support/YouMod.bundle/zh-TW.lproj/Localizable.strings index 8ec0526c..c83b598a 100644 --- a/layout/Library/Application Support/YouMod.bundle/zh-TW.lproj/Localizable.strings +++ b/layout/Library/Application Support/YouMod.bundle/zh-TW.lproj/Localizable.strings @@ -1,11 +1,18 @@ -"NOTE" = "NOTE: Some hiding features may leave a blank box."; "SETTINGS" = "Settings"; +"INTERFACE" = "Interface"; + +"LOOP_DISABLED" = "Loop disabled"; +"LOOP_ENABLED" = "Loop enabled"; "NEW_ISSUES" = "Report issues or request new features"; "NEW_ISSUES_DESC" = "Found bugs or want to request new features? Tap here."; "SOURCE_CODES" = "Source codes"; "SOURCE_CODES_DESC" = "Take a look at the codes!"; +"EXTRA" = "Extra"; +"FIX_PLAYBACK_ISSUES" = "Fix playback issues (Temporary)"; +"FIX_PLAYBACK_ISSUES_DESC" = "Try to fix video playback issues for some users. Turning this on will make the player automatically reloads itself everytime it errors or fails to play the video."; + "PERFER_HEADER" = "YouMod perferences"; "PERFER" = "Perferences"; "IMPORT" = "Import settings"; @@ -24,6 +31,7 @@ "ERROR_NO_KEYS_IMPORT" = "No available keys to import"; "ERROR_NO_KEYS_EXPORT" = "No available keys to export"; "OVERRIDE" = "This will override the current settings. Do you want to continue?"; +"DISABLED" = "Disabled"; "CACHE" = "Cache management"; "CLEARCACHE" = "Clear cache"; @@ -32,12 +40,94 @@ "AUTO_CLEARCACHE_DESC" = "Automatically clears the app cache on startup."; "DOWNLOADING" = "Downloading"; -"DOWNLOAD_MANAGER" = "Download Manager"; -"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails"; +"DOWNLOAD_MANAGER" = "Download manager"; +"DOWNLOAD_MANAGER_DESC" = "Replaces YouTube's video downloader with YouMod one. With ablity to downloading videos, audios, captions, and thumbnails."; "DOWNLOAD_SAVE_PHOTOS" = "Automatically save to Photos"; "DOWNLOAD_SAVE_PHOTOS_DESC" = "Automatically saves downloaded videos and images directly to your Photos app."; -"DOWNLOAD_DRC_AUDIO" = "Prefer DRC Audio"; -"DOWNLOAD_DRC_AUDIO_DESC" = "Prioritizes downloading audio tracks with Dynamic Range Compression for more consistent volume levels."; +"ADD_SHORTS_DOWNLOAD" = "Adds a download button to Shorts"; +"ADD_SHORTS_DOWNLOAD_DESC" = "Adds a download button to Shorts video overlay."; +"DOWNLOAD_SHORTS" = "Download Shorts"; +"COPY_VID_INFO" = "Copy video informations"; +"COPY_ALL_VID_INFO" = "Copy all"; +"COPY_AUTHOR" = "Copy author"; +"COPY_TITLE" = "Copy title"; +"COPY_DESCRIPTION" = "Copy description"; +"COPY_URL" = "Copy video link"; +"COPY_URL_TIMESTAMP" = "Copy video link with timestamp"; +"URL_COPIED" = "Copied video link"; +"URL_TIMESTAMP_COPIED" = "Copied video link with timestamp"; +"ERROR_VIDEOID" = "Cannot get the video ID"; +"ERROR_ADS" = "Cannot copy video link while playing ads"; +"COPIED_AUTHOR" = "Copied video author"; +"COPIED_TITLE" = "Copied video title"; +"COPIED_DESCRIPTION" = "Copied video description"; +"SAVE_THUMBNAIL" = "Save thumbnail"; +"DOWNLOAD_CAPTIONS" = "Download captions"; +"DOWNLOAD_AUDIO" = "Download audio"; +"DOWNLOAD_VIDEO" = "Download video"; +"OPEN_VID_BEFORE" = "Open a video before using the download manager"; +"NO_CAPTIONS_URL" = "No valid caption URLs found"; +"NO_AUDIO_URL" = "No audio URL found"; +"NO_STREAM_URL" = "No stream URL found"; +"CAPTIONS_FAILED" = "Failed to download captions"; +"NO_CAPTIONS" = "No captions available for this video"; +"NO_AUDIO_STREAM_FOUND" = "No audio streams found"; +"NO_VID_AUDIO_STREAM_FOUND" = "No video/audio streams found"; +"COPIED_VID_INFO" = "Copied all video informations"; +"SAVED_TO_PHOTOS" = "Saved to Photos"; +"SAVE_FAILED" = "Save failed"; +"PHOTO_ACCESS_DENINED" = "Photos access denied"; +"THUMBNAIL_FAILED" = "Thumbnail failed"; +"DOWNLOADING_THUMBNAIL" = "Downloading thumbnail..."; +"DOWNLOADING_AUDIO" = "Downloading audio..."; +"DOWNLOADING_VIDEO" = "Downloading video..."; +"DOWNLOADING_CAPTIONS" = "Downloading captions..."; +"NO_THUMBNAIL_FOUND" = "No thumbnail found"; +"DOWNLOAD_FAILED" = "Download failed"; +"COPY_THUMBNAIL" = "Copy thumbnail"; +"SHOW_THUMBNAIL" = "Show thumbnail"; +"THUMBNAIL_OPTIONS" = "Download thumbnail"; +"COPIED_TO_CLIPBOARD" = "Copied to clipboard"; +"DOWNLOAD_COMPLETED" = "Download completed"; +"CANNOT_SAVE_TO_PHOTOS" = "Cannot save to Photos"; +"FINA_VIDEO" = "Finalizing video..."; +"MERGING_VID_FALLBACK" = "Merging video with fallback..."; +"MERGING_VID" = "Merging video..."; +"CONVERTING" = "Converting to"; +"ALREADY_DOWNLOADING" = "Already downloading"; +"DOWNLOAD_CANCELLED" = "Download cancelled"; +"CANCEL" = "Cancel"; +"TRIMMING_AUDIO" = "Trimming audio..."; +"COPY" = "Copy"; +"CONNECTING_TO_SERVER" = "Connecting to the server..."; +"DOWNLOADING_TO_SERVER" = "Downloading to the server..."; +"COPY_COMMENT_TEXT" = "Copy comment text"; +"SAVE_COMMENT_IMAGE" = "Save comment as image"; +"COPY_COMMENT_IMAGE" = "Copy comment as image"; +"COPY_POST_TEXT" = "Copy post text"; +"SAVE_POST_IMAGE" = "Save post as image"; +"COPY_POST_IMAGE" = "Copy post as image"; +"FIX_DOWNLOAD" = "Fix download errors"; +"FIX_DOWNLOAD_DESC" = "Uses external server to download instead, this doesn't support downloading other audio tracks."; +"DOWNLOAD_SERVER" = "Download server"; +"CHOOSE_DOWNLOAD_SERVER" = "Choose download server"; +"DOWNLOAD_COMMENT" = "Save comment information"; +"DOWNLOAD_COMMENT_DESC" = "Allows to copy comment text and save comment as image by long tap."; +"DOWNLOAD_POST" = "Save post information"; +"DOWNLOAD_POST_DESC" = "Allows to copy post text and save post as image by long tap."; +"TRANSLATE_COMMENT" = "Translate comment"; +"TRANSLATE_POST" = "Translate post"; +"SERVER_EUROPRE1" = "Europe"; +"SERVER_ASIA1" = "Thailand - Asia"; +"SHOW_OPTIONS" = "Show options"; +"ENGLISH" = "English"; +"TRANSLATION" = "Translation"; +"LANGUAGE" = "Language"; +"TRANSLATING" = "Translating..."; +"SELECT_LANG" = "Select language"; +"TRANSLATE_FAILED" = "Failed to translate, please try again."; +"SAVE_CURRENT_IMAGE" = "Save current image"; +"COPY_CURRENT_IMAGE" = "Copy current image"; "APPEARANCE" = "Appearance"; "OLED_THEME" = "OLED dark mode"; @@ -46,6 +136,8 @@ "OLED_KEYBOARD_DESC" = "Make the keyboard uses a pure black (OLED) color for dark theme."; "NAVBAR" = "Navigation bar"; +"STICKY_NAVBAR" = "Sticky Navigation bar"; +"STICKY_NAVBAR_DESC" = "Pins the Navigation bar so that it remains visible when scrolling down."; "HIDE_YT_LOGO" = "Hide YouTube logo"; "HIDE_YT_LOGO_DESC" = "Hide YouTube logo in the navigation bar."; "PREMIUM_LOGO" = "Use YouTube Premium logo"; @@ -62,34 +154,82 @@ "FEED" = "Feed"; "HIDE_SUBBAR" = "Hide subbar"; "HIDE_SUBBAR_DESC" = "Hide subbar at the top of the feed."; +"HIDE_HORI_SHELF" = "Remove horizontal feeds"; +"HIDE_HORI_SHELF_DESC" = "Removes all horizontal feeds (News, Continue Watching, Posts collection, etc.) from the feed, except Playables."; "HIDE_MUSIC_PLAYLISTS" = "Hide music playlist generator"; "HIDE_MUSIC_PLAYLISTS_DESC" = "Hide music playlist generator (Select music tunes in three) in the feed."; "HIDE_FEED_POST" = "Hide posts"; "HIDE_FEED_POST_DESC" = "Hide posts in feed."; +"HIDE_SURVEYS" = "Hide surveys"; +"HIDE_SURVEYS_DESC" = "Hide video surveys that YouTube shows sometimes."; "HIDE_SHORTS_SHELF" = "Hide Shorts shelf"; "HIDE_SHORTS_SHELF_DESC" = "Hide Shorts shelf in the feed, including in the Subscriptions tab."; +"KEEP_SHORTS_SUBSCRIPT" = "Keep Shorts in Subscriptions"; +"KEEP_SHORTS_SUBSCRIPT_DESC" = "Prevents removing Shorts from Subscriptions tab."; "HIDE_SEARCH_HISTORY" = "Hide search history"; "HIDE_SEARCH_HISTORY_DESC" = "Hide previous search history and suggestions when using the search bar.\nNOTE: Your search history will still be accessible in your other YouTube clients."; -"HIDE_SUB_BUTTON" = "Hide Subscribe button"; -"HIDE_SUB_BUTTON_DESC" = "Hides the subscribe button in channels and video descriptions."; -"HIDE_SHOP_BUTTON" = "Hide Shopping button"; -"HIDE_SHOP_BUTTON_DESC" = "Hides the shopping/store button in channels descriptions."; -"HIDE_MEMBER_BUTTON" = "Hide Membership button"; -"HIDE_MEMBER_BUTTON_DESC" = "Hides the 'Join' button in channels and video descriptions."; +"HIDE_PLAYABLES" = "Remove playables"; +"HIDE_PLAYABLES_DESC" = "Removes playables (mini-games) from the feed."; +"HIDE_RELATED_VIDEOS" = "Hide related videos"; +"HIDE_RELATED_VIDEOS_DESC" = "Hide related videos below the player. (Not in the overlay)"; "PLAYER" = "Player"; +"PLAYBACK_SPEED" = "Playback speed"; +"CONTROL_CENTER" = "Control Center"; +"PLAYER_ACTIONS" = "Player actions"; +"GESTURE_HEADER" = "Gestures"; +"QUALITY_WIFI" = "Playback quality on WiFi"; +"QUALITY_WIFI_DESC" = "Automatically applies the selected quality when streaming over Wi-Fi."; +"QUALITY_CELLULAR" = "Playback quality on Cellular"; +"QUALITY_CELLULAR_DESC" = "Automatically applies the selected quality when streaming over mobile data."; +"QUALITY_LOW_POWER" = "Playback quality on Power saver mode"; +"QUALITY_LOW_POWER_DESC" = "Automatically applies the selected quality when when Low power mode is enabled on your device."; +"DEFAULT" = "Default"; +"BEST" = "Best"; +"AUDIO_TRACK" = "Preferred audio track"; +"ORIGINAL" = "Original"; +"SELECT_MANUALLY" = "Select manually"; +"AUDIO_TRACK_SELECT" = "Selected audio track"; +"AUDIO_TRACK_SELECT_DESC" = "Specifies the preferred audio track language to use for playback, if available."; +"NO_AUTO_DUBBED" = "Uses original audio track"; +"NO_AUTO_DUBBED_DESC" = "Uses original audio track if the preferred language track is auto-dubbed or not found."; +"CAPTION_TRACK" = "Preferred captions"; +"CAPTION_TRACK_SELECT" = "Selected captions"; +"CAPTION_TRACK_SELECT_DESC" = "Specifies the preferred captions language to display, if available."; +"DISABLES_CAPTION_TRACK" = "Automatically disable captions"; +"DISABLES_CAPTION_TRACK_DESC" = "Automatically disable captions if the preferred language is auto-generated or not found."; +"DEFAULT_SPEED" = "Default playback rate"; +"DEFAULT_SPEED_DESC" = "Overrides the default playback speed."; +"QUALITY_BUTTON" = "Quality controls button"; +"QUALITY_BUTTON_DESC" = "Displays a quality controls button on overlay."; +"SPEED_BUTTON" = "Speed controls button"; +"SPEED_BUTTON_DESC" = "Displays a speed controls button on overlay."; +"MUTE_BUTTON" = "Mute button"; +"MUTE_BUTTON_DESC" = "Displays a button to mute or unmute the volume on the overlay."; +"SHARE_BUTTON" = "Share button"; +"SHARE_BUTTON_DESC" = "Displays a button to share the video link on the overlay."; +"LOOP_BUTTON" = "Loop button"; +"LOOP_BUTTON_DESC" = "Displays a button to loop the video on the overlay."; +"CAPTION_BUTTON" = "Captions button"; +"CAPTION_BUTTON_DESC" = "Displays a button to copy the video captions on the overlay."; +"HOLD_TO_SPEED" = "Hold to speed"; +"HOLD_TO_SPEED_DESC" = "Uses custom hold to speed playback rate."; "HIDE_AUTOPLAY" = "Hide autoplay switch"; "HIDE_AUTOPLAY_DESC" = "Hide autoplay switch in the video overlay."; "HIDE_CAPTIONS_BUTTON" = "Hide captions button"; "HIDE_CAPTIONS_BUTTON_DESC" = "Hide captions button in the video overlay."; "HIDE_CAST_BUTTON_PLAYER" = "Hide cast button"; "HIDE_CAST_BUTTON_PLAYER_DESC" = "Hide cast button in the video overlay."; -"HIDE_PREV_BUTTON" = "Hide previous/back button"; -"HIDE_PREV_BUTTON_DESC" = "Hide previous/back button in the video overlay."; -"HIDE_NEXT_BUTTON" = "Hide next/skip button"; -"HIDE_NEXT_BUTTON_DESC" = "Hide next/skip button in the video overlay."; +"HIDE_NEXT_AND_PREV_BUTTON" = "Hide previous and next buttons"; +"HIDE_NEXT_AND_PREV_BUTTON_DESC" = "Hide previous and next buttons in the video overlay."; "REPLACE_PREVNEXT_BUTTONS" = "Replace Previous/Next buttons"; "REPLACE_PREVNEXT_BUTTONS_DESC" = "Replaces the Previous and Next video buttons in the player with Rewind and Fast Forward buttons."; +"SKIP_BACKWARD" = "Skip backward"; +"SKIP_BACKWARD_DESC" = "Replaces the previous track button in Control Center with a skip backward button by a set number of seconds."; +"SKIP_FORWARD" = "Skip forward"; +"SKIP_FORWARD_DESC" = "Replaces the next track button in Control Center with a skip forward button by a set number of seconds."; +"REWIND_SECONDS" = "Rewind seconds"; +"FORWARD_SECONDS" = "Fast-forward seconds"; "REMOVE_DARK_OVERLAY" = "Remove dark overlay"; "REMOVE_DARK_OVERLAY_DESC" = "Remove dark overlay from the video overlay."; "HIDE_END_SCREEN" = "Hide endscreen cards"; @@ -102,6 +242,12 @@ "HIDE_PAID_OVERLAY_DESC" = "Hide 'Includes paid promotion' in the video overlay."; "HIDE_WATERMARK" = "Hide watermark"; "HIDE_WATERMARK_DESC" = "Hide channel watermark at the bottom of the video overlay."; +"PAUSE_ON_OVERLAY" = "Pause on overlay"; +"PAUSE_ON_OVERLAY_DESC" = "Sets playback on pause if overlay appears."; +"DISABLES_SNAP_TO_CHAPTER" = "Disable snap to chapters"; +"DISABLES_SNAP_TO_CHAPTER_DESC" = "Prevents the scrubbar stick to the chapters."; +"DISABLES_ENGAGE_PANEL" = "Disables engagement panel in fullscreen"; +"DISABLES_ENGAGE_PANEL_DESC" = "Prevent engagement panel showing in fullscreen."; "GESTURES" = "Enable gestures controls"; "GESTURES_DESC" = "Swipe up or down on the left/right edges of the video screen to adjust brightness, volume, or playback speed."; "GESTURE_AREA" = "Gesture activation area"; @@ -136,6 +282,8 @@ "DISABLES_SHOW_REMAINING_DESC" = "Disable the feature that shows how much time is left in a video when tapping the timestamp."; "ALWAYS_SHOW_REMAINING" = "Always show remaining time"; "ALWAYS_SHOW_REMAINING_DESC" = "Ensure the remaining time is always displayed by default in the player overlay."; +"COPY_TIMESTAMP_ON_PAUSE" = "Copy timestamped links"; +"COPY_TIMESTAMP_ON_PAUSE_DESC" = "Allows to copy timestamped link to the clipboard by pressing pause button."; "SHOW_REMAINING_EXTRA" = "Show extra remaining time"; "SHOW_REMAINING_EXTRA_DESC" = "Adds the estimated finish time to the label in the video overlay."; "HIDE_FULLSCREEN_ACTIONS" = "Hide fullscreen actions"; @@ -148,93 +296,102 @@ "HIDE_CONTENT_WARNING_DESC" = "Automatically skip the sensitive content warning alert in some vidoes."; "AUTO_FULLSCREEN" = "Auto fullscreen"; "AUTO_FULLSCREEN_DESC" = "Automatically enter fullscreen mode when starting video playback."; -"AUTO_DISABLES_CAPTION" = "Auto disable captions"; -"AUTO_DISABLES_CAPTION_DESC" = "Automatically turns off closed captions (CC) whenever a new video starts playing."; "PORTRAIT_FULLSCREEN" = "Portrait fullscren mode"; "PORTRAIT_FULLSCREEN_DESC" = "Enables portrait fullscreen mode support."; "OLD_QUALITY_PICKER" = "Use old quality picker"; "OLD_QUALITY_PICKER_DESC" = "Reverts the video quality selection menu to the classic version (lists all resolutions immediately)."; "EXTRA_SPEED" = "Extra speed options"; "EXTRA_SPEED_DESC" = "Adds more video playback speed options up to 10x."; -"DISABLE_HINTS" = "Disable hints"; -"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "FORCE_MINIPLAYER" = "Force miniplayer"; "FORCE_MINIPLAYER_DESC" = "Allows all videos, including those made for kids, to play in the miniplayer when dismissing the player."; "FORCE_SEEKBAR" = "Always show progress bar"; "FORCE_SEEKBAR_DESC" = "Makes the progress bar (seekbar) remains visible at all times while in fullscreen mode."; -"HIDE_LIKE_BUTTON" = "Hide Like button"; -"HIDE_LIKE_BUTTON_DESC" = "Hide Like button in the action bar under the video overlay."; -"HIDE_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_DISLIKE_BUTTON_DESC" = "Hide Dislike button in the action bar under the video overlay."; -"HIDE_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHARE_BUTTON_DESC" = "Hide Share button in the action bar under the video overlay."; -"HIDE_DOWNLOAD_BUTTON" = "Hide Download button"; -"HIDE_DOWNLOAD_BUTTON_DESC" = "Hide Download button in the action bar under the video overlay."; -"HIDE_CLIP_BUTTON" = "Hide Clip button"; -"HIDE_CLIP_BUTTON_DESC" = "Hide Clip button in the action bar under the video overlay."; -"HIDE_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_REMIX_BUTTON_DESC" = "Hide Remix button in the action bar under the video overlay."; -"HIDE_SAVE_BUTTON" = "Hide Save button"; -"HIDE_SAVE_BUTTON_DESC" = "Hide Save button in the action bar under the video overlay."; +"TAP_TO_SEEK" = "Tap to seek"; +"TAP_TO_SEEK_DESC" = "Quickly jump to any point by tapping the progress bar."; +"DISABLES_ZOOM" = "Disable free zoom gesture"; +"DISABLES_ZOOM_DESC" = "Disables new free zoom gestures."; +"USES_24_HOURS_TIME" = "24-hour format"; +"USES_24_HOURS_TIME_DESC" = "Shows end time in 24-hour format."; +"PAUSE_TWO_FINGERS" = "Pause using two fingers"; +"PAUSE_TWO_FINGERS_DESC" = "Pause/Resume playback by tapping on the screen using two fingers."; +"HIDE_COMMENTS_SECTION" = "Hide comments section"; +"HIDE_COMMENTS_SECTION_DESC" = "Completely removes the entire comments section."; +"HIDE_COMMENTS_PREVIEW" = "Hide preview comments"; +"HIDE_COMMENTS_PREVIEW_DESC" = "Removes the preview box for the top comment while keeping the full section expandable."; +"USE_ANOTHER_MINIPLAYER" = "Uses another miniplayer style"; +"USE_ANOTHER_MINIPLAYER_DESC" = "Uses another miniplayer style redesigned from older YT versions."; +"LOCK_SPEED" = "Lock speed"; +"LOCK_SPEED_DESC" = "Locks at adjusted speed by swiping down."; +"SEEK_ON_OVERLAY" = "Seek on overlay"; +"SEEK_ON_OVERLAY_DESC" = "Allows controlling playback seeking by swiping left and right on the video overlay."; "SHORTS" = "Shorts"; -"HIDE_SHORTS_LIKE_BUTTON" = "Hide Like button"; -"HIDE_SHORTS_LIKE_BUTTON_DESC" = "Hide the Like button in the Shorts player."; -"HIDE_SHORTS_DISLIKE_BUTTON" = "Hide Dislike button"; -"HIDE_SHORTS_DISLIKE_BUTTON_DESC" = "Hide the Dislike button in the Shorts player."; -"HIDE_SHORTS_COMMENT_BUTTON" = "Hide Comment button"; -"HIDE_SHORTS_COMMENT_BUTTON_DESC" = "Hide the Comment button in the Shorts player."; -"HIDE_SHORTS_SHARE_BUTTON" = "Hide Share button"; -"HIDE_SHORTS_SHARE_BUTTON_DESC" = "Hide the Share button in the Shorts player."; -"HIDE_SHORTS_REMIX_BUTTON" = "Hide Remix button"; -"HIDE_SHORTS_REMIX_BUTTON_DESC" = "Hide the Remix button in the Shorts player."; -"HIDE_METADATA_BUTTON" = "Hide sound metadata button"; -"HIDE_METADATA_BUTTON_DESC" = "Hide the button that shows the sound source or music metadata in the Shorts player."; +"SHORTS_ONLY" = "Shorts only mode"; +"SHORTS_ONLY_DESC" = "Limits YouTube functionality to viewing Shorts only. You can disable Shorts only mode by long pressing with two fingers in the Shorts player."; +"SHORTS_ONLY_DISABLED" = "Shorts only mode has been turned off"; +"SHORTS_ACTION" = "Shorts action after the video ends"; +"LOOP" = "Loop video"; +"SKIP_TO_NEXT_SHORTS" = "Skip to next video"; +"PAUSE_SHORTS" = "Stop video"; +"REMOVE_LIVE_SHORTS" = "Remove live videos"; +"REMOVE_LIVE_SHORTS_DESC" = "Removes live videos from the feed."; "HIDE_SHORTS_PRODUCT" = "Hide products"; "HIDE_SHORTS_PRODUCT_DESC" = "Hide tagged products and shopping links in the Shorts overlay."; "HIDE_SHORTS_RECBAR" = "Hide recommendation bar"; "HIDE_SHORTS_RECBAR_DESC" = "Hide the suggested action bar in the Shorts player (e.g. Try this sound, Shopping, Preview comment)."; -"HIDE_SHORTS_COMMIT" = "Hide commissions bar"; -"HIDE_SHORTS_COMMIT_DESC" = "Hide the bar that shows the video got commissions in the Shorts player."; -"HIDE_SHORTS_SUBSCRIPT_BUTTON" = "Hide subscriptions button"; -"HIDE_SHORTS_SUBSCRIPT_BUTTON_DESC" = "Hide the Subscriptions button at the top of the Shorts player."; -"HIDE_SHORTS_LIVE_BUTTON" = "Hide live button"; -"HIDE_SHORTS_LIVE_BUTTON_DESC" = "Hide the Live button at the top of the Shorts player."; -"HIDE_SHORTS_LENS_BUTTON" = "Hide lens button"; -"HIDE_SHORTS_LENS_BUTTON_DESC" = "Hide the Lens button at the top of the Shorts player."; -"HIDE_SHORTS_TRENDS_BUTTON" = "Hide trends button"; -"HIDE_SHORTS_TRENDS_BUTTON_DESC" = "Hide the Trends button at the top of the Shorts player."; -"HIDE_SHORTS_TO_VIDEO" = "Hide link to full video"; -"HIDE_SHORTS_TO_VIDEO_DESC" = "Hide the link that redirects in a Short to a full video in the Shorts player."; "ENABLES_SHORTS_QUALITY" = "Enable Shorts quality picker"; "ENABLES_SHORTS_QUALITY_DESC" = "Force enable the video quality selection menu in Shorts."; "SHOW_SHORTS_SEEKBAR" = "Always show seekbar"; "SHOW_SHORTS_SEEKBAR_DESC" = "Ensure the progress/seek bar is always visible at the bottom of the Shorts player."; +"REMOVE_POSTS_SHORTS" = "Remove posts"; +"REMOVE_POSTS_SHORTS_DESC" = "Remove posts from the feed."; +"SHORTS_FULLSCREEN" = "Pinch to Fullscreen"; +"SHORTS_FULLSCREEN_DESC" = "Manages visibility of the overlay with pinch in and pinch out gestures, displaying Shorts in fullscreen mode."; "TABBAR" = "Tab bar"; "DEFAULT_TAB" = "Default startup tab"; -"DEFAULT_TAB_DESC" = "You can select the tab you want to open when opening the app."; -"HOME_NAME" = "Home"; -"SUBSCRIPT_NAME" = "Subscriptions"; -"LIB_NAME" = "Library (You)"; +"FORSTED_TAB_BAR" = "Translucent tab bar"; +"ENABLED" = "Enabled"; "HIDE_TAB_INDI" = "Hide tab indicators"; "HIDE_TAB_INDI_DESC" = "Hide all tab indicators in the tab bar."; "HIDE_TAB_LABELS" = "Hide tab labels"; "HIDE_TAB_LABELS_DESC" = "Hide all tab labels in the tab bar."; -"HIDE_HOME_TAB" = "Hide Home tab"; -"HIDE_HOME_TAB_DESC" = "Hide Home tab in the tab bar."; -"HIDE_SHORTS_TAB" = "Hide Shorts tab"; -"HIDE_SHORTS_TAB_DESC" = "Hide Shorts tab in the tab bar."; -"HIDE_CREATE_BUTTON" = "Hide Create button"; -"HIDE_CREATE_BUTTON_DESC" = "Hide Create button in the tab bar."; -"HIDE_SUBSCRIPT_TAB" = "Hide Subscriptions tab"; -"HIDE_SUBSCRIPT_TAB_DESC" = "Hide Subscriptions tab in the tab bar."; +"HOME_TAB" = "Home"; +"SHORTS_TAB" = "Shorts"; +"CREATE_TAB" = "Create"; +"SUBSCRIPTIONS_TAB" = "Subscriptions"; +"LIBRARY_TAB" = "Library (You)"; +"HISTORY_TAB" = "History"; +"GAMING_TAB" = "Gaming"; +"SPORTS_TAB" = "Sports"; +"NOTI_TAB" = "Notifications"; +"NEWS_TAB" = "News"; +"MUSIC_TAB" = "Music"; +"WATCH_LATER_TAB" = "Watch later"; +"PLAYLIST_TAB" = "Playlists"; +"LIKE_TAB" = "Liked videos"; +"LIVE_TAB" = "Live"; +"POST_TAB" = "Posts"; +"VIDEO_TAB" = "Your videos"; +"MOVIE_TAB" = "Movies"; +"COURSE_TAB" = "Courses"; +"MINIGAME_TAB" = "Playables"; +"FASHION_TAB" = "Fashion & Beauty"; +"LEARNING_TAB" = "Learning"; +"MANAGE_TABS" = "Manage tabs"; +"MANAGE_TABS_DESC" = "Reorder and toggle tabs"; +"TAB_REORDER_HINT" = "Drag to reorder. Toggle to show/hide.\nTab settings are also accessible by long-pressing the first tab in the tab bar."; +"TAB_LIMIT" = "Tab limit"; +"TAB_LIMIT_DESC" = "Only 6 tabs are allowed."; +"ZERO_TAB_DESC" = "At least 1 tab has to be enabled."; "MISCELLANEOUS" = "Miscellaneous"; "BACKGROUND_PLAYBACK" = "Background playback"; "BACKGROUND_PLAYBACK_DESC" = "Enables background playback for videos."; "DISABLES_SHORTS_PIP" = "Disables Shorts PiP"; "DISABLES_SHORTS_PIP_DESC" = "Try to disables Shorts PiP, this might not 100% disables it."; +"DISABLE_HINTS" = "Disable hints"; +"DISABLE_HINTS_DESC" = "Disable the tutorial hints and interactive pop-ups."; "BLOCK_UPGRADE_DIALOGS" = "Block upgrade dialogs"; "BLOCK_UPGRADE_DIALOGS_DESC" = "Block the YouTube dialogs that ask you to upgrade the app."; "ARE_YOU_THERE_DIALOG" = "Hide 'Are you there?' dialog"; @@ -247,7 +404,114 @@ "DISABLES_SNACK_BAR_DESC" = "Disables snack bar when pressing some buttons or doing something in the app."; "HIDE_STARTUP_ANIMATIONS" = "Hide YouTube's startup animations"; "HIDE_STARTUP_ANIMATIONS_DESC" = "Hide YouTube's startup animations when opening the app."; -"HIDE_PLAY_IN_NEXT_QUEUE" = "Hide 'Play next in queue'"; -"HIDE_PLAY_IN_NEXT_QUEUE_DESC" = "Hide the 'Play next in queue' option in the flyout menu."; "HIDE_LIKE_DISLIKE_VOTES" = "Hide like/dislike votes"; -"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; \ No newline at end of file +"HIDE_LIKE_DISLIKE_VOTES_DESC" = "Hide messages that pop up when you like/dislike videos."; +"FLYOUT_MENU" = "Flyout menu"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION" = "Removes 'Play next in queue' option"; +"REMOVE_PLAY_IN_NEXT_QUEUE_OPTION_DESC" = "Removes 'Play next in queue' option from the flyout menu."; +"REMOVE_DOWNLOAD_OPTION" = "Removes 'Download video' option"; +"REMOVE_DOWNLOAD_OPTION_DESC" = "Removes 'Download video' option from the flyout menu."; +"REMOVE_WATCH_LATER_OPTION" = "Removes 'Save to Watch later' option"; +"REMOVE_WATCH_LATER_OPTION_DESC" = "Removes 'Save to Watch Later' option from the flyout menu."; +"REMOVE_SAVE_OPTION" = "Removes 'Save to playlist' option"; +"REMOVE_SAVE_OPTION_DESC" = "Removes 'Save to playlist' option from the flyout menu."; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION" = "Removes 'Remove from playlist' option"; +"REMOVE_REMOVE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Remove from playlist' option from the flyout menu."; +"REMOVE_SHARE_OPTION" = "Removes 'Share' option"; +"REMOVE_SHARE_OPTION_DESC" = "Removes 'Share' option from the flyout menu."; +"REMOVE_NOT_INTERESTED_OPTION" = "Removes 'Not interested' option"; +"REMOVE_NOT_INTERESTED_OPTION_DESC" = "Removes 'Not interested' option from the flyout menu."; +"REMOVE_DONT_RECOMMEND_OPTION" = "Removes 'Don't recommend channel' option"; +"REMOVE_DONT_RECOMMEND_OPTION_DESC" = "Removes 'Don't recommend channel' option from the flyout menu."; +"REMOVE_INFO_OPTION" = "Removes 'Why are you seeing this?' option"; +"REMOVE_INFO_OPTION_DESC" = "Removes 'Why are you seeing this?' option from the flyout menu."; +"REMOVE_FILTER_OPTION" = "Removes 'Search filters' option"; +"REMOVE_FILTER_OPTION_DESC" = "Removes 'Search filters' option from the flyout menu."; +"REMOVE_REPORT_OPTION" = "Removes 'Report' option"; +"REMOVE_REPORT_OPTION_DESC" = "Removes 'Report' option from the flyout menu."; +"REMOVE_YOUTUBE_MUSIC_OPTION" = "Removes 'Listen with YouTube Music' option"; +"REMOVE_YOUTUBE_MUSIC_OPTION_DESC" = "Removes 'Listen with YouTube Music' option from the flyout menu."; +"REMOVE_FEED_BACK_OPTION" = "Removes 'Send feedback' option"; +"REMOVE_FEED_BACK_OPTION_DESC" = "Removes 'Send feedback' option from the flyout menu."; +"REMOVE_CAST_OPTION" = "Removes 'Watch on TV' option"; +"REMOVE_CAST_OPTION_DESC" = "Removes 'Watch on TV' option from the flyout menu."; +"REMOVE_SHUFFLE_OPTION" = "Removes 'Shuffle' option"; +"REMOVE_SHUFFLE_OPTION_DESC" = "Removes 'Shuffle' option from the flyout menu."; +"REMOVE_UN_SUB_OPTION" = "Removes 'Unsubscribe' option"; +"REMOVE_UN_SUB_OPTION_DESC" = "Removes 'Unsubscribe' option from the flyout menu."; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION" = "Removes 'Hide video from playlist' option"; +"REMOVE_HIDE_FROM_PLAYLIST_OPTION_DESC" = "Removes 'Hide video from playlist' option from the flyout menu."; +"REMOVE_HELP_OPTION" = "Removes 'Help & feedback' option"; +"REMOVE_HELP_OPTION_DESC" = "Removes 'Help & feedback' option from the flyout menu."; +"REMOVE_NOTIFY_OPTION" = "Removes 'Notify me' option"; +"REMOVE_NOTIFY_OPTION_DESC" = "Removes 'Notify me' option from the flyout menu."; +"REMOVE_CLEARSCREEN_OPTION" = "Removes 'Clear Screen' option"; +"REMOVE_CLEARSCREEN_OPTION_DESC" = "Removes 'Clear Screen' option from the flyout menu."; +"HIDE_COMMU_GUIDE" = "Hide community guidelines"; +"HIDE_COMMU_GUIDE_DESC" = "Hide community guidelines header."; +"DISABLES_RTL" = "Disable RTL formatting"; +"DISABLES_RTL_DESC" = "Forcefully displays text in left-to-right (LTR) format for languages that are initially displayed in right-to-left (RTL)."; +"DEVICE_UI" = "Interface style"; +"OPEN_LINK" = "Open link"; +"FLOATING_KEYBOARD" = "Floating keyboard"; +"FLOATING_KEYBOARD_DESC" = "Force-enables the floating keyboard. Useful for iPhones with iPad style spoofing.\nNote: Works ONLY when iPad style is selected."; +"AUTO_OPEN_LINK" = "Auto-check clipboard"; +"AUTO_OPEN_LINK_DESC" = "Check clipboard for YouTube-related links each time the app is opened.\nFor proper functionality, go to iOS Settings → YouTube → Paste from other apps and select 'Allow'"; + +"SB_DESCRIPTION" = "Skip sponsored segments automatically using community-submitted data."; +"SB_ENABLE" = "Enable SponsorBlock"; +"SB_ENABLE_DESC" = "Activate the SponsorBlock extension to skip sponsored segments."; +"SB_SHOW_BUTTON" = "Show overlay button"; +"SB_SHOW_BUTTON_DESC" = "Display a SponsorBlock toggle button in the player overlay."; +"SB_SHOW_NOTIFICATIONS" = "Show notifications"; +"SB_SHOW_NOTIFICATIONS_DESC" = "Display notifications when segments are skipped. Notifications for manual-skip categories are always shown."; +"SB_HAPTIC_FEEDBACK" = "Haptic feedback"; +"SB_HAPTIC_FEEDBACK_DESC" = "Provide haptic feedback when a segment is skipped."; +"SB_SEGMENTS_IN_PLAYER" = "Segments in player"; +"SB_SEGMENTS_IN_PLAYER_DESC" = "Display colored segments on the progress bar of the main video player."; +"SB_SEGMENTS_IN_FEED" = "Segments in feed"; +"SB_SEGMENTS_IN_FEED_DESC" = "Display colored segments on the progress bar of players in the feed."; +"SB_SEGMENTS_IN_MINIPLAYER" = "Segments in mini-player"; +"SB_SEGMENTS_IN_MINIPLAYER_DESC" = "Display colored segments on the progress bar of the mini-player."; +"SB_SHOW_DURATION" = "Show duration without segments"; +"SB_SHOW_DURATION_DESC" = "Display the total video duration excluding skippable segments."; +"SB_CATEGORIES_HEADER" = "Segment categories"; +"SB_SEGMENT_COLOR" = "Segment color"; + +"SB_ACTION_DISABLE" = "Disable"; +"SB_ACTION_AUTO_SKIP" = "Auto-skip"; +"SB_ACTION_ASK" = "Ask before skipping"; +"SB_ACTION_DISPLAY" = "Show on seek bar only"; +"SB_ACTION_SKIP_TO" = "Skip to segment"; + +"SB_CAT_sponsor" = "Sponsor"; +"SB_CAT_sponsor_DESC" = "Paid promotion for a product or service not directly related to the creator."; +"SB_CAT_intro" = "Intro"; +"SB_CAT_intro_DESC" = "Intermission or intro animation typically seen at the start of a video."; +"SB_CAT_outro" = "Endcards"; +"SB_CAT_outro_DESC" = "Endcards, credits, or outros at the end of the video."; +"SB_CAT_interaction" = "Interaction"; +"SB_CAT_interaction_DESC" = "Reminders to like, subscribe, or follow on social media."; +"SB_CAT_selfpromo" = "Self-promotion"; +"SB_CAT_selfpromo_DESC" = "Unpaid promotion of the creator's own products, merchandise, or other content."; +"SB_CAT_music_offtopic" = "Non-music"; +"SB_CAT_music_offtopic_DESC" = "Non-music sections in music videos (e.g. talking, introductions)."; +"SB_CAT_preview" = "Preview"; +"SB_CAT_preview_DESC" = "Recap or preview of content from another video or previous episode."; +"SB_CAT_hook" = "Hook/Greetings"; +"SB_CAT_hook_DESC" = "Narrated trailers for the upcoming video, greetings and goodbyes."; +"SB_CAT_poi_highlight" = "Highlight"; +"SB_CAT_poi_highlight_DESC" = "The key moment or highlight point of the video. Use 'Skip to' to jump directly to it."; +"SB_CAT_filler" = "Filler"; +"SB_CAT_filler_DESC" = "Tangential content, jokes, or filler that is not directly related to the main topic."; + +"SB_SKIPPED" = "%@ segment has been skipped"; +"SB_DETECTED" = "%@ segment detected.\nWould you like to skip the segment?"; +"SB_SKIP_NOW" = "Skip"; +"SB_UNSKIP" = "Unskip"; +"SB_JUMPED_TO_HIGHLIGHT" = "Jumped to highlight"; +"SB_JUMP_TO_HIGHLIGHT" = "Highlight available. Jump to the point?"; +"SB_SEGMENT_COLOR_SUFFIX" = "Segment color"; +"SB_SECTION_MAIN" = "Main"; +"SB_SKIP_ALERT_DURATION" = "Skip alert duration"; +"SB_UNSKIP_ALERT_DURATION" = "Unskip alert duration"; \ No newline at end of file