diff --git a/Jellyfin.Plugin.Themerr.Tests/Jellyfin.Plugin.Themerr.Tests.csproj b/Jellyfin.Plugin.Themerr.Tests/Jellyfin.Plugin.Themerr.Tests.csproj index cf033c5..fa743d6 100644 --- a/Jellyfin.Plugin.Themerr.Tests/Jellyfin.Plugin.Themerr.Tests.csproj +++ b/Jellyfin.Plugin.Themerr.Tests/Jellyfin.Plugin.Themerr.Tests.csproj @@ -30,6 +30,10 @@ + + + + PreserveNewest diff --git a/Jellyfin.Plugin.Themerr.Tests/TestThemerrController.cs b/Jellyfin.Plugin.Themerr.Tests/TestThemerrController.cs index cc17cef..bbdb05c 100644 --- a/Jellyfin.Plugin.Themerr.Tests/TestThemerrController.cs +++ b/Jellyfin.Plugin.Themerr.Tests/TestThemerrController.cs @@ -1,5 +1,6 @@ using System.Collections; using Jellyfin.Plugin.Themerr.Api; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -25,7 +26,18 @@ public TestThemerrController(ITestOutputHelper output) Mock mockLibraryManager = new(); Mock> mockLogger = new(); - _controller = new ThemerrController(mockLibraryManager.Object, mockLogger.Object); + Mock mockServerConfigurationManager = new(); + + // Create a TestableServerConfiguration with UICulture set to "en-US" + var testableServerConfiguration = new TestableServerConfiguration("en-US"); + + // Setup the Configuration property of the IServerConfigurationManager mock to return the TestableServerConfiguration + mockServerConfigurationManager.Setup(x => x.Configuration).Returns(testableServerConfiguration); + + _controller = new ThemerrController( + mockLibraryManager.Object, + mockLogger.Object, + mockServerConfigurationManager.Object); } /// @@ -56,4 +68,69 @@ public void TestGetProgress() // todo: add tests for when there are items } + + /// + /// Test GetTranslations from API. + /// + [Fact] + [Trait("Category", "Unit")] + public void TestGetTranslations() + { + var actionResult = _controller.GetTranslations(); + Assert.IsType(actionResult); + + // Cast the result to OkObjectResult to access the data + var okResult = actionResult as OkObjectResult; + + // Access the data returned by the API + var data = okResult?.Value as Dictionary; + + Assert.NotNull(data); + + // Assert the data contains the expected keys + Assert.True(data.ContainsKey("locale")); + Assert.True(data.ContainsKey("fallback")); + } + + /// + /// Test GetCultureResource function. + /// + [Fact] + [Trait("Category", "Unit")] + public void TestGetCultureResource() + { + // list of english cultures + var enCultures = new List + { + "de", + "en", + "en-GB", + "en-US", + "es", + "fr", + "it", + "ru", + "sv", + "zh" + }; + + foreach (var t in enCultures) + { + var result = _controller.GetCultureResource(t); + Assert.IsType>(result); + + // replace - with _ in the culture + var t2 = t.Replace("-", "_"); + + // en is not included in the list + if (t != "en") + { + // assert that `en_<>.json` is in the list + Assert.Contains(t2 + ".json", result); + } + + // assert that `en` is NOT in the list + Assert.DoesNotContain("en.json", result); + } + } } diff --git a/Jellyfin.Plugin.Themerr.Tests/TestableServerConfiguration.cs b/Jellyfin.Plugin.Themerr.Tests/TestableServerConfiguration.cs new file mode 100644 index 0000000..bb9f45e --- /dev/null +++ b/Jellyfin.Plugin.Themerr.Tests/TestableServerConfiguration.cs @@ -0,0 +1,18 @@ +using MediaBrowser.Model.Configuration; + +namespace Jellyfin.Plugin.Themerr.Tests; + +/// +/// Represents a testable server configuration for unit testing. +/// +public class TestableServerConfiguration : ServerConfiguration +{ + /// + /// Initializes a new instance of the class. + /// + /// Mocked UI culture. + public TestableServerConfiguration(string uiCulture) + { + UICulture = uiCulture; + } +} diff --git a/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs b/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs index a7b21b9..dee3554 100644 --- a/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs +++ b/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs @@ -1,8 +1,12 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Mime; +using System.Reflection; using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -24,18 +28,22 @@ public class ThemerrController : ControllerBase { private readonly ThemerrManager _themerrManager; private readonly ILogger _logger; + private readonly IServerConfigurationManager _configurationManager; /// /// Initializes a new instance of the class. /// /// The library manager. /// The logger. + /// The configuration manager. public ThemerrController( ILibraryManager libraryManager, - ILogger logger) + ILogger logger, + IServerConfigurationManager configurationManager) { _themerrManager = new ThemerrManager(libraryManager, logger); _logger = logger; + _configurationManager = configurationManager; } /// @@ -120,5 +128,111 @@ public ActionResult GetProgress() return new JsonResult(tmpObject); } + + /// + /// Get the localization strings from Locale/{selected_locale}.json. + /// + /// + /// JSON object containing localization strings. + [HttpGet("GetTranslations")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetTranslations() + { + // get the locale from the user's settings + var culture = _configurationManager.Configuration.UICulture; + + _logger.LogInformation("Server culture: {ServerCulture}", culture); + + // get file paths from LocalizationManager + var filePaths = GetCultureResource(culture); + + // Get the current assembly + var assembly = Assembly.GetExecutingAssembly(); + + for (var i = 0; i < filePaths.Count; i++) + { + // construct the resource name + var resourceName = $"Jellyfin.Plugin.Themerr.Locale.{filePaths[i]}"; + + // Get the resource stream + using var stream = assembly.GetManifestResourceStream(resourceName); + + if (stream == null) + { + _logger.LogWarning( + "Locale resource does not exist: {ResourceName}", + resourceName.Replace(Environment.NewLine, string.Empty)); + continue; + } + + // Initialize the result dictionary + var result = new Dictionary(); + + // read the resource content + using var reader = new StreamReader(stream); + var json = reader.ReadToEnd(); + + // deserialize the JSON content into a dictionary + var localizedStrings = JsonConvert.DeserializeObject>(json); + + // Add the localized strings to the 'locale' key + result["locale"] = localizedStrings; + + // Now get the fallback resource + var fallbackResourceName = "Jellyfin.Plugin.Themerr.Locale.en.json"; + using var fallbackStream = assembly.GetManifestResourceStream(fallbackResourceName); + + if (fallbackStream != null) + { + // read the fallback resource content + using var fallbackReader = new StreamReader(fallbackStream); + var fallbackJson = fallbackReader.ReadToEnd(); + + // deserialize the fallback JSON content into a dictionary + var fallbackLocalizedStrings = + JsonConvert.DeserializeObject>(fallbackJson); + + // Add the fallback localized strings to the 'fallback' key + result["fallback"] = fallbackLocalizedStrings; + } + else + { + _logger.LogError("Fallback locale resource does not exist: {ResourceName}", fallbackResourceName); + } + + // return the result as a JSON object + return Ok(result); + } + + // return an error if we get this far + return StatusCode(StatusCodes.Status500InternalServerError); + } + + /// + /// Get the resources of the given culture. + /// + /// + /// The culture to get the resource for. + /// A list of file names. + public List GetCultureResource(string culture) + { + string tmp; + var fileNames = new List(); + var parts = culture.Split('-'); + + if (parts.Length == 2) + { + tmp = parts[0].ToLowerInvariant() + "_" + parts[1].ToUpperInvariant(); + fileNames.Add(tmp + ".json"); + } + + tmp = parts[0].ToLowerInvariant(); + if (tmp != "en") + { + fileNames.Add(tmp + ".json"); + } + + return fileNames; + } } } diff --git a/Jellyfin.Plugin.Themerr/Configuration/configPage.html b/Jellyfin.Plugin.Themerr/Configuration/configPage.html index 1de93a9..bce43bc 100644 --- a/Jellyfin.Plugin.Themerr/Configuration/configPage.html +++ b/Jellyfin.Plugin.Themerr/Configuration/configPage.html @@ -20,7 +20,7 @@

