From 601467745166fb05a2fac088d450ef67d9289525 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 29 Dec 2022 12:28:14 +0100 Subject: [PATCH] #448 #449 show description on viewer overlay; editing description clears related exif fields --- CHANGELOG.md | 8 +++ .../channel/calls/MetadataFetchHandler.kt | 20 +++++-- .../aves/metadata/metadataextractor/Helper.kt | 9 +++- lib/l10n/app_en.arb | 1 + lib/model/entry_metadata_edition.dart | 5 +- lib/model/metadata/fields.dart | 4 ++ lib/model/settings/defaults.dart | 1 + lib/model/settings/settings.dart | 6 +++ lib/widgets/settings/viewer/overlay.dart | 12 +++++ .../viewer/overlay/details/description.dart | 25 +++++++++ .../viewer/overlay/details/details.dart | 54 +++++++++++++------ .../viewer/overlay/details/shooting.dart | 5 +- test_driver/driver_screenshots.dart | 1 + untranslated.json | 54 +++++++++++++++++++ 14 files changed, 182 insertions(+), 23 deletions(-) create mode 100644 lib/widgets/viewer/overlay/details/description.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 91bdb7498..245dfd2bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Viewer: optionally show description on overlay + +### Changed + +- editing description writes XMP `dc:description`, and clears Exif `ImageDescription` / `UserComment` + ## [v1.7.8] - 2022-12-20 ### Added diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt index c23c7d85a..619e090a5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt @@ -615,7 +615,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { if (!metadataMap.containsKey(KEY_XMP_TITLE) || !metadataMap.containsKey(KEY_XMP_SUBJECTS)) { for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) { if (!metadataMap.containsKey(KEY_XMP_TITLE)) { - dir.getSafeString(IptcDirectory.TAG_OBJECT_NAME) { metadataMap[KEY_XMP_TITLE] = it } + dir.getSafeString(IptcDirectory.TAG_OBJECT_NAME, acceptBlank = false) { metadataMap[KEY_XMP_TITLE] = it } } if (!metadataMap.containsKey(KEY_XMP_SUBJECTS)) { dir.keywords?.let { metadataMap[KEY_XMP_SUBJECTS] = it.joinToString(XMP_SUBJECTS_SEPARATOR) } @@ -1151,6 +1151,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { // return description from these fields (by precedence): // - XMP / dc:description // - IPTC / caption-abstract + // - Exif / UserComment // - Exif / ImageDescription private fun getDescription(call: MethodCall, result: MethodChannel.Result) { val mimeType = call.argument("mimeType") @@ -1171,7 +1172,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { val xmpMeta = dir.xmpMeta try { if (xmpMeta.doesPropExist(XMP.DC_DESCRIPTION_PROP_NAME)) { - xmpMeta.getSafeLocalizedText(XMP.DC_DESCRIPTION_PROP_NAME) { description = it } + xmpMeta.getSafeLocalizedText(XMP.DC_DESCRIPTION_PROP_NAME, acceptBlank = false) { description = it } } } catch (e: XMPException) { Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e) @@ -1179,12 +1180,23 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { } if (description == null) { for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) { - dir.getSafeString(IptcDirectory.TAG_CAPTION) { description = it } + dir.getSafeString(IptcDirectory.TAG_CAPTION, acceptBlank = false) { description = it } + } + } + if (description == null) { + for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) { + // user comment field specifies encoding, unlike other string fields + if (dir.containsTag(ExifSubIFDDirectory.TAG_USER_COMMENT)) { + val string = dir.getDescription(ExifSubIFDDirectory.TAG_USER_COMMENT) + if (string.isNotBlank()) { + description = string + } + } } } if (description == null) { for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) { - dir.getSafeString(ExifIFD0Directory.TAG_IMAGE_DESCRIPTION) { description = it } + dir.getSafeString(ExifIFD0Directory.TAG_IMAGE_DESCRIPTION, acceptBlank = false) { description = it } } } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt index 8f6672ce9..ed247f4cf 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt @@ -117,8 +117,13 @@ object Helper { // extensions - fun Directory.getSafeString(tag: Int, save: (value: String) -> Unit) { - if (this.containsTag(tag)) save(this.getString(tag)) + fun Directory.getSafeString(tag: Int, acceptBlank: Boolean = true, save: (value: String) -> Unit) { + if (this.containsTag(tag)) { + val string = this.getString(tag) + if (acceptBlank || string.isNotBlank()) { + save(string) + } + } } fun Directory.getSafeBoolean(tag: Int, save: (value: Boolean) -> Unit) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8f1c08b35..6c9d93587 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -731,6 +731,7 @@ "settingsViewerShowInformationSubtitle": "Show title, date, location, etc.", "settingsViewerShowRatingTags": "Show rating & tags", "settingsViewerShowShootingDetails": "Show shooting details", + "settingsViewerShowDescription": "Show description", "settingsViewerShowOverlayThumbnails": "Show thumbnails", "settingsViewerEnableOverlayBlurEffect": "Blur effect", diff --git a/lib/model/entry_metadata_edition.dart b/lib/model/entry_metadata_edition.dart index 21ba5d001..7aa91fae6 100644 --- a/lib/model/entry_metadata_edition.dart +++ b/lib/model/entry_metadata_edition.dart @@ -235,7 +235,10 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry { final description = fields[DescriptionField.description]; if (canEditExif && editDescription) { - metadata[MetadataType.exif] = {MetadataField.exifImageDescription.toPlatform!: description}; + metadata[MetadataType.exif] = { + MetadataField.exifImageDescription.toPlatform!: null, + MetadataField.exifUserComment.toPlatform!: null, + }; } if (canEditIptc) { diff --git a/lib/model/metadata/fields.dart b/lib/model/metadata/fields.dart index 552909243..17a344aa6 100644 --- a/lib/model/metadata/fields.dart +++ b/lib/model/metadata/fields.dart @@ -37,6 +37,7 @@ enum MetadataField { exifGpsTrackRef, exifGpsVersionId, exifImageDescription, + exifUserComment, mp4GpsCoordinates, mp4RotationDegrees, mp4Xmp, @@ -119,6 +120,7 @@ extension ExtraMetadataField on MetadataField { case MetadataField.exifGpsTrackRef: case MetadataField.exifGpsVersionId: case MetadataField.exifImageDescription: + case MetadataField.exifUserComment: return MetadataType.exif; case MetadataField.mp4GpsCoordinates: case MetadataField.mp4RotationDegrees: @@ -220,6 +222,8 @@ extension ExtraMetadataField on MetadataField { return 'GPSVersionID'; case MetadataField.exifImageDescription: return 'ImageDescription'; + case MetadataField.exifUserComment: + return 'UserComment'; default: return null; } diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index db0c59c8d..9317cd1f0 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -79,6 +79,7 @@ class SettingsDefaults { static const showOverlayOnOpening = true; static const showOverlayMinimap = false; static const showOverlayInfo = true; + static const showOverlayDescription = false; static const showOverlayRatingTags = false; static const showOverlayShootingDetails = false; static const showOverlayThumbnailPreview = false; diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index aa747b127..124408268 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -115,6 +115,7 @@ class Settings extends ChangeNotifier { static const showOverlayOnOpeningKey = 'show_overlay_on_opening'; static const showOverlayMinimapKey = 'show_overlay_minimap'; static const showOverlayInfoKey = 'show_overlay_info'; + static const showOverlayDescriptionKey = 'show_overlay_description'; static const showOverlayRatingTagsKey = 'show_overlay_rating_tags'; static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details'; static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview'; @@ -563,6 +564,10 @@ class Settings extends ChangeNotifier { set showOverlayInfo(bool newValue) => setAndNotify(showOverlayInfoKey, newValue); + bool get showOverlayDescription => getBool(showOverlayDescriptionKey) ?? SettingsDefaults.showOverlayDescription; + + set showOverlayDescription(bool newValue) => setAndNotify(showOverlayDescriptionKey, newValue); + bool get showOverlayRatingTags => getBool(showOverlayRatingTagsKey) ?? SettingsDefaults.showOverlayRatingTags; set showOverlayRatingTags(bool newValue) => setAndNotify(showOverlayRatingTagsKey, newValue); @@ -1002,6 +1007,7 @@ class Settings extends ChangeNotifier { case showOverlayOnOpeningKey: case showOverlayMinimapKey: case showOverlayInfoKey: + case showOverlayDescriptionKey: case showOverlayRatingTagsKey: case showOverlayShootingDetailsKey: case showOverlayThumbnailPreviewKey: diff --git a/lib/widgets/settings/viewer/overlay.dart b/lib/widgets/settings/viewer/overlay.dart index dab35532b..cf76ca7f1 100644 --- a/lib/widgets/settings/viewer/overlay.dart +++ b/lib/widgets/settings/viewer/overlay.dart @@ -56,6 +56,18 @@ class ViewerOverlayPage extends StatelessWidget { ); }, ), + Selector>( + selector: (context, s) => Tuple2(s.showOverlayInfo, s.showOverlayDescription), + builder: (context, s, child) { + final showInfo = s.item1; + final current = s.item2; + return SwitchListTile( + value: current, + onChanged: showInfo ? (v) => settings.showOverlayDescription = v : null, + title: Text(context.l10n.settingsViewerShowDescription), + ); + }, + ), if (!device.isTelevision) SettingsSwitchListTile( selector: (context, s) => s.showOverlayMinimap, diff --git a/lib/widgets/viewer/overlay/details/description.dart b/lib/widgets/viewer/overlay/details/description.dart new file mode 100644 index 000000000..02a476fdf --- /dev/null +++ b/lib/widgets/viewer/overlay/details/description.dart @@ -0,0 +1,25 @@ +import 'package:aves/theme/icons.dart'; +import 'package:aves/utils/constants.dart'; +import 'package:aves/widgets/viewer/overlay/details/details.dart'; +import 'package:decorated_icon/decorated_icon.dart'; +import 'package:flutter/material.dart'; + +class OverlayDescriptionRow extends StatelessWidget { + final String description; + + const OverlayDescriptionRow({ + super.key, + required this.description, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + DecoratedIcon(AIcons.description, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)), + const SizedBox(width: ViewerDetailOverlayContent.iconPadding), + Expanded(child: Text(description, strutStyle: Constants.overflowStrutStyle)), + ], + ); + } +} diff --git a/lib/widgets/viewer/overlay/details/details.dart b/lib/widgets/viewer/overlay/details/details.dart index 38ff26049..fe0414169 100644 --- a/lib/widgets/viewer/overlay/details/details.dart +++ b/lib/widgets/viewer/overlay/details/details.dart @@ -8,6 +8,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/overlay/details/date.dart'; +import 'package:aves/widgets/viewer/overlay/details/description.dart'; import 'package:aves/widgets/viewer/overlay/details/location.dart'; import 'package:aves/widgets/viewer/overlay/details/position_title.dart'; import 'package:aves/widgets/viewer/overlay/details/rating_tags.dart'; @@ -43,9 +44,9 @@ class _ViewerDetailOverlayState extends State { return index < entries.length ? entries[index] : null; } - late Future _detailLoader; + late Future?> _detailLoader; AvesEntry? _lastEntry; - OverlayMetadata? _lastDetails; + List? _lastDetails; @override void initState() { @@ -63,7 +64,14 @@ class _ViewerDetailOverlayState extends State { void _initDetailLoader() { final requestEntry = entry; - _detailLoader = requestEntry != null ? metadataFetchService.getOverlayMetadata(requestEntry) : SynchronousFuture(null); + if (requestEntry == null) { + _detailLoader = SynchronousFuture(null); + } else { + _detailLoader = Future.wait([ + settings.showOverlayShootingDetails ? metadataFetchService.getOverlayMetadata(requestEntry) : Future.value(null), + settings.showOverlayDescription ? metadataFetchService.getDescription(requestEntry) : Future.value(null), + ]); + } } @override @@ -75,7 +83,7 @@ class _ViewerDetailOverlayState extends State { builder: (context, constraints) { final availableWidth = constraints.maxWidth; - return FutureBuilder( + return FutureBuilder?>( future: _detailLoader, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) { @@ -85,10 +93,14 @@ class _ViewerDetailOverlayState extends State { if (_lastEntry == null) return const SizedBox(); final mainEntry = _lastEntry!; + final shootingDetails = _lastDetails![0]; + final description = _lastDetails![1]; + final multiPageController = widget.multiPageController; Widget _buildContent({AvesEntry? pageEntry}) => ViewerDetailOverlayContent( pageEntry: pageEntry ?? mainEntry, - details: _lastDetails, + shootingDetails: shootingDetails, + description: description, position: widget.hasCollection ? '${widget.index + 1}/${entries.length}' : null, availableWidth: availableWidth, multiPageController: multiPageController, @@ -110,7 +122,8 @@ class _ViewerDetailOverlayState extends State { class ViewerDetailOverlayContent extends StatelessWidget { final AvesEntry pageEntry; - final OverlayMetadata? details; + final OverlayMetadata? shootingDetails; + final String? description; final String? position; final double availableWidth; final MultiPageController? multiPageController; @@ -126,7 +139,8 @@ class ViewerDetailOverlayContent extends StatelessWidget { const ViewerDetailOverlayContent({ super.key, required this.pageEntry, - required this.details, + required this.shootingDetails, + required this.description, required this.position, required this.availableWidth, required this.multiPageController, @@ -136,7 +150,8 @@ class ViewerDetailOverlayContent extends StatelessWidget { Widget build(BuildContext context) { final infoMaxWidth = availableWidth - padding.horizontal; final showRatingTags = settings.showOverlayRatingTags; - final showShooting = settings.showOverlayShootingDetails; + final showShootingDetails = settings.showOverlayShootingDetails; + final showDescription = settings.showOverlayDescription; return AnimatedBuilder( animation: pageEntry.metadataChangeNotifier, @@ -156,8 +171,8 @@ class ViewerDetailOverlayContent extends StatelessWidget { builder: (context, orientation, child) { final twoColumns = orientation == Orientation.landscape && infoMaxWidth / 2 > _subRowMinWidth; final subRowWidth = twoColumns ? min(_subRowMinWidth, infoMaxWidth / 2) : infoMaxWidth; - final collapsedShooting = twoColumns && showShooting; - final collapsedLocation = twoColumns && !showShooting; + final collapsedShooting = twoColumns && showShootingDetails; + final collapsedLocation = twoColumns && !showShootingDetails; final rows = []; if (positionTitle.isNotEmpty) { @@ -176,7 +191,7 @@ class ViewerDetailOverlayContent extends StatelessWidget { ); } else { rows.add(_buildDateSubRow(subRowWidth)); - if (showShooting) { + if (showShootingDetails) { rows.add(_buildShootingFullRow(context, subRowWidth)); } } @@ -186,6 +201,9 @@ class ViewerDetailOverlayContent extends StatelessWidget { if (showRatingTags) { rows.add(_buildRatingTagsFullRow(context)); } + if (showDescription) { + rows.add(_buildDescriptionFullRow(context)); + } return Column( mainAxisSize: MainAxisSize.min, @@ -214,20 +232,26 @@ class ViewerDetailOverlayContent extends StatelessWidget { builder: (context) => OverlayRatingTagsRow(entry: pageEntry), ); + Widget _buildDescriptionFullRow(BuildContext context) => _buildFullRowSwitcher( + context: context, + visible: description != null, + builder: (context) => OverlayDescriptionRow(description: description!), + ); + Widget _buildShootingFullRow(BuildContext context, double subRowWidth) => _buildFullRowSwitcher( context: context, - visible: details != null && details!.isNotEmpty, + visible: shootingDetails != null && shootingDetails!.isNotEmpty, builder: (context) => SizedBox( width: subRowWidth, - child: OverlayShootingRow(details: details!), + child: OverlayShootingRow(details: shootingDetails!), ), ); Widget _buildShootingSubRow(BuildContext context, double subRowWidth) => _buildSubRowSwitcher( context: context, subRowWidth: subRowWidth, - visible: details != null && details!.isNotEmpty, - builder: (context) => OverlayShootingRow(details: details!), + visible: shootingDetails != null && shootingDetails!.isNotEmpty, + builder: (context) => OverlayShootingRow(details: shootingDetails!), ); Widget _buildLocationFullRow(BuildContext context) => _buildFullRowSwitcher( diff --git a/lib/widgets/viewer/overlay/details/shooting.dart b/lib/widgets/viewer/overlay/details/shooting.dart index e28dfb7c8..0d8ea39b9 100644 --- a/lib/widgets/viewer/overlay/details/shooting.dart +++ b/lib/widgets/viewer/overlay/details/shooting.dart @@ -10,7 +10,10 @@ import 'package:intl/intl.dart'; class OverlayShootingRow extends StatelessWidget { final OverlayMetadata details; - const OverlayShootingRow({super.key, required this.details}); + const OverlayShootingRow({ + super.key, + required this.details, + }); @override Widget build(BuildContext context) { diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index bad37cb77..672e2bf73 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -47,6 +47,7 @@ Future configureAndLaunch() async { ..showOverlayOnOpening = true ..showOverlayMinimap = false ..showOverlayInfo = true + ..showOverlayDescription = false ..showOverlayRatingTags = false ..showOverlayShootingDetails = false ..showOverlayThumbnailPreview = false diff --git a/untranslated.json b/untranslated.json index 917f19c09..5e9326135 100644 --- a/untranslated.json +++ b/untranslated.json @@ -452,6 +452,7 @@ "settingsViewerShowInformationSubtitle", "settingsViewerShowRatingTags", "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", "settingsViewerShowOverlayThumbnails", "settingsViewerEnableOverlayBlurEffect", "settingsViewerSlideshowTile", @@ -592,9 +593,18 @@ "de": [ "columnCount", + "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives" ], + "el": [ + "settingsViewerShowDescription" + ], + + "es": [ + "settingsViewerShowDescription" + ], + "fa": [ "columnCount", "clearTooltip", @@ -914,6 +924,7 @@ "settingsViewerShowInformationSubtitle", "settingsViewerShowRatingTags", "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", "settingsViewerShowOverlayThumbnails", "settingsViewerEnableOverlayBlurEffect", "settingsViewerSlideshowTile", @@ -1059,6 +1070,10 @@ "filePickerUseThisFolder" ], + "fr": [ + "settingsViewerShowDescription" + ], + "gl": [ "columnCount", "entryActionShareImageOnly", @@ -1379,6 +1394,7 @@ "settingsViewerShowInformationSubtitle", "settingsViewerShowRatingTags", "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", "settingsViewerShowOverlayThumbnails", "settingsViewerEnableOverlayBlurEffect", "settingsViewerSlideshowTile", @@ -1526,6 +1542,14 @@ "filePickerUseThisFolder" ], + "id": [ + "settingsViewerShowDescription" + ], + + "it": [ + "settingsViewerShowDescription" + ], + "ja": [ "columnCount", "chipActionFilterIn", @@ -1535,13 +1559,19 @@ "keepScreenOnVideoPlayback", "subtitlePositionTop", "subtitlePositionBottom", + "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives", "settingsWidgetDisplayedItem" ], + "ko": [ + "settingsViewerShowDescription" + ], + "lt": [ "columnCount", "keepScreenOnVideoPlayback", + "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives" ], @@ -1551,6 +1581,7 @@ "entryActionShareVideoOnly", "entryInfoActionRemoveLocation", "keepScreenOnVideoPlayback", + "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives" ], @@ -1569,6 +1600,7 @@ "widgetDisplayedItemRandom", "widgetDisplayedItemMostRecent", "settingsViewerShowRatingTags", + "settingsViewerShowDescription", "settingsSubtitleThemeTextPositionTile", "settingsSubtitleThemeTextPositionDialogTitle", "settingsAccessibilityShowPinchGestureAlternatives", @@ -1878,6 +1910,7 @@ "settingsViewerShowInformationSubtitle", "settingsViewerShowRatingTags", "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", "settingsViewerShowOverlayThumbnails", "settingsViewerEnableOverlayBlurEffect", "settingsViewerSlideshowTile", @@ -2385,6 +2418,7 @@ "settingsViewerShowInformationSubtitle", "settingsViewerShowRatingTags", "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", "settingsViewerShowOverlayThumbnails", "settingsViewerEnableOverlayBlurEffect", "settingsViewerSlideshowTile", @@ -2547,12 +2581,21 @@ "widgetDisplayedItemRandom", "widgetDisplayedItemMostRecent", "settingsViewerShowRatingTags", + "settingsViewerShowDescription", "settingsSubtitleThemeTextPositionTile", "settingsSubtitleThemeTextPositionDialogTitle", "settingsAccessibilityShowPinchGestureAlternatives", "settingsWidgetDisplayedItem" ], + "ro": [ + "settingsViewerShowDescription" + ], + + "ru": [ + "settingsViewerShowDescription" + ], + "th": [ "itemCount", "columnCount", @@ -2748,6 +2791,7 @@ "settingsViewerShowInformationSubtitle", "settingsViewerShowRatingTags", "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", "settingsViewerShowOverlayThumbnails", "settingsViewerEnableOverlayBlurEffect", "settingsViewerSlideshowTile", @@ -2895,12 +2939,22 @@ "filePickerUseThisFolder" ], + "tr": [ + "settingsViewerShowDescription" + ], + + "uk": [ + "settingsViewerShowDescription" + ], + "zh": [ + "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives" ], "zh_Hant": [ "columnCount", + "settingsViewerShowDescription", "settingsAccessibilityShowPinchGestureAlternatives" ] }