Skip to content

Commit

Permalink
Merge pull request #13 from Ramo-Y/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Ramo-Y authored Jul 14, 2024
2 parents 89cae27 + b3bf9b1 commit 0481e1a
Show file tree
Hide file tree
Showing 18 changed files with 926 additions and 46 deletions.
43 changes: 43 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
- [Description](#description)
- [Documentation](#documentation)
- [Branches](#branches)
- [Workflows](#workflows)
- [Variables and secrets](#variables-and-secrets)
- [.env file](#env-file)
- [Languages](#languages)
- [Resource file](#resource-file)
- [Integration Tests](#integration-tests)
- [Add language to the supported cultures](#add-language-to-the-supported-cultures)
- [Version](#version)
- [Database fields](#database-fields)
- [Primary key](#primary-key)
- [All fields](#all-fields)
- [Foreign Keys](#foreign-keys)
- [Working locally](#working-locally)
- [Local build](#local-build)
- [Running the container](#running-the-container)

# Description
You are welcome to participate in the development of this tool, in this file some information and rules for the development are described.

Expand All @@ -15,6 +34,7 @@ The workflows are described in the following table:
| ci | [build-test.yml](./.github/workflows/build-test.yml) | All branches and pull requests to develop and master | Builds and tests the software |
| release | [build-test-release.yml](./.github/workflows/build-test-release.yml) | Commits to develop, master and release/* branches | Builds and tests software and pushes it to the Docker Hub if the tests were successful |
| manual_release | [build-test-release_manually.yml](./.github/workflows/build-test-release_manually.yml) | Manual, can be triggeret on any branch | Builds and tests software and pushes it to the Docker Hub if the tests were successful |
| update-readme | [update-dockerhub-readme.yml](./.github/workflows/update-dockerhub-readme.yml) | Commits on master branch | Updates ReadMe on docker hub |

Version names are automatically set by the reusable workflow `set_version`, based on branch names. See in the file [set_version.yml](./.github/workflows/set_version.yml)

Expand All @@ -31,6 +51,29 @@ In order for the workflows to run on your GitHub account, the following variable
# .env file
In order that parameter values are not directly in the docker-compose.yml, the .env file is used here. This allows the parameters and values to be specified as a key-value pair in this file. The [CreateEnvFile.ps1](./src/CreateEnvFile.ps1) script was created to avoid having to create the file manually. If new parameters are defined for the docker-compose.yml file, the script should be extended accordingly.

# Languages
A new language can be added very easily, you need Visual Studio, you can download it [here](https://visualstudio.microsoft.com/downloads/). In this example, we will add `Spanish` as a new language.

## Resource file
Create a new resource file in the folder [Resources](./src/BulkRename/Resources) and provide your language code between the file `SharedResource` name and the extension `resx`, for example `SharedResource.es.resx`. Copy all the keys from the [default language file](./src/BulkRename/Resources/SharedResource.resx) which is English, and add the translations. Check out this [Microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization/provide-resources?view=aspnetcore-8.0) to learn more about resource files.

### Integration Tests
There are a few integration tests, that will ensure that all the language keys, that exist in the English version, have also been translated to the new language. Please run [these tests](./src/BulkRename.IntegrationTests/Resources/LanguageResourcesTests.cs) before creating a pull request.

## Add language to the supported cultures
Go to the class [Program.cs](./src/BulkRename/Program.cs) and add your language to the `supportedCultures` with the corresponding culture.

```c#
var supportedCultures = new[]
{
new CultureInfo(defaultCulture),
new CultureInfo("hu"),
new CultureInfo("de"),
// Add here your new CultureInfo
new CultureInfo("es")
};
```

# Version
The version is set in the following files:
- VERSION in [Dockerfile](./src/Dockerfile)
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
[![Docker Pulls](https://img.shields.io/docker/pulls/ramoy/bulkrename.svg)](https://hub.docker.com/r/ramoy/bulkrename)
[![MIT Licensed](https://img.shields.io/github/license/ramo-y/BulkRename.svg)](https://github.com/Ramo-Y/BulkRename/blob/master/LICENSE)
[![Activity](https://img.shields.io/github/commit-activity/m/Ramo-Y/BulkRename)](https://github.com/Ramo-Y/BulkRename/pulse)
![ci](https://img.shields.io/github/actions/workflow/status/Ramo-Y/BulkRename/build-test.yml?label=ci)
![release](https://img.shields.io/github/actions/workflow/status/Ramo-Y/BulkRename/build-test-release.yml?label=release)
[![ci](https://img.shields.io/github/actions/workflow/status/Ramo-Y/BulkRename/build-test.yml?label=ci)](https://github.com/Ramo-Y/BulkRename/actions/workflows/build-test.yml)
[![release](https://img.shields.io/github/actions/workflow/status/Ramo-Y/BulkRename/build-test-release.yml?label=release)](https://github.com/Ramo-Y/BulkRename/actions/workflows/build-test-release.yml)
[![GitHub contributors](https://img.shields.io/github/all-contributors/Ramo-Y/BulkRename)](#contributors-)
![GitHub Sponsors](https://img.shields.io/github/sponsors/Ramo-Y)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/Ramo-Y)](https://github.com/sponsors/Ramo-Y)
![GitHub Downloads](https://img.shields.io/github/downloads/Ramo-Y/BulkRename/total)
![Open Issues](https://img.shields.io/github/issues/Ramo-Y/BulkRename)
![Closed Issues](https://img.shields.io/github/issues-closed/Ramo-Y/BulkRename)
[![Open Issues](https://img.shields.io/github/issues/Ramo-Y/BulkRename)](https://github.com/Ramo-Y/BulkRename/issues)
[![Closed Issues](https://img.shields.io/github/issues-closed/Ramo-Y/BulkRename)](https://github.com/Ramo-Y/BulkRename/issues?q=is%3Aissue+is%3Aclosed)

</div>

Expand All @@ -34,6 +34,7 @@
- [Renamed series overview](#renamed-series-overview)
- [History](#history)
- [Development](#development)
- [Adding new languages](#adding-new-languages)
- [Contributors ✨](#contributors-)


Expand Down Expand Up @@ -178,6 +179,9 @@ Call up the history page by pressing **History (8)**. The first time, the page w
# Development
Please read the [development documentation](./DEVELOPMENT.md) if you would like to participate in the development.

## Adding new languages
This app can provide multiple languages and includes already the languages `English`, `German`, and `Hungarian`. A new language can be added within a few steps, to do this, please check out the [documentation](./DEVELOPMENT.md#languages).

## Contributors ✨

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
Expand Down
15 changes: 15 additions & 0 deletions src/BulkRename.IntegrationTests/Resources/LanguageResourceEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace BulkRename.IntegrationTests.Resources
{
internal class LanguageResourceEntry
{
public LanguageResourceEntry(string name, string value)
{
Name = name;
Value = value;
}

public string Name { get; }

public string Value { get; }
}
}
173 changes: 173 additions & 0 deletions src/BulkRename.IntegrationTests/Resources/LanguageResourcesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
namespace BulkRename.IntegrationTests.Resources
{
using System.Collections.Generic;
using System.Linq;
using System.Xml;

[TestFixture]
internal class LanguageResourcesTests
{
private const string APP_RESOURCES = "SharedResource";

private const string APP_RESOURCES_FILE_ENDING = "resx";

private const string DEFAULT_LANGUAGE_CODE = "en";

private const string DEFAULT_LANGUAGE_RESOURCE_FILENAME = "SharedResource.resx";

private const int LANGUAGE_CODE_LENGTH = 2;

private List<string> GetLanguageResourceFiles()
{
var files = Directory.GetFiles(@"../../../../BulkRename/Resources/", "*.resx").ToList();
return files;
}

private List<LanguageResourceEntry> ReadLanguageResourceEntries(string filePath)
{
var list = new List<LanguageResourceEntry>();

var document = new XmlDocument();
document.Load(filePath);
var node = document.GetElementsByTagName("data");
foreach (XmlElement element in node)
{
var name = element.GetAttribute("name");
var value = element.ChildNodes[1]!.InnerText;

list.Add(new LanguageResourceEntry(name, value));
}

return list;
}

private Dictionary<string, List<LanguageResourceEntry>> GetLanguageDictionary()
{
var languageDictionary = new Dictionary<string, List<LanguageResourceEntry>>();
var files = GetLanguageResourceFiles();

var defaultLanguageFile = files.Single(f => Path.GetFileName(f).Equals(DEFAULT_LANGUAGE_RESOURCE_FILENAME));
var defaultLanguageResourceEntries = ReadLanguageResourceEntries(defaultLanguageFile);
languageDictionary.Add(DEFAULT_LANGUAGE_CODE, defaultLanguageResourceEntries);

foreach (var filePath in files)
{
var fileName = Path.GetFileName(filePath);
if (fileName == DEFAULT_LANGUAGE_RESOURCE_FILENAME)
{
continue;
}

var parts = fileName.Split('.');
var language = parts[1];
var languageResourceEntries = ReadLanguageResourceEntries(filePath);

languageDictionary.Add(language, languageResourceEntries);
}

return languageDictionary;
}

[Test]
public void GetAllLanguageFiles_CheckNamingConvention_NamingIsCorrect()
{
var files = GetLanguageResourceFiles();

var defaultFileErrorMessage = $"There should be a file called '{DEFAULT_LANGUAGE_RESOURCE_FILENAME}', it actually doesn't exist";
Assert.That(files.Any(f => Path.GetFileName(f).Equals(DEFAULT_LANGUAGE_RESOURCE_FILENAME)), defaultFileErrorMessage);

foreach (var file in files)
{
var fileName = Path.GetFileName(file);
if (fileName == DEFAULT_LANGUAGE_RESOURCE_FILENAME)
{
continue;
}

var parts = fileName.Split('.');
var appResourcesPart = parts[0];
var appResourcesPartErrorMessage = $"Language resource file name has to start with '{APP_RESOURCES}', actual one is '{appResourcesPart}'";
Assert.That(appResourcesPart, Is.EqualTo(APP_RESOURCES), appResourcesPartErrorMessage);

var languageCodePart = parts[1];
var languageCodeErrorMessage = $"Language code should be {LANGUAGE_CODE_LENGTH} characters string, actual one is named '{languageCodePart}'";
Assert.That(languageCodePart, Has.Length.EqualTo(LANGUAGE_CODE_LENGTH), languageCodeErrorMessage);

var fileEndingPart = parts[2];
var fileEndingPartErrorMessage = $"The language resource file name has to end with '{APP_RESOURCES_FILE_ENDING}', actual is '{fileEndingPart}'";
Assert.That(fileEndingPart, Is.EqualTo(APP_RESOURCES_FILE_ENDING), fileEndingPartErrorMessage);
}
}

[Test]
public void ReadAllLanguageResources_CheckKeysCount_AllKeysCountAreSame()
{
// arrange
var languageDictionary = GetLanguageDictionary();

// act
languageDictionary.TryGetValue(DEFAULT_LANGUAGE_CODE, out var defaultLanguageList);
var defaultCount = defaultLanguageList!.Count;

foreach (var dictionary in languageDictionary)
{
if (dictionary.Key.Equals(DEFAULT_LANGUAGE_CODE))
{
continue;
}

var currentCount = dictionary.Value.Count;
var dictionaryCountErrorMessage = $"The language '{dictionary.Key}' should have {defaultCount} entries but actually has {currentCount} entries";

// assert
Assert.That(currentCount, Is.EqualTo(defaultCount), dictionaryCountErrorMessage);
}
}

[Test]
public void ReadAllLanguageResources_CheckAllValues_NoneOfThemIsEmpty()
{
// arrange
var languageDictionary = GetLanguageDictionary();

foreach (var dictionary in languageDictionary)
{
foreach (var entry in dictionary.Value)
{
// act
var entryHasEmptyValueErrorMessage = $"Entry with the name '{entry.Name}' has an empty value in '{dictionary.Key}' language";

// assert
Assert.That(entry.Value, Is.Not.Empty, entryHasEmptyValueErrorMessage);
}
}
}

[Test]
public void ReadAllLanguageResources_CheckOtherLanguages_AllEntriesExistInDefaultLanguage()
{
// arrange
var languageDictionary = GetLanguageDictionary();

// act
languageDictionary.TryGetValue(DEFAULT_LANGUAGE_CODE, out var defaultLanguageList);

foreach (var dictionary in languageDictionary)
{
if (dictionary.Key.Equals(DEFAULT_LANGUAGE_CODE))
{
continue;
}

foreach (var entry in dictionary.Value)
{
var exists = defaultLanguageList!.Any(l => l.Name.Equals(entry.Name));
var entryDoesntExistInDefaultLanguageMessage = $"Entry with the name '{entry.Name}' does not exist in default language but in language '{dictionary.Key}'";

// assert
Assert.That(exists, entryDoesntExistInDefaultLanguageMessage);
}
}
}
}
}
39 changes: 39 additions & 0 deletions src/BulkRename/Constants/LocalizationConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace BulkRename.Constants
{
public static class LocalizationConstants
{
public const string AN_ERROR_OCCURED_HEADER = "AnErrorOccuredHeader";

public const string DETAILED_ERROR_MESSAGE = "DetailedErrorMessage";

public const string DEVELOPMENT_MODE = "DevelopmentMode";

public const string ERROR = "Error";

public const string HISTORY = "History";

public const string HOME = "Home";

public const string LOAD_HISTORY = "LoadHistory";

public const string NEW_NAME = "NewName";

public const string OLD_NAME = "OldName";

public const string PREVIEW_RENAMING_OF_TV_SHOWS = "PreviewRenamingOfTvShows";

public const string RENAMED_ON = "RenamedOn";

public const string REQUEST = "Request";

public const string SERIES = "Series";

public const string SUBMIT_RENAMING = "Submit Renaming";

public const string SUCCESSFULLY_RENAMED_FILES = "SuccessfullyRenamedFiles";

public const string SWAPPING_TO_DEVELOPMENT_MODE_DISPLAY = "SwappingToDevelopmentModeDisplay";

public const string WELCOME_TO_BULK_RENAME = "WelcomeToBulkRename";
}
}
10 changes: 8 additions & 2 deletions src/BulkRename/Controllers/HistoryController.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
namespace BulkRename.Controllers
{
using BulkRename.Constants;
using BulkRename.Interfaces;
using BulkRename.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

public class HistoryController : Controller
{
private readonly IPersistanceService _persistanceService;

private static readonly Dictionary<string, List<Series>> _dictionary = [];

public HistoryController(IPersistanceService persistanceService)
private readonly string _renamedOn;

public HistoryController(IPersistanceService persistanceService, IStringLocalizer<SharedResource> sharedLocalizer)
{
_persistanceService = persistanceService;

_renamedOn = sharedLocalizer[LocalizationConstants.RENAMED_ON];
}

public IActionResult Index()
Expand Down Expand Up @@ -47,7 +53,7 @@ public async Task<IActionResult> LoadHistory()
});
}

var key = $"{renamingSessionToEpisode.RenamingSession.RenName}, Renamed on: {renamingSessionToEpisode.RenamingSession.RenExecutingDateTime:yyyy-MM-dd HH:mm:ss}";
var key = $"{renamingSessionToEpisode.RenamingSession.RenName}, {_renamedOn}: {renamingSessionToEpisode.RenamingSession.RenExecutingDateTime:yyyy-MM-dd HH:mm:ss}";
_dictionary.Add(key, series);
}

Expand Down
Loading

0 comments on commit 0481e1a

Please sign in to comment.