Themerr

Discord Support Center + href="https://app.lizardbyte.dev/support" data-localize="support_center">Support Center
-

This plugin relies on the TMDB provider. +

This plugin relies on the TMDB provider. Please make sure it is enabled!


@@ -40,25 +40,25 @@

Themerr

-
diff --git a/Jellyfin.Plugin.Themerr/Jellyfin.Plugin.Themerr.csproj b/Jellyfin.Plugin.Themerr/Jellyfin.Plugin.Themerr.csproj index 60bb032..10648cb 100644 --- a/Jellyfin.Plugin.Themerr/Jellyfin.Plugin.Themerr.csproj +++ b/Jellyfin.Plugin.Themerr/Jellyfin.Plugin.Themerr.csproj @@ -20,6 +20,10 @@ + + + + diff --git a/Jellyfin.Plugin.Themerr/Locale/de.json b/Jellyfin.Plugin.Themerr/Locale/de.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/de.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/en.json b/Jellyfin.Plugin.Themerr/Locale/en.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/en.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/en_GB.json b/Jellyfin.Plugin.Themerr/Locale/en_GB.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/en_GB.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/en_US.json b/Jellyfin.Plugin.Themerr/Locale/en_US.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/en_US.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/es.json b/Jellyfin.Plugin.Themerr/Locale/es.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/es.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/fr.json b/Jellyfin.Plugin.Themerr/Locale/fr.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/fr.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/it.json b/Jellyfin.Plugin.Themerr/Locale/it.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/it.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/ru.json b/Jellyfin.Plugin.Themerr/Locale/ru.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/ru.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/sv.json b/Jellyfin.Plugin.Themerr/Locale/sv.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/sv.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/Jellyfin.Plugin.Themerr/Locale/zh.json b/Jellyfin.Plugin.Themerr/Locale/zh.json new file mode 100644 index 0000000..4532bb3 --- /dev/null +++ b/Jellyfin.Plugin.Themerr/Locale/zh.json @@ -0,0 +1,24 @@ +{ + "add_button": "Add", + "contribute": "Contribute", + "contribute_button": "Contribute", + "edit_button": "Edit", + "movie": "Movie", + "no_theme_song": "No theme song", + "plugin_relies": "This plugin relies on the TMDB provider. Please make sure it is enabled!", + "save": "Save", + "series": "Series", + "status": "Status", + "support_center": "Support Center", + "themerr_provided": "Themerr provided", + "title": "Title", + "type": "Type", + "unexpected_error_occured": "An unexpected error occurred", + "unexpected_error_occured_creating_dashboard": "Unexpected error occurred creating progress dashboard", + "update_interval": "Update Interval", + "update_interval_desc": "The interval, in minutes, between theme song updates.", + "update_theme_songs": "Update Theme Songs", + "updating_theme_songs": "Updating theme songs...", + "user_provided": "User provided", + "year": "Year" +} diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..e26b5c3 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,17 @@ +--- +"base_path": "." +"base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) +"preserve_hierarchy": true # false will flatten tree on crowdin, but doesn't work with dest option +"pull_request_labels": [ + "crowdin", + "l10n" +] + +"files": [ + { + "source": "/Jellyfin.Plugin.Themerr/Locale/en.json", + "dest": "/themerr-jellyfin.json", + "translation": "/Jellyfin.Plugin.Themerr/Locale/%two_letters_code%.%file_extension%", + "update_option": "update_as_unapproved" + } +] diff --git a/docs/source/conf.py b/docs/source/conf.py index 01af483..9675ab9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -111,14 +111,8 @@ 'Jellyfin.Controller.MediaBrowser.Common.Plugins': ( 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Common/Plugins/%s.cs', ), - 'Jellyfin.Controller.MediaBrowser.Model.Plugins': ( - 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Model/Plugins/%s.cs', - ), - 'Jellyfin.Controller.MediaBrowser.Model.Serialization': ( - 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Model/Serialization/%s.cs', - ), - 'Jellyfin.Controller.MediaBrowser.Model.Tasks': ( - 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Model/Tasks/%s.cs', + 'Jellyfin.Controller.MediaBrowser.Controller.Configuration': ( + 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Controller/Configuration/%s.cs', ), 'Jellyfin.Controller.MediaBrowser.Controller.Entities': ( 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Controller/Entities/%s.cs', @@ -135,6 +129,15 @@ 'Jellyfin.Controller.MediaBrowser.Controller.Plugins': ( 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Controller/Plugins/%s.cs', ), + 'Jellyfin.Controller.MediaBrowser.Model.Plugins': ( + 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Model/Plugins/%s.cs', + ), + 'Jellyfin.Controller.MediaBrowser.Model.Serialization': ( + 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Model/Serialization/%s.cs', + ), + 'Jellyfin.Controller.MediaBrowser.Model.Tasks': ( + 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Model/Tasks/%s.cs', + ), } # Types that are in an external package. Use the format @@ -186,6 +189,38 @@ 'PluginManifest', ], }, + 'Jellyfin.Controller.MediaBrowser.Controller.Configuration': { + '': [ + 'IServerConfigurationManager', + ], + }, + 'Jellyfin.Controller.MediaBrowser.Controller.Entities': { + '': [ + 'BaseItem', + ], + }, + 'Jellyfin.Controller.MediaBrowser.Controller.Entities.Library': { + '': [ + 'ILibraryManager', + ], + }, + 'Jellyfin.Controller.MediaBrowser.Controller.Entities.Movies': { + '': [ + 'BoxSet', + 'Movie', + ], + }, + 'Jellyfin.Controller.MediaBrowser.Controller.Entities.TV': { + '': [ + 'Series', + ], + }, + 'Jellyfin.Controller.MediaBrowser.Controller.Plugins': { + '': [ + 'IRunBeforeStartup', + 'IServerEntryPoint', + ], + }, 'Jellyfin.Controller.MediaBrowser.Model.Plugins': { '': [ 'BasePluginConfiguration', @@ -217,33 +252,6 @@ 'TaskTriggerInfo', ], }, - 'Jellyfin.Controller.MediaBrowser.Controller.Entities': { - '': [ - 'BaseItem', - ], - }, - 'Jellyfin.Controller.MediaBrowser.Controller.Entities.Library': { - '': [ - 'ILibraryManager', - ], - }, - 'Jellyfin.Controller.MediaBrowser.Controller.Entities.Movies': { - '': [ - 'BoxSet', - 'Movie', - ], - }, - 'Jellyfin.Controller.MediaBrowser.Controller.Entities.TV': { - '': [ - 'Series', - ], - }, - 'Jellyfin.Controller.MediaBrowser.Controller.Plugins': { - '': [ - 'IRunBeforeStartup', - 'IServerEntryPoint', - ], - }, } # [Advanced] Rename type before generating external link. Commonly used for generic types diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst new file mode 100644 index 0000000..f6bc9be --- /dev/null +++ b/docs/source/contributing/localization.rst @@ -0,0 +1,63 @@ +Localization +============ +Themerr-jellyfin and related LizardByte projects are being localized into various languages. The default language is +`en` (English). + + .. image:: https://app.lizardbyte.dev/uno/crowdin/LizardByte_graph.svg + +CrowdIn +------- +The translations occur on `CrowdIn `__. Anyone is free to contribute to +localization there. + +**Translations Basics** + - The brand names `LizardByte` and `Themerr` should never be translated. + - Other brand names should never be translated. + Examples: + + - Jellyfin + +**CrowdIn Integration** + How does it work? + + When a change is made to the source locale file, those strings get pushed to CrowdIn automatically. + + When translations are updated on CrowdIn, a push gets made to the `l10n_master` branch and a PR is made. + Once PR is merged, all updated translations are part of the project and will be included in the + next release. + +Extraction +---------- + +Themerr-jellyfin uses a custom translation implementation for localizing the html config page. +The implementation uses a JSON key-value pair to map the strings to their respective translations. + +The following is a simple example of how to use it. + +- Add the string to `Jellyfin.Plugin.Themerr/Locale/en.json`, in English. + .. code-block:: json + + { + "hello": "Hello!" + } + + .. note:: The json keys should be sorted alphabetically. You can use `jsonabc `__ + to sort the keys. + +- Use the string in the config page. + .. code-block:: html + +

Hello!

+ +.. note:: + - The `data-localize` attribute should be the same as the key in the JSON file. + - The `innerText` of the element should be the default English string, incase the translations cannot be properly + loaded. + - The `data-localize` attribute can be added to any element that supports `innerText`. + - Once the page is loaded, the `innerText` will be replaced with their respective translations. + - If the translation is not found, there will be a fallback to the default English string. + +- Use the string in javascript. + .. code-block:: javascript + + const hello = translate("hello"); diff --git a/docs/source/toc.rst b/docs/source/toc.rst index 87275eb..5b8adc6 100644 --- a/docs/source/toc.rst +++ b/docs/source/toc.rst @@ -15,6 +15,7 @@ contributing/database contributing/build + contributing/localization contributing/testing .. toctree::