l10n: typography fixes for arabic/persian

This commit is contained in:
Thibault Deckers 2024-05-19 01:28:26 +02:00
parent ea53420c17
commit dd6258d8ac
18 changed files with 108 additions and 53 deletions

3
devtools_options.yaml Normal file
View file

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View file

@ -11,6 +11,7 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/theme/text.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:intl/intl.dart';
extension ExtraAvesEntryProps on AvesEntry {
bool get isValid => !isMissingAtPath && sizeBytes != 0 && width > 0 && height > 0;
@ -51,9 +52,10 @@ extension ExtraAvesEntryProps on AvesEntry {
// text
String get resolutionText {
final ws = width;
final hs = height;
String getResolutionText(String locale) {
final numberFormat = NumberFormat('0', locale);
final ws = numberFormat.format(width);
final hs = numberFormat.format(height);
return isRotated ? '$hs${AText.resolutionSeparator}$ws' : '$ws${AText.resolutionSeparator}$hs';
}

View file

@ -47,10 +47,11 @@ extension ExtraCoordinateFormat on CoordinateFormat {
final min = minDecimal.toInt();
final sec = (minDecimal - min) * 60;
var minText = NumberFormat('0' * (minuteSecondPadding ? 2 : 1), locale).format(min);
var secText = NumberFormat('${'0' * (minuteSecondPadding ? 2 : 1)}${secondDecimals > 0 ? '.${'0' * secondDecimals}' : ''}', locale).format(sec);
final degText = NumberFormat('0', locale).format(deg);
final minText = NumberFormat('0' * (minuteSecondPadding ? 2 : 1), locale).format(min);
final secText = NumberFormat('${'0' * (minuteSecondPadding ? 2 : 1)}${secondDecimals > 0 ? '.${'0' * secondDecimals}' : ''}', locale).format(sec);
return '$deg° $minText $secText';
return '$degText° $minText $secText';
}
static List<String> _toDecimal(AppLocalizations l10n, LatLng latLng) {

View file

@ -45,3 +45,13 @@ bool shouldUseNativeDigits(Locale? countrifiedLocale) {
return true;
}
}
bool canHaveLetterSpacing(String locale) {
switch (locale) {
case 'ar':
case 'fa':
return false;
default:
return true;
}
}

View file

@ -1,5 +1,5 @@
import 'package:aves/model/device.dart';
import 'package:aves/ref/locales.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/about/policy_page.dart';
import 'package:aves/widgets/common/basic/link_chip.dart';
@ -10,13 +10,6 @@ import 'package:flutter/material.dart';
class AppReference extends StatelessWidget {
static const avesGithub = 'https://github.com/deckerst/aves';
static const _appTitleStyle = TextStyle(
fontSize: 20,
fontWeight: FontWeight.normal,
letterSpacing: 1.0,
fontFeatures: [FontFeature.enable('smcp')],
);
const AppReference({super.key});
@override
@ -38,27 +31,35 @@ class AppReference extends StatelessWidget {
}
Widget _buildAvesLine(BuildContext context) {
final locale = context.l10n.localeName;
final textScaler = MediaQuery.textScalerOf(context);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
AvesLogo(
size: textScaler.scale(_appTitleStyle.fontSize!) * 1.3,
size: textScaler.scale(_getAppTitleStyle(locale).fontSize!) * 1.3,
),
const SizedBox(width: 8),
Text(
context.l10n.appName,
style: _appTitleStyle,
style: _getAppTitleStyle(locale),
),
const SizedBox(width: 8),
Text(
device.packageVersion,
style: _appTitleStyle,
style: _getAppTitleStyle(locale),
),
],
);
}
TextStyle _getAppTitleStyle(String locale) => TextStyle(
fontSize: 20,
fontWeight: FontWeight.normal,
letterSpacing: canHaveLetterSpacing(locale) ? 1 : 0,
fontFeatures: const [FontFeature.enable('smcp')],
);
static List<Widget> buildLinks(BuildContext context) {
final l10n = context.l10n;
return [

View file

@ -122,6 +122,9 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
}
Widget _buildStep(int step, String text, String buttonText, VoidCallback onPressed) {
final locale = context.l10n.localeName;
final numberFormat = NumberFormat.decimalPattern(locale);
final isMonochrome = settings.themeColorMode == AvesThemeColorMode.monochrome;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
@ -136,7 +139,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
)),
shape: BoxShape.circle,
),
child: Text('$step'),
child: Text(numberFormat.format(step)),
),
const SizedBox(width: 8),
Expanded(child: Text(text)),

View file

