Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ use those once changed in the game.
**Testing, feedback and translations**

- **[Slick Daddy](https://github.com/slick-daddy)** tested the launchers and the website in depth and sent the detailed feedback behind many fixes: relink errors and full screen on Windows, the game's button that follows its state, the interface polish, and the website's settings, rate limits and pages.
- **VK** translated the Windows/Linux launcher into German, Spanish, French, Portuguese, Indonesian, Russian, Japanese, Hindi, Bengali, Bhojpuri, Arabic and Urdu, and tested it: default display settings, the stick sensitivity on Home, language switching and the nightly builds request came from VK's reports.
- **VK** translated the Windows/Linux launcher into German, Spanish, French, Portuguese, Indonesian, Russian, Japanese, Hindi, Bengali, Bhojpuri, Arabic, Urdu, Czech, Danish, Hungarian, Norwegian, Romanian, Swedish, Filipino, Malay, Simplified and Traditional Chinese, Korean, Khmer, Lao and Amharic, and tested it: default display settings, the stick sensitivity on Home, language switching and the nightly builds request came from VK's reports.

Our code is written independently; where we followed someone's design or idea, it is named here.

Expand Down
5 changes: 5 additions & 0 deletions TRANSLATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ The Windows/Linux launcher has these languages so far:
| Turkish | `tr.json` | the AnyPS5 team |
| German, Spanish, French, Portuguese, Indonesian, Russian, Japanese | `de`, `es`, `fr`, `pt`, `id`, `ru`, `ja` | VK |
| Hindi, Bengali, Bhojpuri, Arabic, Urdu | `hi`, `bn`, `bho`, `ar`, `ur` | VK |
| Czech, Danish, Hungarian, Norwegian, Romanian, Swedish, Filipino, Malay | `cs`, `da`, `hu`, `no`, `ro`, `sv`, `fil`, `ms` | VK |
| Simplified Chinese, Traditional Chinese, Korean, Khmer, Lao, Amharic | `zh-Hans`, `zh-Hant`, `ko`, `km`, `lo`, `am` | VK |

Arabic and Urdu read right to left, but the launcher's screens are not mirrored for them yet.
The launcher starts in the computer's language the first time when it has a file for it. Chinese
follows the script or region (zh-Hant for Taiwan, Hong Kong and Macau). Norwegian Bokmål and
Nynorsk read `no.json`, and Tagalog reads `fil.json`.
Improvements to any of these files are welcome, the same way as a new language. A hosted Weblate project is coming, where several people can translate one language
at once in the browser; until then, translate the files as below. The files stay the same
either way, so nothing you do now is lost.
Expand Down
1,399 changes: 1,399 additions & 0 deletions assets/l10n/am.json

Large diffs are not rendered by default.

1,399 changes: 1,399 additions & 0 deletions assets/l10n/cs.json

Large diffs are not rendered by default.

1,397 changes: 1,397 additions & 0 deletions assets/l10n/da.json

Large diffs are not rendered by default.

1,399 changes: 1,399 additions & 0 deletions assets/l10n/fil.json

Large diffs are not rendered by default.

1,377 changes: 1,377 additions & 0 deletions assets/l10n/hu.json

Large diffs are not rendered by default.

1,394 changes: 1,394 additions & 0 deletions assets/l10n/km.json

Large diffs are not rendered by default.

1,399 changes: 1,399 additions & 0 deletions assets/l10n/ko.json

Large diffs are not rendered by default.

1,398 changes: 1,398 additions & 0 deletions assets/l10n/lo.json

Large diffs are not rendered by default.

1,399 changes: 1,399 additions & 0 deletions assets/l10n/ms.json

Large diffs are not rendered by default.

1,398 changes: 1,398 additions & 0 deletions assets/l10n/no.json

Large diffs are not rendered by default.

1,398 changes: 1,398 additions & 0 deletions assets/l10n/ro.json

Large diffs are not rendered by default.

1,397 changes: 1,397 additions & 0 deletions assets/l10n/sv.json

Large diffs are not rendered by default.

1,399 changes: 1,399 additions & 0 deletions assets/l10n/zh-Hans.json

Large diffs are not rendered by default.

1,399 changes: 1,399 additions & 0 deletions assets/l10n/zh-Hant.json

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions lib/ui/app_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,13 @@ class Preferences {
return folders;
}

static Preferences load(String file) {
/// The launcher's choices in [file]. Without the file (the first start) the launcher's language
/// is the first of the computer's languages it has a file for: [systemLocales], else the ones
/// the system gives, as Settings lists them.
static Preferences load(String file, {List<String>? systemLocales}) {
if (!File(file).existsSync()) {
return Preferences()..language = Language.forSystem(systemLocales ?? Preferences.systemLocales()).code;
}
try {
final json = jsonDecode(File(file).readAsStringSync());
return json is Map<String, dynamic> ? Preferences.fromJson(json) : Preferences();
Expand All @@ -455,6 +461,13 @@ class Preferences {
}
}

/// The computer's languages, the one it is set to first: Windows' list of display languages
/// and Linux's LANGUAGE and LANG as Flutter reads them, then Dart's locale name.
static List<String> systemLocales() => [
for (final locale in PlatformDispatcher.instance.locales) locale.toLanguageTag(),
Platform.localeName,
];

void save(String file) {
try {
File(file).parent.createSync(recursive: true);
Expand Down Expand Up @@ -5918,7 +5931,7 @@ class AppModel extends ChangeNotifier {
}

/// How many language cards the first step has in a row.
static const languageColumns = 6;
static const languageColumns = 7;

/// The language and theme steps show their choice at once.
void _previewChoice() {
Expand Down
105 changes: 66 additions & 39 deletions lib/ui/onboarding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -194,45 +194,72 @@ class _LanguageStep extends StatelessWidget {
).copyWith(letterSpacing: 0.8),
),
SizedBox(height: 16 * scale),
// Rows of cards, AppModel.languageColumns to a row: the step's arrows move along them.
Wrap(
spacing: 20 * scale,
runSpacing: 18 * scale,
children: [
for (final (index, language) in T.languages.indexed)
_ChoiceCard(
focused: model.onboardingChoice == index,
width: 200 * scale,
height: 96 * scale,
padding: 18,
onTap: () {
model.onboardingChoice = index;
model.onboardingConfirm();
},
child: Builder(
builder: (context) {
final color = model.onboardingChoice == index ? Colors.black : Colors.white;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
language.code.toUpperCase(),
style: AppText.display(13 * scale, weight: FontWeight.w700, color: color.withValues(alpha: 0.6)),
),
SizedBox(height: 6 * scale),
Text(
language.displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: AppText.display(22 * scale, weight: FontWeight.w500, color: color),
),
],
);
},
),
),
],
// Rows of cards, AppModel.languageColumns to a row: the step's arrows move along them. The
// cards share the space the step has: as wide and tall as they fit, up to 200 by 96.
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
const columns = AppModel.languageColumns;
final rows = (T.languages.length / columns).ceil();
final spacing = 20 * scale;
final runSpacing = 18 * scale;
final width = math.min(200 * scale, (constraints.maxWidth - spacing * (columns - 1)) / columns);
// Room under the last row, so it does not run into the hint bar.
final room = constraints.maxHeight - 28 * scale;
final height = math.min(96 * scale, (room - runSpacing * (rows - 1)) / rows);
// A short card leaves the code out: the name is what a person looks for.
final withCode = height >= 80 * scale;
return Wrap(
spacing: spacing,
runSpacing: runSpacing,
children: [
for (final (index, language) in T.languages.indexed)
_ChoiceCard(
focused: model.onboardingChoice == index,
width: width,
height: height,
padding: height >= 96 * scale ? 18 : 12,
onTap: () {
model.onboardingChoice = index;
model.onboardingConfirm();
},
child: Builder(
builder: (context) {
final color = model.onboardingChoice == index ? Colors.black : Colors.white;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (withCode) ...[
Text(
language.code.toUpperCase(),
style: AppText.display(
13 * scale,
weight: FontWeight.w700,
color: color.withValues(alpha: 0.6),
),
),
SizedBox(height: 6 * scale),
],
// A long name (Bahasa Indonesia) is drawn smaller, never cut off.
FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: Text(
language.displayName,
maxLines: 1,
style: AppText.display(22 * scale, weight: FontWeight.w500, color: color),
),
),
],
);
},
),
),
],
);
},
),
),
],
);
Expand Down
49 changes: 49 additions & 0 deletions lib/ui/strings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,55 @@ class Language {
static Language byCode(String? code) =>
T.languages.firstWhere((language) => language.code == code, orElse: () => english);

/// The language the launcher starts in the first time: the first of the computer's
/// [preferred] languages it has a file for, English when it has none of them.
static Language forSystem(Iterable<String> preferred) =>
byCode(matchLocale(preferred, [for (final language in T.languages) language.code]));

/// Codes a system may give for a language whose file has another one: Norwegian Bokmål and
/// Nynorsk read the Norwegian file, Tagalog the Filipino one, and Java's old code for
/// Indonesian the Indonesian one.
static const _sameLanguage = {'nb': 'no', 'nn': 'no', 'tl': 'fil', 'in': 'id'};

/// The regions that write Chinese in traditional characters when the system gives no script.
static const _traditional = {'TW', 'HK', 'MO'};

/// The first of [preferred] (locale names as systems give them: zh-Hant-TW, pt_BR.UTF-8, nb-NO)
/// one of [codes] (the files' codes: zh-Hans, pt-BR, no) is for, or null. A locale takes the
/// file of its language and script or region (pt-BR), else its language's (pt). Chinese takes
/// zh-Hant for Taiwan, Hong Kong and Macau or the Hant script, and zh-Hans otherwise. The same
/// rules as the Mac launcher's LanguageFiles.match.
static String? matchLocale(Iterable<String> preferred, Iterable<String> codes) {
final byKey = {for (final code in codes) code.replaceAll('_', '-').toLowerCase(): code};
for (final name in preferred) {
// en_US.UTF-8 and de_DE@euro are Linux's: the part before the dot or @ names the locale.
final parts = name.split(RegExp('[.@]')).first.split(RegExp('[-_]')).where((part) => part.isNotEmpty).toList();
if (parts.isEmpty) continue;
final language = _sameLanguage[parts.first.toLowerCase()] ?? parts.first.toLowerCase();
String? script;
String? region;
for (final part in parts.skip(1)) {
if (part.length == 4 && script == null) {
script = part[0].toUpperCase() + part.substring(1).toLowerCase();
} else if ((part.length == 2 || part.length == 3) && region == null) {
region = part.toUpperCase();
}
}
if (language == 'zh') {
script ??= _traditional.contains(region) ? 'Hant' : 'Hans';
}
for (final candidate in [
if (script != null && region != null) '$language-$script-$region',
if (script != null) '$language-$script',
if (region != null) '$language-$region',
language,
]) {
if (byKey[candidate.toLowerCase()] case final code?) return code;
}
}
return null;
}

@override
bool operator ==(Object other) => other is Language && other.code == code;

Expand Down
7 changes: 6 additions & 1 deletion packaging/linux/build-deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ depends="libc6 (>= $glibc)"
for package in $packages; do depends="$depends, $package"; done
depends="$depends, xdg-utils"

# The launcher's own font has Latin letters only. Flutter draws every other script with a
# system font that has it (fontconfig), so the Noto fonts are recommended: Khmer, Lao, Ethiopic,
# Devanagari, Bengali and Arabic in fonts-noto-core, Chinese, Japanese and Korean in fonts-noto-cjk.
recommends="libsecret-tools, libsdl2-2.0-0, fonts-noto-core | fonts-noto, fonts-noto-cjk"

size=$(du -sk "$PKG" | cut -f1)
cat > "$PKG/DEBIAN/control" <<CONTROL
Package: anyps5-desktop
Expand All @@ -45,7 +50,7 @@ Architecture: amd64
Maintainer: Utku Halis <personal@utkuhalis.com.tr>
Installed-Size: $size
Depends: $depends
Recommends: libsecret-tools, libsdl2-2.0-0
Recommends: $recommends
Homepage: https://github.com/AnyTogether/AnyPS5-Desktop
Description: PS5-style launcher for AnyPS5
Finds PS5 game dumps in a games folder, unlocks them with the AnyPS5 toolkit
Expand Down
26 changes: 26 additions & 0 deletions test/tool/check_translations_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
Expand Down Expand Up @@ -118,6 +119,31 @@ void main() {
}).problems,
isEmpty,
);
// Khmer ends a sentence with its khan, Amharic with ።, Lao with a full stop, and Chinese may
// put the placeholder in full-width brackets.
final scripts = {
'km': 'បង្វិលឧបករណ៍បញ្ជា។ (%@)',
'am': 'መቆጣጠሪያውን ያዙሩ። (%@)',
'lo': 'ໝຸນຕົວຄວບຄຸມ. (%@)',
'zh-Hans': '请重新安装 AnyPS5 Desktop。(%@)',
};
for (final MapEntry(key: code, value: text) in scripts.entries) {
expect(
checkLanguage(code, jsonEncode({'languageName': code, 'sdlMissingDetail': text}), {
...english,
'sdlMissingDetail': 'Reinstall AnyPS5 Desktop. (%@)',
}).problems,
isEmpty,
reason: code,
);
}
expect(
checkLanguage('km', jsonEncode({'languageName': 'ខ្មែរ', 'gyroDetail': 'បង្វិលឧបករណ៍បញ្ជា'}), {
...english,
'gyroDetail': 'Turn the controller.',
}).problems.single,
contains('gyroDetail'),
);
});

test('the code may not pick a text by the language, or hold a Turkish one', () {
Expand Down
Binary file modified test/ui/goldens/onboarding_language.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_am.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_cs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_da.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_fil.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_hu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_km.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_ko.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_lo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_ms.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_no.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_ro.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_sv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_zh-Hans.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/ui/goldens/update_notice_zh-Hant.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading