diff --git a/Source/FileLiberator/UtilityExtensions.cs b/Source/FileLiberator/UtilityExtensions.cs index 29f5f050..5c23f477 100644 --- a/Source/FileLiberator/UtilityExtensions.cs +++ b/Source/FileLiberator/UtilityExtensions.cs @@ -59,7 +59,7 @@ public static class UtilityExtensions Title = libraryBook.Book.Title, Subtitle = libraryBook.Book.Subtitle, TitleWithSubtitle = libraryBook.Book.TitleWithSubtitle, - Locale = new RegionInfoDto(libraryBook.Book.Locale), + Locale = new LocaleDto(libraryBook.Book.Locale), YearPublished = libraryBook.Book.DatePublished?.Year, DatePublished = libraryBook.Book.DatePublished, diff --git a/Source/LibationFileManager/LibationFileManager.csproj b/Source/LibationFileManager/LibationFileManager.csproj index 1049f7da..4ef6419c 100644 --- a/Source/LibationFileManager/LibationFileManager.csproj +++ b/Source/LibationFileManager/LibationFileManager.csproj @@ -6,6 +6,7 @@ + diff --git a/Source/LibationFileManager/Templates/LibraryBookDto.cs b/Source/LibationFileManager/Templates/LibraryBookDto.cs index 7645089c..fda444b2 100644 --- a/Source/LibationFileManager/Templates/LibraryBookDto.cs +++ b/Source/LibationFileManager/Templates/LibraryBookDto.cs @@ -10,7 +10,7 @@ public class BookDto public string? Title { get; set; } public string? Subtitle { get; set; } public string? TitleWithSubtitle { get; set; } - public RegionInfoDto? Locale { get; set; } + public LocaleDto? Locale { get; set; } public int? YearPublished { get; set; } public IEnumerable? Authors { get; set; } diff --git a/Source/LibationFileManager/Templates/RegionInfoDto.cs b/Source/LibationFileManager/Templates/LocaleDto.cs similarity index 59% rename from Source/LibationFileManager/Templates/RegionInfoDto.cs rename to Source/LibationFileManager/Templates/LocaleDto.cs index 3a64b9d7..6e8da8f8 100644 --- a/Source/LibationFileManager/Templates/RegionInfoDto.cs +++ b/Source/LibationFileManager/Templates/LocaleDto.cs @@ -3,47 +3,74 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using AudibleApi; using FileManager.NamingTemplate; namespace LibationFileManager.Templates; -public partial record RegionInfoDto : IFormattable +public partial record LocaleDto : IFormattable { + public string Original { get; } + public Locale Locale { get; set; } private RegionInfo? Value { get; } private CultureInfo? Culture { get; } private string DefaultFormat { get; } - public string Original { get; } - public RegionInfoDto(string hint) : this(hint, "{O}") + + public LocaleDto(string hint) : this(hint, "{O}") { } - public RegionInfoDto(string hint, string defaultFormat) : this(GetRegion(hint), hint, defaultFormat) + public LocaleDto(string hint, string defaultFormat) : this(Localization.Get(hint), hint, defaultFormat) { } - public RegionInfoDto(RegionInfo? value, string hint, string defaultFormat) + public LocaleDto(Locale locale, string hint, string defaultFormat) { + var (regionInfo, cultureInfo) = GetRegion(locale.Language, locale.CountryCode, hint); + Original = hint; + Locale = locale; + Value = regionInfo; + Culture = cultureInfo ?? (regionInfo is null ? null : GetCultureInfo(regionInfo)); DefaultFormat = defaultFormat; - Value = value; - Culture = value is null ? null : GetCultureInfo(value); } - private static RegionInfo? GetRegion(string input) + private static (RegionInfo?, CultureInfo?) GetRegion(string language, string countrcode, string input) { - if (input.StartsWith("pre-amazon - ", StringComparison.OrdinalIgnoreCase)) input = input.Substring(13); - if (string.Equals(input, "uk", StringComparison.OrdinalIgnoreCase)) return new RegionInfo("GB"); + CultureInfo? culture = null; + if (language != string.Empty) + try + { + culture = CultureInfo.GetCultureInfo(language.Length == 2 ? $"{language}-{countrcode}" : language); + } + catch + { + // ignored + } + try { - return new RegionInfo(input.ToUpperInvariant()); + return (new RegionInfo(countrcode), culture); + } + catch + { + // ignored + } + + if (culture is not null) + return (new RegionInfo(culture.Name), culture); + + try + { + return (new RegionInfo(input), culture); } catch { return CultureInfo.GetCultures(CultureTypes.SpecificCultures) - .Select(c => new RegionInfo(c.Name)) - .FirstOrDefault(r => - string.Equals(r.EnglishName, input, StringComparison.OrdinalIgnoreCase)); + .Select(c => (new RegionInfo(c.Name), c)) + .FirstOrDefault(rAndC => + string.Equals(rAndC.Item1.EnglishName, input, StringComparison.OrdinalIgnoreCase)); } } @@ -68,9 +95,9 @@ public partial record RegionInfoDto : IFormattable } - private static readonly Dictionary> FormatReplacements = new(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary> FormatReplacements = new(StringComparer.OrdinalIgnoreCase) { - { "ID", dto => dto.Value?.Name }, + { "ID", dto => dto.Locale?.MarketPlaceId }, { "I", dto => dto.Value?.TwoLetterISORegionName }, { "I2", dto => dto.Value?.TwoLetterISORegionName }, { "I3", dto => dto.Value?.ThreeLetterISORegionName }, @@ -78,6 +105,8 @@ public partial record RegionInfoDto : IFormattable { "E", dto => dto.Value?.EnglishName }, { "N", dto => dto.Value?.NativeName }, { "O", dto => dto.Original }, + { "T", dto => dto.Locale.TopDomain }, + { "L", dto => dto.Culture?.Name }, { "D", dto => dto.GetLocalizedRegionName() }, // localized }; diff --git a/Source/LibationFileManager/Templates/TemplateEditor[T].cs b/Source/LibationFileManager/Templates/TemplateEditor[T].cs index 9205b36c..2ea0f04a 100644 --- a/Source/LibationFileManager/Templates/TemplateEditor[T].cs +++ b/Source/LibationFileManager/Templates/TemplateEditor[T].cs @@ -63,7 +63,7 @@ public class TemplateEditor : ITemplateEditor where T : Templates, ITemplate, Title = "A Study in Scarlet", TitleWithSubtitle = "A Study in Scarlet: A Sherlock Holmes Novel", Subtitle = "A Sherlock Holmes Novel", - Locale = new RegionInfoDto("us"), + Locale = new LocaleDto("us"), YearPublished = 2017, Authors = [new("Arthur Conan Doyle", "B000AQ43GQ"), new("Stephen Fry - introductions", "B000APAGVS")], Narrators = [new("Stephen Fry", null)], diff --git a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs index 68106541..c06540f0 100644 --- a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs +++ b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs @@ -44,7 +44,7 @@ namespace TemplatesTests FileDate = new DateTime(2023, 1, 28, 0, 0, 0), AudibleProductId = "asin", Title = "A Study in Scarlet: A Sherlock Holmes Novel", - Locale = new RegionInfoDto("us"), + Locale = new LocaleDto("us"), YearPublished = null, // explicitly null Authors = [new("Arthur Conan Doyle", "B000AQ43GQ"), new("Stephen Fry - introductions", "B000APAGVS")], Narrators = [], // explicitly empty list @@ -820,51 +820,52 @@ namespace TemplatesTests // platform-specific tests here—unlike path-related differences, which are defined and testable. // test known locales - [DataRow("us", "", - "ID:US, 2:US, 3:USA, W:USA, D:Estados Unidos, E:United States, N:United States, O:us")] - [DataRow("uk", "", - "ID:GB, 2:GB, 3:GBR, W:GBR, D:Reino Unido, E:United Kingdom, N:United Kingdom, O:uk")] - [DataRow("germany", "", - "ID:DE, 2:DE, 3:DEU, W:DEU, D:Alemania, E:Germany, N:Deutschland, O:germany")] + [DataRow("us", "", + "ID:AF2M0KC94RCEA, 2:US, 3:USA, W:USA, D:Estados Unidos, E:United States, N:United States, O:us, T:.com, L:en-US")] + [DataRow("uk", "", + "ID:A2I9A3Q2GNFNGQ, 2:GB, 3:GBR, W:GBR, D:Reino Unido, E:United Kingdom, N:United Kingdom, O:uk, T:.co.uk, L:en-GB")] + [DataRow("germany", "", + "ID:AN7V1F1VY261K, 2:DE, 3:DEU, W:DEU, D:Alemania, E:Germany, N:Deutschland, O:germany, T:.de, L:de-DE")] // Skip NativeName (see above) - [DataRow("france", "", "ID:FR, 2:FR, 3:FRA, W:FRA, D:Francia, E:France, O:france")] - [DataRow("australia", "", "ID:AU, 2:AU, 3:AUS, W:AUS, D:Australia, E:Australia, O:australia")] - [DataRow("india", "", "ID:IN, 2:IN, 3:IND, W:IND, D:India, E:India, O:india")] - [DataRow("spain", "", "ID:ES, 2:ES, 3:ESP, W:ESP, D:España, E:Spain, O:spain")] - [DataRow("italy", "", "ID:IT, 2:IT, 3:ITA, W:ITA, D:Italia, E:Italy, O:italy")] - [DataRow("canada", "", "ID:CA, 2:CA, 3:CAN, W:CAN, D:Canadá, E:Canada, O:canada")] - [DataRow("japan", "", "ID:JP, 2:JP, 3:JPN, W:JPN, D:Japón, E:Japan, O:japan")] - [DataRow("brazil", "", "ID:BR, 2:BR, 3:BRA, W:BRA, D:Brasil, E:Brazil, O:brazil")] + [DataRow("france", "", "ID:A2728XDNODOQ8T, 2:FR, 3:FRA, W:FRA, D:Francia, E:France, O:france, T:.fr, L:fr-FR")] + [DataRow("australia", + "", "ID:AN7EY7DTAW63G, 2:AU, 3:AUS, W:AUS, D:Australia, E:Australia, O:australia, T:.com.au, L:en-AU")] + [DataRow("india", "", "ID:AJO3FBRUE6J4S, 2:IN, 3:IND, W:IND, D:India, E:India, O:india, T:.in, L:en-IN")] + [DataRow("spain", "", "ID:ALMIKO4SZCSAR, 2:ES, 3:ESP, W:ESP, D:España, E:Spain, O:spain, T:.es, L:es-ES")] + [DataRow("italy", "", "ID:A2N7FU2W2BU2ZC, 2:IT, 3:ITA, W:ITA, D:Italia, E:Italy, O:italy, T:.it, L:it-IT")] + [DataRow("canada", "", "ID:A2CQZ5RBY40XE, 2:CA, 3:CAN, W:CAN, D:Canadá, E:Canada, O:canada, T:.ca, L:en-CA")] + [DataRow("japan", "", "ID:A1QAP3MOU4173J, 2:JP, 3:JPN, W:JPN, D:Japón, E:Japan, O:japan, T:.co.jp, L:ja-JP")] + [DataRow("brazil", "", "ID:A10J1VAYUDTYRN, 2:BR, 3:BRA, W:BRA, D:Brasil, E:Brazil, O:brazil, T:.com.br, L:pt-BR")] // test historical locales - [DataRow("pre-amazon - us", "", "ID:US, O:pre-amazon - us")] - [DataRow("pre-amazon - uk", "", "ID:GB, O:pre-amazon - uk")] - [DataRow("pre-amazon - germany", "", "ID:DE, O:pre-amazon - germany")] + [DataRow("pre-amazon - us", "", "ID:AF2M0KC94RCEA, O:pre-amazon - us, T:.com, L:en-US")] + [DataRow("pre-amazon - uk", "", "ID:A2I9A3Q2GNFNGQ, O:pre-amazon - uk, T:.co.uk, L:en-GB")] + [DataRow("pre-amazon - germany", "", "ID:AN7V1F1VY261K, O:pre-amazon - germany, T:.de, L:de-DE")] // test upcoming locales - [DataRow("be", "", "ID:BE, E:Belgium, O:be")] - [DataRow("nl", "", "ID:NL, E:Netherlands, O:nl")] - [DataRow("se", "", "ID:SE, E:Sweden, O:se")] - [DataRow("pl", "", "ID:PL, E:Poland, O:pl")] - [DataRow("ie", "", "ID:IE, E:Ireland, O:ie")] - [DataRow("sg", "", "ID:SG, E:Singapore, O:sg")] - [DataRow("za", "", "ID:ZA, E:South Africa, O:za")] - [DataRow("ae", "", "ID:AE, E:United Arab Emirates, O:ae")] - [DataRow("sa", "", "ID:SA, E:Saudi Arabia, O:sa")] - [DataRow("eg", "", "ID:EG, E:Egypt, O:eg")] + [DataRow("be", "", "ID:, E:Belgium, O:be")] + [DataRow("nl", "", "ID:, E:Netherlands, O:nl")] + [DataRow("se", "", "ID:, E:Sweden, O:se")] + [DataRow("pl", "", "ID:, E:Poland, O:pl")] + [DataRow("ie", "", "ID:, E:Ireland, O:ie")] + [DataRow("sg", "", "ID:, E:Singapore, O:sg")] + [DataRow("za", "", "ID:, E:South Africa, O:za")] + [DataRow("ae", "", "ID:, E:United Arab Emirates, O:ae")] + [DataRow("sa", "", "ID:, E:Saudi Arabia, O:sa")] + [DataRow("eg", "", "ID:, E:Egypt, O:eg")] // Skip EnglishName: the official English name of Turkey changed to 'Türkiye', and the returned value now depends on // the OS/globalization provider (Windows-NLS vs. ICU). Tests would not be stable. // A future lookup may still need to account for whichever English name Audible chooses to use. - [DataRow("tr", "", "ID:TR, E:---, O:tr")] + [DataRow("tr", "", "ID:, E:---, O:tr")] // test some different localizations - should change only D(isplayNames) [DataRow("fr", "", "D:Frankreich, E:France, N:France, O:fr")] [DataRow("fr", "", "D:Francja")] [DataRow("fr", "", "D:Francia")] - public void Region_test(string country, string template, string expected) + public void Locale_test(string country, string template, string expected) { var bookDto = Shared.GetLibraryBook(); - bookDto.Locale = new RegionInfoDto(country); + bookDto.Locale = new LocaleDto(country); var result = ""; diff --git a/docs/features/naming-templates.md b/docs/features/naming-templates.md index 7ceabced..27ac4a63 100644 --- a/docs/features/naming-templates.md +++ b/docs/features/naming-templates.md @@ -226,10 +226,10 @@ You can use custom formatters to construct customized DateTime string. For more You can specify which part of a region you are interested in. -| Formatter | Description | Example Usage | Example Result | -|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------|--------------------| -| \{O \| I \| I2 \| I3 \| E \| N \| W \| ID\} | Formats the region using
the region part tags.
\{O:[Text_Formatter](#text-formatters)\} = Region as provided by audible
\{I:[Text_Formatter](#text-formatters)\} = Two letter ISO code
\{I2:[Text_Formatter](#text-formatters)\} = Two letter ISO code
\{I3:[Text_Formatter](#text-formatters)\} = Three letter ISO code
\{E:[Text_Formatter](#text-formatters)\} = English name
\{N:[Text_Formatter](#text-formatters)\} = Native name - OS dependent
\{W:[Text_Formatter](#number-formatters)\} = Unique Windows code
\{ID:[Text_Formatter](#text-formatters)\} = Region code

Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.

Default is \{O\} | `` | US (United States) | -| \{D\} **†** | Display name interpreted by the current language settings.
To ensure output in a specific language the lang-code to use might be specified with a leading '@'.
Formatter part is also optional and introduced by the colon.
\{D@LANG:[Text_Formatter](#text-formatters)\} | `` | ESTADOS UNIDOS | +| Formatter | Description | Example Usage | Example Result | +|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|---------------------------------------| +| \{O \| I \| I2 \| I3 \| E \| N \| W \| L \| T \| ID\} | Formats the region using
the region part tags.
\{O:[Text_Formatter](#text-formatters)\} = Region as used in Libation
\{I:[Text_Formatter](#text-formatters)\} = Two letter ISO code
\{I2:[Text_Formatter](#text-formatters)\} = Two letter ISO code
\{I3:[Text_Formatter](#text-formatters)\} = Three letter ISO code
\{E:[Text_Formatter](#text-formatters)\} = English name
\{N:[Text_Formatter](#text-formatters)\} = Native name - OS dependent
\{W:[Text_Formatter](#number-formatters)\} = Unique Windows code
\{L:[Text_Formatter](#text-formatters)\} = Lang code used for this region/store
\{T:[Text_Formatter](#number-formatters)\} = TLD under which the audible store is hosted
\{ID:[Text_Formatter](#text-formatters)\} = Region code


Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.

Default is \{O\} | ``
`www.audible.` | US (United States)
www.audible.com | +| \{D\} **†** | Display name interpreted by the current language settings.
To ensure output in a specific language the lang-code to use might be specified with a leading '@'.
Formatter part is also optional and introduced by the colon.
\{D@LANG:[Text_Formatter](#text-formatters)\} | `` | ESTADOS UNIDOS | **†** LANG may be any code from the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) standard like `es` for Spanish, `en` for English, `de` for German, etc. or even a [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) like 'fr-CA'.