Files
ReelShort/lib/l10n/app_localizations_en.dart
T
Vicky d7eff4ca59 feat(client): 对接详情/播放/进度续看/收藏(K1 看剧闭环)+ 三态 UI
后端接口从 6 个扩到 13 个,全部走真数据:

- 新增 api:playback_api(/app/episodes/{id}/play)、watch_api(/app/watch/*)、
  library_api(/app/favorites*);票据从 CatalogApi 搬到 PlaybackApi
- 新增 repository:PlaybackRepository(含票据 TTL 缓存,默认 10 分钟,时钟可注入)、
  FavoriteRepository、WatchHistoryRepository 换成服务端模型(每剧一条续看记录,删掉 demoProfileId)
- PlayerLogic:起播前取票(拿不到给三态 UI,失败可重试);accessType 驱动锁定文案,
  不在前端猜;签名地址原样交给播放器(只补 host,不动 path/query);
  进度上报节流 5s → 10s,切集/退出/切后台各补一次,播完带 finished
- 续看定位:服务端记录里的那一集才 seek(其它集从头播),续看位置进详情/播放页时读一次
- FavoriteController:收藏以服务端为准,乐观更新 + 失败回滚;退出登录清空
- UI 三态:详情页(加载/失败可重试/空剧集 + 续看进度条 + 收藏按钮)、
  播放页(票据加载中/失败可重试/后端判定不可播)、我的页(续看失败可重试、收藏三态、最近观看)
- l10n 新增 8 个键(en/zh/id 三份):网络/进度/收藏失败、锁定提示、续看文案等

测试:flutter test 188 全过(存量 27 个测试文件未跑红);新增票据过期重取、进度节流、
续看定位、收藏回滚、签名 query 原样保留、真实后端端到端联调(live_watch_flow)
2026-10-16 17:39:05 +08:00

583 lines
12 KiB
Dart

// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get appName => 'FruitShorts';
@override
String get demo => 'DEMO';
@override
String get homeHeadline => 'Watch now';
@override
String get homeSubtitle => 'Free short drama preview.';
@override
String get mediaNotice => 'User-provided local demo video · Free preview';
@override
String get featuredDemo => 'FEATURED · TECHNICAL DEMO';
@override
String get demoTitle => 'The Signal';
@override
String get demoSynopsis =>
'A short local video preview. More episodes can be added when media is available.';
@override
String get homeTab => 'Home';
@override
String get openQuickMenu => 'Open quick menu';
@override
String get quickAccess => 'Quick access';
@override
String get currentSeries => 'Current series';
@override
String get discoverTab => 'Discover';
@override
String get searchDiscover => 'Search dramas, actors, genres...';
@override
String get featured => 'Featured';
@override
String get suspense => 'Suspense';
@override
String get romance => 'Romance';
@override
String get actionGenre => 'Action';
@override
String get filter => 'Filter';
@override
String get topCharts => 'Top Charts';
@override
String get newReleases => 'New Releases';
@override
String get trendingMicroDramas => 'Trending Micro-Dramas';
@override
String get seeAll => 'See All';
@override
String get artworkOnly => 'Artwork';
@override
String get artworkOnlyNotice =>
'This is a visual catalog item. No video is available yet.';
@override
String get videoPending => 'Video pending';
@override
String get watchNow => 'Watch free';
@override
String get noSearchResults => 'No matching dramas.';
@override
String get demoCatalogNotice =>
'Three demo dramas share the same local video.';
@override
String get myTab => 'My';
@override
String get localViewer => 'Local Viewer';
@override
String get localDemoProfile => 'Local demo profile';
@override
String get continueWatching => 'Continue Watching';
@override
String watchedPercent(int percent) {
return 'Watched $percent%';
}
@override
String get resume => 'Resume';
@override
String get history => 'History';
@override
String get watchlist => 'Watchlist';
@override
String get likedTab => 'Liked';
@override
String get all => 'All';
@override
String get inProgress => 'In Progress';
@override
String get completed => 'Completed';
@override
String get noSavedShows => 'No shows here yet.';
@override
String get preferences => 'Preferences';
@override
String get appLanguage => 'App Language';
@override
String get followingTab => 'Following';
@override
String get animatedTab => 'Animated';
@override
String get recommendedTab => 'Recommended';
@override
String get noCategoryDramas => 'No dramas in this category yet.';
@override
String get trendingTab => 'Trending';
@override
String get liveActionTab => 'Live Action';
@override
String get search => 'Search';
@override
String get free => 'Free';
@override
String get mute => 'Mute';
@override
String get unmute => 'Unmute';
@override
String get like => 'Like';
@override
String get save => 'Save';
@override
String get comments => 'Comments';
@override
String get share => 'Share';
@override
String get localOnlyComments => 'Only visible on this device';
@override
String get noComments => 'No comments yet.';
@override
String get writeComment => 'Write a comment';
@override
String get postComment => 'Post';
@override
String get shareFailed => 'Could not open the share sheet.';
@override
String get nowPlaying => 'Playing now';
@override
String get watchFullscreen => 'Watch in landscape';
@override
String get exitFullscreen => 'Exit fullscreen';
@override
String get pictureInPicture => 'Picture in picture';
@override
String get pipUnavailable =>
'Picture in picture is unavailable on this device';
@override
String get originalTag => 'Original';
@override
String get dramaTag => 'Short drama';
@override
String episodeLabel(int number) {
return 'Episode $number';
}
@override
String get exploreSeries => 'Explore series';
@override
String episodeCount(int count) {
return '$count EPISODES';
}
@override
String technicalDemoCount(int count) {
return 'FREE PREVIEW · $count EPISODES';
}
@override
String get episodes => 'Episodes';
@override
String get watchEpisodeOne => 'Watch episode 1';
@override
String continueEpisode(int number) {
return 'Continue episode $number';
}
@override
String get freeTestMedia => 'Same demo clip · free';
@override
String get lockedStoreUnavailable => 'Locked · store unavailable';
@override
String get freeTestClip => 'Free test clip';
@override
String get locked => 'Locked';
@override
String get freeSamplesNotice => 'Two free samples · store setup pending';
@override
String get demoTestMedia => 'FREE · SAME DEMO CLIP';
@override
String episodeProgress(int number, int total) {
return 'EPISODE $number / $total';
}
@override
String get testVideoNotice => 'Self-generated technical test video';
@override
String lockedTitle(int number) {
return 'Episode $number is locked';
}
@override
String get lockedBody =>
'Unlocking will be available after store and verification setup. This demo does not process payments.';
@override
String get playbackError => 'This clip could not be played.';
@override
String get catalogError => 'The catalog could not be loaded.';
@override
String get detailError => 'The episode list could not be loaded.';
@override
String get noMediaNotice => 'This episode has no playable media yet.';
@override
String get noStories => 'No stories available yet.';
@override
String get retry => 'Retry';
@override
String get backToSeries => 'Back to series';
@override
String get nextEpisode => 'Next episode';
@override
String get pause => 'Pause';
@override
String get play => 'Play';
@override
String get seekVideo => 'Seek video';
@override
String get seekFailed => 'Could not change playback position.';
@override
String get captions => 'Captions';
@override
String get captionsOff => 'Captions off';
@override
String get englishCaptions => 'English captions';
@override
String get chineseCaptions => 'Chinese captions';
@override
String get language => 'Language';
@override
String get systemLanguage => 'System';
@override
String get english => 'English';
@override
String get simplifiedChinese => '简体中文';
@override
String get episodeOneTitle => 'First Light';
@override
String get episodeTwoTitle => 'A New Frequency';
@override
String get episodeThreeTitle => 'Crossing Over';
@override
String get episodeFourTitle => 'After the Echo';
@override
String get dramaSignalTitle => 'The Signal';
@override
String get dramaSignalSynopsis =>
'A demo series for vertical playback. Every episode reuses one local clip.';
@override
String get dramaCityTitle => 'City Lights & Velvet Nights';
@override
String get dramaCitySynopsis =>
'A demo romance series. The same local video is used to try switching and resume.';
@override
String get dramaVengeanceTitle => 'Vengeance Rising';
@override
String get dramaVengeanceSynopsis =>
'A demo action series. The same local video is used to try switching and resume.';
@override
String get dramaEchoesTitle => 'Echoes of Fate';
@override
String get genreSuspenseMystery => 'Suspense · Mystery';
@override
String get genreUrbanRomance => 'Urban Romance';
@override
String get genreActionThriller => 'Action · Thriller';
@override
String get genreDramaMystery => 'Drama · Mystery';
@override
String get continueWatchingHeading => 'CONTINUE WATCHING';
@override
String get preferencesHeading => 'PREFERENCES';
@override
String get brandMark => 'FRUITSHORTS';
@override
String get shareFooter => 'FruitShorts';
@override
String get shortsTab => 'Shorts';
@override
String get myListTab => 'My List';
@override
String get watchFull => 'Watch Full';
@override
String get swipeForNext => 'Swipe up for next';
@override
String get clearScreen => 'Clear screen';
@override
String get exitClearScreen => 'Exit clear screen';
@override
String get logIn => 'Log in';
@override
String get logInHint => 'Keep your coins and list';
@override
String get rewards => 'Rewards';
@override
String get forYou => 'For You';
@override
String get hot => 'Hot';
@override
String get newest => 'New';
@override
String get favorite => 'Favorite';
@override
String get myAccount => 'My account';
@override
String get coins => 'Coins';
@override
String get topUpWallet => 'Top Up Wallet';
@override
String get vipMembership => 'VIP Membership';
@override
String get notActive => 'Not Active';
@override
String get joinVip => 'Join VIP';
@override
String get earnRewards => 'Earn Rewards';
@override
String get freeBonus => 'Get Free Bonus';
@override
String get watchHistory => 'Watch History';
@override
String get settings => 'Settings';
@override
String get signOut => 'Sign Out';
@override
String get mockUserName => 'Vicky sheng';
@override
String get mockUserId => 'ID: M1CZWVYE';
@override
String get mockNotice => 'Sample data only.';
@override
String get topBadge => 'TOP 1';
@override
String get signIn => 'Sign In';
@override
String get signUp => 'Sign Up';
@override
String get authSignInTitle => 'Welcome back';
@override
String get authSignUpTitle => 'Create your account';
@override
String get authSubtitle => 'Sign in with your email and password.';
@override
String get authEmailLabel => 'Email';
@override
String get authPasswordLabel => 'Password';
@override
String get authPasswordHint => 'At least 8 characters, letters and numbers';
@override
String get authEmailRequired => 'Enter your email';
@override
String get authEmailInvalid => 'That email doesn\'t look right';
@override
String get authPasswordRequired => 'Enter your password';
@override
String get authPasswordLength => 'Password must be 8-64 characters';
@override
String get authPasswordWeak => 'Password needs both letters and numbers';
@override
String get authNetworkError => 'Network error, please try again';
@override
String get authSwitchToSignUp => 'New here? Create an account';
@override
String get authSwitchToSignIn => 'Already have an account? Sign in';
@override
String get authGateTitle => 'Sign in to continue';
@override
String get authGateMessage => 'Favorites and comments need an account.';
@override
String get authGateAction => 'Sign In';
@override
String get cancel => 'Not now';
@override
String get noEpisodesYet => 'No playable episodes yet.';
@override
String get networkError =>
'Network is unreachable. Please check your connection and retry.';
@override
String get progressError => 'Your watch position could not be loaded.';
@override
String get favoritesError => 'Your favorites could not be loaded.';
@override
String get favoriteToggleFailed =>
'Could not update favorites. Please retry.';
@override
String get remove => 'Remove';
@override
String get unlockComingSoon => 'Unlocking with coins is coming soon.';
@override
String resumeProgress(Object position, Object total) {
return 'Resume at $position / $total';
}
}