mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-09 10:00:27 +00:00
feat: new ui (#1455)
This commit is contained in:
742 files changed
+59951
-5934
No files matched your search
@@ -13,7 +13,7 @@ on:
|
||||
default: "false"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25"
|
||||
GO_VERSION: "1.25.4"
|
||||
FLUTTER_VERSION: "3.41.6"
|
||||
GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
|
||||
GA4_API_SECRET: ${{ secrets.GA4_API_SECRET }}
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
VERSION: ${{ needs.get-release.outputs.version }}
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
|
||||
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/main/Files/Languages/ChineseSimplified.isl" -OutFile "ChineseSimplified.isl"
|
||||
mv ChineseSimplified.isl "C:\Program Files (x86)\Inno Setup 6\Languages\"
|
||||
|
||||
go build -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$env:VERSION" -buildmode=c-shared -o ui/flutter/windows/libgopeed.dll github.com/GopeedLab/gopeed/bind/desktop
|
||||
@@ -407,13 +407,17 @@ jobs:
|
||||
overwrite: true
|
||||
build-snap:
|
||||
if: ${{ github.event.inputs.platform == 'all' || github.event.inputs.platform == 'snap' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [get-release]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- name: Install Snap host build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||
- name: Setup LXD
|
||||
uses: canonical/[email protected]
|
||||
with:
|
||||
@@ -477,6 +481,8 @@ jobs:
|
||||
\$CRAFT_PART_INSTALL/usr/bin/flutter doctor
|
||||
build-packages:
|
||||
- clang
|
||||
- llvm-18
|
||||
- lld
|
||||
- cmake
|
||||
- curl
|
||||
- libgtk-3-dev
|
||||
@@ -505,6 +511,9 @@ jobs:
|
||||
set +e
|
||||
dart pub get
|
||||
set -eux
|
||||
llvm_bin_dir="\$(dirname "\$(readlink -f "\$(command -v clang)")")"
|
||||
test -x "\$llvm_bin_dir/llvm-ar"
|
||||
test -x "\$llvm_bin_dir/ld.lld"
|
||||
flutter build linux --dart-define="UPDATE_CHANNEL=linuxSnap" --dart-define="GA4_MEASUREMENT_ID=$GA4_MEASUREMENT_ID" --dart-define="GA4_API_SECRET=$GA4_API_SECRET"
|
||||
mkdir -p \$CRAFT_PART_INSTALL/bin/
|
||||
cp -r build/linux/*/release/bundle/* \$CRAFT_PART_INSTALL/bin/
|
||||
@@ -687,12 +696,15 @@ jobs:
|
||||
VERSION: ${{ needs.get-release.outputs.version }}
|
||||
run: |
|
||||
sudo apt update -y
|
||||
sudo apt install -y pv bsdmainutils
|
||||
wget -O qdk2_0.32.bionic_amd64.deb "https://github.com/qnap-dev/qdk2/releases/download/v0.32/qdk2_0.32.bionic_amd64.deb"
|
||||
dpkg -X qdk2_0.32.bionic_amd64.deb qdk2 # Direct installs will fail due to missing dependencies!
|
||||
[[ -d qdk2 ]] || exit 1
|
||||
qdk_version=2.5.3
|
||||
qdk_package="qdk_${qdk_version}_amd64.deb"
|
||||
curl --fail --location --retry 3 --retry-all-errors \
|
||||
--output "${qdk_package}" \
|
||||
"https://github.com/qnap-dev/QDK/releases/download/v${qdk_version}/${qdk_package}"
|
||||
echo "17b3841b7d4590a4ee025844ba583304b5e3c497d9fa8934d5175131d3908022 ${qdk_package}" | sha256sum --check --strict
|
||||
sudo apt-get install -y "./${qdk_package}"
|
||||
command -v qbuild
|
||||
|
||||
export PATH=$(pwd)/qdk2/usr/bin:$(pwd)/qdk2/usr/share/qdk2/QDK/bin:${PATH}
|
||||
wget -O Gopeed.template.tar.gz "https://github.com/GopeedLab/QpkgBuild/raw/refs/heads/master/template/Gopeed.template.tar.gz"
|
||||
tar -zxf Gopeed.template.tar.gz
|
||||
[[ -d Gopeed ]] || exit 1
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
name: l10n
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "ui/flutter/lib/l10n/**"
|
||||
- "ui/flutter/l10n.yaml"
|
||||
- ".github/workflows/scripts/check_l10n.dart"
|
||||
- ".github/workflows/l10n.yml"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "ui/flutter/lib/l10n/**"
|
||||
- "ui/flutter/l10n.yaml"
|
||||
- ".github/workflows/scripts/check_l10n.dart"
|
||||
- ".github/workflows/l10n.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: l10n-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.41.6"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate Flutter translations
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ui/flutter
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
- run: flutter pub get
|
||||
- name: Validate ARB catalogs
|
||||
run: dart run ../../.github/workflows/scripts/check_l10n.dart
|
||||
- name: Generate localizations
|
||||
run: flutter gen-l10n
|
||||
- name: Analyze generated localization integration
|
||||
run: flutter analyze
|
||||
@@ -17,7 +17,7 @@ on:
|
||||
- "Dockerfile"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25"
|
||||
GO_VERSION: "1.25.4"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// Validate Flutter ARB translation catalogs before localization code generation.
|
||||
//
|
||||
// Usage (from ui/flutter):
|
||||
// dart run ../../.github/workflows/scripts/check_l10n.dart
|
||||
//
|
||||
// What it checks:
|
||||
// - Every ARB file uses the repository's canonical two-space JSON formatting.
|
||||
// - Every app_<locale>.arb file declares the matching @@locale value.
|
||||
// - Message values are not empty.
|
||||
// - Every locale has exactly the same message keys as app_en.arb.
|
||||
// - Every translation preserves the placeholder variables used by app_en.arb.
|
||||
//
|
||||
// Notes:
|
||||
// - This script does not judge translation quality.
|
||||
// - It does not generate localization code or fully parse ICU MessageFormat.
|
||||
// `flutter gen-l10n` runs immediately afterwards in CI to validate ICU syntax
|
||||
// and generate app_localizations*.dart.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
/// Runs repository-specific checks that `flutter gen-l10n` does not enforce.
|
||||
void main() {
|
||||
final directory = Directory('lib/l10n');
|
||||
final templateFile = File('${directory.path}/app_en.arb');
|
||||
final template = _readArb(templateFile);
|
||||
final templateKeys = _messageKeys(template);
|
||||
var failed = false;
|
||||
|
||||
for (final file in directory.listSync().whereType<File>().where(
|
||||
(file) => file.path.endsWith('.arb'),
|
||||
)) {
|
||||
final messages = _readArb(file);
|
||||
if (!_hasCanonicalFormat(file, messages)) {
|
||||
stderr.writeln(
|
||||
'${file.path}: ARB formatting is not canonical (use two-space JSON indentation and a trailing newline)',
|
||||
);
|
||||
failed = true;
|
||||
}
|
||||
final expectedLocale = file.uri.pathSegments.last
|
||||
.replaceFirst('app_', '')
|
||||
.replaceFirst('.arb', '');
|
||||
if (messages['@@locale'] != expectedLocale) {
|
||||
stderr.writeln('${file.path}: @@locale must be $expectedLocale');
|
||||
failed = true;
|
||||
}
|
||||
final keys = _messageKeys(messages);
|
||||
final empty =
|
||||
keys.where((key) => (messages[key] as String).trim().isEmpty).toList()
|
||||
..sort();
|
||||
if (empty.isNotEmpty) {
|
||||
stderr.writeln('${file.path}: empty messages $empty');
|
||||
failed = true;
|
||||
}
|
||||
if (file.path == templateFile.path) continue;
|
||||
final missing = templateKeys.difference(keys);
|
||||
final extra = keys.difference(templateKeys);
|
||||
if (missing.isNotEmpty || extra.isNotEmpty) {
|
||||
stderr.writeln(
|
||||
'${file.path}: missing ${missing.toList()..sort()}, extra ${extra.toList()..sort()}',
|
||||
);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
for (final key in templateKeys.intersection(keys)) {
|
||||
final expected = _placeholders(template[key] as String);
|
||||
final actual = _placeholders(messages[key] as String);
|
||||
if (!_sameSet(expected, actual)) {
|
||||
stderr.writeln(
|
||||
'${file.path}: $key placeholders $actual do not match $expected',
|
||||
);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) exitCode = 1;
|
||||
}
|
||||
|
||||
/// Reads one ARB file as its top-level JSON object.
|
||||
Map<String, Object?> _readArb(File file) =>
|
||||
jsonDecode(file.readAsStringSync()) as Map<String, Object?>;
|
||||
|
||||
/// Checks the exact JSON layout so whitespace-only formatting drift fails CI.
|
||||
bool _hasCanonicalFormat(File file, Map<String, Object?> arb) {
|
||||
const encoder = JsonEncoder.withIndent(' ');
|
||||
return file.readAsStringSync() == '${encoder.convert(arb)}\n';
|
||||
}
|
||||
|
||||
/// Returns user-facing message keys and excludes ARB metadata keys (`@`/`@@`).
|
||||
Set<String> _messageKeys(Map<String, Object?> arb) =>
|
||||
arb.keys.where((key) => !key.startsWith('@')).toSet();
|
||||
|
||||
/// Extracts both simple placeholders (`{name}`) and ICU selector variables.
|
||||
///
|
||||
/// For example, this returns `count` from
|
||||
/// `{count, plural, =1{1 file} other{{count} files}}`.
|
||||
Set<String> _placeholders(String message) => RegExp(
|
||||
r'\{([A-Za-z_][A-Za-z0-9_]*)(?=\s*(?:\}|,\s*(?:plural|select|selectordinal)\s*,))',
|
||||
).allMatches(message).map((match) => match.group(1)!).toSet();
|
||||
|
||||
/// Compares sets without depending on iteration order.
|
||||
bool _sameSet(Set<String> left, Set<String> right) =>
|
||||
left.length == right.length && left.containsAll(right);
|
||||
@@ -10,7 +10,7 @@
|
||||
// (including the common pattern where Flutter concatenates the base URL with a relative path)
|
||||
// - Download them into: build/web/assets/gstatic/<path>
|
||||
// - Rewrite "https://fonts.gstatic.com/s/" -> "assets/gstatic/" so runtime loads locally
|
||||
// - Only download font subsets needed by supported locales (parsed from message.dart)
|
||||
// - Only download font subsets needed by supported locales (parsed from lib/l10n/*.arb)
|
||||
//
|
||||
// Notes:
|
||||
// - This script DOES NOT call `flutter build web`. It only patches the output.
|
||||
@@ -23,15 +23,18 @@ import 'dart:io';
|
||||
/// Uses language code prefix to automatically detect the script system.
|
||||
/// Returns empty set if base fonts (Latin/Cyrillic/Greek) are sufficient.
|
||||
Set<String> _getFontFamiliesForLocale(String locale) {
|
||||
final lang = locale.toLowerCase().split('_').first;
|
||||
final normalizedLocale = locale.toLowerCase().replaceAll('-', '_');
|
||||
final lang = normalizedLocale.split('_').first;
|
||||
|
||||
// CJK languages - need specific large font files
|
||||
if (lang == 'zh') {
|
||||
// Simplified vs Traditional Chinese
|
||||
if (locale.contains('cn') || locale.contains('sg')) {
|
||||
return {'notosanssc'}; // Simplified Chinese
|
||||
if (normalizedLocale.contains('_tw') ||
|
||||
normalizedLocale.contains('_hk') ||
|
||||
normalizedLocale.contains('_mo')) {
|
||||
return {'notosanstc', 'notosanshk'}; // Traditional Chinese (TW, HK, MO)
|
||||
}
|
||||
return {'notosanstc', 'notosanshk'}; // Traditional Chinese (TW, HK, MO)
|
||||
return {'notosanssc'}; // Simplified Chinese, including generic zh
|
||||
}
|
||||
if (lang == 'ja') return {'notosansjp'}; // Japanese
|
||||
if (lang == 'ko') return {'notosanskr'}; // Korean
|
||||
@@ -95,29 +98,25 @@ const _baseFontFamilies = <String>{
|
||||
// to reduce bundle size. Add them back if emoji support is needed.
|
||||
};
|
||||
|
||||
/// Parse supported locales from message.dart file.
|
||||
/// Reads the import statements and extracts locale codes like 'zh_cn', 'en_us', etc.
|
||||
Set<String> _parseSupportedLocales(File messageFile) {
|
||||
/// Parse supported locales from Flutter gen-l10n ARB filenames.
|
||||
/// Filenames follow app_<locale>.arb, for example app_zh_CN.arb or app_en.arb.
|
||||
Set<String> _parseSupportedLocales(Directory l10nDirectory) {
|
||||
final locales = <String>{};
|
||||
|
||||
if (!messageFile.existsSync()) {
|
||||
_fail('Warning: message.dart not found, only base fonts will be used');
|
||||
if (!l10nDirectory.existsSync()) {
|
||||
_fail('Localization directory not found: ${l10nDirectory.path}');
|
||||
}
|
||||
|
||||
final content = messageFile.readAsStringSync();
|
||||
|
||||
// Match import statements like: import 'langs/zh_cn.dart';
|
||||
final importRegex = RegExp(r"import\s+'langs/(\w+)\.dart'");
|
||||
for (final match in importRegex.allMatches(content)) {
|
||||
final locale = match.group(1);
|
||||
if (locale != null) {
|
||||
locales.add(locale);
|
||||
}
|
||||
final arbName = RegExp(r'^app_(.+)\.arb$');
|
||||
for (final entity in l10nDirectory.listSync()) {
|
||||
if (entity is! File) continue;
|
||||
final filename = entity.uri.pathSegments.last;
|
||||
final locale = arbName.firstMatch(filename)?.group(1);
|
||||
if (locale != null && locale.isNotEmpty) locales.add(locale);
|
||||
}
|
||||
|
||||
if (locales.isEmpty) {
|
||||
_fail(
|
||||
'Warning: No locales found in message.dart, only base fonts will be used');
|
||||
_fail('No app_<locale>.arb files found in ${l10nDirectory.path}');
|
||||
}
|
||||
|
||||
return locales;
|
||||
@@ -139,7 +138,9 @@ Set<String> _getRequiredFontFamilies(Set<String> locales) {
|
||||
/// Font paths have the format: "fontfamily/version/filename.ext"
|
||||
/// Example: "notosanssc/v36/xxx.ttf" -> font family is "notosanssc"
|
||||
bool _fontMatchesRequiredFamilies(
|
||||
String fontPath, Set<String> requiredFamilies) {
|
||||
String fontPath,
|
||||
Set<String> requiredFamilies,
|
||||
) {
|
||||
// Extract font family from path (first segment before '/')
|
||||
final slashIndex = fontPath.indexOf('/');
|
||||
if (slashIndex == -1) {
|
||||
@@ -158,10 +159,9 @@ Future<void> main(List<String> args) async {
|
||||
// but will also try to locate pubspec.yaml by walking up.
|
||||
final flutterDir = _findFlutterProjectRoot(Directory.current);
|
||||
|
||||
// Parse supported locales from message.dart
|
||||
final messageFile =
|
||||
File(_join(flutterDir.path, 'lib', 'i18n', 'message.dart'));
|
||||
final supportedLocales = _parseSupportedLocales(messageFile);
|
||||
// Parse supported locales from Flutter gen-l10n ARB files.
|
||||
final l10nDirectory = Directory(_join(flutterDir.path, 'lib', 'l10n'));
|
||||
final supportedLocales = _parseSupportedLocales(l10nDirectory);
|
||||
stdout.writeln('Supported locales: ${supportedLocales.join(', ')}');
|
||||
|
||||
// Get required font families based on supported locales
|
||||
@@ -203,7 +203,8 @@ Future<void> main(List<String> args) async {
|
||||
if (p.contains('://') || p.startsWith('data:')) continue;
|
||||
if (p.startsWith('/') ||
|
||||
p.startsWith('assets/') ||
|
||||
p.startsWith('packages/')) continue;
|
||||
p.startsWith('packages/'))
|
||||
continue;
|
||||
if (p.contains('..')) continue;
|
||||
relAssetsUnderS.add(p);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25"
|
||||
GO_VERSION: "1.25.4"
|
||||
FLUTTER_VERSION: "3.41.6"
|
||||
|
||||
jobs:
|
||||
|
||||
Reference in new issue
Block a user