@ -218,6 +218,9 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
@override
Widget build(BuildContext context) {
final locale = context.l10n.localeName;
final percentFormat = NumberFormat.percentPattern(locale);
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final progressColor = colorScheme.primary;
@ -230,7 +233,6 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
final processedCount = processed.length.toDouble();
final total = widget.itemCount;
final percent = total == null || total == 0 ? 0.0 : min(1.0, processedCount / total);
final percentFormat = NumberFormat.percentPattern();
return FadeTransition(
opacity: _animation,
child: Stack(
@ -351,6 +353,9 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
@override
Widget build(BuildContext context) {
final locale = context.l10n.localeName;
final numberFormat = NumberFormat('0', locale);
final textScaler = MediaQuery.textScalerOf(context);
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
@ -388,7 +393,7 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
// because we cannot use the app context theme here
foreground: widget.progressColor,
center: ChangeHighlightText(
'${(remainingDurationMillis / 1000).ceil()}',
numberFormat.format((remainingDurationMillis / 1000).ceil()),
style: contentTextStyle.copyWith(
shadows: [
Shadow(

View file

@ -1,8 +1,9 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/ref/locales.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/basic/text/outlined.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/common/fx/highlight_decoration.dart';
import 'package:aves_model/aves_model.dart';
@ -40,7 +41,7 @@ class HighlightTitle extends StatelessWidget {
final style = TextStyle(
shadows: shadows(context),
fontSize: fontSize,
letterSpacing: 1.0,
letterSpacing: canHaveLetterSpacing(context.l10n.localeName) ? 1 : 0,
fontFeatures: const [FontFeature.enable('smcp')],
);

View file

@ -146,9 +146,11 @@ class _GeoMapState extends State<GeoMap> {
selector: (context, s) => s.mapStyle,
builder: (context, mapStyle, child) {
final isHeavy = ExtraEntryMapStyle.isHeavy(mapStyle);
final locale = context.l10n.localeName;
Widget _buildMarkerWidget(MarkerKey<AvesEntry> key) => ImageMarker(
key: key,
count: key.count,
locale: locale,
buildThumbnailImage: (extent) => ThumbnailImage(
entry: key.entry,
extent: extent,
@ -482,9 +484,11 @@ class _GeoMapState extends State<GeoMap> {
} else {
markerEntry = geoEntry.entry!;
}
final locale = context.l10n.localeName;
final markerLocation = LatLng(geoEntry.latitude!, geoEntry.longitude!);
Widget markerBuilder(BuildContext context) => ImageMarker(
count: geoEntry.pointsSize,
locale: locale,
drawArrow: false,
buildThumbnailImage: (extent) => ThumbnailImage(
entry: markerEntry,

View file

@ -1,4 +1,3 @@
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/trash.dart';
import 'package:aves/model/settings/settings.dart';
@ -8,6 +7,7 @@ import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/location/country.dart';
import 'package:aves/model/source/location/place.dart';
import 'package:aves/model/source/tag.dart';
import 'package:aves/ref/locales.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/android_file_utils.dart';
@ -111,6 +111,9 @@ class _AppDrawerState extends State<AppDrawer> {
}
Widget _buildHeader(BuildContext context) {
final l10n = context.l10n;
final locale = l10n.localeName;
Future<void> goTo(String routeName, WidgetBuilder pageBuilder) async {
Navigator.maybeOf(context)?.pop();
await Future.delayed(ADurations.drawerTransitionAnimation);
@ -145,13 +148,13 @@ class _AppDrawerState extends State<AppDrawer> {
OutlinedText(
textSpans: [
TextSpan(
text: context.l10n.appName,
style: const TextStyle(
text: l10n.appName,
style: TextStyle(
color: Colors.white,
fontSize: 38,
fontWeight: FontWeight.w300,
letterSpacing: 1.0,
fontFeatures: [FontFeature.enable('smcp')],
letterSpacing: canHaveLetterSpacing(locale) ? 1 : 0,
fontFeatures: const [FontFeature.enable('smcp')],
),
),
],
@ -177,7 +180,7 @@ class _AppDrawerState extends State<AppDrawer> {
onPressed: () => goTo(AboutPage.routeName, (_) => const AboutPage()),
style: drawerButtonStyle,
icon: const Icon(AIcons.info),
label: Text(context.l10n.drawerAboutButton),
label: Text(l10n.drawerAboutButton),
),
OutlinedButton.icon(
// key is expected by test driver
@ -185,7 +188,7 @@ class _AppDrawerState extends State<AppDrawer> {
onPressed: () => goTo(SettingsPage.routeName, (_) => const SettingsPage()),
style: drawerButtonStyle,
icon: const Icon(AIcons.settings),
label: Text(context.l10n.drawerSettingsButton),
label: Text(l10n.drawerSettingsButton),
),
],
),

View file

@ -6,6 +6,7 @@ import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/ref/locales.dart';
import 'package:aves/widgets/about/about_page.dart';
import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/basic/insets.dart';
@ -90,6 +91,9 @@ class _TvRailState extends State<TvRail> {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final locale = l10n.localeName;
final navEntries = _getNavEntries(context);
return DirectionalSafeArea(
end: false,
@ -103,13 +107,13 @@ class _TvRailState extends State<TvRail> {
logo,
const SizedBox(width: 16),
Text(
context.l10n.appName,
style: const TextStyle(
l10n.appName,
style: TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.w300,
letterSpacing: 1.0,
fontFeatures: [FontFeature.enable('smcp')],
letterSpacing: canHaveLetterSpacing(locale) ? 1 : 0,
fontFeatures: const [FontFeature.enable('smcp')],
),
),
],

View file

@ -1,21 +1,24 @@
import 'package:aves/theme/styles.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/basic/text/outlined.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class LinearPercentIndicatorText extends StatelessWidget {
final double percent;
final percentFormat = NumberFormat.percentPattern();
LinearPercentIndicatorText({
const LinearPercentIndicatorText({
super.key,
required this.percent,
});
@override
Widget build(BuildContext context) {
final locale = context.l10n.localeName;
final percentFormat = NumberFormat.percentPattern(locale);
return OutlinedText(
textSpans: [
TextSpan(

View file

@ -32,6 +32,7 @@ import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class BasicSection extends StatefulWidget {
@ -277,15 +278,6 @@ class _BasicInfoState extends State<_BasicInfo> {
AvesEntry get entry => widget.entry;
int get megaPixels => (entry.width * entry.height / 1000000).round();
// guess whether this is a photo, according to file type
bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(entry.mimeType) || entry.isRaw;
bool get showMegaPixels => isPhoto && megaPixels > 0;
String get rasterResolutionText => '${entry.resolutionText}${showMegaPixels ? '$megaPixels MP' : ''}';
static const ownerPackageNamePropKey = 'owner_package_name';
static const iconSize = 20.0;
@ -331,7 +323,7 @@ class _BasicInfoState extends State<_BasicInfo> {
l10n.viewerInfoLabelTitle: title,
l10n.viewerInfoLabelDate: dateText,
if (entry.isVideo) ..._buildVideoRows(context),
if (showResolution) l10n.viewerInfoLabelResolution: context.applyDirectionality(rasterResolutionText),
if (showResolution) l10n.viewerInfoLabelResolution: context.applyDirectionality(getRasterResolutionText(locale)),
l10n.viewerInfoLabelSize: context.applyDirectionality(sizeText),
if (!entry.trashed) l10n.viewerInfoLabelUri: entry.uri,
if (path != null) l10n.viewerInfoLabelPath: path,
@ -384,4 +376,20 @@ class _BasicInfoState extends State<_BasicInfo> {
),
];
}
String getRasterResolutionText(String locale) {
var s = entry.getResolutionText(locale);
// guess whether this is a photo, according to file type
final isPhoto = [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(entry.mimeType) || entry.isRaw;
if (isPhoto) {
final numberFormat = NumberFormat('0', locale);
final megaPixels = (entry.width * entry.height / 1000000).round();
if (megaPixels > 0) {
s += '${numberFormat.format(megaPixels)} MP';
}
}
return s;
}
}

View file

@ -69,9 +69,12 @@ class _ColorSectionSliverState extends State<ColorSectionSliver> {
children: [
ColorIndicator(value: v.color),
const SizedBox(width: 8),
SelectableText(
'#${v.color.hex}',
style: const TextStyle(fontFamily: 'monospace'),
Directionality(
textDirection: TextDirection.ltr,
child: SelectableText(
'#${v.color.hex}',
style: const TextStyle(fontFamily: 'monospace'),
),
),
],
),

View file

@ -29,7 +29,7 @@ class OverlayDateRow extends StatelessWidget {
final resolutionText = entry.isSvg
? entry.aspectRatioText
: entry.isSized
? entry.resolutionText
? entry.getResolutionText(locale)
: '';
return Row(

View file

@ -4,10 +4,12 @@ import 'package:collection/collection.dart';
import 'package:custom_rounded_rectangle_border/custom_rounded_rectangle_border.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:intl/intl.dart' as intl;
import 'package:latlong2/latlong.dart';
class ImageMarker extends StatelessWidget {
final int? count;
final intl.NumberFormat numberFormat;
final bool drawArrow;
final Widget Function(double extent) buildThumbnailImage;
@ -20,12 +22,13 @@ class ImageMarker extends StatelessWidget {
static const innerRadius = Radius.circular(outerBorderRadiusDim - outerBorderWidth);
static const innerBorderRadius = BorderRadius.all(innerRadius);
const ImageMarker({
ImageMarker({
super.key,
required this.count,
required String locale,
this.drawArrow = true,
required this.buildThumbnailImage,
});
}) : numberFormat = intl.NumberFormat.decimalPattern(locale);
@override
Widget build(BuildContext context) {
@ -107,7 +110,7 @@ class ImageMarker extends StatelessWidget {
),
),
child: Text(
'$count',
numberFormat.format(count),
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.onPrimary,

View file

@ -102,7 +102,7 @@ packages:
source: hosted
version: "4.0.2"
intl:
dependency: transitive
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf

View file

@ -15,6 +15,7 @@ dependencies:
equatable:
fluster:
flutter_map:
intl:
latlong2:
provider: