From 62c018d4c6931ec34e204da83b781a609a906ce4 Mon Sep 17 00:00:00 2001 From: Luca <106596790+RealLHI@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:47:59 +0100 Subject: [PATCH 1/2] add(formats): added new JavaProperties format (#59) --- Ashampoo-Translation-Systems.sln | 12 ++ ...Systems.Formats.JavaProperties.Test.csproj | 42 ++++ .../FormatTest.cs | 111 +++++++++++ .../GlobalUsings.cs | 1 + .../Startup.cs | 12 ++ .../_TestFiles_/messages.properties | 186 ++++++++++++++++++ .../_TestFiles_/messages_de.properties | 186 ++++++++++++++++++ ...tion.Systems.Formats.JavaProperties.csproj | 27 +++ .../JavaPropertiesBuilder.cs | 68 +++++++ .../JavaPropertiesFormat.cs | 133 +++++++++++++ LICENSE | 2 +- 11 files changed, 779 insertions(+), 1 deletion(-) create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties.Test/FormatTest.cs create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties.Test/GlobalUsings.cs create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Startup.cs create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties.Test/_TestFiles_/messages.properties create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties.Test/_TestFiles_/messages_de.properties create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties/Ashampoo.Translation.Systems.Formats.JavaProperties.csproj create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesBuilder.cs create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormat.cs diff --git a/Ashampoo-Translation-Systems.sln b/Ashampoo-Translation-Systems.sln index f471c2b..8ff73ec 100644 --- a/Ashampoo-Translation-Systems.sln +++ b/Ashampoo-Translation-Systems.sln @@ -38,6 +38,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.System EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.Systems.Formats", "src\Ashampoo.Translation.Systems.Formats\src\Ashampoo.Translation.Systems.Formats.csproj", "{53389A5D-6790-4E38-A2C5-1B0D74B76DCF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.Systems.Formats.JavaProperties", "Ashampoo.Translation.Systems.Formats.JavaProperties\Ashampoo.Translation.Systems.Formats.JavaProperties.csproj", "{4481C61F-6BF8-4FE4-8576-1FFEA0B67BF8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.Systems.Formats.JavaProperties.Test", "Ashampoo.Translation.Systems.Formats.JavaProperties.Test\Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj", "{B31DF60F-D7D6-4D9A-BE2A-3A7089E27118}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -124,5 +128,13 @@ Global {3D6B80E7-4C45-4400-A973-C468C6BB1EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {3D6B80E7-4C45-4400-A973-C468C6BB1EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {3D6B80E7-4C45-4400-A973-C468C6BB1EE6}.Release|Any CPU.Build.0 = Release|Any CPU + {4481C61F-6BF8-4FE4-8576-1FFEA0B67BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4481C61F-6BF8-4FE4-8576-1FFEA0B67BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4481C61F-6BF8-4FE4-8576-1FFEA0B67BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4481C61F-6BF8-4FE4-8576-1FFEA0B67BF8}.Release|Any CPU.Build.0 = Release|Any CPU + {B31DF60F-D7D6-4D9A-BE2A-3A7089E27118}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B31DF60F-D7D6-4D9A-BE2A-3A7089E27118}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B31DF60F-D7D6-4D9A-BE2A-3A7089E27118}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B31DF60F-D7D6-4D9A-BE2A-3A7089E27118}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj new file mode 100644 index 0000000..0eb00a8 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj @@ -0,0 +1,42 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + Always + + + Always + + + + diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/FormatTest.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/FormatTest.cs new file mode 100644 index 0000000..a4ddfa5 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/FormatTest.cs @@ -0,0 +1,111 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; +using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; +using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; + +namespace Ashampoo.Translation.Systems.Formats.JavaProperties.Test; + +public class FormatTest : FormatTestBase +{ + private readonly IFormatFactory _formatFactory; + + public FormatTest(IFormatFactory formatFactory) + { + _formatFactory = formatFactory; + } + + [Fact] + public void IsAssignableFrom() + { + IFormat format = CreateFormat(); + format.Should().BeAssignableTo(typeof(ITranslationUnits)); + } + + [Fact] + public void NewFormat() + { + IFormat format = CreateFormat(); + + format.Should().NotBeNull().And.BeEmpty(); + format.Header.SourceLanguage.Should().BeNull(); + format.Header.TargetLanguage.Should().BeEmpty(); + } + + [Fact] + public void ReadFromFile() + { + IFormat format = + CreateAndReadFromFile("messages_de.properties", new FormatReadOptions() { TargetLanguage = "de-DE" }); + const string id = "aboutTheApp"; + + foreach (var unit in format) + { + unit.Should().ContainSingle(); + } + + format.Count.Should().Be(186); + + var foundById = format[id]; + foundById.Should().NotBeNull(); + var translationString = foundById!["de-DE"] as AbstractTranslationString; + translationString.Should().NotBeNull(); + translationString!.Value.Should().Be("Über Photos"); + translationString.Comment.Should().BeNull(); + translationString.Id.Should().Be(id); + } + + [Fact] + public void ImportSuccessTest() + { + IFormat format = + CreateAndReadFromFile("messages.properties", new FormatReadOptions() { TargetLanguage = "en-US" }); + + const string id = "albums"; + const string value = "Import Test"; + + var importedWithUnits = format.ImportMockTranslationWithUnits("en-US", id); + importedWithUnits.Should().NotBeNull().And.ContainSingle(); + (format[id]?["en-US"] as ITranslationString)?.Value.Should().Be(value); + } + + [Fact] + public void NoMatchImportTest() + { + IFormat format = + CreateAndReadFromFile("messages.properties", new FormatReadOptions() { TargetLanguage = "en-US" }); + + const string id = "Not a matching Id"; + + var imported = format.ImportMockTranslationWithUnits("en-US", id); + + imported.Should().BeEmpty(); + } + + [Fact] + public void ImportEqualTranslationTest() + { + IFormat format = + CreateAndReadFromFile("messages.properties", new FormatReadOptions() { TargetLanguage = "en-US" }); + + const string id = "albums"; + const string value = "Albums"; + + var imported = format.ImportMockTranslationWithUnits("en-US", id, value); + + imported.Should().BeEmpty(); + } + + [Fact] + public async Task ConvertTest() + { + var mockFormat = + MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "Convert ID", "Convert Test"); + var assignOptions = new AssignOptions { TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }; + var javaProperties = await mockFormat.ConvertToAsync(_formatFactory, assignOptions); + + javaProperties.Should().NotBeNull().And.ContainSingle(); + javaProperties.First().Id.Should().Be("Convert ID"); + (javaProperties["Convert ID"]?["en-US"] as ITranslationString)?.Value.Should().Be("Convert Test"); + } +} \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/GlobalUsings.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Startup.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Startup.cs new file mode 100644 index 0000000..a2bcddf --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Startup.cs @@ -0,0 +1,12 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.JavaProperties.Test; + +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddFormatFactory().RegisterFormat(); + } +} \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/_TestFiles_/messages.properties b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/_TestFiles_/messages.properties new file mode 100644 index 0000000..a2e2db6 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/_TestFiles_/messages.properties @@ -0,0 +1,186 @@ +aboutTheApp=About Photos +addAnotherSource=Add another +albumCreatedHint=New album created. +albumDeleteConfirmationText=Your photos will not be deleted. +albumDeleteConfirmationTitle=Delete album? +albumDeletedHint=Album deleted. +albumRenamedHint=Album renamed. +albums=Albums +albumsEmptyPlaceholder=No albums present. +analyticsUserConsentSendErrorReportsAndStatics=Send errors & statistics +analyticsUserConsentSendNothing=Send nothing +analyticsUserConsentSendOnlyErrorReports=Send only errors +analyticsUserConsentTextPrivacyPolicyHint=For more information please see our privacy policy. +analyticsUserConsentTextReasoning=To improve the app and to find errors, we would like to collect anonymous usage statistics and error reports. +analyticsUserConsentTextSettingsHint=You can change your decision at any time in the settings. +analyticsUserConsentTitle=Help to improve this app! +androidWritePermissionConfirmationConfirm=Open system dialog +androidWritePermissionConfirmationText=To modify files, write permissions are required.\n\nPlease grant access to the following folder in the subsequent system dialog: +androidWritePermissionConfirmationTitle=Write permissions required +androidWritePermissionsUpdated=Permissions updated +appCrashed=Apologies, unfortunately, Photos has crashed! :( +appLaunchFailed=Photos failed to launch. +authFailed=Auth failed. +awaitingCode=Waiting for login in browser... +browserNotFound=Browser not found. +cancel=Cancel +cancelSync=Cancel sync +cantConnectSource=Connection failed. +changeTakenDateDialogAdjusted=Adjusted +changeTakenDateDialogConfirmButton=Set new date +changeTakenDateDialogOriginal=Original +changeTakenDateDialogSetDate=Custom +changeTakenDateDialogShiftDate=Time Shift +changeTakenDateDialogTime=Time +changeTakenDateDialogTitle=Adjust date taken +choose=Choose +chooseEditorPath=Choose your editor +choosePhotoDirectory=Choose a directory that contains your photos +chooseSaveDirectory=Choose a save directory +cloudSharingNotSupportedHint=Sharing of cloud files is not supported. +commandExecutionFailed=Command execution failed. +create=Create +december=December +deleteAlbum=Delete this album +deleteMultiplePhotos=Delete photos +deleteOnePhoto=Delete photo +deletionFailed=Deletion failed! +disconnect=Disconnect +editor=Editor +emptySearchPlaceholderAlbum=Album names +emptySearchPlaceholderCamera=Cameras such as "Canon 60D" +emptySearchPlaceholderDate=Dates such as "March" and "2010" +emptySearchPlaceholderFolder=Folder names +emptySearchPlaceholderKeyword=Keywords such as "cat" +emptySearchPlaceholderLocation=Locations such as "New York" +emptySearchPlaceholderRating=Ratings from "0" to "5" +emptySearchPlaceholderTitle=What can I search for? +february=February +feedback=Feedback +feedbackText=Do you miss a feature?\nCan we improve something?\nPlease let us know! +fileNotFound=The file is not available. Was it deleted or moved? +fileProtectionHintButtonText=Okay +fileProtectionHintText=This file is write protected and can't be updated. +fileProtectionHintTitle=Write protected file +fileSaved=Saved. +fileSaving=Saving... +focusRating=Focus rating +foldersAlbumGroup=Folders +galleryAddPhotos=Add your first photo source: +galleryNoPhotosFound=No photos found. +genericErrorMessage=An error occured. +gridStyle=Grid Style +keywordInputPlaceholder=Add keyword... +keywordsAlbumGroup=Keywords +keywordsEmptyPlaceholder=No keywords present +lastSynced=Last synced +leadDeveloper=Lead Developer +librariesWeUse=Libraries we use +librariesWeUseThanks=We thank developers and supporters of the following libraries that we use in this app. +licenseStatusCheckFailedText=Communication with the license server failed. Please check your connection and try again. +licenseStatusCheckFailedTitle=License check failed +licenseStatusExpiredText=Please connect to the Internet to renew your license. +licenseStatusExpiredTitle=License expired +licenseStatusInvalidText=Please contact our support. +licenseStatusInvalidTitle=Invalid license +licenseStatusNoLicenseText=If you already have a license, please contact our support. +licenseStatusNoLicenseTitle=No license found +licenseStatusNotCheckedTitle=Checking license... +licenseStatusViewCheckLicenseButton=Retry license check +licenseStatusViewLogoutButton=Logout +locationsAlbumGroup=Locations +loggedIn=You are now logged in. +loginAccountRequired=To get started, please log in with your existing Ashampoo account or create one for free. +loginAppPurpose=Ashampoo Photos helps you organize your photo collection. +loginInBrowser=Login in browser +managePermissions=Manage permissions +memoryWarningText=Cleaning up... +memoryWarningTitle=High memory usage! +newAlbum=New album +newAlbumTextFieldPlaceholder=Enter album title +noCameraInformation=No camera information +noLensInformation=No lens information +openFileLocation=Open file location +openUserFeedbackPortalInBrowser=Open feedback portal +openWithDefaultApp=Open with default app +openWithExternalEditor=Open with external editor +personsAlbumGroup=Persons +photoAddedToAlbumHint=Photo added to album. +photoCouldNotBeLoaded=Photo could not be loaded. +photoCount=Photo count +photoEditFeedbackText=Here we are planning photo editing tools. +photoMenu=Photo +photoSourceApplePhotos=Apple Photos +photoSourceConnect=Connect +photoSourceDisconnectConfirmationText=By disconnecting the source all photos will be removed from the app and thereby all ratings, tags and edits will be permanently deleted. +photoSourceDisconnectConfirmationTitle=Are you sure? +photoSourceDisconnectedHint=Source disconnected. +photoSourceDropbox=Dropbox +photoSourceGoogleDrive=Google Drive +photoSourceLocalFolder=Local folder +photoSourceNewConnection=New source +photoSourceOneDrive=OneDrive +photoSourceSmbFolder=Network folder +photoSourceSynologyNasFolder=Synology NAS +photoSourceSystemPhotoLibrary=Photo library +photoSourceWhatDoYouMissHere=Missing a source? +photoSourcesPlaceholder=Connect a photo source! +photos=Photos +photosAddedToAlbumHint=Photos added to album. +photosRemovedFromAlbumHint=Photos removed from album. +pleaseContactSupport=Please contact our support! +pleaseRotateYourDevice=Please rotate your device. +privacyPolicy=Privacy policy +processors=processors +ratingsAlbumGroup=Ratings +rename=Rename +renameAlbum=Rename album +runtimeEnvironment=Runtime environment +saveAs=Save as... +search=Search +searchFieldPlaceholder=Search photos +select=Select +selectAlbum=Select album +selectPhotos=Select photos +settings=Settings +settingsEmbedMetadata=Embed metadata (JPG & PNG) +settingsSendAnalyticsData=Send usage statistics +settingsSendErrorReports=Send error reports +shortcutAddToAlbum=Add photo to an album +shortcutClose=Close / Back +shortcutConfirm=Confirm +shortcutDeletePhoto=Delete photo +shortcutDeleteRating=Delete rating +shortcutRatePhoto=Rate photo +shortcutSharePhoto=Share photo +shortcutShowHideInfoBox=Show/hide info box +shortcutShowPrevNextPhoto=Show prev/next photo +shortcutTagPhoto=Tag photo +shortcutToggleFullscreen=Toggle full screen +shortcuts=Keyboard shortcuts +showAll=Show all +software=Software +sortOrder=Sort order +sourceAlreadyAdded=Source was already added. +sources=Sources +supportId=Support ID +syncNow=Sync now +syncStateStepGeocoding=Geocoding... +syncStateStepGrouping=Grouping images... +syncStateStepInitializing=Initializing sync... +syncStateStepReadingFileList=Reading file list... +syncStateStepReadingMetadata=Reading metadata... +syncStateStepThumbnailing=Creating thumbnails... +syncStatusUnknown=Never synced. +syncing=Syncing... +systemPhotoLibraryFullAccess=All photos +systemPhotoLibraryLimitedAccess=Selected photos +systemPhotoLibraryNoAccess=Access denied +takenDateChangedHint=Date taken changed. +theme=Design +themeDark=Dark +themeLight=Light +themeSystemDefault=System +tileSize=Tile Size +userAlbumGroup=My albums +welcomeToPhotos=Welcome to Photos! diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/_TestFiles_/messages_de.properties b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/_TestFiles_/messages_de.properties new file mode 100644 index 0000000..f7a6408 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/_TestFiles_/messages_de.properties @@ -0,0 +1,186 @@ +aboutTheApp=Über Photos +addAnotherSource=Weitere hinzufügen +albumCreatedHint=Neues Album erstellt. +albumDeleteConfirmationText=Deine Fotos werden nicht gelöscht. +albumDeleteConfirmationTitle=Album löschen? +albumDeletedHint=Album gelöscht. +albumRenamedHint=Album umbenannt. +albums=Alben +albumsEmptyPlaceholder=Keine Alben vorhanden +analyticsUserConsentSendErrorReportsAndStatics=Fehler & Statistiken senden +analyticsUserConsentSendNothing=Nichts senden +analyticsUserConsentSendOnlyErrorReports=Nur Fehler senden +analyticsUserConsentTextPrivacyPolicyHint=Weitere Informationen findest du in unserer Datenschutzerklärung. +analyticsUserConsentTextReasoning=Um die App zu verbessern und Fehler zu finden, würden wir gerne anonymisierte Nutzungstatistiken und Fehlerberichte erfassen. +analyticsUserConsentTextSettingsHint=Du kannst deine Entscheidung jederzeit in den Einstellungen ändern. +analyticsUserConsentTitle=Hilf die App zu verbessern! +androidWritePermissionConfirmationConfirm=System-Dialog öffnen +androidWritePermissionConfirmationText=Um Dateien zu ändern werden Schreibrechte benötigt.\n\nBitte erlaube im nachfolgenden System-Dialog Zugriff auf folgenden Ordner: +androidWritePermissionConfirmationTitle=Schreibrechte benötigt +androidWritePermissionsUpdated=Berechtigungen aktualisiert +appCrashed=Entschuldigung, leider ist Photos abgestürzt! :( +appLaunchFailed=Photos konnte nicht geladen werden. +authFailed=Anmeldung fehlgeschlagen. +awaitingCode=Warte auf Anmeldung im Browser... +browserNotFound=Es wurde kein Browser gefunden. +cancel=Abbrechen +cancelSync=Abgleich abbrechen +cantConnectSource=Verbindung fehlgeschlagen. +changeTakenDateDialogAdjusted=Angepasst +changeTakenDateDialogConfirmButton=Aufnahmedatum setzen +changeTakenDateDialogOriginal=Original +changeTakenDateDialogSetDate=Benutzerdefiniert +changeTakenDateDialogShiftDate=Zeitverschiebung +changeTakenDateDialogTime=Zeit +changeTakenDateDialogTitle=Aufnahmedatum anpassen +choose=Auswählen +chooseEditorPath=Wähle deinen Editor aus +choosePhotoDirectory=Wähle einen Ordner mit deinen Bildern aus +chooseSaveDirectory=Wähle einen Speicherort +cloudSharingNotSupportedHint=Das Teilen von Cloud-Dateien wird nicht unterstützt. +commandExecutionFailed=Befehl konnte nicht ausgeführt werden. +create=Erstellen +december=Dezember +deleteAlbum=Dieses Album löschen +deleteMultiplePhotos= Fotos löschen +deleteOnePhoto=Foto löschen +deletionFailed=Löschen fehlgeschlagen! +disconnect=Trennen +editor=Editor +emptySearchPlaceholderAlbum=Namen von Alben +emptySearchPlaceholderCamera=Kameras wie "Canon 60D" +emptySearchPlaceholderDate=Zeiträume wie "2010" und "März" +emptySearchPlaceholderFolder=Namen von Ordnern +emptySearchPlaceholderKeyword=Schlagwörter wie "Katze" +emptySearchPlaceholderLocation=Orte wie "Berlin" +emptySearchPlaceholderRating=Bewertungen von "0" bis "5" +emptySearchPlaceholderTitle=Wonach kann ich suchen? +february=Februar +feedback=Feedback +feedbackText=Vermisst du ein Feature?\nKönnen wir was verbessern?\nBitte lass es uns wissen! +fileNotFound=Die Datei ist nicht verfügbar. Wurde sie gelöscht oder verschoben? +fileProtectionHintButtonText=Okay +fileProtectionHintText=Diese Datei ist schreibgeschützt und kann nicht aktualisiert werden. +fileProtectionHintTitle=Geschützte Datei +fileSaved=Gespeichert. +fileSaving=Speichern... +focusRating=Fokusbewertung +foldersAlbumGroup=Ordner +galleryAddPhotos=Füge deine erste Fotoquelle hinzu: +galleryNoPhotosFound=Es wurden keine Fotos gefunden. +genericErrorMessage=Es ist ein Fehler aufgetreten. +gridStyle=Anordnung +keywordInputPlaceholder=Schlagwort hinzufügen... +keywordsAlbumGroup=Schlagwörter +keywordsEmptyPlaceholder=Keine Schlagwörter vorhanden +lastSynced=Letzter Abgleich +leadDeveloper=Lead Developer +librariesWeUse=Verwendete Bibliotheken +librariesWeUseThanks=Wir danken den Entwicklern und Unterstützern folgender Bibliotheken, die wir bei dieser App einsetzen. +licenseStatusCheckFailedText=Fehler bei der Kommunikation mit dem Lizenzserver. Bitte überprüfe die Internetverbindung und versuche es noch einmal. +licenseStatusCheckFailedTitle=Lizenzprüfung fehlgeschlagen +licenseStatusExpiredText=Bitte stelle eine Internetverbindung her, um die Lizenz zu erneuern. +licenseStatusExpiredTitle=Lizenz abgelaufen +licenseStatusInvalidText=Bitte kontaktiere unseren Support. +licenseStatusInvalidTitle=Ungültige Lizenz +licenseStatusNotCheckedTitle=Überprüfe Lizenz... +licenseStatusStatusNoLicenseText=Falls du bereits eine Lizenz hast, kontaktiere bitte unseren Support. +licenseStatusStatusNoLicenseTitle=Lizenz nicht gefunden +licenseStatusViewCheckLicenseButton=Lizenz erneut prüfen +licenseStatusViewLogoutButton=Ausloggen +locationsAlbumGroup=Orte +loggedIn=Du bist nun angemeldet. +loginAccountRequired=Um zu starten, melde dich bitte mit deinem bestehenden Ashampoo Konto an oder erstelle kostenlos ein neues. +loginAppPurpose=Ashampoo Photos hilft dir deine Fotosammlung zu organisieren. +loginInBrowser=Im Browser anmelden +managePermissions=Berechtigungen verwalten +memoryWarningText=Bereinige Speicher... +memoryWarningTitle=Hoher Speicherverbrauch! +newAlbum=Neues Album +newAlbumTextFieldPlaceholder=Albumtitel eingeben +noCameraInformation=Keine Kamerainformationen +noLensInformation=Keine Objektivinformationen +openFileLocation=Dateipfad öffnen +openUserFeedbackPortalInBrowser=Feedback-Portal öffnen +openWithDefaultApp=Öffnen mit Standard-App +openWithExternalEditor=Öffnen mit externem Editor +personsAlbumGroup=Personen +photoAddedToAlbumHint=Foto zu Album hinzugefügt. +photoCouldNotBeLoaded=Foto konnte nicht geladen werden. +photoCount=Anzahl Fotos +photoEditFeedbackText=Hier planen wir Tools zum Bearbeiten. +photoMenu=Foto +photoSourceApplePhotos=Apple Fotos +photoSourceConnect=Verbinden +photoSourceDisconnectConfirmationText=Durch das Trennen der Quelle werden alle Fotos aus der App entfernt und damit Bewertungen, Tags und Bearbeitungen unwiderruflich gelöscht. +photoSourceDisconnectConfirmationTitle=Bist du sicher? +photoSourceDisconnectedHint=Quelle getrennt. +photoSourceDropbox=Dropbox +photoSourceGoogleDrive=Google Drive +photoSourceLocalFolder=Lokaler Ordner +photoSourceNewConnection=Neue Quelle +photoSourceOneDrive=OneDrive +photoSourceSmbFolder=Netzwerk-Freigabe +photoSourceSynologyNasFolder=Synology NAS +photoSourceSystemPhotoLibrary=Foto-Bibliothek +photoSourceWhatDoYouMissHere=Was fehlt\ndir hier? +photoSourcesPlaceholder=Füge eine Fotoquelle hinzu! +photos=Fotos +photosAddedToAlbumHint=Fotos zu Album hinzugefügt. +photosRemovedFromAlbumHint=Fotos aus Album entfernt. +pleaseContactSupport=Bitte kontaktiere unseren Support! +pleaseRotateYourDevice=Bitte drehe dein Gerät. +privacyPolicy=Datenschutzerklärung +processors=Prozessoren +ratingsAlbumGroup=Bewertungen +rename=Umbenennen +renameAlbum=Album umbenennen +runtimeEnvironment=Laufzeitumgebung +saveAs=Speichern unter... +search=Suchen +searchFieldPlaceholder=Fotos suchen +select=Auswählen +selectAlbum=Album auswählen +selectPhotos=Fotos auswählen +settings=Einstellungen +settingsEmbedMetadata=Metadaten einbetten (JPG & PNG) +settingsSendAnalyticsData=Nutzungsstatistiken senden +settingsSendErrorReports=Fehlerberichte senden +shortcutAddToAlbum=Foto zu einem Album hinzufügen +shortcutClose=Schließen / Zurück +shortcutConfirm=Bestätigen +shortcutDeletePhoto=Foto löschen +shortcutDeleteRating=Bewertung löschen +shortcutRatePhoto=Foto bewerten +shortcutSharePhoto=Foto teilen +shortcutShowHideInfoBox=Zeige/verstecke Info-Kasten +shortcutShowPrevNextPhoto=Zeige vorheriges/nächstes Foto +shortcutTagPhoto=Foto verschlagworten +shortcutToggleFullscreen=Vollbild umschalten +shortcuts=Tastaturkürzel +showAll=Alle anzeigen +software=Software +sortOrder=Sortierung +sourceAlreadyAdded=Diese Quelle ist bereits vorhanden. +sources=Quellen +supportId=Support ID +syncNow=Jetzt abgleichen +syncStateStepGeocoding=Geocoding... +syncStateStepGrouping=Gruppiere Bilder... +syncStateStepInitializing=Starte Sync... +syncStateStepReadingFileList=Lese Dateiliste... +syncStateStepReadingMetadata=Lese Metadaten... +syncStateStepThumbnailing=Erstelle Vorschau... +syncStatusUnknown=Noch nie abgeglichen. +syncing=Synchronisiere... +systemPhotoLibraryFullAccess=Alle Fotos +systemPhotoLibraryLimitedAccess=Ausgewählte Fotos +systemPhotoLibraryNoAccess=Kein Zugriff +takenDateChangedHint=Aufnahmedatum geändert. +theme=Design +themeDark=Dunkel +themeLight=Hell +themeSystemDefault=System +tileSize=Kachelgröße +userAlbumGroup=Meine Alben +welcomeToPhotos=Willkommen bei Photos! diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties/Ashampoo.Translation.Systems.Formats.JavaProperties.csproj b/Ashampoo.Translation.Systems.Formats.JavaProperties/Ashampoo.Translation.Systems.Formats.JavaProperties.csproj new file mode 100644 index 0000000..fe444d1 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties/Ashampoo.Translation.Systems.Formats.JavaProperties.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + ash-logo-icon-big-128x.png + + + + + true + + ash-logo-icon-big-128x.png + + + true + + LICENSE + + + + + + + + diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesBuilder.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesBuilder.cs new file mode 100644 index 0000000..2e3a1b3 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesBuilder.cs @@ -0,0 +1,68 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; +using CommunityToolkit.Diagnostics; + +namespace Ashampoo.Translation.Systems.Formats.JavaProperties; + +public class JavaPropertiesBuilder : IFormatBuilderWithTarget +{ + private string _targetLanguage = string.Empty; + private readonly Dictionary _translations = new(); + + /// + public IFormat Build() + { + Guard.IsNotNullOrWhiteSpace(_targetLanguage); + + JavaPropertiesFormat format = new() + { + Header = + { + TargetLanguage = _targetLanguage + } + }; + + foreach (var translation in _translations) + { + DefaultTranslationUnit unit = new(translation.Key); + DefaultTranslationString translationString = new(translation.Key, translation.Value, _targetLanguage); + unit.Add(translationString); + format.Add(unit); + } + + return format; + } + + /// + public void Add(string id, string target) + { + _translations.Add(id, target); + } + + /// + public void SetTargetLanguage(string language) + { + _targetLanguage = language; + } + + /// + /// This is not supported by the JavaProperties format + /// + /// + /// + public void SetHeaderInformation(IFormatHeader header) + { + throw new NotSupportedException("Header information's are not supported by the JavaProperties format"); + } + + /// + /// This is not supported by the JavaProperties format + /// + /// + /// + /// + public void AddHeaderInformation(string key, string value) + { + throw new NotSupportedException("Header information's are not supported by the JavaProperties format"); + } +} \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormat.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormat.cs new file mode 100644 index 0000000..e97a893 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormat.cs @@ -0,0 +1,133 @@ +using System.Text; +using System.Text.RegularExpressions; +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.IO; +using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; +using CommunityToolkit.Diagnostics; +using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; + +namespace Ashampoo.Translation.Systems.Formats.JavaProperties; + +public partial class JavaPropertiesFormat : AbstractTranslationUnits, IFormat +{ + private static readonly Regex KeyValueRegex = MyRegex(); + public IFormatHeader Header { get; } = new DefaultFormatHeader(); + public FormatLanguageCount LanguageCount => FormatLanguageCount.OnlyTarget; + + /// + public void Read(Stream stream, FormatReadOptions? options = null) + { + ReadAsync(stream, options).Wait(); + } + + /// + public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) + { + if (!await ConfigureOptionsAsync(options)) + { + // TODO: Make options a not nullable param to avoid this "Not null" call + options!.IsCancelled = true; + return; + } + + Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage); + + using StreamReader reader = new(stream); + using LineReader lineReader = new(reader); + + await ReadTranslations(lineReader); + } + + // TODO: Can this be made Protected in a abstract class to avoid duplicate code? + private async Task ConfigureOptionsAsync(FormatReadOptions? options) + { + if (string.IsNullOrWhiteSpace(options?.TargetLanguage)) + { + Guard.IsNotNull(options?.FormatOptionsCallback); + + FormatStringOption targetLanguageOption = new("Target language", true); + FormatOptions formatOptions = new() + { + Options = new FormatOption[] + { + targetLanguageOption + } + }; + + await options.FormatOptionsCallback.Invoke(formatOptions); // Invoke callback + if (formatOptions.IsCanceled) return false; + + Header.TargetLanguage = targetLanguageOption.Value; + } + else + { + Header.TargetLanguage = options.TargetLanguage; + } + + return true; + } + + private async Task ReadTranslations(LineReader reader) + { + await reader.SkipEmptyLinesAsync(); + while (await reader.HasMoreLinesAsync()) + { + var translation = ParseLine(await reader.ReadLineAsync(), reader.LineNumber); + var unit = new DefaultTranslationUnit(translation.Id) { translation }; + Add(unit); + } + } + + private ITranslation ParseLine(string? line, int lineNumber) + { + Guard.IsNotNullOrWhiteSpace(line); + + var match = KeyValueRegex.Match(line); + if (!match.Success) + throw new UnsupportedFormatException(this, $"Unsupported line: {line} at line number {lineNumber}."); + + var id = match.Groups["key"].Value; + var value = match.Groups["value"].Value; + + + return new DefaultTranslationString(id, value, Header.TargetLanguage); + } + + /// + public void Write(Stream stream) + { + WriteAsync(stream).Wait(); + } + + /// + /// + /// + /// + public async Task WriteAsync(Stream stream) + { + await using StreamWriter writer = new(stream, Encoding.Latin1); + + foreach (var translation in this.SelectMany(translationUnit => translationUnit)) + { + if (translation is AbstractTranslationString translationString) + { + await writer.WriteLineAsync($"{translationString.Id}={translationString.Value}"); + } + } + + await writer.FlushAsync(); + } + + /// + public Func BuildFormatProvider() + { + return builder => builder.SetId("javaproperties") + .SetSupportedFileExtensions(new[] { ".properties" }) + .SetFormatType() + .SetFormatBuilder() + .Create(); + } + + [GeneratedRegex("(?.*?)=(?.*)")] + private static partial Regex MyRegex(); +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 58fb437..8cc9587 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Ashampoo GmbH & Co. KG +Copyright (c) 2024 Ashampoo GmbH & Co. KG Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From ab430a637269f0384d0ebad0c7988b6a0d391b96 Mon Sep 17 00:00:00 2001 From: Luca <106596790+RealLHI@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:06:56 +0100 Subject: [PATCH 2/2] Refactore/refactor formats (#60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: removed id from ITranslation * refactor: Removed ITranslationString, ITranslation/AbstractTranslationUnit not deriving from IEnumerable/HashSet * refactor: Removed Converters & AssignOptions, name refactoring * refactor: namings, removed ITranslationUnits * refactor: naming, removed inheritance IDictionary from IFormatHeader * Add WriteAsync to IFormat * refactor: moved SetTargetLanguage to IFormatBuilder * refactor: Removed empty Abstract class, removed unused propertys, refactor naming * refactor: made AdditionalHeaders abstract, removed unused/unimportant methods * refactor: ashlang tests rebuild to FluentAssertions * refactor: rebuild TsProj, PO, ResX, NLang on FluentAssertions * refactor: rewrite Json and Gengo tests with FluentAssertions * refactor: now running on .net8 * chore: updated all packages * refactor: add missing comments * refactor: switched to collection expressions * refactor: implementing Stronglys, first format now runs with them * Add language strongly * refactor: implement Strongly type Language * refactor: changed ToString calls on Language to Value * refactor: add IsNullOrWhitespace to Language * refactor: add comments * fix workflows * refactor: target language of FormatReadOptions not nullable, fix ConfigureOptions in POFormat * cleanup * refactor: user generics instead of assembly scanning * removed package configuration from project files * updated workflows * updated dependencies * cleanup (major) --------- Co-authored-by: Tjorven Kämpfer <62958434+tjorvenK@users.noreply.github.com> --- .github/workflows/build-and-test.yml | 6 +- .github/workflows/release-packages.yml | 41 +-- ...euse-generate-version-and-init-release.yml | 2 +- .../workflows/reuse-publish-nuget-package.yml | 6 +- Ashampoo-Translation-Systems.sln | 12 - ...Systems.Formats.JavaProperties.Test.csproj | 14 +- .../FormatTest.cs | 88 +----- .../Startup.cs | 12 - ...tion.Systems.Formats.JavaProperties.csproj | 4 +- .../DependencyInjectionExtension.cs | 31 ++ .../JavaPropertiesFormat.cs | 62 ++-- ...lder.cs => JavaPropertiesFormatBuilder.cs} | 22 +- .../JavaPropertiesFormatProvider.cs | 25 ++ ...mpoo.Translation.Systems.Components.csproj | 47 --- .../src/Components/DisplayFormat.razor | 66 ---- .../src/Components/DisplayFormat.razor.cs | 149 --------- .../src/Components/ExportFormat.razor | 6 - .../src/Components/ExportFormat.razor.cs | 66 ---- .../src/Components/UploadFormat.razor | 19 -- .../src/Components/UploadFormat.razor.cs | 120 ------- .../src/DependencyInjection.cs | 28 -- .../ConfigureFormatOptionsDialog.razor | 29 -- .../ConfigureFormatOptionsDialog.razor.cs | 35 --- .../src/Dialogs/SelectFormatDialog.razor | 18 -- .../src/Dialogs/SelectFormatDialog.razor.cs | 29 -- .../FormatUploadedNotificationHandler.cs | 32 -- ...ImportedTranslationsNotificationHandler.cs | 43 --- .../SimpleWarningNotificationHandler.cs | 32 -- .../FormatUploadedNotification.cs | 8 - .../ImportedTranslationsNotification.cs | 11 - .../src/Notifications/SimpleWarning.cs | 11 - .../src/Pages/Converter.razor | 31 -- .../src/Pages/Converter.razor.cs | 44 --- .../src/README.md | 2 - .../src/Services/FormatService.cs | 150 --------- .../src/Services/IFileService.cs | 39 --- .../src/Services/IFormatService.cs | 120 ------- .../src/Services/WebFileService.cs | 71 ----- .../src/wwwroot/Scripts/WebFileService.js | 46 --- .../src/wwwroot/background.png | Bin 378 -> 0 bytes .../src/AbstractFormatHeader.cs | 11 +- ...lation.Systems.Formats.Abstractions.csproj | 25 +- .../src/AssignOptions.cs | 29 -- .../src/DefaultFormatFactory.cs | 40 +-- .../src/DefaultFormatHeader.cs | 9 +- .../src/DefaultFormatProvider.cs | 47 --- .../src/DependencyInjection.cs | 118 ------- .../src/FormatExtensions.cs | 239 -------------- .../src/FormatOptions.cs | 20 -- .../src/FormatProviderBuilder.cs | 92 ------ .../src/FormatProviderExtensions.cs | 30 -- .../src/FormatProviderLoader.cs | 76 ----- .../src/FormatReadOptions.cs | 6 +- .../src/GlobalUsings.cs | 1 - .../src/IFormat.cs | 81 ++++- .../src/IFormatBuilder.cs | 15 +- .../src/IFormatBuilderWithSourceAndTarget.cs | 17 +- .../src/IFormatBuilderWithTarget.cs | 11 +- .../src/IFormatFactory.cs | 38 +-- .../src/IFormatHeader.cs | 14 +- .../src/IFormatProvider.cs | 17 +- .../src/IO/LineReader.cs | 22 +- ...matLanguageCount.cs => LanguageSupport.cs} | 4 +- .../src/Models/Language.cs | 35 +++ .../Translation/AbstractTranslationString.cs | 23 +- .../Translation/AbstractTranslationUnit.cs | 55 +--- .../Translation/AbstractTranslationUnits.cs | 72 ----- .../Translation/DefaultTranslationString.cs | 6 +- .../src/Translation/ITranslation.cs | 13 +- .../src/Translation/ITranslationString.cs | 12 - .../src/Translation/ITranslationUnit.cs | 83 +++-- .../TranslationFilter/AndTranslationFilter.cs | 40 --- .../DefaultTranslationFilter.cs | 24 -- .../TranslationFilter/ITranslationFilter.cs | 28 -- .../IsEmptyTranslationFilter.cs | 41 --- .../MatchIdTranslationFilter.cs | 67 ---- .../MatchValueTranslationFilter.cs | 88 ------ .../TranslationFilter/OrTranslationFilter.cs | 41 --- .../Exceptions/ParserExceptions.cs | 68 ---- .../Extensions/EnumeratorTokenExtensions.cs | 61 ---- .../src/TranslationFilterParser/Lexer.cs | 156 --------- .../src/TranslationFilterParser/Parser.cs | 295 ------------------ .../src/TranslationFilterParser/Token.cs | 61 ---- ....Systems.Formats.Abstractions.Tests.csproj | 23 -- .../tests/LanguageParserTests.cs | 34 -- .../tests/Lexer.cs | 50 --- .../tests/MatchValueFilter.cs | 56 ---- .../tests/Parser.cs | 143 --------- .../tests/Startup.cs | 10 - .../src/AshLangFormat.cs | 31 +- .../src/AshLangFormatBuilder.cs | 44 +-- .../src/AshLangFormatHeader.cs | 29 +- .../src/AshLangFormatProvider.cs | 27 ++ ...Translation.Systems.Formats.AshLang.csproj | 19 +- .../src/Chunk/ChunkWriter.cs | 20 +- .../src/Chunk/LanguageChunk.cs | 11 +- .../src/DependencyInjectionExtension.cs | 31 ++ .../src/SourceTranslationString.cs | 28 +- .../src/TargetTranslationString.cs | 27 +- .../tests/AshLangFormatTest.cs | 164 +++------- ...ation.Systems.Formats.AshLang.Tests.csproj | 14 +- .../tests/ChunkReaderTest.cs | 55 ++-- .../tests/PluginLoaderTest.cs | 20 -- .../tests/Startup.cs | 26 -- ...o.Translation.Systems.Formats.Gengo.csproj | 21 +- .../src/DependencyInjectionExtension.cs | 31 ++ .../src/GengoFormat.cs | 83 ++--- .../src/GengoFormatBuilder.cs | 46 +-- .../src/GengoFormatProvider.cs | 27 ++ ...slation.Systems.Formats.Gengo.Tests.csproj | 14 +- .../tests/FormatTest.cs | 188 +++-------- .../tests/Startup.cs | 14 - ...oo.Translation.Systems.Formats.Json.csproj | 19 +- .../src/DependencyInjectionExtension.cs | 31 ++ .../src/JsonFormat.cs | 83 ++--- .../src/JsonFormatBuilder.cs | 27 +- .../src/JsonFormatProvider.cs | 27 ++ ...nslation.Systems.Formats.Json.Tests.csproj | 14 +- .../tests/FormatTest.cs | 148 ++------- .../tests/Startup.cs | 14 - ...o.Translation.Systems.Formats.NLang.csproj | 19 +- .../src/DependencyInjectionExtension.cs | 31 ++ .../src/NLangFormat.cs | 60 ++-- .../src/NLangFormatBuilder.cs | 27 +- .../src/NLangFormatProvider.cs | 27 ++ .../src/TranslationString.cs | 7 +- .../src/TranslationUnit.cs | 2 +- ...slation.Systems.Formats.NLang.Tests.csproj | 14 +- .../tests/FormatTest.cs | 108 ++----- .../tests/Startup.cs | 14 - ...mpoo.Translation.Systems.Formats.PO.csproj | 19 +- .../src/DependencyInjectionExtension.cs | 31 ++ .../src/Message.cs | 9 +- .../src/MessageString.cs | 16 +- .../src/POFormat.cs | 50 ++- .../src/POFormatBuilder.cs | 32 +- .../src/POFormatProvider.cs | 28 ++ .../src/POHeader.cs | 24 +- .../src/TranslationUnit.cs | 2 +- ...ranslation.Systems.Formats.PO.Tests.csproj | 14 +- .../tests/FormatTest.cs | 107 +------ .../tests/Startup.cs | 14 - ...oo.Translation.Systems.Formats.ResX.csproj | 19 +- .../src/DependencyInjectionExtension.cs | 31 ++ .../src/ResXFormat.cs | 45 +-- .../src/ResXFormatBuilder.cs | 35 ++- .../src/ResXFormatProvider.cs | 27 ++ ...nslation.Systems.Formats.ResX.Tests.csproj | 14 +- .../tests/FormatTest.cs | 111 +------ .../tests/Startup.cs | 14 - ....Translation.Systems.Formats.TsProj.csproj | 19 +- .../src/DependencyInjectionExtension.cs | 31 ++ .../src/TranslationStringSource.cs | 29 +- .../src/TranslationStringTarget.cs | 24 +- .../src/TsProjFormat.cs | 81 ++--- .../src/TsProjFormatBuilder.cs | 72 +++-- .../src/TsProjFormatProvider.cs | 25 ++ .../tests/AshLangExportedTest.cs | 12 +- ...lation.Systems.Formats.TsProj.Tests.csproj | 14 +- .../tests/Startup.cs | 14 - .../tests/TsProjFormatTest.cs | 125 +------- .../tests/WebExportedTest.cs | 9 +- ...shampoo.Translation.Systems.Formats.csproj | 5 +- .../src/DependencyInjection.cs | 47 +++ ...hampoo.Translation.Systems.TestBase.csproj | 11 +- .../FormatTestBase.cs | 2 - .../Helper.cs | 16 +- .../MockFormatWithTranslationUnits.cs | 41 +-- .../MockHeader.cs | 7 +- .../MockTranslationString.cs | 7 +- 170 files changed, 1632 insertions(+), 5367 deletions(-) delete mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Startup.cs create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties/DependencyInjectionExtension.cs rename Ashampoo.Translation.Systems.Formats.JavaProperties/{JavaPropertiesBuilder.cs => JavaPropertiesFormatBuilder.cs} (68%) create mode 100644 Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormatProvider.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Ashampoo.Translation.Systems.Components.csproj delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Components/DisplayFormat.razor delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Components/DisplayFormat.razor.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Components/ExportFormat.razor delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Components/ExportFormat.razor.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Components/UploadFormat.razor delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Components/UploadFormat.razor.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/DependencyInjection.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Dialogs/ConfigureFormatOptionsDialog.razor delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Dialogs/ConfigureFormatOptionsDialog.razor.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Dialogs/SelectFormatDialog.razor delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Dialogs/SelectFormatDialog.razor.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/NotificationHandlers/FormatUploadedNotificationHandler.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/NotificationHandlers/ImportedTranslationsNotificationHandler.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/NotificationHandlers/SimpleWarningNotificationHandler.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Notifications/FormatUploadedNotification.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Notifications/ImportedTranslationsNotification.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Notifications/SimpleWarning.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Pages/Converter.razor delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Pages/Converter.razor.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/README.md delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Services/FormatService.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Services/IFileService.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Services/IFormatService.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/Services/WebFileService.cs delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/wwwroot/Scripts/WebFileService.js delete mode 100644 src/Ashampoo.Translation.Systems.Components/src/wwwroot/background.png delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/AssignOptions.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DefaultFormatProvider.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DependencyInjection.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatExtensions.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderBuilder.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderExtensions.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderLoader.cs rename src/Ashampoo.Translation.Systems.Formats.Abstractions/src/{FormatLanguageCount.cs => LanguageSupport.cs} (93%) create mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Models/Language.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationUnits.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslationString.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/AndTranslationFilter.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/DefaultTranslationFilter.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/ITranslationFilter.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/IsEmptyTranslationFilter.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/MatchIdTranslationFilter.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/MatchValueTranslationFilter.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/OrTranslationFilter.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Exceptions/ParserExceptions.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Extensions/EnumeratorTokenExtensions.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Lexer.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Parser.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Token.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Ashampoo.Translation.Systems.Formats.Abstractions.Tests.csproj delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/LanguageParserTests.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Lexer.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/MatchValueFilter.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Parser.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Startup.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatProvider.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.AshLang/src/DependencyInjectionExtension.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.AshLang/tests/PluginLoaderTest.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.AshLang/tests/Startup.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.Gengo/src/DependencyInjectionExtension.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormatProvider.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Gengo/tests/Startup.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.Json/src/DependencyInjectionExtension.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormatProvider.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.Json/tests/Startup.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.NLang/src/DependencyInjectionExtension.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormatProvider.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.NLang/tests/Startup.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.PO/src/DependencyInjectionExtension.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.PO/src/POFormatProvider.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.PO/tests/Startup.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.ResX/src/DependencyInjectionExtension.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormatProvider.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.ResX/tests/Startup.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.TsProj/src/DependencyInjectionExtension.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormatProvider.cs delete mode 100644 src/Ashampoo.Translation.Systems.Formats.TsProj/tests/Startup.cs create mode 100644 src/Ashampoo.Translation.Systems.Formats/src/DependencyInjection.cs diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 58b37c2..809918c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -12,15 +12,15 @@ on: - "**.json" env: - DOTNET_VERSION: "7.0.x" # The .NET SDK version to use + DOTNET_VERSION: "8.x" # The .NET SDK version to use jobs: build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 60c143c..cb62e1e 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -27,48 +27,11 @@ jobs: run: | TAG=${{ needs.generate-version.outputs.version }} echo ::set-output name=version::${TAG#v} - - create-release-branch: - needs: generate-version - if: ${{ contains(github.event.pull_request.labels.*.name, 'release') }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - ref: main - fetch-depth: 0 - - name: Create Release Branch - run: git checkout -b release/${{ needs.generate-version.outputs.version }} - - name: Initialize mandatory git config - run: | - git config user.name "GitHub Actions" - git config user.email noreply@github.com - - name: Push new branch - run: git push origin release/${{ needs.generate-version.outputs.version}} - set-matrix: - needs: [ generate-version ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: set-matrix - id: set-matrix - run: echo "::set-output name=matrix::$(ls src/Ashampoo.Translation.Systems.*/src/Ashampoo.Translation.Systems.*.csproj | sed -r 's/.*\/(.*).csproj$/\1/' | jq -R -s -c 'split("\n")[:-1]'))" - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - publish-nuget-packages: - needs: [ generate-version, remove-prefix-from-version ,set-matrix ] - strategy: - matrix: - project_name: ${{ fromJson(needs.set-matrix.outputs.matrix) }} + needs: [ generate-version, remove-prefix-from-version ] uses: ./.github/workflows/reuse-publish-nuget-package.yml with: - project_name: ${{ matrix.project_name }} + project_name: "Ashampoo.Translation.Systems.Formats" version: ${{ needs.remove-prefix-from-version.outputs.version }} secrets: inherit \ No newline at end of file diff --git a/.github/workflows/reuse-generate-version-and-init-release.yml b/.github/workflows/reuse-generate-version-and-init-release.yml index cfd0819..0742105 100644 --- a/.github/workflows/reuse-generate-version-and-init-release.yml +++ b/.github/workflows/reuse-generate-version-and-init-release.yml @@ -25,7 +25,7 @@ jobs: version: ${{ steps.PrereleaseVersion.outputs.version || steps.ReleaseVersion.outputs.version }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/reuse-publish-nuget-package.yml b/.github/workflows/reuse-publish-nuget-package.yml index 2bd34b2..3587eac 100644 --- a/.github/workflows/reuse-publish-nuget-package.yml +++ b/.github/workflows/reuse-publish-nuget-package.yml @@ -13,7 +13,7 @@ on: type: string env: - DOTNET_VERSION: "7.0.x" # The .NET SDK version to use + DOTNET_VERSION: "8.x" # The .NET SDK version to use jobs: publish-nuget-package: @@ -23,11 +23,11 @@ jobs: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} diff --git a/Ashampoo-Translation-Systems.sln b/Ashampoo-Translation-Systems.sln index 8ff73ec..1994a81 100644 --- a/Ashampoo-Translation-Systems.sln +++ b/Ashampoo-Translation-Systems.sln @@ -22,10 +22,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.System EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.Systems.Formats.TsProj.Tests", "src\Ashampoo.Translation.Systems.Formats.TsProj\tests\Ashampoo.Translation.Systems.Formats.TsProj.Tests.csproj", "{4DDC2740-501A-4760-8384-21B9D2F994DD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.Systems.Components", "src\Ashampoo.Translation.Systems.Components\src\Ashampoo.Translation.Systems.Components.csproj", "{D8C6BB19-A1D1-4CEC-A605-31B8A2C58FDC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.Systems.Formats.Abstractions.Tests", "src\Ashampoo.Translation.Systems.Formats.Abstractions\tests\Ashampoo.Translation.Systems.Formats.Abstractions.Tests.csproj", "{5C56057B-2927-4CB0-B9E2-13154699CD8E}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.Systems.TestBase", "src\tests\Ashampoo.Translation.Systems.TestBase\Ashampoo.Translation.Systems.TestBase.csproj", "{44657DFF-FC50-40E9-82E8-8FC9E4EDE1B4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ashampoo.Translation.Systems.Formats.ResX.Tests", "src\Ashampoo.Translation.Systems.Formats.ResX\tests\Ashampoo.Translation.Systems.Formats.ResX.Tests.csproj", "{E0532127-BDF8-4E7A-9A4E-A41C17B6B27E}" @@ -92,14 +88,6 @@ Global {4DDC2740-501A-4760-8384-21B9D2F994DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DDC2740-501A-4760-8384-21B9D2F994DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DDC2740-501A-4760-8384-21B9D2F994DD}.Release|Any CPU.Build.0 = Release|Any CPU - {D8C6BB19-A1D1-4CEC-A605-31B8A2C58FDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D8C6BB19-A1D1-4CEC-A605-31B8A2C58FDC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D8C6BB19-A1D1-4CEC-A605-31B8A2C58FDC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D8C6BB19-A1D1-4CEC-A605-31B8A2C58FDC}.Release|Any CPU.Build.0 = Release|Any CPU - {5C56057B-2927-4CB0-B9E2-13154699CD8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C56057B-2927-4CB0-B9E2-13154699CD8E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C56057B-2927-4CB0-B9E2-13154699CD8E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C56057B-2927-4CB0-B9E2-13154699CD8E}.Release|Any CPU.Build.0 = Release|Any CPU {44657DFF-FC50-40E9-82E8-8FC9E4EDE1B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {44657DFF-FC50-40E9-82E8-8FC9E4EDE1B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {44657DFF-FC50-40E9-82E8-8FC9E4EDE1B4}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj index 0eb00a8..75065aa 100644 --- a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Ashampoo.Translation.Systems.Formats.JavaProperties.Test.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable @@ -11,14 +11,10 @@ - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/FormatTest.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/FormatTest.cs index a4ddfa5..8147dd2 100644 --- a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/FormatTest.cs +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/FormatTest.cs @@ -1,6 +1,6 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; using Ashampoo.Translation.Systems.TestBase; using FluentAssertions; @@ -8,104 +8,36 @@ namespace Ashampoo.Translation.Systems.Formats.JavaProperties.Test; public class FormatTest : FormatTestBase { - private readonly IFormatFactory _formatFactory; - - public FormatTest(IFormatFactory formatFactory) - { - _formatFactory = formatFactory; - } - - [Fact] - public void IsAssignableFrom() - { - IFormat format = CreateFormat(); - format.Should().BeAssignableTo(typeof(ITranslationUnits)); - } - [Fact] public void NewFormat() { IFormat format = CreateFormat(); - format.Should().NotBeNull().And.BeEmpty(); + format.Should().NotBeNull(); + format.TranslationUnits.Should().BeEmpty(); format.Header.SourceLanguage.Should().BeNull(); - format.Header.TargetLanguage.Should().BeEmpty(); + format.Header.TargetLanguage.Value.Should().BeEmpty(); } [Fact] public void ReadFromFile() { IFormat format = - CreateAndReadFromFile("messages_de.properties", new FormatReadOptions() { TargetLanguage = "de-DE" }); + CreateAndReadFromFile("messages_de.properties", new FormatReadOptions() { TargetLanguage = new Language("de-DE") }); const string id = "aboutTheApp"; - foreach (var unit in format) + foreach (var unit in format.TranslationUnits) { - unit.Should().ContainSingle(); + unit.Translations.Should().ContainSingle(); } - format.Count.Should().Be(186); + format.TranslationUnits.Count.Should().Be(186); - var foundById = format[id]; + var foundById = format.TranslationUnits.GetTranslationUnit(id); foundById.Should().NotBeNull(); - var translationString = foundById!["de-DE"] as AbstractTranslationString; + var translationString = foundById.Translations.GetTranslation(new Language("de-DE")); translationString.Should().NotBeNull(); translationString!.Value.Should().Be("Über Photos"); translationString.Comment.Should().BeNull(); - translationString.Id.Should().Be(id); - } - - [Fact] - public void ImportSuccessTest() - { - IFormat format = - CreateAndReadFromFile("messages.properties", new FormatReadOptions() { TargetLanguage = "en-US" }); - - const string id = "albums"; - const string value = "Import Test"; - - var importedWithUnits = format.ImportMockTranslationWithUnits("en-US", id); - importedWithUnits.Should().NotBeNull().And.ContainSingle(); - (format[id]?["en-US"] as ITranslationString)?.Value.Should().Be(value); - } - - [Fact] - public void NoMatchImportTest() - { - IFormat format = - CreateAndReadFromFile("messages.properties", new FormatReadOptions() { TargetLanguage = "en-US" }); - - const string id = "Not a matching Id"; - - var imported = format.ImportMockTranslationWithUnits("en-US", id); - - imported.Should().BeEmpty(); - } - - [Fact] - public void ImportEqualTranslationTest() - { - IFormat format = - CreateAndReadFromFile("messages.properties", new FormatReadOptions() { TargetLanguage = "en-US" }); - - const string id = "albums"; - const string value = "Albums"; - - var imported = format.ImportMockTranslationWithUnits("en-US", id, value); - - imported.Should().BeEmpty(); - } - - [Fact] - public async Task ConvertTest() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "Convert ID", "Convert Test"); - var assignOptions = new AssignOptions { TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }; - var javaProperties = await mockFormat.ConvertToAsync(_formatFactory, assignOptions); - - javaProperties.Should().NotBeNull().And.ContainSingle(); - javaProperties.First().Id.Should().Be("Convert ID"); - (javaProperties["Convert ID"]?["en-US"] as ITranslationString)?.Value.Should().Be("Convert Test"); } } \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Startup.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Startup.cs deleted file mode 100644 index a2bcddf..0000000 --- a/Ashampoo.Translation.Systems.Formats.JavaProperties.Test/Startup.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions; -using Microsoft.Extensions.DependencyInjection; - -namespace Ashampoo.Translation.Systems.Formats.JavaProperties.Test; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddFormatFactory().RegisterFormat(); - } -} \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties/Ashampoo.Translation.Systems.Formats.JavaProperties.csproj b/Ashampoo.Translation.Systems.Formats.JavaProperties/Ashampoo.Translation.Systems.Formats.JavaProperties.csproj index fe444d1..163d193 100644 --- a/Ashampoo.Translation.Systems.Formats.JavaProperties/Ashampoo.Translation.Systems.Formats.JavaProperties.csproj +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties/Ashampoo.Translation.Systems.Formats.JavaProperties.csproj @@ -1,10 +1,10 @@ - net7.0 + net8.0 enable enable - ash-logo-icon-big-128x.png + true diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties/DependencyInjectionExtension.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties/DependencyInjectionExtension.cs new file mode 100644 index 0000000..2806023 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties/DependencyInjectionExtension.cs @@ -0,0 +1,31 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.JavaProperties; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services for the Gengo format. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection AddJavaPropertiesFormatFeatures(this IServiceCollection services) + { + services.AddSingleton() + .AddSingleton>(sp => sp.GetRequiredService()) + .AddSingleton>(sp => sp.GetRequiredService()); + + return services; + } +} \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormat.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormat.cs index e97a893..b9c82ed 100644 --- a/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormat.cs +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormat.cs @@ -2,17 +2,27 @@ using System.Text.RegularExpressions; using Ashampoo.Translation.Systems.Formats.Abstractions; using Ashampoo.Translation.Systems.Formats.Abstractions.IO; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using CommunityToolkit.Diagnostics; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.Formats.JavaProperties; -public partial class JavaPropertiesFormat : AbstractTranslationUnits, IFormat +/// +/// Represents a Java properties file format. +/// +public partial class JavaPropertiesFormat : IFormat { private static readonly Regex KeyValueRegex = MyRegex(); + + /// public IFormatHeader Header { get; } = new DefaultFormatHeader(); - public FormatLanguageCount LanguageCount => FormatLanguageCount.OnlyTarget; + + /// + public LanguageSupport LanguageSupport => LanguageSupport.OnlyTarget; + + /// + public ICollection TranslationUnits { get; } = new List(); /// public void Read(Stream stream, FormatReadOptions? options = null) @@ -30,7 +40,7 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) return; } - Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage); + Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage.Value); using StreamReader reader = new(stream); using LineReader lineReader = new(reader); @@ -41,27 +51,27 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) // TODO: Can this be made Protected in a abstract class to avoid duplicate code? private async Task ConfigureOptionsAsync(FormatReadOptions? options) { - if (string.IsNullOrWhiteSpace(options?.TargetLanguage)) + if (string.IsNullOrWhiteSpace(options?.TargetLanguage.Value)) { Guard.IsNotNull(options?.FormatOptionsCallback); FormatStringOption targetLanguageOption = new("Target language", true); FormatOptions formatOptions = new() { - Options = new FormatOption[] - { + Options = + [ targetLanguageOption - } + ] }; await options.FormatOptionsCallback.Invoke(formatOptions); // Invoke callback if (formatOptions.IsCanceled) return false; - Header.TargetLanguage = targetLanguageOption.Value; + Header.TargetLanguage = Language.Parse(targetLanguageOption.Value); } else { - Header.TargetLanguage = options.TargetLanguage; + Header.TargetLanguage = (Language)options.TargetLanguage!; } return true; @@ -72,13 +82,11 @@ private async Task ReadTranslations(LineReader reader) await reader.SkipEmptyLinesAsync(); while (await reader.HasMoreLinesAsync()) { - var translation = ParseLine(await reader.ReadLineAsync(), reader.LineNumber); - var unit = new DefaultTranslationUnit(translation.Id) { translation }; - Add(unit); + TranslationUnits.Add(ParseLine(await reader.ReadLineAsync(), reader.LineNumber)); } } - private ITranslation ParseLine(string? line, int lineNumber) + private ITranslationUnit ParseLine(string? line, int lineNumber) { Guard.IsNotNullOrWhiteSpace(line); @@ -89,8 +97,14 @@ private ITranslation ParseLine(string? line, int lineNumber) var id = match.Groups["key"].Value; var value = match.Groups["value"].Value; - - return new DefaultTranslationString(id, value, Header.TargetLanguage); + var translation = new DefaultTranslationString(id, value, Header.TargetLanguage); + return new DefaultTranslationUnit(id) + { + Translations = + { + translation + } + }; } /// @@ -107,27 +121,17 @@ public async Task WriteAsync(Stream stream) { await using StreamWriter writer = new(stream, Encoding.Latin1); - foreach (var translation in this.SelectMany(translationUnit => translationUnit)) + foreach (var translationUnit in TranslationUnits) { - if (translation is AbstractTranslationString translationString) + foreach (var translation in translationUnit.Translations) { - await writer.WriteLineAsync($"{translationString.Id}={translationString.Value}"); + await writer.WriteLineAsync($"{translationUnit.Id}={translation.Value}"); } } await writer.FlushAsync(); } - /// - public Func BuildFormatProvider() - { - return builder => builder.SetId("javaproperties") - .SetSupportedFileExtensions(new[] { ".properties" }) - .SetFormatType() - .SetFormatBuilder() - .Create(); - } - [GeneratedRegex("(?.*?)=(?.*)")] private static partial Regex MyRegex(); } \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesBuilder.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormatBuilder.cs similarity index 68% rename from Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesBuilder.cs rename to Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormatBuilder.cs index 2e3a1b3..cdcfb6a 100644 --- a/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesBuilder.cs +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormatBuilder.cs @@ -1,18 +1,22 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using CommunityToolkit.Diagnostics; namespace Ashampoo.Translation.Systems.Formats.JavaProperties; -public class JavaPropertiesBuilder : IFormatBuilderWithTarget +/// +/// Builder for the . +/// +public class JavaPropertiesFormatBuilder : IFormatBuilderWithTarget { - private string _targetLanguage = string.Empty; + private Language _targetLanguage = Language.Empty; private readonly Dictionary _translations = new(); /// - public IFormat Build() + public JavaPropertiesFormat Build() { - Guard.IsNotNullOrWhiteSpace(_targetLanguage); + Guard.IsNotNullOrWhiteSpace(_targetLanguage.Value); JavaPropertiesFormat format = new() { @@ -26,8 +30,8 @@ public IFormat Build() { DefaultTranslationUnit unit = new(translation.Key); DefaultTranslationString translationString = new(translation.Key, translation.Value, _targetLanguage); - unit.Add(translationString); - format.Add(unit); + unit.Translations.Add(translationString); + format.TranslationUnits.Add(unit); } return format; @@ -40,7 +44,7 @@ public void Add(string id, string target) } /// - public void SetTargetLanguage(string language) + public void SetTargetLanguage(Language language) { _targetLanguage = language; } @@ -52,7 +56,7 @@ public void SetTargetLanguage(string language) /// public void SetHeaderInformation(IFormatHeader header) { - throw new NotSupportedException("Header information's are not supported by the JavaProperties format"); + // Do nothing, JavaPropertiesFormat does not support header information } /// @@ -63,6 +67,6 @@ public void SetHeaderInformation(IFormatHeader header) /// public void AddHeaderInformation(string key, string value) { - throw new NotSupportedException("Header information's are not supported by the JavaProperties format"); + // Do nothing, JavaPropertiesFormat does not support header information } } \ No newline at end of file diff --git a/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormatProvider.cs b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormatProvider.cs new file mode 100644 index 0000000..dc5e260 --- /dev/null +++ b/Ashampoo.Translation.Systems.Formats.JavaProperties/JavaPropertiesFormatProvider.cs @@ -0,0 +1,25 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; + +namespace Ashampoo.Translation.Systems.Formats.JavaProperties; + +/// +/// The format provider for the Java properties format. +/// +public sealed class JavaPropertiesFormatProvider : IFormatProvider +{ + /// + public string Id { get; } = "javaProperties"; + /// + public JavaPropertiesFormat Create() => new(); + + /// + public bool SupportsFileName(string fileName) + { + return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + } + + /// + public string[] SupportedFileExtensions { get; } = [".properties"]; + /// + public IFormatBuilder GetFormatBuilder() => new JavaPropertiesFormatBuilder(); +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Components/src/Ashampoo.Translation.Systems.Components.csproj b/src/Ashampoo.Translation.Systems.Components/src/Ashampoo.Translation.Systems.Components.csproj deleted file mode 100644 index 96bb625..0000000 --- a/src/Ashampoo.Translation.Systems.Components/src/Ashampoo.Translation.Systems.Components.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - net7.0 - enable - enable - Ashampoo.Translation.Systems.Components - tjorvenK - ashampoo - Package containg basic blazor components and services for creating a converter. - LICENSE - localization;blazor;razor;ASP.Net,c#,dotnet - true - true - true - true - snupkg - ash-logo-icon-big-128x.png - v - true - prerelease - README.md - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Components/src/Components/DisplayFormat.razor b/src/Ashampoo.Translation.Systems.Components/src/Components/DisplayFormat.razor deleted file mode 100644 index 29b5ba4..0000000 --- a/src/Ashampoo.Translation.Systems.Components/src/Components/DisplayFormat.razor +++ /dev/null @@ -1,66 +0,0 @@ -@using Ashampoo.Translation.Systems.Formats.Abstractions -@using Ashampoo.Translation.Systems.Formats.Abstractions.Translation -@using MudBlazor - -@* ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract *@ -@if (Format is not null) -{ - - - - - - - - - ID - - @if (!string.IsNullOrWhiteSpace(sourceLanguage)) - { - - - Soure: @Format.Header.SourceLanguage - - - } - - - Target: @Format.Header.TargetLanguage - - - - - - - - @if (!string.IsNullOrWhiteSpace(sourceLanguage)) - { - - - - } - - - - - - @context.Id - @if (Format.LanguageCount != FormatLanguageCount.OnlyTarget) - { - - - - } - - - - - - - - -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Components/src/Components/DisplayFormat.razor.cs b/src/Ashampoo.Translation.Systems.Components/src/Components/DisplayFormat.razor.cs deleted file mode 100644 index 342c83e..0000000 --- a/src/Ashampoo.Translation.Systems.Components/src/Components/DisplayFormat.razor.cs +++ /dev/null @@ -1,149 +0,0 @@ -using Ashampoo.Translation.Systems.Components.Services; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Microsoft.AspNetCore.Components; - -namespace Ashampoo.Translation.Systems.Components.Components; - -/// -/// Component for displaying translations of a format in a table. -/// -public partial class DisplayFormat : ComponentBase -{ - /// - /// The format to display, passed to the component as a parameter. - /// - [Parameter] public IFormat Format { get; set; } = default!; - - [Inject] private IFormatService FormatService { get; init; } = default!; - - private string sourceLanguage = default!; - private string targetLanguage = default!; - - private string searchString = ""; - private bool filterForEmptyTranslations; - private ITranslationUnit selectedTranslation = default!; - private (string, string) backupTranslations = ("", ""); - - /// - /// Get the source and target languages from the instance in . - /// - /// - /// A representing an asynchronous operation. - public override async Task SetParametersAsync(ParameterView parameters) - { - if (parameters.TryGetValue(nameof(Format), out var format)) - { - if (format is not null) - { - if (format.Header.SourceLanguage is null && format.LanguageCount != FormatLanguageCount.OnlyTarget) - throw new ArgumentNullException(nameof(format.Header.SourceLanguage)); - - sourceLanguage = format.Header.SourceLanguage ?? ""; - targetLanguage = format.Header.TargetLanguage; - } - } - - await base.SetParametersAsync(parameters); - } - - /// - /// Save the original translations before editing. - /// - /// - private void BackupUnit(object element) - { - if (element is not ITranslationUnit unit) throw new ArgumentException("Expected ITranslationUnit."); - - var source = (unit.TryGet(sourceLanguage) as ITranslationString)?.Value ?? ""; - var target = (unit[targetLanguage] as ITranslationString)?.Value ?? ""; - - backupTranslations = (source, target); - } - - /// - /// Reset the current changes. - /// - /// - private void ResetTranslations(object element) - { - if (element is ITranslationUnit translationUnit) - { - if (!string.IsNullOrWhiteSpace(sourceLanguage)) - translationUnit.AsTranslationString(sourceLanguage).Value = backupTranslations.Item1; - - translationUnit.AsTranslationString(targetLanguage).Value = backupTranslations.Item2; - } - else - throw new ArgumentException("Expected ITranslationUnit."); - } - - /// - /// Change the languages of translations. - /// - /// - /// - private async Task SwitchLanguage(string oldLanguage) - { - await FormatService.SwitchLanguageAsync(Format, oldLanguage); - - sourceLanguage = Format.Header.SourceLanguage ?? ""; - targetLanguage = Format.Header.TargetLanguage; - } - - /// - /// Required filter function from MudBlazor - /// - /// - /// if the or one of the translations contains the search string; - /// otherwise - /// - private bool FilterFunc1(ITranslationUnit unit) => FilterFunc(unit, searchString); - - /// - /// Filter function for table. - /// - /// - /// The translation unit to filter. - /// - /// - /// The search string. - /// - /// - /// if the or one of the translations contains ; - /// otherwise . - /// - private bool FilterFunc(ITranslationUnit unit, string search) - { - if (filterForEmptyTranslations) - return string.IsNullOrWhiteSpace((unit.TryGet(targetLanguage) as ITranslationString)?.Value); - - if (unit.Id.Contains(search)) return true; - if ((unit.TryGet(sourceLanguage) as ITranslationString)?.Value.Contains(search) ?? false) return true; - return (unit.TryGet(targetLanguage) as ITranslationString)?.Value.Contains(search) ?? false; - } -} - -/// -/// Provides extension methods for . -/// -public static class TranslationExtensions -{ - /// - /// Get the translation string for the given language. - /// - /// - /// The translation unit. - /// - /// - /// The language to get the translation string for. - /// - /// - /// The translation string for the given language. - /// - public static ITranslationString AsTranslationString(this ITranslationUnit unit, string language) - { - var translation = unit[language]; - return (ITranslationString)translation; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Components/src/Components/ExportFormat.razor b/src/Ashampoo.Translation.Systems.Components/src/Components/ExportFormat.razor deleted file mode 100644 index 4f7f31b..0000000 --- a/src/Ashampoo.Translation.Systems.Components/src/Components/ExportFormat.razor +++ /dev/null @@ -1,6 +0,0 @@ -@using MudBlazor - - - Export File - \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Components/src/Components/ExportFormat.razor.cs b/src/Ashampoo.Translation.Systems.Components/src/Components/ExportFormat.razor.cs deleted file mode 100644 index 60ba613..0000000 --- a/src/Ashampoo.Translation.Systems.Components/src/Components/ExportFormat.razor.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Ashampoo.Translation.Systems.Components.Services; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using Microsoft.AspNetCore.Components; - -namespace Ashampoo.Translation.Systems.Components.Components; - -/// -/// Component for exporting a format to a file. -/// -public partial class ExportFormat : ComponentBase -{ - /// - /// The format to export, passed in from the parent component as a parameter. - /// - [Parameter] public IFormat? Format { get; set; } - - /// - /// The name of the file to export to. - /// - [Parameter] public string FileName { get; set; } = ""; - - [Inject] private IFormatService FormatService { get; init; } = default!; - - [Inject] private IFormatFactory FormatFactory { get; init; } = default!; - - [Inject] private IFileService FileService { get; init; } = default!; - - /// - /// Export the to a file and save it to the computer. - /// - /// A representing an asynchronous operation. - private async Task SaveFile() - { - if (Format is null) return; - - var newFormatId = await FormatService.GetFormatIdAsync(); // Get a new format id. - if (newFormatId == null) return; - - // Convert the format into the new format - var convertedFormat = await FormatService.ConvertToAsync(Format, newFormatId, FormatOptionsCallback); - if (convertedFormat.Count == 0) return; - - // Get the format provider for the new format - var formatProvider = FormatFactory.GetFormatProvider(convertedFormat); - - var ms = new MemoryStream(); - convertedFormat.Write(ms); // Write the converted format to the memory stream for async purposes. - ms.Position = 0; - - var fileExtension = formatProvider.SupportedFileExtensions; // Get the file extension for the new format. - - await FileService.SaveFile(ms, FileName, fileExtension); // Save the file to the computer. - } - - /// - /// Callback to configure the options of the . - /// - /// - /// - private async Task FormatOptionsCallback(FormatOptions options) - { - await FormatService.ConfigureFormatOptionsAsync(options); - - return !options.IsCanceled; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Components/src/Components/UploadFormat.razor b/src/Ashampoo.Translation.Systems.Components/src/Components/UploadFormat.razor deleted file mode 100644 index bc5d3ae..0000000 --- a/src/Ashampoo.Translation.Systems.Components/src/Components/UploadFormat.razor +++ /dev/null @@ -1,19 +0,0 @@ -@using Microsoft.AspNetCore.Components.Forms -@using MudBlazor - public class DefaultFormatFactory : IFormatFactory { - private readonly Dictionary formatProviders; + private readonly Dictionary> _formatProviders; /// /// Initializes a new instance of the class. @@ -13,66 +13,50 @@ public class DefaultFormatFactory : IFormatFactory /// /// The format providers to use. /// - public DefaultFormatFactory(IEnumerable formatProviders) + public DefaultFormatFactory(IEnumerable> formatProviders) { - this.formatProviders = formatProviders.ToDictionary(formatProvider => formatProvider.Id.ToLower(), + _formatProviders = formatProviders.ToDictionary(formatProvider => formatProvider.Id.ToLower(), formatProvider => formatProvider); } /// public IFormat CreateFormat(string formatId) { - return formatProviders[formatId.ToLower()].Create(); + return _formatProviders[formatId.ToLower()].Create(); } /// - public IFormatProvider GetFormatProvider(IFormat format) + public IFormatProvider GetFormatProvider(T format) where T : class, IFormat { return TryGetFormatProvider(format) ?? throw new Exception("Format provider not found"); // TODO: throw specific exception } /// - public IFormatProvider GetFormatProvider(string formatId) + public IFormatProvider GetFormatProvider(string formatId) { return TryGetFormatProvider(formatId) ?? throw new Exception("Format provider not found"); // TODO: throw specific exception } /// - public IEnumerable GetFormatProviders() => formatProviders.Values; + public IEnumerable> GetFormatProviders() => _formatProviders.Values; /// public IFormat? TryCreateFormatByFileName(string fileName) { // Test every format provider to see if it can handle the file name. Return the first one that can. - return formatProviders.Values.FirstOrDefault(provider => provider.SupportsFileName(fileName))?.Create(); + return _formatProviders.Values.FirstOrDefault(provider => provider.SupportsFileName(fileName))?.Create(); } /// - public IFormatProvider? TryGetFormatProvider(IFormat format) + public IFormatProvider? TryGetFormatProvider(T format) where T : class, IFormat { - return formatProviders.Values.FirstOrDefault(provider => provider.FormatType == format.GetType()); + return _formatProviders.Values.OfType>().FirstOrDefault(); } /// - public IFormatProvider? TryGetFormatProvider(string formatId) + public IFormatProvider? TryGetFormatProvider(string formatId) { - return formatProviders.Values.FirstOrDefault(provider => + return _formatProviders.Values.FirstOrDefault(provider => string.Equals(provider.Id, formatId, StringComparison.CurrentCultureIgnoreCase)); } - - - /// - public IFormatProvider GetFormatProvider(Type formatType) - { - if (!typeof(IFormat).IsAssignableFrom(formatType)) - throw new ArgumentException($"Expected Type of IFormat, got: {formatType} instead."); - return TryGetFormatProvider(formatType) ?? throw new Exception( "Format provider not found"); // TODO: throw specific exception - } - - - /// - public IFormatProvider? TryGetFormatProvider(Type formatType) - { - return formatProviders.Values.FirstOrDefault(provider => provider.FormatType == formatType); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DefaultFormatHeader.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DefaultFormatHeader.cs index 84f4da5..0088bf1 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DefaultFormatHeader.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DefaultFormatHeader.cs @@ -1,3 +1,5 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// @@ -6,8 +8,11 @@ namespace Ashampoo.Translation.Systems.Formats.Abstractions; public class DefaultFormatHeader : AbstractFormatHeader { /// - public override string TargetLanguage { get; set; } = ""; + public override Language TargetLanguage { get; set; } = Language.Empty; + + /// + public override Language? SourceLanguage { get; set; } /// - public override string? SourceLanguage { get; set; } + public override Dictionary AdditionalHeaders { get; set; } = []; } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DefaultFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DefaultFormatProvider.cs deleted file mode 100644 index 885794b..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DefaultFormatProvider.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Ashampoo.Translation.Systems.Formats.Abstractions; - -/// -/// Default implementation of the interface . -/// -internal class DefaultFormatProvider : IFormatProvider -{ - public DefaultFormatProvider - ( - string id, - string[] supportedFileExtensions, - Type formatType, - Type formatBuilderType - ) - { - Id = id; - SupportedFileExtensions = supportedFileExtensions; - FormatType = formatType; - FormatBuilderType = formatBuilderType; - } - - public string Id { get; } - - public string[] SupportedFileExtensions { get; } - - public Type FormatType { get; } - public Type FormatBuilderType { get; } - - public IFormat Create() - { - // create new instance from type - return Activator.CreateInstance(FormatType) as IFormat ?? - throw new InvalidOperationException("Could not create instance of format."); - } - - public IFormatBuilder GetFormatBuilder() - { - // create new instance from type - return Activator.CreateInstance(FormatBuilderType) as IFormatBuilder ?? - throw new InvalidOperationException("Could not create instance of format builder."); - } - - public bool SupportsFileName(string fileName) - { - return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DependencyInjection.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DependencyInjection.cs deleted file mode 100644 index 223b08d..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/DependencyInjection.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions; - -/// -/// Static class that contains extension methods for . -/// -public static class DependencyInjection -{ - /// - /// Register the in the . - /// - /// - /// The to add the to. - /// - /// - /// The so that additional calls can be chained. - /// - public static IServiceCollection AddFormatFactory(this IServiceCollection services) - { - services.AddSingleton(); - - return services; - } - - /// - /// Options class for configuring the service registration of . - /// - public class FormatFactoryOptions - { - /// - /// A list of strings representing - /// file paths to plugins that will be loaded. - /// - public List PluginPaths { get; } = new(); - } - - /// - /// Extension method that registers the in the . - /// - /// - /// The to add the to. - /// - /// - /// The options to configure the . - /// - /// - /// The so that additional calls can be chained. - /// - public static IServiceCollection AddFormatFactory(this IServiceCollection services, - Action configuration) - { - FormatFactoryOptions options = new(); - configuration(options); - - services.AddSingleton(sp => - { - var loggerFactory = sp.GetRequiredService(); - var logger = loggerFactory.CreateLogger(); - - var loader = new FormatProviderLoader(logger); - foreach (var path in options.PluginPaths) - { - loader.LoadPlugins(path); - } - - return new DefaultFormatFactory(loader.FormatProviders); - }); - return services; - } - - /// - /// Registers all necessary services and the format implementations. - /// - /// - /// The to register the services with. - /// - /// - /// The for chaining. - /// - /// - /// Thrown if something went wrong during the registration. - /// - public static IServiceCollection RegisterFormats(this IServiceCollection services) - { - services.AddFormatFactory(configuration => - { - var path = Path.GetDirectoryName(typeof(DependencyInjection).Assembly.Location); - - if (string.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException(nameof(path), "Assembly location could not be found."); - - configuration.PluginPaths.Add(path); - }); - return services; - } - - /// - /// Register the given with the service collection. - /// - /// - /// The to add the to. - /// - /// - /// The type of to register. - /// - /// - /// The so that additional calls can be chained. - /// - public static IServiceCollection RegisterFormat(this IServiceCollection services) where T : IFormat - { - var format = Activator.CreateInstance(); - services.AddFormatProvider(format.BuildFormatProvider()); - - return services; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatExtensions.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatExtensions.cs deleted file mode 100644 index eb0f4c9..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatExtensions.cs +++ /dev/null @@ -1,239 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; -using CommunityToolkit.Diagnostics; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions; - -/// -/// Provides extension methods for the interface. -/// -public static class FormatExtensions -{ - /// - /// Validates if the generic is implemented by the format and returns it or throws an exception otherwise. - /// - /// - /// - /// - /// - public static T GetRequired(this IFormat format) - { - if (format is T result) return result; - throw new UnsupportedFormatException(format, - $"Interface '{typeof(T).FullName}' not supported by format '{format.GetType().FullName}'"); - } - - /// - /// Imports translations from another format into the calling one - /// - /// - /// - /// - public static IList ImportFrom(this IFormat format, ITranslationUnits formatToImport) - { - List imported = new(); - foreach (var translationUnit in formatToImport) - { - foreach (var translation in translationUnit) - { - var id = translationUnit.Id; - var language = translation.Language; - var value = (translation as ITranslationString)?.Value; - if (value is null) throw new Exception("Expected translation string"); - - if (format[id]?.TryGet(language) is not ITranslationString translationString) continue; - if (translationString.Value.Equals(value)) continue; - - translationString.Value = value; - imported.Add(translation); - } - } - - return imported; - } - - /// - /// Convert current instance of IFormat into the IFormat specified by the type. - /// - /// - /// - /// - /// - /// - /// - /// - /// A new instance of . - public static IFormat ConvertTo(this IFormat format, Type type, IFormatFactory formatFactory, - AssignOptions options) - { - return ConvertToAsync(format, type, formatFactory, options).Result; - } - - /// - /// Convert current instance of IFormat into the IFormat specified by the type. - /// - /// - /// - /// - /// - /// - /// - /// - /// A representing an asynchronous operation. - public static async Task ConvertToAsync(this IFormat format, Type type, IFormatFactory formatFactory, - AssignOptions options) - { - var formatBuilder = formatFactory.GetFormatProvider(type).GetFormatBuilder(); - var formatToConvertTo = formatBuilder switch - { - IFormatBuilderWithTarget targetBuilder => await BuildWithTarget(format, targetBuilder, options), - IFormatBuilderWithSourceAndTarget targetAndSourceBuilder => await BuildWithSourceAndTarget(format, - targetAndSourceBuilder, options), - _ => throw new NotImplementedException() - }; - - return formatToConvertTo; - } - - /// - /// Convert current instance of IFormat into the IFormat specified by the type. - /// - /// - /// - /// - /// - /// - /// - /// - /// A new instance of IFormat. - public static T ConvertTo(this IFormat format, IFormatFactory formatFactory, - AssignOptions options) where T : IFormat - { - var converted = ConvertToAsync(format, typeof(T), formatFactory, options).Result; - if (converted is not T result) throw new ArgumentException("Expected format of type " + typeof(T).FullName); - - return result; - } - - /// - /// Convert current instance of IFormat into the IFormat specified by the type. - /// - /// - /// - /// - /// - /// - /// - /// - /// A representing an asynchronous operation. - public static async Task ConvertToAsync(this IFormat format, IFormatFactory formatFactory, - AssignOptions options) where T : IFormat - { - var converted = await ConvertToAsync(format, typeof(T), formatFactory, options); - if (converted is not T result) throw new ArgumentException("Expected format of type " + typeof(T).FullName); - - return result; - } - - private static async Task BuildWithTarget - ( - IFormat format, - IFormatBuilderWithTarget formatBuilder, - AssignOptions options - ) - { - if (!await ConfigureOptions(false, options)) throw new InvalidOperationException(); - - var targetLanguage = options.TargetLanguage; - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); - formatBuilder.SetTargetLanguage(targetLanguage); - - var filter = options.Filter ?? new DefaultTranslationFilter(); - - foreach (var unit in format) - { - if (!filter.IsValid(unit)) continue; - - var id = unit.Id; - var translationString = unit.TryGet(targetLanguage) as ITranslationString; - - formatBuilder.Add(id, translationString?.Value ?? string.Empty); - } - - return formatBuilder.Build(); - } - - private static async Task BuildWithSourceAndTarget - ( - IFormat format, - IFormatBuilderWithSourceAndTarget formatBuilder, - AssignOptions options - ) - { - if (!await ConfigureOptions(true, options)) throw new InvalidOperationException(); - var filter = options.Filter ?? new DefaultTranslationFilter(); - - var targetLanguage = options.TargetLanguage; - var sourceLanguage = options.SourceLanguage; - - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); - Guard.IsNotNullOrWhiteSpace(sourceLanguage, nameof(sourceLanguage)); - - formatBuilder.SetTargetLanguage(targetLanguage); - formatBuilder.SetSourceLanguage(sourceLanguage); - formatBuilder.SetHeaderInformation(format.Header); - - foreach (var unit in format) - { - if (!filter.IsValid(unit)) continue; - - var id = unit.Id; - var targetTranslationString = unit.TryGet(targetLanguage) as ITranslationString; - var sourceTranslationString = unit.TryGet(sourceLanguage) as ITranslationString; - - formatBuilder.Add(id, sourceTranslationString?.Value ?? string.Empty, - targetTranslationString?.Value ?? string.Empty); - } - - return formatBuilder.Build(); - } - - private static async Task ConfigureOptions(bool sourceAndTarget, AssignOptions? assignOptions) - { - var setTargetLanguage = string.IsNullOrWhiteSpace(assignOptions?.TargetLanguage); - var setSourceLanguage = string.IsNullOrWhiteSpace(assignOptions?.SourceLanguage) && sourceAndTarget; - var setFilter = assignOptions?.Filter is null; - - if (!setTargetLanguage && !setSourceLanguage && !setFilter) return true; - - if (assignOptions?.FormatOptionsCallback is null) - throw new InvalidOperationException("Callback for Format options required."); - - FormatStringOption targetLanguageOption = new("Target language", true); - FormatStringOption sourceLanguageOption = new("Source language", true); - FormatFilterOption onlyUntranslated = new("Only untranslated"); - - List optionList = new(); - - if (setSourceLanguage) optionList.Add(sourceLanguageOption); - if (setTargetLanguage) optionList.Add(targetLanguageOption); - if (setFilter) optionList.Add(onlyUntranslated); - - var formatOptions = new FormatOptions - { - Options = optionList.ToArray() - }; - - await assignOptions.FormatOptionsCallback.Invoke(formatOptions); - if (formatOptions.IsCanceled) return false; - - if (setTargetLanguage) assignOptions.TargetLanguage = targetLanguageOption.Value; - if (setSourceLanguage) assignOptions.SourceLanguage = sourceLanguageOption.Value; - if (setFilter) - assignOptions.Filter = onlyUntranslated.SetFilter - ? new IsEmptyTranslationFilter(targetLanguageOption.Value) - : assignOptions.Filter; - - return true; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatOptions.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatOptions.cs index c253d76..2c565e3 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatOptions.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatOptions.cs @@ -28,26 +28,6 @@ public record FormatStringOption(string Name, bool Required = false) : FormatOpt public string Value { get; set; } = ""; } -/// -/// Format option for a boolean value. -/// Indicates whether a -/// -/// should be applied. -/// -/// -/// -/// -/// -/// -/// -public record FormatFilterOption(string Name, bool Required = false) : FormatOption(Name, Required) -{ - /// - /// The value of the option. - /// - public bool SetFilter { get; set; } = Required; -} - /// /// Configuration object for a containing a list of FormatOptions. /// diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderBuilder.cs deleted file mode 100644 index 7225cc0..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderBuilder.cs +++ /dev/null @@ -1,92 +0,0 @@ -namespace Ashampoo.Translation.Systems.Formats.Abstractions; - -/// -/// Builder to create an . -/// -public class FormatProviderBuilder -{ - private string formatProviderId = ""; - private string[] supportedFileExtensions = Array.Empty(); - private Type formatType = default!; - private Type formatBuilderType = default!; - - /// - /// Sets the format provider id. - /// - /// - /// The id to set. - /// - /// - /// The current instance of to allow chaining. - /// - public FormatProviderBuilder SetId(string id) - { - formatProviderId = id; - return this; - } - - /// - /// Sets the supported file extensions. - /// - /// - /// The file extensions to set. - /// - /// - /// The current instance of to allow chaining. - /// - public FormatProviderBuilder SetSupportedFileExtensions(string[] fileExtensions) - { - this.supportedFileExtensions = fileExtensions; - return this; - } - - /// - /// Sets the format type. - /// - /// - /// The type of the . - /// - /// - /// The current instance of to allow chaining. - /// - public FormatProviderBuilder SetFormatType() where T : IFormat - { - formatType = typeof(T); - return this; - } - - /// - /// Sets the format builder type. - /// - /// - /// The type of the . - /// - /// - /// The current instance of to allow chaining. - /// - public FormatProviderBuilder SetFormatBuilder() where T : IFormatBuilder - { - formatBuilderType = typeof(T); - return this; - } - - /// - /// Builds the . - /// - /// - /// The . - /// - /// - /// Thrown if the format provider id is not set, the format type is not set, - /// or the supported file extensions are not set. - /// - public IFormatProvider Create() - { - if (string.IsNullOrEmpty(formatProviderId)) throw new InvalidOperationException("formatProviderId must be set"); - if (supportedFileExtensions is null || supportedFileExtensions.Length == 0) - throw new InvalidOperationException("supportedFileExtensions must be set"); - if (formatType is null) throw new InvalidOperationException("formatType must be set"); - - return new DefaultFormatProvider(formatProviderId, supportedFileExtensions, formatType, formatBuilderType); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderExtensions.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderExtensions.cs deleted file mode 100644 index c9ff4a0..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions; - -/// -/// Static class that contains extension methods for . -/// -public static class FormatProviderExtensions -{ - /// - /// Adds the format provider to the service collection. - /// - /// - /// The service collection. - /// - /// - /// Function that creates the format provider. - /// - /// - public static IServiceCollection AddFormatProvider(this IServiceCollection services, - Func formatProviderFactory) - { - services.AddSingleton(_ => - { - var builder = new FormatProviderBuilder(); - return formatProviderFactory(builder); - }); - return services; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderLoader.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderLoader.cs deleted file mode 100644 index d23812d..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatProviderLoader.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Reflection; -using System.Runtime.Loader; -using Microsoft.Extensions.Logging; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions; - -internal class FormatProviderLoader -{ - private readonly ILogger logger; - public List FormatProviders { get; } = new(); - - public FormatProviderLoader(ILogger logger) - { - this.logger = logger; - } - - public void LoadPlugins(string path) - { - // Load all the assemblies in the given path - var fileNames = - Directory.GetFiles(path, "Ashampoo.Translation.Systems.Formats.*.dll", SearchOption.TopDirectoryOnly) - .Select(p => Path.GetFileNameWithoutExtension(p)).ToArray(); - - foreach (var filename in fileNames) - { - var assembly = LoadAssembly(filename); - var provider = CreateFormatProvider(assembly); - FormatProviders.AddRange(provider); - } - } - - private Assembly LoadAssembly(string fileName) - { - logger.LogInformation("loading assembly {FileName}", fileName); - - var assemblyName = new AssemblyName(fileName); - return AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName); - } - - private IEnumerable CreateFormatProvider(Assembly assembly) - { - var builder = new FormatProviderBuilder(); - foreach (var type in assembly.GetTypes()) - { - if (typeof(IFormat).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract) - { - if (Activator.CreateInstance(type) is not IFormat format) continue; - yield return format.BuildFormatProvider()(builder); - } - } - } -} - -internal class PluginLoadContext : AssemblyLoadContext -{ - private readonly AssemblyDependencyResolver resolver; - - public PluginLoadContext(string pluginPath) - { - resolver = new AssemblyDependencyResolver(pluginPath); - } - - protected override Assembly? Load(AssemblyName assemblyName) - { - var assemblyPath = resolver.ResolveAssemblyToPath(assemblyName); - - return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null; - } - - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - var libraryPath = resolver.ResolveUnmanagedDllToPath(unmanagedDllName); - - return libraryPath != null ? LoadUnmanagedDllFromPath(libraryPath) : IntPtr.Zero; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatReadOptions.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatReadOptions.cs index 20f7b0d..2928b06 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatReadOptions.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatReadOptions.cs @@ -1,3 +1,5 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// @@ -8,11 +10,11 @@ public class FormatReadOptions /// /// The target language of the format. /// - public string? TargetLanguage { get; init; } + public Language TargetLanguage { get; init; } = Language.Empty; /// /// The source language of the format. /// - public string? SourceLanguage { get; init; } + public Language? SourceLanguage { get; init; } /// /// Indicates whether the read has been cancelled. diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/GlobalUsings.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/GlobalUsings.cs index 053b13e..8d2d750 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/GlobalUsings.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/GlobalUsings.cs @@ -4,4 +4,3 @@ global using System.IO; global using System.Threading.Tasks; global using System.Text.RegularExpressions; -global using System.Text; \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormat.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormat.cs index 1c0b974..00f0331 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormat.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormat.cs @@ -1,13 +1,14 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; +using System.Diagnostics.CodeAnalysis; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; +using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// /// Interface for a translation format. /// -public interface IFormat : ITranslationUnits +public interface IFormat { - /// /// Reads the format from the given stream. /// @@ -41,15 +42,11 @@ void Read(Stream stream, FormatReadOptions? options = null) // TODO: require opt void Write(Stream stream); /// - /// A function to build a for this format. + /// Writes the format to the given stream asynchronously. /// - /// - /// A function that takes a and returns a new . - /// - /// - /// If the format does not support building a . - /// - Func BuildFormatProvider(); + /// + /// + Task WriteAsync(Stream stream); /// /// The containing the header information for this format. @@ -59,5 +56,65 @@ void Read(Stream stream, FormatReadOptions? options = null) // TODO: require opt /// /// Information about how many languages the format can handle. /// - FormatLanguageCount LanguageCount { get; } + LanguageSupport LanguageSupport { get; } + + /// + /// Gets the collection of translation units associated with the format. + /// + ICollection TranslationUnits { get; } +} + +/// +/// Provides extension methods for collections of translation units. +/// +public static class TranslationUnitCollectionExtensions +{ + /// + /// Tries to get a translation unit from the collection by its ID. + /// + /// The collection of translation units. + /// The ID of the translation unit to get. + /// When this method returns, contains the translation unit with the specified ID, if found; otherwise, null. + /// true if a translation unit with the specified ID is found; otherwise, false. + public static bool TryGetTranslationUnit(this ICollection translationUnits, string id, + [NotNullWhen(true)] out ITranslationUnit? translationUnit) + { + var foundTranslationUnit = translationUnits.FirstOrDefault(t => t.Id == id); + if (foundTranslationUnit is null) + { + translationUnit = null; + return false; + } + + translationUnit = foundTranslationUnit; + return true; + } + + /// + /// Gets a translation unit from the collection by its ID. + /// + /// The collection of translation units. + /// The ID of the translation unit to get. + /// The translation unit with the specified ID. + /// No translation unit with the specified ID is found. + public static ITranslationUnit GetTranslationUnit(this ICollection translationUnits, string id) => + translationUnits.First(t => t.Id == id); + + /// + /// Adds a new translation to the collection or updates an existing one. + /// + /// The collection of translations. + /// The language of the translation to add or update. + /// The new translation. + public static void AddOrUpdateTranslationUnit(this ICollection translations, Language language, ITranslation value) + { + var existingTranslation = translations.FirstOrDefault(t => t.Language == language); + + if (existingTranslation is not null) + { + translations.Remove(existingTranslation); + } + + translations.Add(value); + } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilder.cs index 021b7ae..f575c11 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilder.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilder.cs @@ -1,9 +1,11 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// /// Interface for a builder for a . /// -public interface IFormatBuilder +public interface IFormatBuilder where T : class, IFormat { /// /// Builds the instance of . @@ -11,9 +13,8 @@ public interface IFormatBuilder /// /// The instance of . /// - IFormat Build(); + T Build(); - /// /// Set the header information. All information will be added to the header and will overwrite /// existing information. All previous information will be removed. @@ -33,4 +34,12 @@ public interface IFormatBuilder /// The value of the information. /// void AddHeaderInformation(string key, string value); + + /// + /// Set the target language. + /// + /// + /// The language to set. + /// + void SetTargetLanguage(Language language); } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilderWithSourceAndTarget.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilderWithSourceAndTarget.cs index c4319a3..aef66ed 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilderWithSourceAndTarget.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilderWithSourceAndTarget.cs @@ -1,9 +1,12 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// -/// Interface for a for formats that support two languages. +/// Interface for a for formats that support two languages. /// -public interface IFormatBuilderWithSourceAndTarget : IFormatBuilder +/// is of type . +public interface IFormatBuilderWithSourceAndTarget : IFormatBuilder where T : class, IFormat { /// /// Add a translation for the source and target language. @@ -18,18 +21,12 @@ public interface IFormatBuilderWithSourceAndTarget : IFormatBuilder /// The target translation. /// void Add(string id, string source, string target); + /// /// Set the source language. /// /// /// The language to set. /// - void SetSourceLanguage(string language); - /// - /// Set the target language. - /// - /// - /// The language to set. - /// - void SetTargetLanguage(string language); + void SetSourceLanguage(Language language); } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilderWithTarget.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilderWithTarget.cs index d928a8d..b31099a 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilderWithTarget.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatBuilderWithTarget.cs @@ -1,9 +1,9 @@ namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// -/// Interface for a for formats that only support one language. +/// Interface for a for formats that only support one language. /// -public interface IFormatBuilderWithTarget : IFormatBuilder +public interface IFormatBuilderWithTarget : IFormatBuilder where T : class, IFormat { /// /// Add a translation for the target language. @@ -15,11 +15,4 @@ public interface IFormatBuilderWithTarget : IFormatBuilder /// The translation. /// void Add(string id, string target); - /// - /// Set the target language. - /// - /// - /// The language to set. - /// - void SetTargetLanguage(string language); } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatFactory.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatFactory.cs index de5afa7..55b5b45 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatFactory.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatFactory.cs @@ -11,7 +11,7 @@ public interface IFormatFactory /// /// A collection of all available format providers. /// - IEnumerable GetFormatProviders(); + IEnumerable> GetFormatProviders(); /// /// Creates a new instance of by the given . @@ -45,7 +45,7 @@ public interface IFormatFactory /// /// Thrown if the format provider for the specified format is not found. /// - IFormatProvider GetFormatProvider(IFormat format); + IFormatProvider GetFormatProvider(T format) where T : class, IFormat; /// /// Try to get the for the specified . @@ -56,7 +56,7 @@ public interface IFormatFactory /// /// The for the specified , or null if not found. /// - IFormatProvider? TryGetFormatProvider(IFormat format); + IFormatProvider? TryGetFormatProvider(T format) where T : class, IFormat ; /// /// Gets the format provider for a format with the specified id. @@ -68,7 +68,7 @@ public interface IFormatFactory /// /// Thrown if the format provider for the specified format is not found. /// - IFormatProvider GetFormatProvider(string formatId); + IFormatProvider GetFormatProvider(string formatId); /// /// Try to get the for the specified format id. @@ -79,33 +79,5 @@ public interface IFormatFactory /// /// The for the specified format id, or null if not found. /// - IFormatProvider? TryGetFormatProvider(string formatId); - - /// - /// Get the for the specified . - /// - /// - /// The format type to get the format provider for. - /// - /// - /// The for the specified . - /// - /// - /// Thrown if the specified is not a supported format. - /// - /// - /// Thrown if the format provider for the specified format is not found. - /// - IFormatProvider GetFormatProvider(Type formatType); - - /// - /// Try to get the for the specified . - /// - /// - /// The format type to get the format provider for. - /// - /// - /// The for the specified , or null if not found. - /// - IFormatProvider? TryGetFormatProvider(Type formatType); + IFormatProvider? TryGetFormatProvider(string formatId); } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatHeader.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatHeader.cs index 0a2df48..d8c7ed7 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatHeader.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatHeader.cs @@ -1,16 +1,24 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// /// Interface for a header containing information about an . /// -public interface IFormatHeader : IDictionary +public interface IFormatHeader { /// /// The target language of the . /// - string TargetLanguage { get; set; } + Language TargetLanguage { get; set; } + /// /// The source language of the . /// - string? SourceLanguage { get; set; } + Language? SourceLanguage { get; set; } + + /// + /// Gets or sets the additional headers associated with the format. + /// + Dictionary AdditionalHeaders { get; set; } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatProvider.cs index 6f74635..57722d6 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatProvider.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IFormatProvider.cs @@ -4,7 +4,7 @@ namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// Interface for a provider for a specific . /// Contains additional information about the format. /// -public interface IFormatProvider +public interface IFormatProvider where T : class, IFormat { /// /// Returns the id of the format provider. @@ -15,7 +15,7 @@ public interface IFormatProvider /// Creates a new instance of the format. /// /// - IFormat Create(); + T Create(); /// /// Returns true if the format provider supports the given file name, otherwise false. @@ -29,20 +29,9 @@ public interface IFormatProvider /// string[] SupportedFileExtensions { get; } - /// - /// Returns the type of the format implementation. - /// - Type FormatType { get; } - - /// - /// Returns the type of the format builder implementation. - /// - /// - Type FormatBuilderType { get; } - /// /// Creates a new instance of the format builder. /// /// - IFormatBuilder GetFormatBuilder(); + IFormatBuilder GetFormatBuilder(); } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IO/LineReader.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IO/LineReader.cs index e9dccac..09a414d 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IO/LineReader.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/IO/LineReader.cs @@ -6,8 +6,8 @@ namespace Ashampoo.Translation.Systems.Formats.Abstractions.IO; /// public class LineReader : IDisposable { - private readonly TextReader textReader; - private string? lastLine; + private readonly TextReader _textReader; + private string? _lastLine; /// /// The number of the line that is currently being read. @@ -23,7 +23,7 @@ public class LineReader : IDisposable public LineReader(TextReader reader) { // This should create a Thread-Safe StreamReader. - textReader = TextReader.Synchronized(reader); + _textReader = TextReader.Synchronized(reader); } /// @@ -44,14 +44,14 @@ public LineReader(TextReader reader) /// public async Task ReadLineAsync() { - if (lastLine is null) + if (_lastLine is null) { LineNumber++; - return await textReader.ReadLineAsync(); + return await _textReader.ReadLineAsync(); } - var line = lastLine; - lastLine = null; + var line = _lastLine; + _lastLine = null; return line; } @@ -75,12 +75,12 @@ public LineReader(TextReader reader) /// public async Task PeekLineAsync() { - if (lastLine is not null) return lastLine; + if (_lastLine is not null) return _lastLine; LineNumber++; - lastLine = await textReader.ReadLineAsync(); + _lastLine = await _textReader.ReadLineAsync(); - return lastLine; + return _lastLine; } /// @@ -112,7 +112,7 @@ EndOfStream won't work with all async. stream implementations. /// public void Dispose() { - textReader.Dispose(); + _textReader.Dispose(); GC.SuppressFinalize(this); } diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatLanguageCount.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/LanguageSupport.cs similarity index 93% rename from src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatLanguageCount.cs rename to src/Ashampoo.Translation.Systems.Formats.Abstractions/src/LanguageSupport.cs index ef7b94d..571e657 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/FormatLanguageCount.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/LanguageSupport.cs @@ -3,16 +3,18 @@ namespace Ashampoo.Translation.Systems.Formats.Abstractions; /// /// Enum to differentiate between different translation format types. /// -public enum FormatLanguageCount +public enum LanguageSupport { /// /// Indicates that the format only has support for one language. /// OnlyTarget, + /// /// Indicates that the format has support for two languages. /// SourceAndTarget, + /// /// Indicates that the format has support for multiple languages. /// diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Models/Language.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Models/Language.cs new file mode 100644 index 0000000..8548974 --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Models/Language.cs @@ -0,0 +1,35 @@ +using StronglyTypedIds; + +namespace Ashampoo.Translation.Systems.Formats.Abstractions.Models; + +/// +/// Represents a strongly typed identifier for a language. +/// +[StronglyTypedId(Template.String)] +public readonly partial struct Language; + +/// +/// Provides extension methods for the Language struct. +/// +public static class LanguageExtensions +{ + /// + /// Checks if the Language instance is null or its value is whitespace. + /// + /// The Language instance to check. + /// True if the Language instance is null or its value is whitespace, otherwise false. + public static bool IsNullOrWhitespace(this Language? language) + { + return string.IsNullOrWhiteSpace(language?.Value); + } + + /// + /// Checks if the value of the Language instance is whitespace. + /// + /// The Language instance to check. + /// True if the value of the Language instance is whitespace, otherwise false. + public static bool IsNullOrWhitespace(this Language language) + { + return string.IsNullOrWhiteSpace(language.Value); + } +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationString.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationString.cs index 5e2b450..1f9ebd8 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationString.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationString.cs @@ -1,24 +1,25 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions.Translation; /// -/// Abstract base class for the interface. +/// Abstract base class for the interface. /// -public abstract class AbstractTranslationString : ITranslationString +public abstract class AbstractTranslationString : ITranslation { /// - public virtual string Value { get; set; } = ""; - - /// - public virtual string Id { get; set; } = ""; + public string Value { get; set; } - /// - public virtual string? Comment { get; set; } + /// + /// Gets or sets the ID of the translation string. + /// + protected string Id { get; set; } /// - public virtual bool IsEmpty => string.IsNullOrWhiteSpace(Value); + public string? Comment { get; set; } /// - public virtual string Language { get; set; } = ""; + public Language Language { get; set; } /// /// Base constructor for the class. @@ -35,7 +36,7 @@ public abstract class AbstractTranslationString : ITranslationString /// /// The comment of the translation string. /// - protected AbstractTranslationString(string id, string value, string language, string? comment = null) + protected AbstractTranslationString(string id, string value, Language language, string? comment = null) { Id = id; Value = value; diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationUnit.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationUnit.cs index 49845e7..6c13a0a 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationUnit.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationUnit.cs @@ -3,9 +3,8 @@ namespace Ashampoo.Translation.Systems.Formats.Abstractions.Translation; /// /// Abstract base class for the interface. /// -public abstract class AbstractTranslationUnit : HashSet, ITranslationUnit +public abstract class AbstractTranslationUnit : ITranslationUnit { - /// /// Base constructor for the class. /// @@ -13,61 +12,13 @@ public abstract class AbstractTranslationUnit : HashSet, ITranslat /// The id of the translation unit. /// protected AbstractTranslationUnit(string id) - : base(new TranslationUnitEqualityComparer()) { Id = id; } /// public string Id { get; init; } - - /// - /// Get or set the translation for the given language. - /// - /// The language of the you are trying to get or set. - /// The you are trying to set. - /// The is null or whitespace or the ist null. - /// The could not be added. - public ITranslation this[string language] - { - get { return this.First(x => x.Language == language); } - set - { - if (string.IsNullOrWhiteSpace(language)) - throw new ArgumentNullException($"AbstractTranslationUnit: {nameof(language)} can not be null."); - if (value is null) - throw new ArgumentNullException($"AbstractTranslationUnit: {nameof(value)} can not be null."); - if (Add(value)) return; - - RemoveWhere(x => x.Language == language); - if (!Add(value)) - throw new InvalidOperationException( - $"AbstractTranslationUnit: not able to add translation {value.Id}."); - } - } - - /// - /// Try to get the translation for the given language. - /// - /// The language of the you are trying to get. - /// The for the given language, or null if it could not be found. - public ITranslation? TryGet(string language) - { - return this.FirstOrDefault(x => x.Language == language); - } -} - -internal class TranslationUnitEqualityComparer : IEqualityComparer -{ - public bool Equals(ITranslation? x, ITranslation? y) - { - if (x is null || y is null) return false; - return x.Language == y.Language; // Translation is unique by language. - } - - public int GetHashCode(ITranslation obj) - { - return HashCode.Combine(obj.Language); - } + /// + public ICollection Translations { get; } = new HashSet(); } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationUnits.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationUnits.cs deleted file mode 100644 index 296c8ff..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/AbstractTranslationUnits.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -/// -/// Abstract base class for the interface. -/// -public abstract class AbstractTranslationUnits : HashSet -{ - /// - /// Base constructor for the class. - /// - /// - /// The collection of objects - /// to add to the object. - /// - protected AbstractTranslationUnits(IEnumerable collection) - : base(collection, new TranslationUnitsEqualityComparer()) - { - } - - /// - /// Base constructor for the class. - /// - protected AbstractTranslationUnits() - : base(new TranslationUnitsEqualityComparer()) - { - } - - /// - /// The id of the object. - /// - public virtual string Id { get; init; } = ""; - - /// - /// Get or set the translation unit for the given id. - /// - /// The id of the - /// The you are trying to set. - /// The id is null or whitespace, or the value is null. - /// The could not be added. - public ITranslationUnit? this[string id] - { - //TODO: dont return null if not found, make try get instead - get { return this.FirstOrDefault(x => x.Id == id); } - set - { - if (string.IsNullOrWhiteSpace(id)) - throw new ArgumentNullException($"AbstractTranslationUnits: {nameof(id)} can not be null."); - if (value is null) - throw new ArgumentNullException($"AbstractTranslationUnits: {nameof(value)} can not be null"); - - if (Add(value)) return; - RemoveWhere(x => x.Id == id); - if (!Add(value)) - throw new InvalidOperationException( - $"AbstractTranslationUnits: not able to add translation unit {value.Id}."); - } - } -} - -internal class TranslationUnitsEqualityComparer : IEqualityComparer -{ - public bool Equals(ITranslationUnit? x, ITranslationUnit? y) - { - if (x is null || y is null) return false; - return x.Id == y.Id; // TranslationUnit is unique by id - } - - public int GetHashCode(ITranslationUnit obj) - { - return HashCode.Combine(obj.Id); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/DefaultTranslationString.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/DefaultTranslationString.cs index f4f8dda..bf31016 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/DefaultTranslationString.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/DefaultTranslationString.cs @@ -1,7 +1,9 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions.Translation; /// -/// Default implementation of the interface. +/// Default implementation of the interface. /// public class DefaultTranslationString : AbstractTranslationString { @@ -20,7 +22,7 @@ public class DefaultTranslationString : AbstractTranslationString /// /// The comment of the translation string. /// - public DefaultTranslationString(string id, string value, string language, string? comment = null) : base(id, value, + public DefaultTranslationString(string id, string value, Language language, string? comment = null) : base(id, value, language, comment) { } diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslation.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslation.cs index ecd3054..ad44257 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslation.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslation.cs @@ -1,3 +1,5 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions.Translation; /// @@ -6,22 +8,17 @@ namespace Ashampoo.Translation.Systems.Formats.Abstractions.Translation; public interface ITranslation { /// - /// Unique id of the translation. + /// The value of the translation. /// - string Id { get; } + string Value { get; set; } /// /// Optional comment for this translation. /// string? Comment { get; set; } - /// - /// Indicates whether this translation is empty. - /// - bool IsEmpty { get; } - /// /// The language of the translation. /// - string Language { get; set; } + Language Language { get; set; } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslationString.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslationString.cs deleted file mode 100644 index 5ef4b91..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslationString.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -/// -/// Interface for translations that only have a single value. -/// -public interface ITranslationString : ITranslation -{ - /// - /// The value of the translation. - /// - string Value { get; set; } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslationUnit.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslationUnit.cs index dcf38e2..340450e 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslationUnit.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/Translation/ITranslationUnit.cs @@ -1,52 +1,75 @@ +using System.Diagnostics.CodeAnalysis; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.Abstractions.Translation; /// /// Interface for a container that contains . /// -public interface ITranslationUnit : IEnumerable +public interface ITranslationUnit { /// /// The id of the translation unit. /// string Id { get; } + /// - /// The count of translations in the translation unit. - /// - int Count { get; } - - /// - /// Gets the with the specified language. + /// Gets the collection of translations associated with the translation unit. /// - /// - /// The language of the translation. - /// - ITranslation this[string language] { get; set; } - /// - /// Try to get the with the specified language. - /// - /// - /// The language of the translation. - /// - /// - /// The with the specified language. - /// - ITranslation? TryGet(string language); + ICollection Translations { get; } } /// -/// Interface for a container that contains . +/// Provides extension methods for collections of translations. /// -public interface ITranslationUnits : IEnumerable +public static class TranslationCollectionExtensions { /// - /// The count of translation units. + /// Tries to get a translation from the collection by its language. /// - int Count { get; } + /// The collection of translations. + /// The language of the translation to get. + /// When this method returns, contains the translation with the specified language, if found; otherwise, null. + /// true if a translation with the specified language is found; otherwise, false. + public static bool TryGetTranslation(this ICollection translations, Language language, + [NotNullWhen(true)] out ITranslation? translation) + { + var foundTranslation = translations.FirstOrDefault(t => t.Language == language); + if (foundTranslation is null) + { + translation = null; + return false; + } + + translation = foundTranslation; + return true; + } + + /// + /// Gets a translation from the collection by its language. + /// + /// The collection of translations. + /// The language of the translation to get. + /// The translation with the specified language. + /// No translation with the specified language is found. + public static ITranslation GetTranslation(this ICollection translations, Language language) => + translations.First(t => t.Language == language); + /// - /// Gets the with the specified id. + /// Adds a new translation to the collection or updates an existing one. /// - /// - /// The id of the . - /// - ITranslationUnit? this[string id] { get; set; } + /// The collection of translations. + /// The language of the translation to add or update. + /// The new translation. + public static void AddOrUpdateTranslation(this ICollection translations, Language language, ITranslation value) + { + var existingTranslation = translations.FirstOrDefault(t => t.Language == language); + + if (existingTranslation is not null) + { + translations.Remove(existingTranslation); + } + + translations.Add(value); + } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/AndTranslationFilter.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/AndTranslationFilter.cs deleted file mode 100644 index 0820004..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/AndTranslationFilter.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; - -/// -/// Implementation of , representing an And-combination of filters. -/// -public class AndTranslationFilter : ITranslationFilter -{ - private readonly List filters = new(); - - /// - /// Initializes a new instance of the class, with the given filters. - /// - /// - public AndTranslationFilter(params ITranslationFilter[] filters) - { - this.filters.AddRange(filters); - } - - /// - /// Determine whether the given is accepted by every filter in the list. - /// - /// - /// The to test. - /// - /// - /// True if the is accepted by every filter in the list, false otherwise. - /// - public bool IsValid(ITranslationUnit translationUnit) - { - return filters.All(filter => filter.IsValid(translationUnit)); - } - - /// - public override string ToString() - { - return "AND"; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/DefaultTranslationFilter.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/DefaultTranslationFilter.cs deleted file mode 100644 index 343ccb4..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/DefaultTranslationFilter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; - -/// -/// An implementation of the interface that is valid for every . -/// -public class DefaultTranslationFilter : ITranslationFilter -{ - /// - public string? Language { get; init; } - - - /// - /// Check if the is valid for the filter. - /// - /// - /// The to check. - /// - /// - /// , this filter is valid for every . - /// - public bool IsValid(ITranslationUnit translationUnit) => true; -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/ITranslationFilter.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/ITranslationFilter.cs deleted file mode 100644 index c7c03b2..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/ITranslationFilter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; - -/// -/// Interface for translation filters. -/// -public interface ITranslationFilter -{ - /// - /// Check if the is valid under the filter. - /// - /// The that is to be validated. - /// A indicating whether the is valid under the filter. - bool IsValid(ITranslationUnit translationUnit); - - /// - /// The optional language for the filter, not all filters require a language. - /// - /// - /// Thrown if the filter does not support a language. - /// - string? Language - { - get => throw new InvalidOperationException($"{GetType()} has no language support."); - init => throw new InvalidOperationException($"{GetType()} has no language support."); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/IsEmptyTranslationFilter.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/IsEmptyTranslationFilter.cs deleted file mode 100644 index 7f3e261..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/IsEmptyTranslationFilter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; - -/// -/// An implementation of that filters indicates -/// whether a translation is empty for the specified language. -/// -public class IsEmptyTranslationFilter : ITranslationFilter -{ - /// - public string? Language { get; init; } - - /// - /// Initializes a new instance of the class, with the specified language. - /// If no language is specified, the filter will match all languages. - /// - /// - public IsEmptyTranslationFilter(string? language = null) - { - Language = language; - } - - - /// - /// Determines whether the specified translation is empty for the specified language, or for all languages. - /// - /// - /// The translation unit to test. - /// - /// - /// if the translation is empty for the specified language, or for all languages; - /// otherwise, . - /// - public bool IsValid(ITranslationUnit translationUnit) - { - if (Language is not null) return translationUnit.TryGet(Language)?.IsEmpty ?? true; - - return translationUnit.Any(translation => translation.IsEmpty); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/MatchIdTranslationFilter.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/MatchIdTranslationFilter.cs deleted file mode 100644 index ed0e5a5..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/MatchIdTranslationFilter.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; - -/// -/// An implementation of the interface, that checks, if the id of the matches a specified value. -/// The Filter can be created to match "Contains", "StartsWith" or "EndsWith" for the id. -/// -public class MatchIdTranslationFilter : ITranslationFilter -{ - private readonly Regex regex; - - private MatchIdTranslationFilter(Regex regex) - { - this.regex = regex; - } - - /// - /// Creates a new that checks, if the id of an contains the input. - /// - /// The string to match against the id. - /// A new instance of . - public static MatchIdTranslationFilter Contains(string input) - { - return new MatchIdTranslationFilter(new Regex(input)); - } - - /// - /// Creates a new that checks, if the id of an starts with the input. - /// - /// The string to match against the id. - /// A new instance of . - public static MatchIdTranslationFilter StartsWith(string input) - { - return new MatchIdTranslationFilter(new Regex($"^{input}")); - } - - /// - /// Creates a new that checks, if the id of an ends with the input. - /// - /// The string to match against the id. - /// A new instance of . - public static MatchIdTranslationFilter EndsWith(string input) - { - return new MatchIdTranslationFilter(new Regex($"{input}$")); - } - - /// - /// Checks, if the id of an matches the input. - /// - /// - /// The to check against the input. - /// - /// - /// True, if the id of the matches the input. - /// - public bool IsValid(ITranslationUnit translationUnit) - { - return regex.IsMatch(translationUnit.Id); - } - - /// - public override string ToString() - { - return $"Id = {regex}"; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/MatchValueTranslationFilter.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/MatchValueTranslationFilter.cs deleted file mode 100644 index 6f0a26e..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/MatchValueTranslationFilter.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; - -/// -/// An implementation of the interface, that checks, if the value of an matches a specified value. -/// The Filter can be created to match "Contains", "StartsWith" or "EndsWith" for the id. -/// -public class MatchValueTranslationFilter : ITranslationFilter -{ - private readonly Regex regex; - - /// - public string? Language { get; init; } - - private MatchValueTranslationFilter(Regex regex, string? language = null) - { - this.regex = regex; - Language = language; - } - - /// - /// Creates a new that checks, if the value of the for the given language contains the input. - /// - /// The string to match against the value. - /// The language of the translation that will be checked. - /// A new instance of . - public static MatchValueTranslationFilter Contains(string input, string? language = null) - { - return new MatchValueTranslationFilter(new Regex(input), language); - } - - /// - /// Creates a new that checks, if the value of the for the given language starts with the input. - /// - /// The string to match against the value. - /// The language of the translation that will be checked. - /// A new instance of . - public static MatchValueTranslationFilter StartsWith(string input, string? language = null) - { - return new MatchValueTranslationFilter(new Regex($"^{input}"), language); - } - - /// - /// Creates a new that checks, if the value of the for the given language ends with the input. - /// - /// The string to match against the value. - /// The language of the translation that will be checked. - /// A new instance of . - public static MatchValueTranslationFilter EndsWith(string input, string? language = null) - { - return new MatchValueTranslationFilter(new Regex($"{input}$"), language); - } - - /// - /// Checks, if the value of the for the given language matches the input. - /// - /// - /// The containing the that will be checked. - /// - /// - /// True, if the value of the for the given language matches the input. - /// - public bool IsValid(ITranslationUnit translationUnit) - { - if (Language is not null) - { - var translation = translationUnit.TryGet(Language); - var translationString = translation as ITranslationString; - - return translationString is not null && regex.IsMatch(translationString.Value); - } - - foreach (var translation in translationUnit) - { - if (translation is ITranslationString translationString && regex.IsMatch(translationString.Value)) - return true; - } - - return false; - } - - /// - public override string ToString() - { - return $"Value = {regex}"; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/OrTranslationFilter.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/OrTranslationFilter.cs deleted file mode 100644 index 2dbaf5e..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilter/OrTranslationFilter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; - -/// -/// Implementation of the interface, representing an OR-combination of other filters. -/// -public class OrTranslationFilter : ITranslationFilter -{ - private readonly List filters = new(); - - /// - /// Initializes a new instance of the class, with the given filters. - /// - /// - public OrTranslationFilter(params ITranslationFilter[] filters) - { - this.filters.AddRange(filters); - } - - /// - /// Determines whether any of the filters in this filter matches the given . - /// - /// - /// The to check. - /// - /// - /// if any of the filters in this filter matches the given ; - /// otherwise, . - /// - public bool IsValid(ITranslationUnit translationUnit) - { - return filters.Any(filter => filter.IsValid(translationUnit)); - } - - /// - public override string ToString() - { - return "OR"; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Exceptions/ParserExceptions.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Exceptions/ParserExceptions.cs deleted file mode 100644 index 2877f4a..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Exceptions/ParserExceptions.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser.Exceptions; - -/// -/// Exception that is thrown when a filter string is invalid. -/// -public class ParserException : Exception -{ - /// - public ParserException(string message) : base(message) - { - } -} - -/// -public class UnexpectedTokenException : ParserException -{ - /// - /// The token that was unexpected. - /// - public Token Token { get; init; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The token that was unexpected. - /// - public UnexpectedTokenException(Token token) : base( - $"Unexpected token: '{token.Type}' at position: '{token.Position}'.") - { - Token = token; - } -} - -/// -public class ExpectedTokenException : ParserException -{ - /// - /// The token that was expected. - /// - public TokenType Type { get; init; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The token type that was expected. - /// - public ExpectedTokenException(TokenType type) : base($"Expected token: '{type}'.") - { - Type = type; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The token type that was expected. - /// - /// - /// The position where the token was expected. - /// - public ExpectedTokenException(TokenType type, int position) : base( - $"Expected token: '{type}' at position: '{position}'.") - { - Type = type; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Extensions/EnumeratorTokenExtensions.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Extensions/EnumeratorTokenExtensions.cs deleted file mode 100644 index af36818..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Extensions/EnumeratorTokenExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser.Exceptions; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser.Extensions; - -/// -/// Static class, providing extension methods for parsing a from a string. -/// -public static class EnumeratorTokenExtensions -{ - /// - /// Ensures, that the next is of the specified type. - /// - /// An containing tokens. - /// The required type of the next token. - /// There was no text token in the . - /// The next token was not of the specified type. - public static void Required(this IEnumerator token, TokenType type) - { - if (!token.MoveNext()) throw new ExpectedTokenException(type); - if (token.Current.Type != type) throw new UnexpectedTokenException(token.Current); - } - - /// - /// Checks, if the current token in the is of the specified type. - /// - /// - /// - /// if the current token is of the specified type, otherwise . - public static bool IsType(this IEnumerator token, TokenType type) - { - return token.Current.Type == type; - } - - /// - /// Get the value of the current token. - /// - /// The containing the tokens. - /// - public static string GetValue(this IEnumerator token) - { - return token.Current.Value; - } - - /// - /// Compares the value of the current token with the specified value. - /// - /// - /// The containing the tokens. - /// - /// - /// The value to compare with. - /// - /// - /// if the value of the current token is equal to the specified value, otherwise . - /// - public static bool IsValue(this IEnumerator token, string value) - { - return string.Equals(token.Current.Value, value, StringComparison.CurrentCultureIgnoreCase); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Lexer.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Lexer.cs deleted file mode 100644 index 6b8b1cb..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Lexer.cs +++ /dev/null @@ -1,156 +0,0 @@ -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser; - -/// -/// This class is used to parse a filter string and return a list of tokens. -/// -public class Lexer : IDisposable -{ - private readonly List tokens = new(); - private int position; - private readonly TextReader reader; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The input string to tokenize. - /// - public Lexer(string input) - { - reader = new StringReader(input); - } - - /// - /// Tokenizes the input string. - /// - /// A list of tokens. - public List Tokenize() - { - ParseToken(); - return tokens; - } - - //private bool hasMove(); - //private char nextChar(); -> position++ - // char peakChar(''); - - /// - /// Parses all the tokens in the input string. - /// - /// - private void ParseToken() - { - while (HasMore()) - { - var c = PeekChar(); - if (char.IsWhiteSpace(c)) - { - NextChar(); - continue; - } - - if (char.IsLetter(c)) - ParseName(); - else if (c == '"') - ParseString(); - else if (c == '(') - { - tokens.Add(new Token { Type = TokenType.OpenBracket, Value = c.ToString(), Position = position }); - NextChar(); - } - else if (c == ')') - { - tokens.Add(new Token { Type = TokenType.CloseBracket, Value = c.ToString(), Position = position }); - NextChar(); - } - else if (c == ',') - { - tokens.Add(new Token { Type = TokenType.Comma, Value = c.ToString(), Position = position }); - NextChar(); - } - else - throw new Exception($"Unexpected character '{c}'"); - } - } - - /// - /// Parses a name token. - /// - private void ParseName() - { - StringBuilder builder = new(); - var start = position; - char c; - do - { - c = NextChar(); - builder.Append(c); - c = PeekChar(); - } while (char.IsLetterOrDigit(c) || c == '.'); // read until we hit a dot or digit - - if (builder.ToString().ToLower().Equals("and")) // create AND token - { - tokens.Add(new Token { Type = TokenType.And, Value = "AND", Position = start }); - return; - } - - if (builder.ToString().ToLower().Equals("or")) // create OR token - { - tokens.Add(new Token { Type = TokenType.Or, Value = "OR", Position = start }); - return; - } - - tokens.Add(new Token { Type = TokenType.Name, Value = builder.ToString(), Position = start }); // create name token - } - - /// - /// Parses a string token. - /// - private void ParseString() - { - StringBuilder builder = new(); - var start = position; - var c = NextChar(); //skip leading '"' - - while ((c = PeekChar()) != '"') // read until we hit a '"' - { - builder.Append(c); - NextChar(); - } - - NextChar(); //skip trailing '"' - - tokens.Add(new Token { Type = TokenType.String, Value = builder.ToString(), Position = start }); // create string token - } - - private bool HasMore() - { - return reader.Peek() != -1; - } - - private char NextChar() - { - position++; - return (char)reader.Read(); - } - - private char PeekChar() - { - return (char)reader.Peek(); - } - - /// - public void Dispose() - { - reader.Dispose(); - GC.SuppressFinalize(this); - } - - /// - /// Destructor. - /// - ~Lexer() - { - Dispose(); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Parser.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Parser.cs deleted file mode 100644 index 743ec32..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Parser.cs +++ /dev/null @@ -1,295 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser.Exceptions; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser.Extensions; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser; - -/// -/// Parses a filter string into a filter object. -/// -public class Parser -{ - /// - /// The filter that was parsed from the input string. - /// - public ITranslationFilter Filter { get; private set; } - - /// - /// Create a new parser. - /// - /// The filter string - /// - /// Thrown if the input string is invalid. - /// - public Parser(string input) - { - var tokens = new Lexer(input).Tokenize(); - Filter = ParseExpressions(tokens.GetEnumerator(), 0) ?? throw new Exception(); //TODO: throw correct exception - } - - private enum Match - { - Value, - Id - } - - /// - /// Parse the tokens into a filter object. - /// - /// - /// The tokens to parse. - /// - /// - /// The index of the current bracket. - /// - /// - /// The filter object. - /// - /// - /// Thrown if the tokens are invalid. - /// - /// - /// Thrown if the filter string is invalid. - /// - /// - /// Thrown if the filter string is invalid. - /// - private ITranslationFilter ParseExpressions(IEnumerator token, int bracketIndex) - { - if (!token.MoveNext()) - throw new ParserException("Expected name or open bracket."); - - ITranslationFilter ParseNameOrBracket() - { - if (token.IsType(TokenType.Name)) - return ParseName(token); - - if (token.IsType(TokenType.OpenBracket)) - return ParseExpressions(token, bracketIndex + 1); - - throw new UnexpectedTokenException(token.Current); - } - - var left = ParseNameOrBracket(); - - var currentPosition = token.Current.Position; - if (!token.MoveNext()) - { - if (bracketIndex > 0) - throw new ExpectedTokenException(TokenType.CloseBracket, currentPosition); - return left; - } - - if (token.IsType(TokenType.And)) - { - var right = ParseExpressions(token, bracketIndex); - return new AndTranslationFilter(left, right); - } - - if (token.IsType(TokenType.Or)) - { - var right = ParseExpressions(token, bracketIndex); - return new OrTranslationFilter(left, right); - } - - if (token.IsType(TokenType.CloseBracket)) - { - if (bracketIndex > 0) - return left; - - throw new UnexpectedTokenException(token.Current); - } - - throw new UnexpectedTokenException(token.Current); - } - - /// - /// Parse a name token. - /// - /// - /// The tokens to parse. - /// - /// - /// - private ITranslationFilter ParseName(IEnumerator token) - { - if (token.IsValue("StartsWith") || token.IsValue("Value.StartsWith")) - return ParseStartsWith(token, Match.Value); - if (token.IsValue("EndsWith") || token.IsValue("Value.EndsWith")) return ParseEndsWith(token, Match.Value); - if (token.IsValue("Contains") || token.IsValue("Value.Contains")) return ParseContains(token, Match.Value); - if (token.IsValue("Id.StartsWith")) return ParseStartsWith(token, Match.Id); - if (token.IsValue("Id.EndsWith")) return ParseEndsWith(token, Match.Id); - if (token.IsValue("Id.Contains")) return ParseContains(token, Match.Id); - if (token.IsValue("IsEmpty")) return ParseIsEmpty(token); - throw new UnexpectedTokenException(token.Current); - } - - /// - /// Parse a name token, that corresponds to a contains filter. - /// - /// - /// - /// - /// - /// - private ITranslationFilter ParseContains(IEnumerator token, Match match) - { - // Input string should look like this: - // (Id. || Value.)Contains("{value}" || Contains("{value}", "{language}") when match = Match.Value - - token.Required(TokenType.OpenBracket); - token.Required(TokenType.String); - - var value = token.GetValue(); - - string? language = null; - - try - { - token.Required(TokenType.CloseBracket); - } - catch (Exception e) - { - //TODO: implement try catch correctly - if (e is not UnexpectedTokenException) throw new Exception(e.Message); - - if (token.Current.Type != TokenType.Comma || match != Match.Value) - throw new UnexpectedTokenException(token.Current); - - token.Required(TokenType.String); - language = token.GetValue(); - token.Required(TokenType.CloseBracket); - } - - ITranslationFilter filter = match switch - { - Match.Value => MatchValueTranslationFilter.Contains(value, language), - Match.Id => MatchIdTranslationFilter.Contains(value), - _ => throw new Exception($"Unexpected match type: {match}") - }; - - return filter; - } - - /// - /// Parse a name token, that corresponds to a starts with filter. - /// - /// - /// - /// - /// - /// - private ITranslationFilter ParseStartsWith(IEnumerator token, Match match) - { - // Input string should look like this: - // (Id. || Value.)Contains("{value}" || Contains("{value}", "{language}") when match = Match.Value - - token.Required(TokenType.OpenBracket); - token.Required(TokenType.String); - - var value = token.GetValue(); - string? language = null; - - try - { - token.Required(TokenType.CloseBracket); - } - catch (Exception e) - { - if (e is not UnexpectedTokenException) throw new Exception(e.Message); - - if (token.Current.Type != TokenType.Comma || match != Match.Value) - throw new UnexpectedTokenException(token.Current); - - token.Required(TokenType.String); - language = token.GetValue(); - token.Required(TokenType.CloseBracket); - } - - ITranslationFilter filter = match switch - { - Match.Value => MatchValueTranslationFilter.StartsWith(value, language), - Match.Id => MatchIdTranslationFilter.StartsWith(value), - _ => throw new Exception($"Unexpected match type: {match}") - }; - - return filter; - } - - /// - /// Parse a name token, that corresponds to an ends with filter. - /// - /// - /// - /// - /// - /// - private ITranslationFilter ParseEndsWith(IEnumerator token, Match match) - { - // Input string should look like this: - // (Id. || Value.)Contains("{value}" || Contains("{value}", "{language}") when match = Match.Value - - token.Required(TokenType.OpenBracket); - token.Required(TokenType.String); - - var value = token.GetValue(); - - string? language = null; - - try - { - token.Required(TokenType.CloseBracket); - } - catch (Exception e) - { - if (e is not UnexpectedTokenException) throw new Exception(e.Message); - - if (token.Current.Type != TokenType.Comma || match != Match.Value) - throw new UnexpectedTokenException(token.Current); - - token.Required(TokenType.String); - language = token.GetValue(); - token.Required(TokenType.CloseBracket); - } - - ITranslationFilter filter = match switch - { - Match.Value => MatchValueTranslationFilter.EndsWith(value, language), - Match.Id => MatchIdTranslationFilter.EndsWith(value), - _ => throw new Exception($"Unexpected match type: {match}") - }; - - return filter; - } - - /// - /// Parse a name token, that corresponds to an is empty filter. - /// - /// - /// - /// - /// - private ITranslationFilter ParseIsEmpty(IEnumerator token) - { - // Input string should look like this: - // isEmpty() - token.Required(TokenType.OpenBracket); - - string? language = null; - - try - { - token.Required(TokenType.CloseBracket); - } - catch (Exception e) - { - if (e is not UnexpectedTokenException) throw new Exception(e.Message); - if (token.Current.Type != TokenType.String) throw new UnexpectedTokenException(token.Current); - - language = token.GetValue(); - token.Required(TokenType.CloseBracket); - } - - return new IsEmptyTranslationFilter(language); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Token.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Token.cs deleted file mode 100644 index d0a2215..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/src/TranslationFilterParser/Token.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser; - -/// -/// Enum representing the type of a . -/// -public enum TokenType -{ - /// - /// The token represents the name of a filter. - /// - Name, - /// - /// The token represents a string literal. - /// - String, - /// - /// The token represents an And-Filter - /// - And, - /// - /// The token represents an Or-Filter - /// - Or, - /// - /// The token represents an open bracket. - /// - OpenBracket, - /// - /// The token represents a close bracket. - /// - CloseBracket, - /// - /// The token represents a comma. - /// - Comma -} - -/// -/// Represents a token in a filter expression. -/// -public class Token -{ - /// - /// The type of the token. - /// - public TokenType Type { get; init; } - /// - /// The value of the token. - /// - public string Value { get; init; } = string.Empty; - /// - /// The position of the token in the filter expression. - /// - public int Position { get; init; } - - /// - public override string ToString() - { - return @$"{Type} = ""{Value}"""; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Ashampoo.Translation.Systems.Formats.Abstractions.Tests.csproj b/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Ashampoo.Translation.Systems.Formats.Abstractions.Tests.csproj deleted file mode 100644 index 2c46e11..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Ashampoo.Translation.Systems.Formats.Abstractions.Tests.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - net7.0 - enable - false - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/LanguageParserTests.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/LanguageParserTests.cs deleted file mode 100644 index d6e542c..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/LanguageParserTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Xunit; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.Tests; - -public class LanguageParserTests -{ - [Fact] - public void ParseLanguageCountryTest() - { - const string filePath = "Snap14-en-us.json"; - var language = LanguageParser.TryParseLanguageId(filePath); - Assert.Equal("en-US", language); - } - - [Fact] - public void ParseLanguageScriptTagCountryTest() - { - const string filePath = "Snap14-zh-Hant-TW.json"; - var language = LanguageParser.TryParseLanguageId(filePath); - Assert.Equal("zh-Hant-TW", language); - } - - - [Fact] - public void ValidLanguageCodeTest() - { - const string languageCodeChinese = "zh-Hant-TW"; - const string languageCodeEnglish = "en-US"; - var chineseIsValid = LanguageParser.IsValidLanguageCode(languageCodeChinese); - var englishIsValid = LanguageParser.IsValidLanguageCode(languageCodeEnglish); - Assert.True(chineseIsValid); - Assert.True(englishIsValid); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Lexer.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Lexer.cs deleted file mode 100644 index 14de1c7..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Lexer.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser; -using Xunit; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.Tests -{ - public class LexerTest - { - [Fact] - public void TokenizeX() - { - Lexer lexer = new(@"(aadasd"); - var tokens = lexer.Tokenize(); - } - - [Fact] - public void Tokenize() - { - Lexer lexer = new(@"StartsWith(""Hello World"")"); - var tokens = lexer.Tokenize(); - } - - [Fact] - public void Tokenize13() - { - Lexer lexer = new(@"(StartsWith(""Hello World""))"); - var tokens = lexer.Tokenize(); - } - - [Fact] - public void Tokenize2() - { - Lexer lexer = new(@"StartsWith(""Hello World"") AND EndsWith(""sfsdfdsf"")"); - var tokens = lexer.Tokenize(); - } - - [Fact] - public void TokenizeWithLanguage1() - { - Lexer lexer = new(@"StartsWith(""Hello World"", ""en-US"")"); - var tokens = lexer.Tokenize(); - } - - [Fact] - public void TokenizeWithLanguage2() - { - Lexer lexer = new(@"StartsWith(""Hello World"") AND EndsWith(""sfsdfdsf"", ""en-US"")"); - var tokens = lexer.Tokenize(); - } - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/MatchValueFilter.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/MatchValueFilter.cs deleted file mode 100644 index 7a30c67..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/MatchValueFilter.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; -using Ashampoo.Translation.Systems.TestBase; -using Xunit; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.Tests; - -public class MatchValueTranslationFilterTest -{ - [Fact] - public void StartsWith() - { - var unit = MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "hello.id", - "Hello World"); - Assert.NotNull(unit["hello.id"]); - Assert.True(MatchValueTranslationFilter.StartsWith("Hello").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.StartsWith("World").IsValid(unit["hello.id"]!)); - - Assert.True(MatchValueTranslationFilter.StartsWith("Hello", "en-US").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.StartsWith("World", "en-US").IsValid(unit["hello.id"]!)); - - Assert.False(MatchValueTranslationFilter.StartsWith("Hello", "de-DE").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.StartsWith("World", "de-DE").IsValid(unit["hello.id"]!)); - } - - [Fact] - public void EndsWith() - { - var unit = MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "hello.id", - "Hello World"); - Assert.NotNull(unit["hello.id"]); - Assert.True(MatchValueTranslationFilter.EndsWith("World").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.EndsWith("Hello").IsValid(unit["hello.id"]!)); - - Assert.True(MatchValueTranslationFilter.EndsWith("World", "en-US").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.EndsWith("Hello", "en-US").IsValid(unit["hello.id"]!)); - - Assert.False(MatchValueTranslationFilter.EndsWith("World", "de-DE").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.EndsWith("Hello", "de-DE").IsValid(unit["hello.id"]!)); - } - - [Fact] - public void Contains() - { - var unit = MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "hello.id", - "Hello World"); - Assert.NotNull(unit["hello.id"]); - Assert.True(MatchValueTranslationFilter.Contains("lo Wo").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.Contains("lolo").IsValid(unit["hello.id"]!)); - - Assert.True(MatchValueTranslationFilter.Contains("lo Wo", "en-US").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.Contains("lolo", "en-US").IsValid(unit["hello.id"]!)); - - Assert.False(MatchValueTranslationFilter.Contains("lo Wo", "de-DE").IsValid(unit["hello.id"]!)); - Assert.False(MatchValueTranslationFilter.Contains("lolo", "de-DE").IsValid(unit["hello.id"]!)); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Parser.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Parser.cs deleted file mode 100644 index 6bdc817..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Parser.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilterParser.Exceptions; -using Xunit; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.Tests; - -public class ParserTest -{ - [Fact] - public void ParseStartsWith() - { - var parser = new Parser(@"StartsWith(""Hello"")"); - var filter = parser.Filter; - } - - [Fact] - public void ParseIsEmpty() - { - var parser = new Parser(@"isEmpty()"); - var filter = parser.Filter; - } - - [Fact] - public void ParseAnd() - { - var parser = new Parser(@"StartsWith(""Hello"") AND EndsWith(""World"")"); - var filter = parser.Filter; - } - - [Fact] - public void ParseMultipleAnd() - { - var parser = new Parser(@"StartsWith(""Hello"") AND EndsWith(""World"") AND StartsWith(""Ashampoo"")"); - var filter = parser.Filter; - } - - [Fact] - public void ParseOr() - { - var parser = new Parser(@"StartsWith(""Hello"") OR EndsWith(""World"")"); - var filter = parser.Filter; - } - - [Fact] - public void ParseMultipleOr() - { - var parser = new Parser(@"StartsWith(""Hello"") OR EndsWith(""World"") OR StartsWith(""Ashampoo"")"); - var filter = parser.Filter; - } - - [Fact] - public void ParseAndOr() - { - var parser = new Parser(@"StartsWith(""Hello"") AND EndsWith(""World"") OR StartsWith(""Ashampoo"")"); - var filter = parser.Filter; - } - - [Fact] - public void ParseSimpleBracket() - { - var parser = new Parser(@"(StartsWith(""Hello""))"); - var filter = parser.Filter; - } - - [Fact] - public void ParseComplexBracket() - { - var parser = new Parser(@"StartsWith(""Hello"") AND ( EndsWith(""World"") OR EndsWith(""Universe"") ) "); - var filter = parser.Filter; - } - - [Fact] - public void ParseComplexBracket2() - { - var parser = - new Parser( - @"(StartsWith(""Hello"") OR StartsWith(""Hi"")) AND ( EndsWith(""World"") OR EndsWith(""Universe"") ) "); - var filter = parser.Filter; - } - - [Fact] - public void ParseComplexBracket3() - { - var parser = - new Parser( - @"(StartsWith(""Hello"") OR StartsWith(""Hi"")) AND (Contains(""Ever"") OR Contains(""Never"")) AND ( EndsWith(""World"") OR EndsWith(""Universe"") ) "); - var filter = parser.Filter; - } - - [Fact] - public void ParseInvalidBracketExpression() - { - Exception thrownException = Assert.Throws(() => new Parser(@"()")); - Assert.Equal("Unexpected token: 'CloseBracket' at position: '1'.", thrownException.Message); - - thrownException = Assert.Throws(() => new Parser(@"(")); - Assert.Equal("Expected name or open bracket.", thrownException.Message); - - thrownException = Assert.Throws(() => new Parser(@")")); - Assert.Equal("Unexpected token: 'CloseBracket' at position: '0'.", thrownException.Message); - - thrownException = Assert.Throws(() => new Parser(@"(StartsWith(""Hello"")")); - Assert.Equal("Expected token: 'CloseBracket' at position: '19'.", thrownException.Message); - - thrownException = Assert.Throws(() => new Parser(@"StartsWith(""Hello""))")); - Assert.Equal("Unexpected token: 'CloseBracket' at position: '19'.", thrownException.Message); - } - - [Fact] - public void ParseWithLanguage1() - { - var parser = new Parser(@"StartsWith(""Hello"", ""en-US"")"); - var filter = parser.Filter; - Assert.IsAssignableFrom(filter); - Assert.Equal("en-US", filter.Language); - - parser = new Parser(@"EndsWith(""Hello"", ""en-US"")"); - filter = parser.Filter; - Assert.IsAssignableFrom(filter); - Assert.Equal("en-US", filter.Language); - - parser = new Parser(@"Contains(""Hello"", ""en-US"")"); - filter = parser.Filter; - Assert.IsAssignableFrom(filter); - Assert.Equal("en-US", filter.Language); - - parser = new Parser(@"IsEmpty(""en-US"")"); - filter = parser.Filter; - Assert.IsAssignableFrom(filter); - Assert.Equal("en-US", filter.Language); - } - - [Fact] - public void ParseWWithLanguage2() - { - Assert.Throws(() => new Parser(@"Id.StartsWith(""Hello"", ""en-US"")")); - Assert.Throws(() => new Parser(@"Id.EndsWith(""Hello"", ""en-US"")")); - Assert.Throws(() => new Parser(@"Id.Contains(""Hello"", ""en-US"")")); - Assert.Throws(() => new Parser(@"IsEmpty(""Hello"", ""en-US"")")); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Startup.cs b/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Startup.cs deleted file mode 100644 index fac54aa..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Abstractions/tests/Startup.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Ashampoo.Translation.Systems.Formats.Abstractions.Tests; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormat.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormat.cs index 6b9f28e..06e5609 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormat.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormat.cs @@ -1,14 +1,13 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.Formats.AshLang.Chunk; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.Formats.AshLang; /// /// Provides a format for the AshLang format. /// -public class AshLangFormat : AbstractTranslationUnits, IFormat +public class AshLangFormat : IFormat { /// /// The chunks of the ashlang file. @@ -16,24 +15,21 @@ public class AshLangFormat : AbstractTranslationUnits, IFormat public IChunk[] Chunks { get; private set; } /// - public FormatLanguageCount LanguageCount => FormatLanguageCount.SourceAndTarget; + public LanguageSupport LanguageSupport => LanguageSupport.SourceAndTarget; + /// + public ICollection TranslationUnits { get; } = new List(); /// - public Func BuildFormatProvider() + public Task WriteAsync(Stream stream) { - return builder => builder.SetId("ashlang") - .SetSupportedFileExtensions(new[] { ".ashlang" }) - .SetFormatType() - .SetFormatBuilder() - .Create(); + throw new NotImplementedException(); } /// public IFormatHeader Header { get; private set; } - /// /// Initializes a new instance of the class. /// @@ -46,14 +42,14 @@ public AshLangFormat() var appIdChunk = new AppIdChunk(); var versionChunk = new VersionChunk(); var translationChunk = new TranslationChunk(); - Chunks = new IChunk[] - { + Chunks = + [ ashLangFormatHeader.LanguageChunk, appIdChunk, ashLangFormatHeader.XDataChunk, versionChunk, translationChunk - }; + ]; } /// @@ -83,10 +79,13 @@ public Task ReadAsync(Stream stream, FormatReadOptions? options = null) { var translationUnit = new DefaultTranslationUnit(translation.Id) { - new SourceTranslationString(sourceLanguage, translation), - new TargetTranslationString(Header.TargetLanguage, translation) + Translations = + { + new SourceTranslationString(sourceLanguage, translation), + new TargetTranslationString(Header.TargetLanguage, translation) + } }; - Add(translationUnit); + TranslationUnits.Add(translationUnit); } Chunks = reader.Chunks; diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatBuilder.cs index a31ed72..59add0d 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatBuilder.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatBuilder.cs @@ -1,4 +1,5 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.Formats.AshLang.Chunk; using CommunityToolkit.Diagnostics; @@ -8,46 +9,49 @@ namespace Ashampoo.Translation.Systems.Formats.AshLang; /// /// Builder for /// -public class AshLangFormatBuilder : IFormatBuilderWithSourceAndTarget +public class AshLangFormatBuilder : IFormatBuilderWithSourceAndTarget { - private const string SourceLanguage = "en-US"; // AshLang source is always in English - private string? targetLanguage; - private readonly Dictionary translations = new(); - private Dictionary information = new(); + private static readonly Language SourceLanguage = new("en-US"); // AshLang source is always in English + private Language _targetLanguage = Language.Empty; + private readonly Dictionary _translations = new(); + private Dictionary _information = new(); /// - public IFormat Build() + public AshLangFormat Build() { - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); + Guard.IsNotNullOrWhiteSpace(_targetLanguage.Value, nameof(_targetLanguage)); var ashLang = new AshLangFormat { Header = { - TargetLanguage = targetLanguage + TargetLanguage = _targetLanguage } }; var appIdChunk = ashLang.Chunks.OfType().FirstOrDefault(); - if ( appIdChunk is not null && information.TryGetValue("Name", out var name)) + if (appIdChunk is not null && _information.TryGetValue("Name", out var name)) { appIdChunk.Name = name; - information.Remove("Name"); + _information.Remove("Name"); } var translationChunk = (TranslationChunk)ashLang.Chunks.Last(); - foreach (var (id, (source, target)) in translations) + foreach (var (id, (source, target)) in _translations) { var translation = new TranslationChunk.Translation(0, id, target, source, ""); translationChunk.Translations.Add(translation); var translationUnit = new DefaultTranslationUnit(id) { - new SourceTranslationString(SourceLanguage, translation), - new TargetTranslationString(targetLanguage, translation) + Translations = + { + new SourceTranslationString(SourceLanguage, translation), + new TargetTranslationString(_targetLanguage, translation) + } }; - ashLang.Add(translationUnit); + ashLang.TranslationUnits.Add(translationUnit); } @@ -57,30 +61,30 @@ public IFormat Build() /// public void Add(string id, string source, string target) { - translations.Add(id, (source, target)); + _translations.Add(id, (source, target)); } /// - public void SetSourceLanguage(string language) + public void SetSourceLanguage(Language language) { // AshLang source is always in English } /// - public void SetTargetLanguage(string language) + public void SetTargetLanguage(Language language) { - targetLanguage = language; + _targetLanguage = language; } /// public void SetHeaderInformation(IFormatHeader header) { - information = new Dictionary(header); + _information = new Dictionary(header.AdditionalHeaders); } /// public void AddHeaderInformation(string key, string value) { - information.Add(key, value); + _information.Add(key, value); } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatHeader.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatHeader.cs index f9d96b5..1c20b12 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatHeader.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatHeader.cs @@ -1,6 +1,6 @@ -using System.Collections; using System.Diagnostics.CodeAnalysis; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.AshLang.Chunk; namespace Ashampoo.Translation.Systems.Formats.AshLang; @@ -36,35 +36,27 @@ public AshLangFormatHeader(LanguageChunk? languageChunk = null, XDataChunk? xDat public LanguageChunk LanguageChunk { get; } /// - public string this[string key] - { - get => XDataChunk[key]; - set => XDataChunk[key] = value; - } - - /// - public string TargetLanguage + public Language TargetLanguage { get => LanguageChunk.LanguageId; - set - { - if (value is null) throw new ArgumentNullException(nameof(TargetLanguage)); - + set => // TODO: set Language and Country LanguageChunk.LanguageId = value; - } } /// - public string? SourceLanguage + public Language? SourceLanguage { - get => "en-US"; + get => new("en-US"); set { // Do nothing. SourceLanguage is always "en-US" } } + /// + public Dictionary AdditionalHeaders { get; set; } = new(); + /// public ICollection Keys => XDataChunk.Keys; @@ -136,9 +128,4 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) { throw new NotImplementedException(); } - - IEnumerator IEnumerable.GetEnumerator() - { - return XDataChunk.GetEnumerator(); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatProvider.cs new file mode 100644 index 0000000..2632ff5 --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/AshLangFormatProvider.cs @@ -0,0 +1,27 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; + +namespace Ashampoo.Translation.Systems.Formats.AshLang; + +/// +/// for the AshLang format. +/// +public sealed class AshLangFormatProvider : IFormatProvider +{ + /// + public string Id { get; } = "ashlang"; + + /// + public AshLangFormat Create() => new(); + + /// + public bool SupportsFileName(string fileName) + { + return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + } + + /// + public string[] SupportedFileExtensions => [".ashlang"]; + + /// + public IFormatBuilder GetFormatBuilder() => new AshLangFormatBuilder(); +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Ashampoo.Translation.Systems.Formats.AshLang.csproj b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Ashampoo.Translation.Systems.Formats.AshLang.csproj index fd4ca3d..0a26875 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Ashampoo.Translation.Systems.Formats.AshLang.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Ashampoo.Translation.Systems.Formats.AshLang.csproj @@ -1,25 +1,10 @@  - net7.0 + net8.0 enable enable - Ashampoo.Translation.Systems.Formats.AshLang - tjorvenK - ashampoo - Package containing the implementation for the AshLang format. - LICENSE - localization;translation;ashlang true - true - true - true - snupkg - ash-logo-icon-big-128x.png - v - true - prerelease.0 - README.md @@ -33,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Chunk/ChunkWriter.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Chunk/ChunkWriter.cs index 8d58002..ef93bc7 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Chunk/ChunkWriter.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Chunk/ChunkWriter.cs @@ -7,8 +7,8 @@ namespace Ashampoo.Translation.Systems.Formats.AshLang.Chunk; /// public class ChunkWriter { - private readonly BinaryWriter writer; - private readonly IChunk[] chunks; + private readonly BinaryWriter _writer; + private readonly IChunk[] _chunks; /// /// Initializes a new instance of the ChunkWriter class. @@ -17,8 +17,8 @@ public class ChunkWriter /// public ChunkWriter(Stream stream, IChunk[] chunks) { - this.chunks = chunks; - writer = new BinaryWriter(stream); + _chunks = chunks; + _writer = new BinaryWriter(stream); } /// @@ -27,12 +27,12 @@ public ChunkWriter(Stream stream, IChunk[] chunks) public void Write() { // Header - writer.WriteUTF8String("URESFILE"); + _writer.WriteUTF8String("URESFILE"); // Write all chunks. - WriteDataAndUpdateSize(writer, () => + WriteDataAndUpdateSize(_writer, () => { - foreach (var chunk in chunks) + foreach (var chunk in _chunks) { // TODO: Should this be optional? if (chunk.IsEmpty) continue; @@ -40,7 +40,7 @@ public void Write() } }); - writer.Flush(); + _writer.Flush(); } /// @@ -49,9 +49,9 @@ public void Write() /// private void WriteChunk(IChunk chunk) { - writer.WriteUTF8String(chunk.Id); + _writer.WriteUTF8String(chunk.Id); - WriteDataAndUpdateSize(writer, () => { chunk.Write(writer); }); + WriteDataAndUpdateSize(_writer, () => { chunk.Write(_writer); }); } /// diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Chunk/LanguageChunk.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Chunk/LanguageChunk.cs index 6a6b160..8ce8017 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Chunk/LanguageChunk.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/Chunk/LanguageChunk.cs @@ -5,6 +5,8 @@ [ChunkString][ChunkString ][ChunkString ] [LanguageId ][Language Name][Country Name] */ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; + namespace Ashampoo.Translation.Systems.Formats.AshLang.Chunk; /// @@ -17,10 +19,11 @@ public class LanguageChunk : IChunk /// public const string Id = "lang"; string IChunk.Id => Id; + /// /// The language id. /// - public string LanguageId { get; set; } = ""; + public Language LanguageId { get; set; } = Abstractions.Models.Language.Empty; /// /// The language name. /// @@ -31,13 +34,13 @@ public class LanguageChunk : IChunk public string Country { get; set; } = ""; /// - public bool IsEmpty => string.IsNullOrEmpty(LanguageId) && string.IsNullOrEmpty(Language) && + public bool IsEmpty => string.IsNullOrEmpty(LanguageId.ToString()) && string.IsNullOrEmpty(Language) && string.IsNullOrEmpty(Country); /// public void Read(BinaryReader reader) { - LanguageId = ChunkString.Read(reader); + LanguageId = new Language(ChunkString.Read(reader)); Language = ChunkString.Read(reader); Country = ChunkString.Read(reader); } @@ -45,7 +48,7 @@ public void Read(BinaryReader reader) /// public void Write(BinaryWriter writer) { - ChunkString.Write(writer, LanguageId); + ChunkString.Write(writer, LanguageId.ToString()); ChunkString.Write(writer, Language); ChunkString.Write(writer, Country); } diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/DependencyInjectionExtension.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/DependencyInjectionExtension.cs new file mode 100644 index 0000000..4623a5b --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/DependencyInjectionExtension.cs @@ -0,0 +1,31 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.AshLang; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services for the AshLang format. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection AddAshLangFormatFeatures(this IServiceCollection services) + { + services.AddSingleton() + .AddSingleton>(sp => sp.GetRequiredService()) + .AddSingleton>(sp => sp.GetRequiredService()); + + return services; + } +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/SourceTranslationString.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/SourceTranslationString.cs index e50269a..822f9bd 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/SourceTranslationString.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/SourceTranslationString.cs @@ -1,16 +1,17 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.Formats.AshLang.Chunk; namespace Ashampoo.Translation.Systems.Formats.AshLang; /// -/// Implementation of the interface, representing a source translation string for the AshLang format. +/// Implementation of the interface, representing a source translation string for the AshLang format. /// -public class SourceTranslationString : ITranslationString +public class SourceTranslationString : ITranslation { - private readonly TranslationChunk.Translation translation; + private readonly TranslationChunk.Translation _translation; - private readonly string language; + private readonly Language _language; /// /// Initializes a new instance of the class. @@ -21,39 +22,36 @@ public class SourceTranslationString : ITranslationString /// /// The /// - public SourceTranslationString(string language, TranslationChunk.Translation translation) + public SourceTranslationString(Language language, TranslationChunk.Translation translation) { - this.language = language; - this.translation = translation; + _language = language; + _translation = translation; } /// public string Value { - get => translation.Fallback; + get => _translation.Fallback; set { // Do nothing - Sources in AshLang are readonly. } } - /// - public string Id => translation.Id; - /// public string? Comment { - get => translation.Comment; - set => translation.Comment = value ?? ""; + get => _translation.Comment; + set => _translation.Comment = value ?? ""; } /// public bool IsEmpty => string.IsNullOrWhiteSpace(Value); /// - public string Language + public Language Language { - get => language; + get => _language; set { //Do nothing, source language is always en-us diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/TargetTranslationString.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/TargetTranslationString.cs index e348f87..ad625f5 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/src/TargetTranslationString.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/src/TargetTranslationString.cs @@ -1,14 +1,15 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.Formats.AshLang.Chunk; namespace Ashampoo.Translation.Systems.Formats.AshLang; /// -/// Implementation of the interface, representing a target translation string for the AshLang format. +/// Implementation of the interface, representing a target translation string for the AshLang format. /// -public class TargetTranslationString : ITranslationString +public class TargetTranslationString : ITranslation { - private readonly TranslationChunk.Translation translation; + private readonly TranslationChunk.Translation _translation; /// /// Initializes a new instance of the class. @@ -19,32 +20,26 @@ public class TargetTranslationString : ITranslationString /// /// The of the translation string. /// - public TargetTranslationString(string language, TranslationChunk.Translation translation) + public TargetTranslationString(Language language, TranslationChunk.Translation translation) { Language = language; - this.translation = translation; + _translation = translation; } /// public string Value { - get => translation.Value; - set => translation.Value = value; + get => _translation.Value; + set => _translation.Value = value; } - /// - public string Id => translation.Id; - /// public string? Comment { - get => translation.Comment; - set => translation.Comment = value ?? ""; + get => _translation.Comment; + set => _translation.Comment = value ?? ""; } /// - public bool IsEmpty => string.IsNullOrWhiteSpace(Value); - - /// - public string Language { get; set; } + public Language Language { get; set; } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/AshLangFormatTest.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/AshLangFormatTest.cs index 997c461..d0305fc 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/AshLangFormatTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/AshLangFormatTest.cs @@ -1,40 +1,30 @@ using System; using System.IO; -using System.Threading.Tasks; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.AshLang.Tests; public class AshLangFormatTest : FormatTestBase { - private readonly IFormatFactory formatFactory; - - public AshLangFormatTest(IFormatFactory formatFactory) - { - this.formatFactory = formatFactory; - } [Fact] public void NewFormat() { var format = CreateFormat(); - Assert.Empty(format); + format.TranslationUnits.Should().BeEmpty(); // source sets are 'en-US' per default. - Assert.Equal("en-US", format.Header.SourceLanguage); - - format.Header.TargetLanguage = "es-ES"; - Assert.Equal("es-ES", format.Header.TargetLanguage); - - // set Language null must throw. - var thrownException = Assert.Throws(() => format.Header.TargetLanguage = null!); - Assert.Equal("Value cannot be null. (Parameter 'TargetLanguage')", thrownException.Message); - + format.Header.SourceLanguage.Should().Be(new Language("en-US")); + + format.Header.TargetLanguage = new Language("es-ES"); + format.Header.TargetLanguage.Should().Be(new Language("es-ES")); + // author is nullable and can be changed. //Assert.Null(format.Header.Author); //format.Header.Author = "Ashampoo"; @@ -43,64 +33,24 @@ public void NewFormat() //Assert.Null(format.Header.Author); } - [Fact] - public void ImportSuccessTest() - { - var format = CreateAndReadFromFile("normalized_peru-de-DE.ashLang"); - - const string id = "peru.CFileSystemManager.MoveFolderFailed.CreateFolder"; - const string value = "Import Test"; - var imported = format.ImportMockTranslationWithUnits("de-DE", id); - - var translationUnit = format[id]; - Assert.NotNull(translationUnit); - Assert.Equal(value, (translationUnit["de-DE"] as ITranslationString)?.Value); - Assert.Equal(1, imported.Count); - } - - [Fact] - public void NoMatchImportTest() - { - var format = CreateAndReadFromFile("normalized_peru-de-DE.ashLang"); - - const string id = "Not matching Import-Id"; - var imported = format.ImportMockTranslationWithUnits("de-DE", id); - - Assert.Equal(0, imported.Count); - } - - [Fact] - public void ImportEqualTranslationTest() - { - var format = CreateAndReadFromFile("normalized_peru-de-DE.ashLang"); - - const string id = "peru.CFileSystemManager.MoveFolderFailed.CreateFolder"; - const string value = - "Fehler beim Verschieben von Ordner '%SRC%' nach '%DEST%''. Konnte Ordner nicht erstellen."; - var imported = format.ImportMockTranslationWithUnits("de-DE", id, value); - - Assert.Equal(0, imported.Count); - } - [Fact] public void ReadFromFile() { var format = CreateAndReadFromFile("normalized_peru-de-DE.ashLang"); - Assert.Equal(67, format.Count); - Assert.Equal("en-US", format.Header.SourceLanguage); - //Assert.Null(format.Header.Author); - Assert.Equal("de-DE", format.Header.TargetLanguage); - //Assert.Equal("Ashampoo", format.Header.Author); + format.TranslationUnits.Count.Should().Be(67); + format.Header.SourceLanguage.Should().Be(new Language("en-US")); + format.Header.TargetLanguage.Should().Be(new Language("de-DE")); const string id = "peru.CSystem.MoveFileFailed"; - var translationUnit = format[id]; - Assert.NotNull(translationUnit); - Assert.Equal(id, translationUnit.Id); - Assert.Equal("Error moving file '%SRC%' to '%DEST%'.", (translationUnit["en-US"] as ITranslationString)?.Value); - Assert.Equal("Fehler beim Verschieben der Datei '%SRC%' nach '%DEST%'.", - (translationUnit["de-DE"] as ITranslationString)?.Value); + var translationUnit = format.TranslationUnits.GetTranslationUnit(id); + translationUnit.Should().NotBeNull(); + translationUnit.Id.Should().Be(id); + translationUnit.Translations.GetTranslation(new Language("en-US")).Value.Should() + .Be("Error moving file '%SRC%' to '%DEST%'."); + translationUnit.Translations.GetTranslation(new Language("de-DE")).Value.Should() + .Be("Fehler beim Verschieben der Datei '%SRC%' nach '%DEST%'."); } [Fact] @@ -118,69 +68,43 @@ public void ReadAndWrite() //ms.MustBeEqualTo(fs); } - [Fact] - public async Task ConvertTest() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "Convert ID", "Convert Test"); - - var assignOptions = new AssignOptions - { - SourceLanguage = "en-US", - TargetLanguage = "de-DE", - Filter = new DefaultTranslationFilter() - }; - - var ashLang = await mockFormat.ConvertToAsync(formatFactory, assignOptions); - - Assert.Equal("en-US", ashLang.Header.SourceLanguage); - Assert.Equal("de-DE", ashLang.Header.TargetLanguage); - - Assert.Single(ashLang); - Assert.Equal(2, ashLang["Convert ID"]?.Count); - Assert.Equal("Convert Test", (ashLang["Convert ID"]?["en-US"] as ITranslationString)?.Value); - Assert.Null(ashLang["Convert Test"]?.TryGet("de-DE")); - } - [Fact] public void FormatBuilderTest() { - var builder = - (IFormatBuilderWithSourceAndTarget)formatFactory.GetFormatProvider(typeof(AshLangFormat)) - .GetFormatBuilder(); + var builder = new AshLangFormatBuilder(); - builder.SetTargetLanguage("de-DE"); + builder.SetTargetLanguage(new Language("de-DE")); builder.Add("Test ID 1", "Test Source 1", "Test Ziel 1"); builder.Add("Test ID 2", "Test Source 2", string.Empty); builder.Add("Test ID 3", string.Empty, "Test Ziel 2"); var ashLang = builder.Build(); - Assert.Equal("en-US", ashLang.Header.SourceLanguage); - Assert.Equal("de-DE", ashLang.Header.TargetLanguage); - - Assert.Equal(3, ashLang.Count); - Assert.Equal("Test Source 1", (ashLang["Test ID 1"]?["en-US"] as ITranslationString)?.Value); - Assert.Equal("Test Ziel 1", (ashLang["Test ID 1"]?["de-DE"] as ITranslationString)?.Value); - - Assert.Equal("Test Source 2", (ashLang["Test ID 2"]?["en-US"] as ITranslationString)?.Value); - Assert.Equal(string.Empty, (ashLang["Test ID 2"]?.TryGet("de-DE") as ITranslationString)?.Value); - - Assert.Equal(string.Empty, (ashLang["Test ID 3"]?.TryGet("en-US") as ITranslationString)?.Value); - Assert.Equal("Test Ziel 2", (ashLang["Test ID 3"]?["de-DE"] as ITranslationString)?.Value); - - - builder = (IFormatBuilderWithSourceAndTarget)formatFactory.GetFormatProvider(typeof(AshLangFormat)) - .GetFormatBuilder(); - builder.SetSourceLanguage("de-DE"); - - Assert.Throws(() => builder.Build()); - - builder.SetTargetLanguage("de-DE"); - + ashLang.Header.SourceLanguage.Should().Be(new Language("en-US")); + ashLang.Header.TargetLanguage.Should().Be(new Language("de-DE")); + ashLang.TranslationUnits.Count.Should().Be(3); + ashLang.TranslationUnits.GetTranslationUnit("Test ID 1").Translations.GetTranslation(new Language("en-US")).Value.Should() + .Be("Test Source 1"); + ashLang.TranslationUnits.GetTranslationUnit("Test ID 1").Translations.GetTranslation(new Language("de-DE")).Value.Should() + .Be("Test Ziel 1"); + ashLang.TranslationUnits.GetTranslationUnit("Test ID 2").Translations.GetTranslation(new Language("en-US")).Value.Should() + .Be("Test Source 2"); + ashLang.TranslationUnits.GetTranslationUnit("Test ID 2").Translations.GetTranslation(new Language("de-DE")).Value.Should() + .BeEmpty(); + ashLang.TranslationUnits.GetTranslationUnit("Test ID 3").Translations.GetTranslation(new Language("en-US")).Value.Should() + .BeEmpty(); + ashLang.TranslationUnits.GetTranslationUnit("Test ID 3").Translations.GetTranslation(new Language("de-DE")).Value.Should() + .Be("Test Ziel 2"); + + builder = new AshLangFormatBuilder(); + builder.SetSourceLanguage(new Language("de-DE")); + + builder.Invoking(x => x.Build()).Should().Throw(); + builder.SetTargetLanguage(new Language("de-DE")); + var format = builder.Build(); - Assert.Equal("en-US", format.Header.SourceLanguage); - Assert.Equal("de-DE", format.Header.TargetLanguage); + format.Header.SourceLanguage.Should().Be(new Language("en-US")); + format.Header.TargetLanguage.Should().Be(new Language("de-DE")); } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/Ashampoo.Translation.Systems.Formats.AshLang.Tests.csproj b/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/Ashampoo.Translation.Systems.Formats.AshLang.Tests.csproj index c4d2430..2cc726d 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/Ashampoo.Translation.Systems.Formats.AshLang.Tests.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/Ashampoo.Translation.Systems.Formats.AshLang.Tests.csproj @@ -1,19 +1,19 @@  - net7.0 + net8.0 enable false - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/ChunkReaderTest.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/ChunkReaderTest.cs index 7fa8cdf..8a056f6 100644 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/ChunkReaderTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/ChunkReaderTest.cs @@ -1,6 +1,9 @@ using System.IO; +using System.Linq; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.AshLang.Chunk; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.AshLang.Tests; @@ -18,9 +21,11 @@ public void LanguageChunk() { var chunkReader = new ChunkReader(GetNewStream()); var chunk = chunkReader.TryGetOrNull(Chunk.LanguageChunk.Id); - Assert.Equal("de-DE", chunk?.LanguageId); - Assert.Equal("Deutsch", chunk?.Language); - Assert.Equal("Deutschland", chunk?.Country); + + chunk.Should().NotBeNull(); + chunk!.LanguageId.Should().Be(new Language("de-DE")); + chunk.Language.Should().Be("Deutsch"); + chunk.Country.Should().Be("Deutschland"); } [Fact] @@ -29,7 +34,7 @@ public void CommentChunk() var chunkReader = new ChunkReader(GetNewStream()); var chunk = chunkReader.TryGetOrNull(Chunk.CommentChunk.Id); - Assert.Null(chunk); + chunk.Should().BeNull(); } [Fact] @@ -38,8 +43,9 @@ public void AppIdChunk() var chunkReader = new ChunkReader(GetNewStream()); var chunk = chunkReader.TryGetOrNull(Chunk.AppIdChunk.Id); - Assert.Equal("peru", chunk?.Name); - Assert.Equal("1.0.0.204", chunk?.Version); + chunk.Should().NotBeNull(); + chunk!.Name.Should().Be("peru"); + chunk.Version.Should().Be("1.0.0.204"); } [Fact] @@ -48,7 +54,8 @@ public void VersionChunk() var chunkReader = new ChunkReader(GetNewStream()); var chunk = chunkReader.TryGetOrNull(Chunk.VersionChunk.Id); - Assert.Equal(AshLangVersion.AshLangFormatV2, chunk?.Version); + chunk.Should().NotBeNull(); + chunk!.Version.Should().Be(AshLangVersion.AshLangFormatV2); } [Fact] @@ -57,15 +64,12 @@ public void XDataChunk() var chunkReader = new ChunkReader(GetNewStream()); var chunk = chunkReader.TryGetOrNull(Chunk.XDataChunk.Id); - Assert.Equal(4, chunk?.Count); - if (chunk is not null && chunk.ContainsKey("ASH_LANG_AUTHOR")) - Assert.Equal("Ashampoo", chunk["ASH_LANG_AUTHOR"]); - if (chunk is not null && chunk.ContainsKey("ASH_LANG_CREATION_TOOL")) - Assert.Equal("Ashampoo Translation Studio Advanced", chunk["ASH_LANG_CREATION_TOOL"]); - if (chunk is not null && chunk.ContainsKey("ASH_LANG_CREATION_TOOL_VERSION")) - Assert.Equal("1.8.20.1", chunk["ASH_LANG_CREATION_TOOL_VERSION"]); - if (chunk is not null && chunk.ContainsKey("ASH_LANG_MAIL")) - Assert.Equal("translations@ashampoo.com", chunk["ASH_LANG_MAIL"]); + chunk.Should().NotBeNull(); + chunk!.Count.Should().Be(4); + chunk["ASH_LANG_AUTHOR"].Should().Be("Ashampoo"); + chunk["ASH_LANG_CREATION_TOOL"].Should().Be("Ashampoo Translation Studio Advanced"); + chunk["ASH_LANG_CREATION_TOOL_VERSION"].Should().Be("1.8.20.1"); + chunk["ASH_LANG_MAIL"].Should().Be("translations@ashampoo.com"); } [Fact] @@ -74,13 +78,16 @@ public void TranslationChunk() var chunkReader = new ChunkReader(GetNewStream()); var chunk = chunkReader.TryGetOrNull(Chunk.TranslationChunk.Id); - Assert.Equal(67, chunk?.Translations.Count); - var translation = chunk?.Translations[3]; - Assert.Equal("", translation?.Comment); - Assert.Equal("Music files", translation?.Fallback); - Assert.Equal((uint)0, translation?.Flags); - Assert.Equal("peru.filesystem.smart.Music", translation?.Id); - Assert.Equal("peru.filesystem.smart.Music", translation?.Key); - Assert.Equal("Musikdateien", translation?.Value); + chunk.Should().NotBeNull(); + chunk!.Translations.Count.Should().Be(67); + + var translation = chunk.Translations[3]; + translation.Should().NotBeNull(); + translation.Comment.Should().BeEmpty(); + translation.Fallback.Should().Be("Music files"); + translation.Flags.Should().Be(0); + translation.Id.Should().Be("peru.filesystem.smart.Music"); + translation.Key.Should().Be("peru.filesystem.smart.Music"); + translation.Value.Should().Be("Musikdateien"); } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/PluginLoaderTest.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/PluginLoaderTest.cs deleted file mode 100644 index 5684817..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/PluginLoaderTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ashampoo.Translation.Systems.Formats.Abstractions; -using Xunit; - -namespace Ashampoo.Translation.Systems.Formats.AshLang.Tests; - -public class PluginLoaderTest -{ - private readonly IFormatFactory formatFactory; - - public PluginLoaderTest(IFormatFactory formatFactory) - { - this.formatFactory = formatFactory; - } - - [Fact] - public void LoadTest() - { - Assert.NotEmpty(formatFactory.GetFormatProviders()); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/Startup.cs b/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/Startup.cs deleted file mode 100644 index 7c24f3c..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.AshLang/tests/Startup.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.IO; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Ashampoo.Translation.Systems.Formats.AshLang.Tests; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - // services.AddFormatFactory().AddFormatProvider(builder => - // { - // return builder.SetId("ashlang") - // .SetSupportedFileExtensions(new[] { ".ashlang" }) - // .SetFormatType() - // .SetFormatBuilder() - // .Create(); - // }); - - services.AddLogging(b => b.AddConsole()); - - services.AddFormatFactory().RegisterFormat();; - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Gengo/src/Ashampoo.Translation.Systems.Formats.Gengo.csproj b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/Ashampoo.Translation.Systems.Formats.Gengo.csproj index f377f5b..3bddf2c 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Gengo/src/Ashampoo.Translation.Systems.Formats.Gengo.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/Ashampoo.Translation.Systems.Formats.Gengo.csproj @@ -1,25 +1,10 @@  - net7.0 + net8.0 enable enable - Ashampoo.Translation.Systems.Formats.Gengo - tjorvenK - ashampoo - Package containing the implementation for the Gengo format. - LICENSE - localization;translation;gengo true - true - true - true - snupkg - ash-logo-icon-big-128x.png - v - true - prerelease.0 - README.md @@ -28,7 +13,7 @@ - + @@ -38,7 +23,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Ashampoo.Translation.Systems.Formats.Gengo/src/DependencyInjectionExtension.cs b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/DependencyInjectionExtension.cs new file mode 100644 index 0000000..1f2c1af --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/DependencyInjectionExtension.cs @@ -0,0 +1,31 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.Gengo; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services for the Gengo format. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection AddGengoFormatFeatures(this IServiceCollection services) + { + services.AddSingleton() + .AddSingleton>(sp => sp.GetRequiredService()) + .AddSingleton>(sp => sp.GetRequiredService()); + + return services; + } +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormat.cs b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormat.cs index 32e52fb..502b886 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormat.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormat.cs @@ -1,10 +1,10 @@ using System.Text.RegularExpressions; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Microsoft.Toolkit.Diagnostics; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.Formats.Gengo; @@ -12,21 +12,23 @@ namespace Ashampoo.Translation.Systems.Formats.Gengo; /// /// Implementation of the interface for the Gengo translation format. /// -public class GengoFormat : AbstractTranslationUnits, IFormat +public partial class GengoFormat : IFormat { /// public IFormatHeader Header { get; init; } = new DefaultFormatHeader(); /// - public FormatLanguageCount LanguageCount => FormatLanguageCount.SourceAndTarget; + public LanguageSupport LanguageSupport => LanguageSupport.SourceAndTarget; + + /// + public ICollection TranslationUnits { get; } = new List(); private static readonly Regex RegexMarker = - new(@"^\[{3}(?.*)\]{3}$", - RegexOptions.Singleline); // Regex to get the id with square brackets around it. + RegexMarkerGenerator(); // Regex to get the id with square brackets around it. private static readonly Regex - RegexWithoutMarker = new(@"^(?.*)$"); // Regex to get the id without square brackets around it. + RegexWithoutMarker = RegexWithoutMarkerGenerator(); // Regex to get the id without square brackets around it. /// public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) @@ -44,17 +46,18 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) var workbook = WorkbookFactory.Create(stream); // Create the workbook from the stream var sheet = workbook.GetSheetAt(0); // Get the first sheet - Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage, + Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage.Value, nameof(Header.TargetLanguage)); // The target language has to be set ReadTranslations(sheet); // Read the translations from the sheet } private async Task ConfigureOptionsAsync(FormatReadOptions options) { + var setTargetLanguage = - string.IsNullOrWhiteSpace(options.TargetLanguage); // Check if the target language needs to be set + options.TargetLanguage.IsNullOrWhitespace(); // Check if the target language needs to be set var setSourceLanguage = - string.IsNullOrWhiteSpace(options.SourceLanguage); // Check if the source language needs to be set + options.SourceLanguage.IsNullOrWhitespace(); // Check if the source language needs to be set if (setTargetLanguage || setSourceLanguage) { if (options.FormatOptionsCallback is null) @@ -63,7 +66,7 @@ private async Task ConfigureOptionsAsync(FormatReadOptions options) FormatStringOption sourceLanguageOption = new("Source language"); FormatStringOption targetLanguageOption = new("Target language", true); - List optionList = new(); + List optionList = []; if (setSourceLanguage) optionList.Add(sourceLanguageOption); if (setTargetLanguage) optionList.Add(targetLanguageOption); @@ -79,12 +82,12 @@ private async Task ConfigureOptionsAsync(FormatReadOptions options) Header.SourceLanguage = setSourceLanguage - ? sourceLanguageOption.Value + ? Language.Parse(sourceLanguageOption.Value) : options.SourceLanguage; // Set the source language if it was not set Header.TargetLanguage = - setTargetLanguage - ? targetLanguageOption.Value - : options.TargetLanguage!; // Set the target language if it was not set + (setTargetLanguage + ? Language.Parse(targetLanguageOption.Value) + : options.TargetLanguage)!; // Set the target language if it was not set } else { @@ -105,21 +108,23 @@ private void ReadTranslations(ISheet sheet) var translations = CreateTranslations(row); // Create the translations from the row if (translations is null) continue; - var translationUnit = new DefaultTranslationUnit(translations.Item1.Id) // Create the translation unit + var translationUnit = new DefaultTranslationUnit(translations.Item3) // Create the translation unit { - [translations.Item1.Language] = translations.Item1, - [translations.Item2.Language] = translations.Item2 + Translations = + { + translations.Item1, + translations.Item2 + } }; - - Add(translationUnit); // Add the translation unit to the hash set of translation units + TranslationUnits.Add(translationUnit); // Add the translation unit to the hash set of translation units } } - private Tuple? CreateTranslations(IRow row) + private Tuple? CreateTranslations(IRow row) { var idCell = row.TryGetCell(0); // Get the first cell if (idCell is null) return null; // If the cell is null, return null - if(idCell.Address.Column != 0) return null; // If the first cell is not in the first column, skip the row + if (idCell.Address.Column != 0) return null; // If the first cell is not in the first column, skip the row var id = idCell.StringCellValue ?? string.Empty; // Get the id from the cell if (string.IsNullOrWhiteSpace(id)) return null; // If the id is empty, skip the row @@ -128,7 +133,7 @@ private void ReadTranslations(ISheet sheet) var sourceCell = row.TryGetCell(1); // Get the second cell if (sourceCell is null) return null; // If the cell is null, return null - if(sourceCell.Address.Column != 1) return null; // If the second cell is not in the second column, skip the row + if (sourceCell.Address.Column != 1) return null; // If the second cell is not in the second column, skip the row var source = sourceCell.StringCellValue ?? string.Empty; // Get the source from the cell if (string.IsNullOrWhiteSpace(source)) return null; // If the source is empty, skip the row @@ -147,10 +152,10 @@ private void ReadTranslations(ISheet sheet) ( id, target, - Header.TargetLanguage ?? throw new ArgumentNullException(nameof(Header.TargetLanguage)) + Header.TargetLanguage ); - return new Tuple(sourceTranslation, targetTranslation); + return new Tuple(sourceTranslation, targetTranslation, id); } private string RemoveMarker(string str) @@ -171,7 +176,7 @@ public void Write(Stream stream) CreateHeaderRow(sheet); // Create the header row var rowCount = 1; // The row count - foreach (var translationUnit in this) // Loop through all translation units + foreach (var translationUnit in TranslationUnits) // Loop through all translation units { var row = sheet.CreateRow(rowCount); // Create a new row @@ -181,8 +186,8 @@ public void Write(Stream stream) } row.Cells[0].SetCellValue($"[[[{translationUnit.Id}]]]"); // Set the id with square brackets around it - row.Cells[1].SetCellValue((translationUnit.TryGet(Header.SourceLanguage!) as ITranslationString)?.Value); - row.Cells[2].SetCellValue((translationUnit.TryGet(Header.TargetLanguage) as ITranslationString)?.Value); + row.Cells[1].SetCellValue(translationUnit.Translations.GetTranslation((Language)Header.SourceLanguage!).Value); + row.Cells[2].SetCellValue(translationUnit.Translations.GetTranslation(Header.TargetLanguage).Value); rowCount++; } @@ -190,10 +195,17 @@ public void Write(Stream stream) AutosizeColumns(sheet, 0, 3); // Autosize the columns - workbook.Write(stream, true); // Write the workbook to the stream, and leave the stream open TODO: Is this correct? + workbook.Write(stream, + true); // Write the workbook to the stream, and leave the stream open TODO: Is this correct? + } + + /// + public Task WriteAsync(Stream stream) + { + throw new NotImplementedException(); } - private void CreateHeaderRow(ISheet sheet) + private void CreateHeaderRow(ISheet sheet) { var row = sheet.CreateRow(0); // Create a new row @@ -215,13 +227,8 @@ private void AutosizeColumns(ISheet sheet, int start, int count) } } - /// - public Func BuildFormatProvider() - { - return builder => builder.SetId("gengo") - .SetSupportedFileExtensions(new[] { ".xlsx", ".xls" }) - .SetFormatType() - .SetFormatBuilder() - .Create(); - } + [GeneratedRegex(@"^\[{3}(?.*)\]{3}$", RegexOptions.Singleline)] + private static partial Regex RegexMarkerGenerator(); + [GeneratedRegex(@"^(?.*)$")] + private static partial Regex RegexWithoutMarkerGenerator(); } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormatBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormatBuilder.cs index ef539dc..c18f437 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormatBuilder.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormatBuilder.cs @@ -1,4 +1,5 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Microsoft.Toolkit.Diagnostics; @@ -7,23 +8,23 @@ namespace Ashampoo.Translation.Systems.Formats.Gengo; /// /// Builder for the . /// -public class GengoFormatBuilder : IFormatBuilderWithSourceAndTarget +public class GengoFormatBuilder : IFormatBuilderWithSourceAndTarget { - private string? sourceLanguage; - private string? targetLanguage; - private readonly Dictionary translations = new(); + private Language? _sourceLanguage; + private Language? _targetLanguage; + private readonly Dictionary _translations = new(); /// public void Add(string id, string source, string target) { - translations.Add(id, (source, target)); + _translations.Add(id, (source, target)); } /// - public IFormat Build() + public GengoFormat Build() { - Guard.IsNotNullOrWhiteSpace(sourceLanguage, nameof(sourceLanguage)); - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); + Guard.IsNotNullOrWhiteSpace(_sourceLanguage?.Value, nameof(_sourceLanguage)); + Guard.IsNotNullOrWhiteSpace(_targetLanguage?.Value, nameof(_targetLanguage)); //Create new Gengo format and add translations @@ -31,45 +32,48 @@ public IFormat Build() { Header = { - SourceLanguage = sourceLanguage, - TargetLanguage = targetLanguage + SourceLanguage = _sourceLanguage, + TargetLanguage = _targetLanguage.Value } }; // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (var keyValuePair in translations) + foreach (var keyValuePair in _translations) { var sourceTranslationString = new DefaultTranslationString(keyValuePair.Key, keyValuePair.Value.Item1, - sourceLanguage); //Create new translation string + _sourceLanguage.Value); //Create new translation string var targetTranslationString = new DefaultTranslationString(keyValuePair.Key, keyValuePair.Value.Item2, - targetLanguage); //Create new translation string + _targetLanguage.Value); //Create new translation string var translationUnit = new DefaultTranslationUnit(keyValuePair.Key) //Create new translation unit { - sourceTranslationString, - targetTranslationString + Translations = + { + sourceTranslationString, + targetTranslationString + } }; - gengoFormat.Add(translationUnit); //Add translation unit to format + gengoFormat.TranslationUnits.Add(translationUnit); //Add translation unit to format } return gengoFormat; } /// - public void SetSourceLanguage(string language) + public void SetSourceLanguage(Language language) { - sourceLanguage = language; + _sourceLanguage = language; } /// - public void SetTargetLanguage(string language) + public void SetTargetLanguage(Language language) { - targetLanguage = language; + _targetLanguage = language; } - + /// /// This method is not supported because does not support header information, /// it will do nothing. diff --git a/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormatProvider.cs new file mode 100644 index 0000000..fafa2d9 --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.Gengo/src/GengoFormatProvider.cs @@ -0,0 +1,27 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; + +namespace Ashampoo.Translation.Systems.Formats.Gengo; + +/// +/// for the Gengo format. +/// +public sealed class GengoFormatProvider : IFormatProvider +{ + /// + public string Id { get; } = "gengo"; + + /// + public GengoFormat Create() => new(); + + /// + public bool SupportsFileName(string fileName) + { + return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + } + + /// + public string[] SupportedFileExtensions { get; } = [".xlsx", ".xls"]; + + /// + public IFormatBuilder GetFormatBuilder() => new GengoFormatBuilder(); +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/Ashampoo.Translation.Systems.Formats.Gengo.Tests.csproj b/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/Ashampoo.Translation.Systems.Formats.Gengo.Tests.csproj index a90b4f5..7805575 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/Ashampoo.Translation.Systems.Formats.Gengo.Tests.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/Ashampoo.Translation.Systems.Formats.Gengo.Tests.csproj @@ -1,18 +1,14 @@  - net7.0 + net8.0 enable false - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/FormatTest.cs b/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/FormatTest.cs index 2803313..c5630e7 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/FormatTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/FormatTest.cs @@ -2,9 +2,10 @@ using System.IO; using System.Threading.Tasks; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using NPOI.XSSF.UserModel; using Xunit; @@ -12,13 +13,6 @@ namespace Ashampoo.Translation.Systems.Formats.Gengo.Tests; public class FormatTest : FormatTestBase { - private readonly IFormatFactory formatFactory; - - public FormatTest(IFormatFactory formatFactory) - { - this.formatFactory = formatFactory; - } - private static XSSFWorkbook CreateFileWithHeaderRow() { var workbook = new XSSFWorkbook(); @@ -37,30 +31,23 @@ private static XSSFWorkbook CreateFileWithHeaderRow() return workbook; } - [Fact] - public void IsAssignableFrom() - { - IFormat format = CreateFormat(); - - Assert.IsAssignableFrom(format); - } - [Fact] public void NewFormat() { var format = CreateFormat(); - Assert.NotNull(format); - Assert.Empty(format); - Assert.Null(format.Header.SourceLanguage); - Assert.Equal(string.Empty, format.Header.TargetLanguage); - Assert.Equal(FormatLanguageCount.SourceAndTarget, format.LanguageCount); - - format = new GengoFormat { Header = new DefaultFormatHeader() { SourceLanguage = "en-US", TargetLanguage = "de-DE" } }; - Assert.NotNull(format); - Assert.Empty(format); - Assert.Equal("en-US", format.Header.SourceLanguage); - Assert.Equal("de-DE", format.Header.TargetLanguage); + format.Should().NotBeNull(); + format.TranslationUnits.Should().BeEmpty(); + format.Header.SourceLanguage.Should().BeNull(); + format.Header.TargetLanguage.Value.Should().BeEmpty(); + format.LanguageSupport.Should().Be(LanguageSupport.SourceAndTarget); + + format = new GengoFormat + { Header = new DefaultFormatHeader() { SourceLanguage = new Language("en-US"), TargetLanguage = new Language("de-DE") } }; + format.Should().NotBeNull(); + format.TranslationUnits.Should().BeEmpty(); + format.Header.SourceLanguage.Should().Be(new Language("en-US")); + format.Header.TargetLanguage.Should().Be(new Language("de-DE")); } @@ -68,50 +55,50 @@ public void NewFormat() public void ReadFromFile() { var format = CreateAndReadFromFile("normalized-excel-test.xlsx", - new FormatReadOptions { SourceLanguage = "de-DE", TargetLanguage = "en-US" }); + new FormatReadOptions { SourceLanguage = new Language("de-DE"), TargetLanguage = new Language("en-US") }); - Assert.Equal(4, format.Count); + format.TranslationUnits.Count.Should().Be(4); const string id = "MESSAGES.MESSAGE_BETAVERSION_EXPIRED"; - var foundById = format[id]; - var translationString = foundById?["en-US"] as ITranslationString; + var foundById = format.TranslationUnits.GetTranslationUnit(id); + var translationString = foundById.Translations.GetTranslation(new Language("en-US")); const string target = - @"Unfortunately, the beta version of the software has expired. Please install the final version of this software.%CRLFYou can download it from ‘www.ashampoo.com’."; - Assert.NotNull(foundById); - Assert.Equal(2, foundById.Count); - Assert.Equal(target, translationString?.Value); + "Unfortunately, the beta version of the software has expired. Please install the final version of this software.%CRLFYou can download it from ‘www.ashampoo.com’."; + foundById.Should().NotBeNull(); + foundById.Translations.Count.Should().Be(2); + translationString.Value.Should().Be(target); } [Fact] public void ReadFromFileWithoutTarget() { IFormat format = CreateAndReadFromFile("empty-target-excel-test.xlsx", - new FormatReadOptions { SourceLanguage = "de-DE", TargetLanguage = "en-US" }); + new FormatReadOptions { SourceLanguage = new Language("de-DE"), TargetLanguage = new Language("en-US") }); - Assert.Equal("en-US", format.Header.TargetLanguage); - Assert.Equal("de-DE", format.Header.SourceLanguage); + format.Header.TargetLanguage.Should().Be(new Language("en-US")); + format.Header.SourceLanguage.Should().Be(new Language("de-DE")); - Assert.Equal(4, format.Count); - foreach (var unit in format) + format.TranslationUnits.Count.Should().Be(4); + foreach (var unit in format.TranslationUnits) { - Assert.Equal(2, unit.Count); + unit.Translations.Count.Should().Be(2); } const string id = "MESSAGES.MESSAGE_BETAVERSION_EXPIRED"; const string value = "Diese Betaversion der Software ist leider abgelaufen. Bitte installieren Sie die finale Version dieser Software.%CRLFSie können diese z.B. von \"www.ashampoo.com\" herunterladen."; - var foundById = format[id]; - var translationString = foundById?["de-DE"] as ITranslationString; - - Assert.Equal(value, translationString?.Value); + var foundById = format.TranslationUnits.GetTranslationUnit(id); + foundById.Should().NotBeNull(); + foundById.Translations.TryGetTranslation(new Language("de-DE"), out var translation).Should().BeTrue(); + translation?.Value.Should().Be(value); } [Fact] public void ReadAndWrite() { IFormat format = CreateAndReadFromFile("normalized-excel-test.xlsx", - new FormatReadOptions { SourceLanguage = "de-DE", TargetLanguage = "en-US" }); + new FormatReadOptions { SourceLanguage = new Language("de-DE"), TargetLanguage = new Language("en-US") }); var temp = Path.GetTempPath(); using var outStream = new FileStream($"{temp}temp-normalized-excel-test.xlsx", FileMode.Create, @@ -128,7 +115,7 @@ public void ReadAndWrite() public void ReadWithoutTargetAndWrite() { IFormat format = CreateAndReadFromFile("empty-target-excel-test.xlsx", - new FormatReadOptions { SourceLanguage = "de-DE", TargetLanguage = "en-US" }); + new FormatReadOptions { SourceLanguage = new Language("de-DE"), TargetLanguage = new Language("en-US") }); var temp = Path.GetTempPath(); var outStream = new FileStream($"{temp}temp-empty-target-excel-test.xlsx", FileMode.Create, FileAccess.Write, FileShare.ReadWrite); @@ -139,83 +126,6 @@ public void ReadWithoutTargetAndWrite() File.Delete($"{temp}temp-empty-target-excel-test.xlsx"); } - [Fact] - public void ImportSuccessTest() - { - IFormat format = CreateAndReadFromFile("normalized-excel-test.xlsx", - new FormatReadOptions { SourceLanguage = "de-DE", TargetLanguage = "en-US" }); - - const string id = "MESSAGES.MESSAGE_TRANSLATOR_NAME"; - const string valueSource = "Import Test Source"; - const string valueTarget = "Import Test Target"; - var importedWithUnits = - format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: valueSource); - - Assert.NotNull(importedWithUnits); - Assert.Single(importedWithUnits); - Assert.Equal("Import Test Source", (format[id]?["de-DE"] as ITranslationString)?.Value); - - importedWithUnits = format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: valueTarget); - Assert.NotNull(importedWithUnits); - Assert.Single(importedWithUnits); - Assert.Equal("Import Test Target", (format[id]?["en-US"] as ITranslationString)?.Value); - } - - [Fact] - public void NoMatchImportTest() - { - IFormat format = CreateAndReadFromFile("normalized-excel-test.xlsx", - new FormatReadOptions { SourceLanguage = "de-DE", TargetLanguage = "en-US" }); - - const string id = "Not matching Import-Id"; - const string value = "Import Test"; - var imported = format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: value); - Assert.Empty(imported); - } - - [Fact] - public void ImportEqualTranslationTest() - { - IFormat format = CreateAndReadFromFile("normalized-excel-test.xlsx", - new FormatReadOptions { SourceLanguage = "de-DE", TargetLanguage = "en-US" }); - - const string id = "MESSAGES.MESSAGE_TRANSLATOR_NAME"; - const string value = "Ashampoo Development GmbH & Co. KG"; - var imported = format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: value); - Assert.Empty(imported); - } - - [Fact] - public async Task ConvertWithSourceSetTest() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits(language: "de-DE", - id: "Convert Test", value: "Hallo Welt"); - var options = new AssignOptions - { SourceLanguage = "de-DE", TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }; - var convertedFormat = await mockFormat.ConvertToAsync(formatFactory, options); - - Assert.NotNull(convertedFormat); - - Assert.Single(convertedFormat); - Assert.NotNull(convertedFormat["Convert Test"]); - Assert.Equal("Hallo Welt", (convertedFormat["Convert Test"]?["de-DE"] as ITranslationString)?.Value); - } - - [Fact] - public async Task AssignWithSimpleFilter() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits(language: "de-DE", - id: "Convert Test", value: "Hallo Welt"); - var options = new AssignOptions - { Filter = new IsEmptyTranslationFilter(), SourceLanguage = "de-DE", TargetLanguage = "en-US" }; - var assignedFormat = await mockFormat.ConvertToAsync(formatFactory, options); - Assert.NotNull(assignedFormat); - - Assert.Empty(assignedFormat); - } - [Fact] public async Task IncompatibleExcelFileTest() { @@ -232,7 +142,7 @@ public async Task IncompatibleExcelFileTest() ms.Seek(0, SeekOrigin.Begin); var format = CreateFormat(); - var options = new FormatReadOptions { SourceLanguage = "en-US", TargetLanguage = "de-DE" }; + var options = new FormatReadOptions { SourceLanguage = new Language("en-US"), TargetLanguage = new Language("de-DE") }; await format.ReadAsync(ms, options); } @@ -253,18 +163,18 @@ public async Task EmptySourceTest() ms.Seek(0, SeekOrigin.Begin); var format = CreateFormat(); - var options = new FormatReadOptions { SourceLanguage = "en-US", TargetLanguage = "de-DE" }; + var options = new FormatReadOptions { SourceLanguage = new Language("en-US"), TargetLanguage = new Language("de-DE") }; await format.ReadAsync(ms, options); - Assert.Empty(format); + format.TranslationUnits.Should().BeEmpty(); } [Fact] public async Task EmptyCellsTest() { var format = await CreateAndReadFromFileAsync("empty-cells-excel-test.xlsx", - new FormatReadOptions { SourceLanguage = "en-US", TargetLanguage = "de-DE" }); - Assert.Empty(format); + new FormatReadOptions { SourceLanguage = new Language("en-US"), TargetLanguage = new Language("de-DE") }); + format.TranslationUnits.Should().BeEmpty(); } [Fact] @@ -295,8 +205,8 @@ Task OptionsCallback(FormatOptions options) var options = new FormatReadOptions { FormatOptionsCallback = OptionsCallback }; await format.ReadAsync(ms, options); - Assert.True(options.IsCancelled); - Assert.Empty(format); + options.IsCancelled.Should().BeTrue(); + format.TranslationUnits.Should().BeEmpty(); } [Fact] @@ -313,7 +223,7 @@ public async Task NoOptionsCallbackTest() var exception = await Assert.ThrowsAsync(async () => await format.ReadAsync(ms, options)); - Assert.Equal("Callback for Format options required.", exception.Message); + exception.Message.Should().Be("Callback for Format options required."); } [Fact] @@ -345,11 +255,13 @@ Task OptionsCallback(FormatOptions options) var options = new FormatReadOptions { FormatOptionsCallback = OptionsCallback }; await format.ReadAsync(ms, options); - Assert.Equal("en-US", format.Header.SourceLanguage); - Assert.Equal("de-DE", format.Header.TargetLanguage); - Assert.Single(format); - Assert.NotNull(format["ID Test"]); - Assert.Equal("Test source", (format["ID Test"]?["en-US"] as ITranslationString)?.Value); - Assert.Equal("Test target", (format["ID Test"]?["de-DE"] as ITranslationString)?.Value); + format.Header.SourceLanguage.Should().Be(new Language("en-US")); + format.Header.TargetLanguage.Should().Be(new Language("de-DE")); + format.TranslationUnits.Should().ContainSingle(); + format.TranslationUnits.GetTranslationUnit("ID Test").Should().NotBeNull(); + format.TranslationUnits.GetTranslationUnit("ID Test").Translations.GetTranslation(new Language("en-US")).Value.Should() + .Be("Test source"); + format.TranslationUnits.GetTranslationUnit("ID Test").Translations.GetTranslation(new Language("de-DE")).Value.Should() + .Be("Test target"); } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/Startup.cs b/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/Startup.cs deleted file mode 100644 index b19cff2..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Gengo/tests/Startup.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Toolkit.Diagnostics; - -namespace Ashampoo.Translation.Systems.Formats.Gengo.Tests; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddFormatFactory().RegisterFormat(); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Json/src/Ashampoo.Translation.Systems.Formats.Json.csproj b/src/Ashampoo.Translation.Systems.Formats.Json/src/Ashampoo.Translation.Systems.Formats.Json.csproj index 252bc8f..0a26875 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Json/src/Ashampoo.Translation.Systems.Formats.Json.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.Json/src/Ashampoo.Translation.Systems.Formats.Json.csproj @@ -1,25 +1,10 @@  - net7.0 + net8.0 enable enable - Ashampoo.Translation.Systems.Formats.Json - tjorvenK - ashampoo - Package containing the implementation for the Json format. - LICENSE - localization;translation;json true - true - true - true - snupkg - ash-logo-icon-big-128x.png - v - true - prerelease.0 - README.md @@ -33,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Ashampoo.Translation.Systems.Formats.Json/src/DependencyInjectionExtension.cs b/src/Ashampoo.Translation.Systems.Formats.Json/src/DependencyInjectionExtension.cs new file mode 100644 index 0000000..93a71b2 --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.Json/src/DependencyInjectionExtension.cs @@ -0,0 +1,31 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.Json; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services for the Gengo format. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection AddJsonFormatFeatures(this IServiceCollection services) + { + services.AddSingleton() + .AddSingleton>(sp => sp.GetRequiredService()) + .AddSingleton>(sp => sp.GetRequiredService()); + + return services; + } +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormat.cs b/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormat.cs index e08f7f1..6cfaea2 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormat.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormat.cs @@ -3,22 +3,26 @@ using System.Text.Json.Nodes; using System.Text.RegularExpressions; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using CommunityToolkit.Diagnostics; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.Formats.Json; /// /// Implementation of interface, for JSON files. /// -public class JsonFormat : AbstractTranslationUnits, IFormat +public class JsonFormat : IFormat { /// public IFormatHeader Header { get; init; } = new DefaultFormatHeader(); /// - public FormatLanguageCount LanguageCount => FormatLanguageCount.OnlyTarget; + public LanguageSupport LanguageSupport => LanguageSupport.OnlyTarget; + + /// + public ICollection TranslationUnits { get; } = new List(); + private const string Divider = "/"; private static readonly Regex ArrayIdentifierRegex = new(@"\[\d+\]"); @@ -33,7 +37,7 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) return; } - Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage, nameof(Header.TargetLanguage)); //Target language is required + Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage.Value, nameof(Header.TargetLanguage)); //Target language is required var root = await JsonSerializer.DeserializeAsync(stream); // Deserialize JSON Parse(root); // Parse JSON to TranslationUnits @@ -41,7 +45,7 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) private async Task ConfigureOptionsAsync(FormatReadOptions? options) { - if (string.IsNullOrWhiteSpace(options?.TargetLanguage)) + if (string.IsNullOrWhiteSpace(options?.TargetLanguage.Value)) { ArgumentNullException.ThrowIfNull(options?.FormatOptionsCallback, nameof(options.FormatOptionsCallback)); // Format options callback is required @@ -49,20 +53,20 @@ private async Task ConfigureOptionsAsync(FormatReadOptions? options) FormatStringOption targetLanguageOption = new("Target language", true); FormatOptions formatOptions = new() { - Options = new FormatOption[] - { + Options = + [ targetLanguageOption - } + ] }; await options.FormatOptionsCallback.Invoke(formatOptions); // Invoke callback to get format options if (formatOptions.IsCanceled) return false; // If user cancelled, return false - Header.TargetLanguage = targetLanguageOption.Value; + Header.TargetLanguage = Language.Parse(targetLanguageOption.Value); } else { - Header.TargetLanguage = options.TargetLanguage; + Header.TargetLanguage = (Language)options.TargetLanguage!; } return true; @@ -106,10 +110,13 @@ private void ParseObject(string id, JsonElement element) var translationUnit = new DefaultTranslationUnit(nextId) // Create translation unit { - translationString + Translations = + { + translationString + } }; - Add(translationUnit); // Add translation unit to list + TranslationUnits.Add(translationUnit); // Add translation unit to list } else Parse(nextId, property.Value); // Parse next element @@ -139,8 +146,13 @@ private void ParseArray(string id, JsonElement element) ); var translationUnit = new DefaultTranslationUnit(nextId) - { translationString }; // Create translation unit - Add(translationUnit); // Add translation unit to list + { + Translations = + { + translationString + } + }; + TranslationUnits.Add(translationUnit); // Add translation unit to list break; } case JsonValueKind.Array: @@ -181,7 +193,7 @@ public async Task WriteAsync(Stream stream) private void CreateJsonObjects(JsonObject obj) { - foreach (var unit in this) + foreach (var unit in TranslationUnits) { var id = unit.Id; var index = id.IndexOf(Divider, @@ -199,8 +211,8 @@ private void CreateJsonObject(string id, string trailing, JsonObject obj, ITrans { if (trailing.Length == 0) // If trailing is empty, element has no nested elements { - var value = (unit[Header.TargetLanguage] as ITranslationString)?.Value ?? - throw new ArgumentNullException(nameof(ITranslationString.Value)); + var value = unit.Translations.GetTranslation(Header.TargetLanguage).Value ?? + throw new ArgumentNullException(nameof(ITranslation.Value)); var jsonValue = JsonValue.Create(value); // create json element from value obj.Add(id, jsonValue); return; @@ -212,18 +224,18 @@ private void CreateJsonObject(string id, string trailing, JsonObject obj, ITrans { > 0 => trailing[(index + Divider.Length)..], _ => string.Empty - }; + }; if (ArrayIdentifierRegex.IsMatch(front)) // Check if next element is an array item { // Next element is an array item, so current element is an array - + foreach (var (key, value) in obj) { if (key != id) continue; // find the array element with the same id as the current element var array = value?.AsArray() ?? throw new Exception("Expected array."); // get the array - CreateJsonArray(front, newTrailing, array, unit); + CreateJsonArray(front, newTrailing, array, unit); return; } @@ -232,9 +244,9 @@ private void CreateJsonObject(string id, string trailing, JsonObject obj, ITrans obj.Add(id, newArray); return; } - + // Next element is an object element - + foreach (var pair in obj) // Check if object element with the same id exists { if (pair.Key != id) continue; @@ -243,7 +255,7 @@ private void CreateJsonObject(string id, string trailing, JsonObject obj, ITrans CreateJsonObject(front, newTrailing, jsonObj, unit); return; } - + // Object element doesn't exist, create new object var newJsonObj = new JsonObject(); @@ -257,19 +269,24 @@ private void CreateJsonArray(string id, string trailing, JsonArray array, ITrans { if (trailing.Length == 0) // Current element does not have nested elements { - var value = (unit[Header.TargetLanguage] as ITranslationString)?.Value ?? - throw new ArgumentNullException(nameof(ITranslationString.Value)); + var value = unit.Translations.GetTranslation(Header.TargetLanguage).Value ?? + throw new ArgumentNullException(nameof(ITranslation.Value)); var jsonValue = JsonValue.Create(value); array.Add(jsonValue); return; } - + // Current element has nested elements var position = int.Parse(id.Trim('[', ']')); // Get array index from id var index = trailing.IndexOf(Divider, StringComparison.Ordinal); // Get index of next divider - var front = index > 0 ? trailing[..index] : trailing; // Front part of trailing is the id for the next element - var newTrailing = index > 0 ? trailing[(index + Divider.Length)..] : ""; // Trailing part of trailing is the trailing part of the next element + var front = index > 0 + ? trailing[..index] + : trailing; // Front part of trailing is the id for the next element + var newTrailing = + index > 0 + ? trailing[(index + Divider.Length)..] + : ""; // Trailing part of trailing is the trailing part of the next element var element = array.ElementAtOrDefault(position); // Get array element at position @@ -297,14 +314,4 @@ private void CreateJsonArray(string id, string trailing, JsonArray array, ITrans break; } } - - /// - public Func BuildFormatProvider() - { - return builder => builder.SetId("json") - .SetSupportedFileExtensions(new[] { ".json" }) - .SetFormatType() - .SetFormatBuilder() - .Create(); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormatBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormatBuilder.cs index 3c45601..d996b5d 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormatBuilder.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormatBuilder.cs @@ -1,4 +1,5 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using CommunityToolkit.Diagnostics; @@ -7,37 +8,37 @@ namespace Ashampoo.Translation.Systems.Formats.Json; /// /// Builder for the . /// -public class JsonFormatBuilder : IFormatBuilderWithTarget +public class JsonFormatBuilder : IFormatBuilderWithTarget { - private string? targetLanguage; - private readonly Dictionary translations = new(); + private Language? _targetLanguage; + private readonly Dictionary _translations = new(); /// public void Add(string id, string target) { - translations.Add(id, target); + _translations.Add(id, target); } /// - public IFormat Build() + public JsonFormat Build() { - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); + Guard.IsNotNullOrWhiteSpace(_targetLanguage?.Value, nameof(_targetLanguage)); //create new json format and add translations var jsonFormat = new JsonFormat { Header = { - TargetLanguage = targetLanguage + TargetLanguage = (Language)_targetLanguage! } }; - foreach (var translation in translations) + foreach (var translation in _translations) { var translationUnit = new DefaultTranslationUnit(translation.Key); - var translationString = new DefaultTranslationString(translation.Key, translation.Value, targetLanguage); - translationUnit.Add(translationString); - jsonFormat.Add(translationUnit); + var translationString = new DefaultTranslationString(translation.Key, translation.Value, (Language)_targetLanguage); + translationUnit.Translations.Add(translationString); + jsonFormat.TranslationUnits.Add(translationUnit); } return jsonFormat; @@ -63,8 +64,8 @@ public void AddHeaderInformation(string key, string value) } /// - public void SetTargetLanguage(string language) + public void SetTargetLanguage(Language language) { - targetLanguage = language; + _targetLanguage = language; } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormatProvider.cs new file mode 100644 index 0000000..0e6b388 --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.Json/src/JsonFormatProvider.cs @@ -0,0 +1,27 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; + +namespace Ashampoo.Translation.Systems.Formats.Json; + +/// +/// The for the . +/// +public sealed class JsonFormatProvider : IFormatProvider +{ + /// + public string Id { get; } = "json"; + + /// + public JsonFormat Create() => new(); + + /// + public bool SupportsFileName(string fileName) + { + return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + } + + /// + public string[] SupportedFileExtensions { get; } = [".json"]; + + /// + public IFormatBuilder GetFormatBuilder() => new JsonFormatBuilder(); +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Json/tests/Ashampoo.Translation.Systems.Formats.Json.Tests.csproj b/src/Ashampoo.Translation.Systems.Formats.Json/tests/Ashampoo.Translation.Systems.Formats.Json.Tests.csproj index 8ceae5b..7d53566 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Json/tests/Ashampoo.Translation.Systems.Formats.Json.Tests.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.Json/tests/Ashampoo.Translation.Systems.Formats.Json.Tests.csproj @@ -1,18 +1,14 @@  - net7.0 + net8.0 enable false - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Ashampoo.Translation.Systems.Formats.Json/tests/FormatTest.cs b/src/Ashampoo.Translation.Systems.Formats.Json/tests/FormatTest.cs index b8d55e9..2c9fc88 100644 --- a/src/Ashampoo.Translation.Systems.Formats.Json/tests/FormatTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.Json/tests/FormatTest.cs @@ -4,59 +4,44 @@ using System.Text.Json; using System.Threading.Tasks; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.Json.Tests; public class FormatTest : FormatTestBase { - private readonly IFormatFactory formatFactory; - - public FormatTest(IFormatFactory formatFactory) - { - this.formatFactory = formatFactory; - } - - [Fact] - public void IsAssignableFrom() - { - IFormat format = CreateFormat(); - - // provides translation units - Assert.IsAssignableFrom(format); - } - [Fact] public void NewFormat() { var format = CreateFormat(); - Assert.NotNull(format); - Assert.Empty(format); - Assert.Null(format.Header.SourceLanguage); - Assert.Equal(string.Empty, format.Header.TargetLanguage); - Assert.Equal(FormatLanguageCount.OnlyTarget, format.LanguageCount); + format.Should().NotBeNull(); + format.TranslationUnits.Should().BeEmpty(); + format.Header.SourceLanguage.Should().BeNull(); + format.Header.TargetLanguage.Value.Should().BeEmpty(); + format.LanguageSupport.Should().Be(LanguageSupport.OnlyTarget); } [Fact] public void ReadFromFile() { - IFormat format = CreateAndReadFromFile("en-us.json", new FormatReadOptions { TargetLanguage = "en-US" }); - - Assert.Equal(301, format.Count); - Assert.Equal("en-US", format.Header.TargetLanguage); - Assert.Null(format.Header.SourceLanguage); - Assert.Equal("Save", (format["settings/save"]?["en-US"] as ITranslationString)?.Value); + IFormat format = CreateAndReadFromFile("en-us.json", new FormatReadOptions { TargetLanguage = new Language("en-US") }); + + format.TranslationUnits.Should().HaveCount(301); + format.Header.TargetLanguage.Should().Be(new Language("en-US")); + format.Header.SourceLanguage.Should().BeNull(); + format.TranslationUnits.GetTranslationUnit("settings/save").Translations.GetTranslation(new Language("en-US")).Value.Should().Be("Save"); } [Fact] public void ReadAndWrite() { - IFormat format = CreateAndReadFromFile("de-DE.json", new FormatReadOptions { TargetLanguage = "de-DE" }); + IFormat format = CreateAndReadFromFile("de-DE.json", new FormatReadOptions { TargetLanguage = new Language("de-DE") }); var ms = new MemoryStream(); format.Write(ms); @@ -75,106 +60,29 @@ public void ReadAndWrite() [InlineData("json-value-kind-number-test.json")] public async Task InvalidValueKindsTest(string filename) { - var options = new FormatReadOptions { TargetLanguage = "de-DE" }; + var options = new FormatReadOptions { TargetLanguage = new Language("de-DE") }; var exception = await Assert.ThrowsAsync(async () => await CreateAndReadFromFileAsync(filename, options)); - Assert.Equal("Array element must be either an object, array or a string.", exception.Message); + exception.Message.Should().Be("Array element must be either an object, array or a string."); } [Fact] public async Task ReadAndWriteWithReorder() { - var format = await CreateAndReadFromFileAsync("de-DE.json", new FormatReadOptions { TargetLanguage = "de-DE" }); + var format = await CreateAndReadFromFileAsync("de-DE.json", new FormatReadOptions { TargetLanguage = new Language("de-DE") }); - var units = format.OrderBy(u => u.Id); + var units = format.TranslationUnits.OrderBy(u => u.Id); format = new JsonFormat { Header = format.Header }; foreach (var unit in units) { - format.Add(unit); + format.TranslationUnits.Add(unit); } var ms = new MemoryStream(); await format.WriteAsync(ms); ms.Seek(0, SeekOrigin.Begin); } - - [Fact] - public async Task SimpleAssign() - { - var mock = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "ID", "Hello World"); - var converted = await mock.ConvertToAsync(formatFactory, - new AssignOptions { TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }); - - Assert.Single(converted); - Assert.Single(converted["ID"] ?? Enumerable.Empty()); - Assert.Equal("Hello World", (converted["ID"]?["en-US"] as ITranslationString)?.Value); - } - - [Fact] - public async Task ComplexAssign() - { - const string id = "peru.CSystem.CreateUniqueFileFailed"; - var mockFormat = new MockFormatWithTranslationUnits - { - { "en-US", id, "Error creating unique file name." }, - { "de-DE", id, "Fehler beim Erzeugen eines eindeutigen Dateinamens" } - }; - - var optionsEn = new AssignOptions { TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }; - var optionsDe = new AssignOptions { TargetLanguage = "de-DE", Filter = new DefaultTranslationFilter() }; - - var convertedEnUs = await mockFormat.ConvertToAsync(formatFactory, optionsEn); - var convertedDeDe = await mockFormat.ConvertToAsync(formatFactory, optionsDe); - - - Assert.Equal("en-US", convertedEnUs.Header.TargetLanguage); - Assert.Equal("de-DE", convertedDeDe.Header.TargetLanguage); - - Assert.Equal("Error creating unique file name.", - (convertedEnUs[id]?["en-US"] as ITranslationString)?.Value); - Assert.Equal("Fehler beim Erzeugen eines eindeutigen Dateinamens", - (convertedDeDe[id]?["de-DE"] as ITranslationString)?.Value); - } - - [Fact] - public void ImportSuccessTest() - { - IFormat format = CreateAndReadFromFile("en-us.json", new FormatReadOptions { TargetLanguage = "en-US" }); - - const string id = "settings/save"; - const string value = "Import Test"; - - var importedWithUnits = format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: value); - Assert.NotNull(importedWithUnits); - Assert.Single(importedWithUnits); - Assert.Equal("Import Test", (format[id]?["en-US"] as ITranslationString)?.Value); - } - - [Fact] - public void NoMatchImportTest() - { - IFormat format = CreateAndReadFromFile("en-us.json", new FormatReadOptions { TargetLanguage = "en-US" }); - - const string id = "Not matching Import-Id"; - const string value = "Import Test"; - var imported = format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: value); - - Assert.Empty(imported); - } - - [Fact] - public void ImportEqualTranslationTest() - { - IFormat format = CreateAndReadFromFile("en-us.json", new FormatReadOptions { TargetLanguage = "en-US" }); - - const string id = "settings/save"; - const string value = "Save"; - var imported = format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: value); - - Assert.Empty(imported); - } - + [Fact] public async Task OptionsCallbackCancelledTest() { @@ -186,9 +94,9 @@ Task OptionsCallback(FormatOptions options) var format = await CreateAndReadFromFileAsync("en-us.json", new FormatReadOptions { FormatOptionsCallback = OptionsCallback }); - Assert.Null(format.Header.SourceLanguage); - Assert.Equal(string.Empty, format.Header.TargetLanguage); - Assert.Empty(format); + format.Header.SourceLanguage.Should().BeNull(); + format.Header.TargetLanguage.Value.Should().BeEmpty(); + format.TranslationUnits.Should().BeEmpty(); } [Fact] @@ -196,8 +104,8 @@ public async Task NoOptionsCallbackTest() { var exception = await Assert.ThrowsAsync(async () => await CreateAndReadFromFileAsync("en-us.json", new FormatReadOptions())); - Assert.Equal("Value cannot be null. (Parameter 'FormatOptionsCallback')", exception.Message); - Assert.Equal("FormatOptionsCallback", exception.ParamName); + exception.Message.Should().Be("Value cannot be null. (Parameter 'FormatOptionsCallback')"); + exception.ParamName.Should().Be("FormatOptionsCallback"); } [Fact] @@ -212,8 +120,8 @@ Task OptionsCallback(FormatOptions options) var options = new FormatReadOptions { FormatOptionsCallback = OptionsCallback }; var format = await CreateAndReadFromFileAsync("de-DE.json", options); - Assert.Equal("de-DE", format.Header.TargetLanguage); - Assert.Null(format.Header.SourceLanguage); - Assert.Equal(189, format.Count); + format.Header.TargetLanguage.Should().Be(new Language("de-DE")); + format.Header.SourceLanguage.Should().BeNull(); + format.TranslationUnits.Should().HaveCount(189); } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.Json/tests/Startup.cs b/src/Ashampoo.Translation.Systems.Formats.Json/tests/Startup.cs deleted file mode 100644 index fae9891..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.Json/tests/Startup.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using CommunityToolkit.Diagnostics; -using Microsoft.Extensions.DependencyInjection; - -namespace Ashampoo.Translation.Systems.Formats.Json.Tests; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddFormatFactory().RegisterFormat(); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/src/Ashampoo.Translation.Systems.Formats.NLang.csproj b/src/Ashampoo.Translation.Systems.Formats.NLang/src/Ashampoo.Translation.Systems.Formats.NLang.csproj index 0563211..0a26875 100644 --- a/src/Ashampoo.Translation.Systems.Formats.NLang/src/Ashampoo.Translation.Systems.Formats.NLang.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/src/Ashampoo.Translation.Systems.Formats.NLang.csproj @@ -1,25 +1,10 @@  - net7.0 + net8.0 enable enable - Ashampoo.Translation.Systems.Formats.NLang - tjorvenK - ashampoo - Package containing the implementation for the NLang format. - LICENSE - localization;translation;nlang true - true - true - true - snupkg - ash-logo-icon-big-128x.png - v - true - prerelease.0 - README.md @@ -33,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/src/DependencyInjectionExtension.cs b/src/Ashampoo.Translation.Systems.Formats.NLang/src/DependencyInjectionExtension.cs new file mode 100644 index 0000000..ec861ed --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/src/DependencyInjectionExtension.cs @@ -0,0 +1,31 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.NLang; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services for the Gengo format. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection AddNLangFormatFeatures(this IServiceCollection services) + { + services.AddSingleton() + .AddSingleton>(sp => sp.GetRequiredService()) + .AddSingleton>(sp => sp.GetRequiredService()); + + return services; + } +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormat.cs b/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormat.cs index b84b1a6..faf6f5c 100644 --- a/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormat.cs +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormat.cs @@ -2,24 +2,27 @@ using System.Text.RegularExpressions; using Ashampoo.Translation.Systems.Formats.Abstractions; using Ashampoo.Translation.Systems.Formats.Abstractions.IO; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using CommunityToolkit.Diagnostics; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.Formats.NLang; /// /// Implementation of interface for the NLang format. /// -public class NLangFormat : AbstractTranslationUnits, IFormat +public class NLangFormat : IFormat { - private static readonly Regex ReMsg = new(@"(?.*?)=(?.*)"); + private static readonly Regex ReMsg = new("(?.*?)=(?.*)"); /// public IFormatHeader Header { get; init; } = new DefaultFormatHeader(); /// - public FormatLanguageCount LanguageCount => FormatLanguageCount.OnlyTarget; + public LanguageSupport LanguageSupport => LanguageSupport.OnlyTarget; + + /// + public ICollection TranslationUnits { get; } = new List(); /// public void Read(Stream stream, FormatReadOptions? options = null) @@ -37,7 +40,7 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) return; } - Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage, + Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage.Value, nameof(Header.TargetLanguage)); // Target language is required // TODO: Dispose of streams and readers? @@ -49,7 +52,7 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) private async Task ConfigureOptionsAsync(FormatReadOptions? options) { - if (string.IsNullOrWhiteSpace(options?.TargetLanguage)) + if (string.IsNullOrWhiteSpace(options?.TargetLanguage.Value)) { if (options?.FormatOptionsCallback is null) throw new InvalidOperationException("Callback for Format options required."); @@ -57,20 +60,20 @@ private async Task ConfigureOptionsAsync(FormatReadOptions? options) FormatStringOption targetLanguageOption = new("Target language", true); FormatOptions formatOptions = new() { - Options = new FormatOption[] - { + Options = + [ targetLanguageOption - } + ] }; await options.FormatOptionsCallback.Invoke(formatOptions); // Invoke callback if (formatOptions.IsCanceled) return false; - Header.TargetLanguage = targetLanguageOption.Value; + Header.TargetLanguage = Language.Parse(targetLanguageOption.Value); } else { - Header.TargetLanguage = options.TargetLanguage; + Header.TargetLanguage = (Language)options.TargetLanguage!; } return true; @@ -81,18 +84,13 @@ private async Task ReadTranslations(LineReader lineReader) await lineReader.SkipEmptyLinesAsync(); while (await lineReader.HasMoreLinesAsync()) { - var translation = await ReadTranslation(lineReader); // Read translation - TranslationUnit translationUnit = new(id: translation.Id) // Create translation unit - { - [translation.Language] = translation - }; - Add(translationUnit); + TranslationUnits.Add(await ReadTranslation(lineReader)); await lineReader.SkipEmptyLinesAsync(); } } //TODO: add comment support - private async Task ReadTranslation(LineReader lineReader) + private async Task ReadTranslation(LineReader lineReader) { var line = await lineReader.ReadLineAsync() ?? string.Empty; @@ -101,15 +99,23 @@ private async Task ReadTranslation(LineReader lineReader) throw new UnsupportedFormatException(this, $"Unsupported line '{line}' at line number {lineReader.LineNumber}."); - var key = match.Groups["key"].Value; + var id = match.Groups["key"].Value; var value = match.Groups["value"].Value; value = value.Replace("%CRLF", "\n"); - return new TranslationString // Create translation string + + var translation = new TranslationString // Create translation string ( - key, + id, value, Header.TargetLanguage ); + return new TranslationUnit(id) + { + Translations = + { + translation + } + }; } /// @@ -132,7 +138,7 @@ public async Task WriteAsync(Stream stream) // NLang is UTF16 LE var writer = new StreamWriter(stream, Encoding.Unicode); - foreach (var translationUnit in this) + foreach (var translationUnit in TranslationUnits) { if (translationUnit is not TranslationUnit nLangTranslationUnit) throw new Exception($"Unexpected translation unit: {translationUnit.GetType()}"); @@ -141,14 +147,4 @@ public async Task WriteAsync(Stream stream) await writer.FlushAsync(); } - - /// - public Func BuildFormatProvider() - { - return builder => builder.SetId("nlang") - .SetSupportedFileExtensions(new[] { ".nlang3" }) - .SetFormatType() - .SetFormatBuilder() - .Create(); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormatBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormatBuilder.cs index c72a4c0..f011958 100644 --- a/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormatBuilder.cs +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormatBuilder.cs @@ -1,4 +1,5 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using CommunityToolkit.Diagnostics; namespace Ashampoo.Translation.Systems.Formats.NLang; @@ -6,46 +7,46 @@ namespace Ashampoo.Translation.Systems.Formats.NLang; /// /// Builder for . /// -public class NLangFormatBuilder : IFormatBuilderWithTarget +public class NLangFormatBuilder : IFormatBuilderWithTarget { - private string? targetLanguage; - private readonly Dictionary translations = new(); + private Language? _targetLanguage; + private readonly Dictionary _translations = new(); /// public void Add(string id, string target) { - translations.Add(id, target); + _translations.Add(id, target); } /// - public IFormat Build() + public NLangFormat Build() { - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); + Guard.IsNotNullOrWhiteSpace(_targetLanguage?.Value, nameof(_targetLanguage)); //Create new NLang format and add translations var nLangFormat = new NLangFormat { Header = { - TargetLanguage = targetLanguage + TargetLanguage = (Language)_targetLanguage! } }; - foreach (var translation in translations) + foreach (var translation in _translations) { var translationUnit = new TranslationUnit(translation.Key); - var translationString = new TranslationString(translation.Key, translation.Value, targetLanguage); - translationUnit.Add(translationString); - nLangFormat.Add(translationUnit); + var translationString = new TranslationString(translation.Key, translation.Value, (Language)_targetLanguage); + translationUnit.Translations.Add(translationString); + nLangFormat.TranslationUnits.Add(translationUnit); } return nLangFormat; } /// - public void SetTargetLanguage(string language) + public void SetTargetLanguage(Language language) { - targetLanguage = language; + _targetLanguage = language; } /// diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormatProvider.cs new file mode 100644 index 0000000..17d928c --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/src/NLangFormatProvider.cs @@ -0,0 +1,27 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; + +namespace Ashampoo.Translation.Systems.Formats.NLang; + +/// +/// Implementation of for the NLang format. +/// +public sealed class NLangFormatProvider : IFormatProvider +{ + /// + public string Id { get; } = "nlang"; + + /// + public NLangFormat Create() => new(); + + /// + public bool SupportsFileName(string fileName) + { + return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + } + + /// + public string[] SupportedFileExtensions { get; } = [".nlang3"]; + + /// + public IFormatBuilder GetFormatBuilder() => new NLangFormatBuilder(); +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/src/TranslationString.cs b/src/Ashampoo.Translation.Systems.Formats.NLang/src/TranslationString.cs index d5f7238..b889958 100644 --- a/src/Ashampoo.Translation.Systems.Formats.NLang/src/TranslationString.cs +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/src/TranslationString.cs @@ -1,20 +1,21 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; namespace Ashampoo.Translation.Systems.Formats.NLang; /// -/// Implementation of the interface for the NLang format. +/// Implementation of the interface for the NLang format. /// public class TranslationString : AbstractTranslationString { /// - public TranslationString(string id, string value, string language, string comment) : base(id, value, language, + public TranslationString(string id, string value, Language language, string comment) : base(id, value, language, comment) { } /// - public TranslationString(string id, string value, string language) : base(id, value, language) + public TranslationString(string id, string value, Language language) : base(id, value, language) { } diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/src/TranslationUnit.cs b/src/Ashampoo.Translation.Systems.Formats.NLang/src/TranslationUnit.cs index 6f47e5b..e735e5f 100644 --- a/src/Ashampoo.Translation.Systems.Formats.NLang/src/TranslationUnit.cs +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/src/TranslationUnit.cs @@ -20,7 +20,7 @@ public TranslationUnit(string id) : base(id) /// public async Task WriteAsync(TextWriter writer) { - foreach (var translation in this) + foreach (var translation in Translations) { if (translation is TranslationString translationString) await translationString.WriteAsync(writer); } diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/tests/Ashampoo.Translation.Systems.Formats.NLang.Tests.csproj b/src/Ashampoo.Translation.Systems.Formats.NLang/tests/Ashampoo.Translation.Systems.Formats.NLang.Tests.csproj index 7feacbd..dd5d3d3 100644 --- a/src/Ashampoo.Translation.Systems.Formats.NLang/tests/Ashampoo.Translation.Systems.Formats.NLang.Tests.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/tests/Ashampoo.Translation.Systems.Formats.NLang.Tests.csproj @@ -1,18 +1,14 @@  - net7.0 + net8.0 enable false - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/tests/FormatTest.cs b/src/Ashampoo.Translation.Systems.Formats.NLang/tests/FormatTest.cs index 42d4952..be9e0d5 100644 --- a/src/Ashampoo.Translation.Systems.Formats.NLang/tests/FormatTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.NLang/tests/FormatTest.cs @@ -1,71 +1,52 @@ using System.IO; -using System.Linq; -using System.Threading.Tasks; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.NLang.Tests; public class FormatTest : FormatTestBase { - private readonly IFormatFactory formatFactory; - - public FormatTest(IFormatFactory formatFactory) - { - this.formatFactory = formatFactory; - } - - [Fact] - public void IsAssignableFrom() - { - IFormat format = CreateFormat(); - // provides translation units - Assert.IsAssignableFrom(format); - } - [Fact] public void NewFormat() { var format = CreateFormat(); - - Assert.NotNull(format); - Assert.Empty(format); - Assert.Null(format.Header.SourceLanguage); - Assert.Equal(string.Empty, format.Header.TargetLanguage); + + format.Should().NotBeNull(); + format.TranslationUnits.Should().BeEmpty(); + format.Header.SourceLanguage.Should().BeNull(); + format.Header.TargetLanguage.Value.Should().BeEmpty(); } [Fact] public void ReadFromFile() { - IFormat format = CreateAndReadFromFile("de-de.nlang3", new FormatReadOptions { TargetLanguage = "de-DE" }); + IFormat format = CreateAndReadFromFile("de-de.nlang3", new FormatReadOptions { TargetLanguage = new Language("de-DE") }); - - foreach (var translationUnit in format) + foreach (var translationUnit in format.TranslationUnits) { - Assert.Single(translationUnit); + translationUnit.Translations.Should().ContainSingle(); } - - Assert.Equal(3223, format.Count); + format.TranslationUnits.Count.Should().Be(3223); const string id = "Form_BOOT.MenuItem_TASK_Delete"; - var foundById = format[id]; - var translationString = foundById?["de-DE"] as TranslationString; - Assert.NotNull(foundById); - Assert.NotNull(translationString); - Assert.Equal("Löschen", translationString.Value); - Assert.Null(translationString?.Comment); - Assert.Equal(id, translationString?.Id); + var foundById = format.TranslationUnits.GetTranslationUnit(id); + foundById.Should().NotBeNull(); + var translationString = foundById.Translations.GetTranslation(new Language("de-DE")); + translationString.Should().NotBeNull(); + translationString!.Value.Should().Be("Löschen"); + translationString.Comment.Should().BeNull(); } [Fact] public void ReadAndWrite() { - IFormat format = CreateAndReadFromFile("de-de.nlang3", new FormatReadOptions { TargetLanguage = "de-DE" }); + IFormat format = CreateAndReadFromFile("de-de.nlang3", new FormatReadOptions { TargetLanguage = new Language("de-DE") }); var ms = new MemoryStream(); format.Write(ms); @@ -75,57 +56,6 @@ public void ReadAndWrite() //FIXME: compare formats like in the other tests, and not the streams! //fs.MustBeEqualTo(ms); - } - - [Fact] - public void ImportSuccessTest() - { - IFormat format = CreateAndReadFromFile("de-de.nlang3", new FormatReadOptions { TargetLanguage = "de-DE" }); - - const string id = "MESSAGES.MESSAGE_TRANSLATOR_NAME"; - const string value = "Import Test"; - - var importedWithUnits = format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: value); - Assert.NotNull(importedWithUnits); - Assert.Single(importedWithUnits); - Assert.Equal("Import Test", (format[id]?["de-DE"] as ITranslationString)?.Value); - } - - [Fact] - public void NoMatchImportTest() - { - IFormat format = CreateAndReadFromFile("de-de.nlang3", new FormatReadOptions { TargetLanguage = "de-DE" }); - - const string id = "Not matching Import-Id"; - const string value = "Import Test"; - var imported = format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: value); - - Assert.Empty(imported); - } - - [Fact] - public void ImportEqualTranslationTest() - { - IFormat format = CreateAndReadFromFile("de-de.nlang3", new FormatReadOptions { TargetLanguage = "de-DE" }); - - const string id = "MESSAGES.MESSAGE_TRANSLATOR_NAME"; - const string value = "Ashampoo Development GmbH & Co. KG"; - var imported = format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: value); - - Assert.Empty(imported); - } - - [Fact] - public async Task ConvertTest() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "Convert ID", "Convert Test"); - var assignOptions = new AssignOptions { TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }; - var nlang = await mockFormat.ConvertToAsync(formatFactory, assignOptions); - - Assert.NotNull(nlang); - Assert.Single(nlang); - Assert.Equal("Convert ID", nlang.First().Id); - Assert.Equal("Convert Test", (nlang["Convert ID"]?["en-US"] as ITranslationString)?.Value); + } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.NLang/tests/Startup.cs b/src/Ashampoo.Translation.Systems.Formats.NLang/tests/Startup.cs deleted file mode 100644 index 544fb14..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.NLang/tests/Startup.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using CommunityToolkit.Diagnostics; -using Microsoft.Extensions.DependencyInjection; - -namespace Ashampoo.Translation.Systems.Formats.NLang.Tests; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddFormatFactory().RegisterFormat(); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/Ashampoo.Translation.Systems.Formats.PO.csproj b/src/Ashampoo.Translation.Systems.Formats.PO/src/Ashampoo.Translation.Systems.Formats.PO.csproj index 42e4964..0a26875 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/src/Ashampoo.Translation.Systems.Formats.PO.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/Ashampoo.Translation.Systems.Formats.PO.csproj @@ -1,25 +1,10 @@  - net7.0 + net8.0 enable enable - Ashampoo.Translation.Systems.Formats.PO - tjorvenK - ashampoo - Package containing the implementation for the PO format. - LICENSE - localization;translation;po true - true - true - true - snupkg - ash-logo-icon-big-128x.png - v - true - prerelease.0 - README.md @@ -33,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/DependencyInjectionExtension.cs b/src/Ashampoo.Translation.Systems.Formats.PO/src/DependencyInjectionExtension.cs new file mode 100644 index 0000000..deae35f --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/DependencyInjectionExtension.cs @@ -0,0 +1,31 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.PO; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services for the Gengo format. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection AddPOFormatFeatures(this IServiceCollection services) + { + services.AddSingleton() + .AddSingleton>(sp => sp.GetRequiredService()) + .AddSingleton>(sp => sp.GetRequiredService()); + + return services; + } +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/Message.cs b/src/Ashampoo.Translation.Systems.Formats.PO/src/Message.cs index fef6f1a..f676563 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/src/Message.cs +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/Message.cs @@ -1,3 +1,4 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; namespace Ashampoo.Translation.Systems.Formats.PO; @@ -40,16 +41,16 @@ public abstract class Message : ITranslation /// public string Id => !string.IsNullOrWhiteSpace(MsgCtxt) ? $"{MsgCtxt}{Divider}{MsgId}" : MsgId; + /// + public string Value { get; set; } = string.Empty; + /// /// Provides the comment for the ITranslation interface. /// public string? Comment { get; set; } /// - public abstract bool IsEmpty { get; } - - /// - public string Language { get; set; } = ""; + public Language Language { get; set; } = Language.Empty; /// /// Write the message to the given writer. diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/MessageString.cs b/src/Ashampoo.Translation.Systems.Formats.PO/src/MessageString.cs index d77f0f0..b482765 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/src/MessageString.cs +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/MessageString.cs @@ -1,31 +1,29 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; namespace Ashampoo.Translation.Systems.Formats.PO; /// -/// Implementation of ITranslationString and a PO message string. +/// Implementation of and a PO message string. /// -public class MessageString : Message, ITranslationString +public class MessageString : Message, ITranslation { /// /// Message string of the po format. /// - public string MsgStr { get; set; } = ""; + public string MsgStr { get; private set; } = ""; /// - /// Provides the value for the ITranslationString interface. + /// Provides the value for the interface. /// - public string Value + public new string Value { get => MsgStr; set => MsgStr = value; } /// - public override bool IsEmpty => string.IsNullOrWhiteSpace(MsgStr); - - /// - public MessageString(string id, string value, string language, string? comment = null, string msgCtxt = "") + public MessageString(string id, string value, Language language, string? comment = null, string msgCtxt = "") { MsgId = id; Value = value; diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormat.cs b/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormat.cs index 2c97339..9fa5d5c 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormat.cs +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormat.cs @@ -2,24 +2,27 @@ using System.Text.RegularExpressions; using Ashampoo.Translation.Systems.Formats.Abstractions; using Ashampoo.Translation.Systems.Formats.Abstractions.IO; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using CommunityToolkit.Diagnostics; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.Formats.PO; /// /// Implementation of the PO (Portable Object) format. /// -public class POFormat : AbstractTranslationUnits, IFormat +public class POFormat : IFormat { - private static Regex reComment = new(@"#(?[|:,. ]{0,1})(?.*)"); + private static Regex _reComment = new("#(?[|:,. ]{0,1})(?.*)"); /// public IFormatHeader Header { get; init; } = new POHeader(); /// - public FormatLanguageCount LanguageCount => FormatLanguageCount.OnlyTarget; + public LanguageSupport LanguageSupport => LanguageSupport.OnlyTarget; + + /// + public ICollection TranslationUnits { get; } = new List(); /// public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) @@ -36,15 +39,15 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) return; } - Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage, nameof(Header.TargetLanguage)); // check if target language is set + Guard.IsNotNullOrWhiteSpace(Header.TargetLanguage.Value, nameof(Header.TargetLanguage)); // check if target language is set await ReadMessagesAsTranslationsAsync(lineReader); } private async Task ConfigureOptionsAsync(FormatReadOptions? options) { - if (!string.IsNullOrWhiteSpace(Header.TargetLanguage)) return true; - if (string.IsNullOrWhiteSpace(options?.TargetLanguage)) + if (!Header.TargetLanguage.IsNullOrWhitespace()) return true; + if (string.IsNullOrWhiteSpace(options?.TargetLanguage.Value)) { if (options?.FormatOptionsCallback is null) throw new InvalidOperationException("Callback for Format options required."); @@ -52,21 +55,21 @@ private async Task ConfigureOptionsAsync(FormatReadOptions? options) FormatStringOption targetLanguageOption = new("Target language", true); FormatOptions formatOptions = new() { - Options = new FormatOption[] - { + Options = + [ targetLanguageOption - } + ] }; await options.FormatOptionsCallback.Invoke(formatOptions); // invoke callback if (formatOptions.IsCanceled) return false; - Header.TargetLanguage = targetLanguageOption.Value; + Header.TargetLanguage = Language.Parse(targetLanguageOption.Value); } else { - Header.TargetLanguage = options.TargetLanguage; + Header.TargetLanguage = (Language)options.TargetLanguage!; } return true; @@ -87,7 +90,7 @@ private async Task ReadFirstMessageAsHeaderAsync(LineReader lineReader) if (tuple.Length != 2) continue; // skip if not key:value var key = tuple[0].Trim(); var value = tuple[1].Trim(); - Header.Add(key, value); // add key:value to header + Header.AdditionalHeaders.Add(key, value); // add key:value to header } } @@ -105,9 +108,12 @@ private async Task ReadMessagesAsTranslationsAsync(LineReader lineReader) var translationUnit = new TranslationUnit(messageString.Id) // Create translation unit { - [Header.TargetLanguage] = messageString + Translations = + { + messageString + } }; - Add(translationUnit); + TranslationUnits.Add(translationUnit); } } @@ -133,7 +139,7 @@ private async Task ReadMessageAsync(LineReader lineReader, bool omitTar var comment = comments.Count > 0 ? string.Join("", comments) : null; - var language = omitTargetLanguage ? "" : Header.TargetLanguage; + var language = omitTargetLanguage ? Language.Empty : Header.TargetLanguage; return new MessageString(id: msgId, value: msgStr, language: language, comment: comment, msgCtxt: msgCtxt); } @@ -210,7 +216,7 @@ public async Task WriteAsync(Stream stream) await poHeader.WriteAsync(writer); // write messages. - foreach (var unit in this) + foreach (var unit in TranslationUnits) { if (unit is not TranslationUnit poTranslationUnit) throw new Exception($"Unexpected translation unit: {unit.GetType()}"); @@ -220,14 +226,4 @@ public async Task WriteAsync(Stream stream) await writer.FlushAsync(); } - - /// - public Func BuildFormatProvider() - { - return builder => builder.SetId("po") - .SetSupportedFileExtensions(new[] { ".po" }) - .SetFormatType() - .SetFormatBuilder() - .Create(); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormatBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormatBuilder.cs index ff97740..633d118 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormatBuilder.cs +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormatBuilder.cs @@ -1,38 +1,39 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using CommunityToolkit.Diagnostics; namespace Ashampoo.Translation.Systems.Formats.PO; /// -/// Implementation of the for the PO format. +/// Implementation of the for the PO format. /// -public class POFormatBuilder : IFormatBuilderWithTarget +public class POFormatBuilder : IFormatBuilderWithTarget { - private string? targetLanguage; - private readonly Dictionary translations = new(); + private Language? _targetLanguage; + private readonly Dictionary _translations = new(); private const string Divider = "/"; // TODO: move to interface? /// public void Add(string id, string target) { - translations.Add(id, target); + _translations.Add(id, target); } /// - public IFormat Build() + public POFormat Build() { - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); + Guard.IsNotNullOrWhiteSpace(_targetLanguage?.Value, nameof(_targetLanguage)); //Create new PO format and add translations var poFormat = new POFormat { Header = { - TargetLanguage = targetLanguage + TargetLanguage = (Language)_targetLanguage! } }; - foreach (var translation in translations) + foreach (var translation in _translations) { var translationUnit = new TranslationUnit(translation.Key); var index = translation.Key.LastIndexOf(Divider, StringComparison.Ordinal); @@ -40,18 +41,19 @@ public IFormat Build() { var ctxt = translation.Key[..index]; var msgId = translation.Key[(index + 1)..]; - translationUnit.Add(new MessageString(id: msgId, value: translation.Value, language: targetLanguage, + translationUnit.Translations.Add(new MessageString(id: msgId, value: translation.Value, language: (Language)_targetLanguage, msgCtxt: ctxt)); } else - translationUnit.Add(new MessageString(translation.Key, translation.Value, targetLanguage)); + translationUnit.Translations.Add(new MessageString(translation.Key, translation.Value, (Language)_targetLanguage)); - poFormat.Add(translationUnit); + poFormat.TranslationUnits.Add(translationUnit); } return poFormat; } - + + /// public void SetHeaderInformation(IFormatHeader header) { //TODO: implement @@ -64,8 +66,8 @@ public void AddHeaderInformation(string key, string value) } /// - public void SetTargetLanguage(string language) + public void SetTargetLanguage(Language language) { - targetLanguage = language; + _targetLanguage = language; } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormatProvider.cs new file mode 100644 index 0000000..5e55926 --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/POFormatProvider.cs @@ -0,0 +1,28 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; + +namespace Ashampoo.Translation.Systems.Formats.PO; + +/// +/// Implementation of the for the PO format. +/// +// ReSharper disable once InconsistentNaming +public sealed class POFormatProvider : IFormatProvider +{ + /// + public string Id { get; } = "po"; + + /// + public POFormat Create() => new(); + + /// + public bool SupportsFileName(string fileName) + { + return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + } + + /// + public string[] SupportedFileExtensions { get; } = [".po"]; + + /// + public IFormatBuilder GetFormatBuilder() => new POFormatBuilder(); +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/POHeader.cs b/src/Ashampoo.Translation.Systems.Formats.PO/src/POHeader.cs index 98eb553..65a42b2 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/src/POHeader.cs +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/POHeader.cs @@ -1,4 +1,5 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; namespace Ashampoo.Translation.Systems.Formats.PO; @@ -8,17 +9,16 @@ namespace Ashampoo.Translation.Systems.Formats.PO; public class POHeader : AbstractFormatHeader { /// - public override string? SourceLanguage { get; set; } + public override Language? SourceLanguage { get; set; } /// - public override string TargetLanguage + public override Dictionary AdditionalHeaders { get; set; } = new(); + + /// + public override Language TargetLanguage { - get => this["Language"] ?? throw new NullReferenceException("TargetLanguage is not set."); - set - { - if (value is null) throw new ArgumentNullException(nameof(value)); - this["Language"] = value; - } + get => Language.Parse(AdditionalHeaders["Language"]); + set => AdditionalHeaders["Language"] = value.ToString(); } /// @@ -26,13 +26,13 @@ public override string TargetLanguage /// public string? Author { - get => this["Last-Translator"]; + get => AdditionalHeaders["Last-Translator"]; set { if (value is null) - Remove("Last-Translator"); + AdditionalHeaders.Remove("Last-Translator"); else - this["Last-Translator"] = value; + AdditionalHeaders["Last-Translator"] = value; } } @@ -44,7 +44,7 @@ public async Task WriteAsync(TextWriter writer) { await writer.WriteLineAsync("msgid \"\""); await writer.WriteLineAsync("msgstr \"\""); - foreach (var (key, value) in this) + foreach (var (key, value) in AdditionalHeaders) { // skip empty values. if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(value)) continue; diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/src/TranslationUnit.cs b/src/Ashampoo.Translation.Systems.Formats.PO/src/TranslationUnit.cs index 2f88e0d..6e46e88 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/src/TranslationUnit.cs +++ b/src/Ashampoo.Translation.Systems.Formats.PO/src/TranslationUnit.cs @@ -20,7 +20,7 @@ public TranslationUnit(string id) : base(id) /// public async Task WriteAsync(TextWriter writer) { - if (this.FirstOrDefault() is Message message) + if (Translations.FirstOrDefault() is Message message) { await message.WriteAsync(writer); } diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/tests/Ashampoo.Translation.Systems.Formats.PO.Tests.csproj b/src/Ashampoo.Translation.Systems.Formats.PO/tests/Ashampoo.Translation.Systems.Formats.PO.Tests.csproj index 967bdf3..fa2ef1b 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/tests/Ashampoo.Translation.Systems.Formats.PO.Tests.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.PO/tests/Ashampoo.Translation.Systems.Formats.PO.Tests.csproj @@ -1,18 +1,14 @@  - net7.0 + net8.0 enable false - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/tests/FormatTest.cs b/src/Ashampoo.Translation.Systems.Formats.PO/tests/FormatTest.cs index 41093cc..8e414e1 100644 --- a/src/Ashampoo.Translation.Systems.Formats.PO/tests/FormatTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.PO/tests/FormatTest.cs @@ -1,96 +1,49 @@ using System.IO; +using System.Linq; using System.Threading.Tasks; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.PO.Tests; public class FormatTest : FormatTestBase { - private readonly IFormatFactory formatFactory; - - public FormatTest(IFormatFactory formatFactory) - { - this.formatFactory = formatFactory; - } - - [Fact] - public void ImportSuccessTest() - { - IFormat format = CreateAndReadFromFile("normalized_translation_de.po", - new FormatReadOptions { TargetLanguage = "de" }); - - const string id = - "{\\\"cxt\\\": \\\"question_heading\\\", \\\"id\\\": 418828805, \\\"checksum\\\": 1234361483}/What do you think about Ashampoo? What changes would you like to see?"; - const string value = "Import Test"; - var imported = format.ImportMockTranslationWithUnits(language: "de", id: id, value: value); - - Assert.Equal(1, imported.Count); - Assert.Equal(value, (format[id]?["de"] as ITranslationString)?.Value); - } - - [Fact] - public void NoMatchImportTest() - { - IFormat format = CreateAndReadFromFile("normalized_translation_de.po", - new FormatReadOptions { TargetLanguage = "de" }); - - const string id = "Not matching Import-Id"; - const string value = "Import Test"; - var imported = format.ImportMockTranslationWithUnits(language: "de", id: id, value: value); - - Assert.Equal(0, imported.Count); - } - - [Fact] - public void ImportEqualTranslationTest() - { - IFormat format = CreateAndReadFromFile("normalized_translation_de.po", - new FormatReadOptions { TargetLanguage = "de" }); - - const string id = - "What do you think about Ashampoo? What changes would you like to see?{\\\"cxt\\\": \\\"question_heading\\\", \\\"id\\\": 418828805, \\\"checksum\\\": 1234361483}"; - const string value = "Was denken Sie über die Firma Ashampoo? Worüber würden Sie sich freuen?"; - var imported = format.ImportMockTranslationWithUnits(language: "de", id: id, value: value); - - Assert.Equal(0, imported.Count); - } - - [Fact] public void NewFormat() { IFormat format = CreateFormat(); - Assert.Equal(0, format.Count); + format.TranslationUnits.Count.Should().Be(0); } [Fact] public void ReadFromFile() { - var format = CreateAndReadFromFile("translation_de.po", new FormatReadOptions { SourceLanguage = "en-US" }); + var format = CreateAndReadFromFile("translation_de.po", new FormatReadOptions { SourceLanguage = new Language("en-US") }); var poHeader = format.Header as POHeader; - Assert.NotNull(poHeader); + poHeader.Should().NotBeNull(); - Assert.Equal(69, format.Count); - Assert.Equal("de", format.Header.TargetLanguage); - Assert.Equal("FULL NAME ", poHeader.Author); + format.TranslationUnits.Count.Should().Be(69); + format.Header.TargetLanguage.Should().Be(new Language("de")); + poHeader?.Author.Should().Be("FULL NAME "); const string id = "{\\\"cxt\\\": \\\"collector_disqualification\\\", \\\"id\\\": 254239623, \\\"checksum\\\": 2373663968}/Thank you for completing our survey!"; - Assert.Equal("Vielen Dank, dass Sie die Umfrage abgeschlossen haben!", - (format[id]?["de"] as ITranslationString)?.Value); + + format.TranslationUnits.GetTranslationUnit(id).Translations.GetTranslation(new Language("de")).Value.Should() + .Be("Vielen Dank, dass Sie die Umfrage abgeschlossen haben!"); } [Fact] public async Task ReadAndWrite() { var format = await CreateAndReadFromFileAsync("normalized_translation_de.po", - new FormatReadOptions { TargetLanguage = "de" }); + new FormatReadOptions { TargetLanguage = new Language("de") }); var temp = Path.GetTempPath(); var outStream = new FileStream($"{temp}normalized_translation_de.po", FileMode.Create, FileAccess.Write, @@ -112,38 +65,4 @@ public async Task ReadAndWrite() //fs.MustBeEqualTo(ms); File.Delete($"{temp}normalized_translation_de.po"); } - - [Fact] - public async Task ConvertTest() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits(language: "de", id: "Convert Test", - value: "Hallo Welt"); - var options = new AssignOptions { TargetLanguage = "de", Filter = new DefaultTranslationFilter() }; - var convertedFormat = await mockFormat.ConvertToAsync(formatFactory, options); - - Assert.NotNull(convertedFormat); - - Assert.Equal("Hallo Welt", (convertedFormat["Convert Test"]?["de"] as ITranslationString)?.Value); - } - - [Fact] - public async Task AssignWithSimpleFilter() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits(language: "de", - id: "Assign Filter test", value: "Hallo Welt"); - - var options = new AssignOptions { Filter = new IsEmptyTranslationFilter(), TargetLanguage = "de" }; - var assignedFormat = await mockFormat.ConvertToAsync(formatFactory, options); - - Assert.Empty(assignedFormat); - - var mockFormatWithEmptyValue = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits(language: "de", - id: "Assign Filter test", value: ""); - assignedFormat = await mockFormatWithEmptyValue.ConvertToAsync(formatFactory, options); - - Assert.Single(assignedFormat); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.PO/tests/Startup.cs b/src/Ashampoo.Translation.Systems.Formats.PO/tests/Startup.cs deleted file mode 100644 index a16e0d7..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.PO/tests/Startup.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using CommunityToolkit.Diagnostics; -using Microsoft.Extensions.DependencyInjection; - -namespace Ashampoo.Translation.Systems.Formats.PO.Tests; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddFormatFactory().RegisterFormat(); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.ResX/src/Ashampoo.Translation.Systems.Formats.ResX.csproj b/src/Ashampoo.Translation.Systems.Formats.ResX/src/Ashampoo.Translation.Systems.Formats.ResX.csproj index b02d9b5..0a26875 100644 --- a/src/Ashampoo.Translation.Systems.Formats.ResX/src/Ashampoo.Translation.Systems.Formats.ResX.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.ResX/src/Ashampoo.Translation.Systems.Formats.ResX.csproj @@ -1,25 +1,10 @@  - net7.0 + net8.0 enable enable - Ashampoo.Translation.Systems.Formats.ResX - tjorvenK - ashampoo - Package containing the implementation for the ResX format. - LICENSE - localization;translation;resx true - true - true - true - snupkg - ash-logo-icon-big-128x.png - v - true - prerelease.0 - README.md @@ -33,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Ashampoo.Translation.Systems.Formats.ResX/src/DependencyInjectionExtension.cs b/src/Ashampoo.Translation.Systems.Formats.ResX/src/DependencyInjectionExtension.cs new file mode 100644 index 0000000..1e3174c --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.ResX/src/DependencyInjectionExtension.cs @@ -0,0 +1,31 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.ResX; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services for the Gengo format. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection AddResXFormatFeatures(this IServiceCollection services) + { + services.AddSingleton() + .AddSingleton>(sp => sp.GetRequiredService()) + .AddSingleton>(sp => sp.GetRequiredService()); + + return services; + } +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormat.cs b/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormat.cs index 10f02a9..cf4dac4 100644 --- a/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormat.cs +++ b/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormat.cs @@ -2,30 +2,35 @@ using System.Xml; using System.Xml.Serialization; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.Formats.ResX.Elements; using CommunityToolkit.Diagnostics; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.Formats.ResX; /// /// Implementation of interface for the ResX format. /// -public class ResXFormat : AbstractTranslationUnits, IFormat +public class ResXFormat : IFormat { /// public IFormatHeader Header { get; init; } = new DefaultFormatHeader(); /// - public FormatLanguageCount LanguageCount => FormatLanguageCount.OnlyTarget; + public LanguageSupport LanguageSupport => LanguageSupport.OnlyTarget; + + /// + public ICollection TranslationUnits { get; } = new List(); /// /// The root element for the xml structure. /// public Root XmlRoot { get; private set; } - /// + /// + /// Default constructor for the class. + /// public ResXFormat() { XmlRoot = new Root(); @@ -66,27 +71,27 @@ public async Task ReadAsync(Stream stream, FormatReadOptions? options = null) private async Task ConfigureOptions(FormatReadOptions? options) { - if (string.IsNullOrWhiteSpace(options?.TargetLanguage)) + if (string.IsNullOrWhiteSpace(options?.TargetLanguage.Value)) { ArgumentNullException.ThrowIfNull(options?.FormatOptionsCallback, nameof(options.FormatOptionsCallback)); FormatStringOption targetLanguageOption = new("Target language", true); FormatOptions formatOptions = new() { - Options = new FormatOption[] - { + Options = + [ targetLanguageOption - } + ] }; await options.FormatOptionsCallback.Invoke(formatOptions); // Ask user for target language if (formatOptions.IsCanceled) return false; - Header.TargetLanguage = targetLanguageOption.Value; + Header.TargetLanguage = Language.Parse(targetLanguageOption.Value); } else { - Header.TargetLanguage = options.TargetLanguage; + Header.TargetLanguage = (Language)options.TargetLanguage!; } return true; @@ -104,9 +109,15 @@ private void ReadTranslations() var comment = data.Comment; var translationString = new DefaultTranslationString(id, value, Header.TargetLanguage, comment); - var translationUnit = new DefaultTranslationUnit(id) { translationString }; + var translationUnit = new DefaultTranslationUnit(id) + { + Translations = + { + translationString + } + }; - Add(translationUnit); + TranslationUnits.Add(translationUnit); } } @@ -141,14 +152,4 @@ public async Task WriteAsync(Stream stream) await xmlWriter.FlushAsync(); } - - /// - public Func BuildFormatProvider() - { - return builder => builder.SetId("resx") - .SetSupportedFileExtensions(new[] { ".resx" }) - .SetFormatType() - .SetFormatBuilder() - .Create(); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormatBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormatBuilder.cs index 27839c3..3c6c55f 100644 --- a/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormatBuilder.cs +++ b/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormatBuilder.cs @@ -1,4 +1,5 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.Formats.ResX.Elements; using CommunityToolkit.Diagnostics; @@ -6,23 +7,23 @@ namespace Ashampoo.Translation.Systems.Formats.ResX; /// -/// Implementation of the interface for the ResX format. +/// Implementation of the interface for the ResX format. /// -public class ResXFormatBuilder : IFormatBuilderWithTarget +public class ResXFormatBuilder : IFormatBuilderWithTarget { - private string? targetLanguage; - private readonly Dictionary translations = new(); + private Language? _targetLanguage; + private readonly Dictionary _translations = new(); /// - public IFormat Build() + public ResXFormat Build() { - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); + Guard.IsNotNullOrWhiteSpace(_targetLanguage?.Value, nameof(_targetLanguage)); var format = new ResXFormat { Header = { - TargetLanguage = targetLanguage + TargetLanguage = (Language)_targetLanguage! }, XmlRoot = { @@ -30,7 +31,7 @@ public IFormat Build() } }; - foreach (var (id, value) in translations) + foreach (var (id, value) in _translations) { var data = new Data { @@ -39,9 +40,15 @@ public IFormat Build() }; format.XmlRoot.Data.Add(data); - var translationString = new DefaultTranslationString(id, value, targetLanguage); - var translationUnit = new DefaultTranslationUnit(id) { translationString }; - format.Add(translationUnit); + var translationString = new DefaultTranslationString(id, value, (Language)_targetLanguage); + var translationUnit = new DefaultTranslationUnit(id) + { + Translations = + { + translationString + } + }; + format.TranslationUnits.Add(translationUnit); } return format; @@ -50,13 +57,13 @@ public IFormat Build() /// public void Add(string id, string target) { - translations.Add(id, target); + _translations.Add(id, target); } /// - public void SetTargetLanguage(string language) + public void SetTargetLanguage(Language language) { - targetLanguage = language; + _targetLanguage = language; } /// diff --git a/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormatProvider.cs new file mode 100644 index 0000000..c6a99cc --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.ResX/src/ResXFormatProvider.cs @@ -0,0 +1,27 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; + +namespace Ashampoo.Translation.Systems.Formats.ResX; + +/// +/// Implementation of interface for the ResX format. +/// +public sealed class ResXFormatProvider : IFormatProvider +{ + /// + public string Id { get; } = "resx"; + + /// + public ResXFormat Create() => new(); + + /// + public bool SupportsFileName(string fileName) + { + return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + } + + /// + public string[] SupportedFileExtensions { get; } = [".resx"]; + + /// + public IFormatBuilder GetFormatBuilder() => new ResXFormatBuilder(); +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.ResX/tests/Ashampoo.Translation.Systems.Formats.ResX.Tests.csproj b/src/Ashampoo.Translation.Systems.Formats.ResX/tests/Ashampoo.Translation.Systems.Formats.ResX.Tests.csproj index 1174981..4bf498b 100644 --- a/src/Ashampoo.Translation.Systems.Formats.ResX/tests/Ashampoo.Translation.Systems.Formats.ResX.Tests.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.ResX/tests/Ashampoo.Translation.Systems.Formats.ResX.Tests.csproj @@ -1,18 +1,14 @@  - net7.0 + net8.0 enable false - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Ashampoo.Translation.Systems.Formats.ResX/tests/FormatTest.cs b/src/Ashampoo.Translation.Systems.Formats.ResX/tests/FormatTest.cs index d722415..0bea1da 100644 --- a/src/Ashampoo.Translation.Systems.Formats.ResX/tests/FormatTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.ResX/tests/FormatTest.cs @@ -2,130 +2,47 @@ using System.Linq; using System.Threading.Tasks; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.ResX.Tests; public class FormatTest : FormatTestBase { - private readonly IFormatFactory formatFactory; - - public FormatTest(IFormatFactory formatFactory) - { - this.formatFactory = formatFactory; - } - [Fact] public void NewFormat() { var format = CreateFormat(); - - Assert.NotNull(format); - Assert.Empty(format); - Assert.Null(format.Header.SourceLanguage); - Assert.Equal(string.Empty, format.Header.TargetLanguage); + + format.Should().NotBeNull(); + format.TranslationUnits.Should().BeEmpty(); + format.Header.SourceLanguage.Should().BeNull(); + format.Header.TargetLanguage.Value.Should().BeEmpty(); } [Fact] public async Task ReadFromFile() { var format = - await CreateAndReadFromFileAsync("Res.en.resx", new FormatReadOptions { TargetLanguage = "en-US" }); - Assert.Equal(117, format.Count); - Assert.Equal("en-US", format.Header.TargetLanguage); - Assert.Equal("Remove Added Items", (format["Text_RemoveAddedItems"]?["en-US"] as ITranslationString)?.Value); + await CreateAndReadFromFileAsync("Res.en.resx", new FormatReadOptions { TargetLanguage = new Language("en-US") }); + + format.TranslationUnits.Count.Should().Be(117); + format.Header.TargetLanguage.Should().Be(new Language("en-US")); + format.TranslationUnits.GetTranslationUnit("Text_RemoveAddedItems").Translations.GetTranslation(new Language("en-US")) + .Value.Should().Be("Remove Added Items"); } [Fact] public async Task ReadAndWrite() { var format = - await CreateAndReadFromFileAsync("Res.en.resx", new FormatReadOptions { TargetLanguage = "en-US" }); + await CreateAndReadFromFileAsync("Res.en.resx", new FormatReadOptions { TargetLanguage = new Language("en-US") }); await using var ms = new MemoryStream(); await format.WriteAsync(ms); ms.Seek(0, SeekOrigin.Begin); } - - [Fact] - public void ImportSuccessTest() - { - IFormat format = CreateAndReadFromFile("Res.en.resx", new FormatReadOptions { TargetLanguage = "en-US" }); - - const string id = "Button_RemoveAll"; - const string value = "Import Test"; - - var importedWithUnits = format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: value); - Assert.NotNull(importedWithUnits); - Assert.Single(importedWithUnits); - Assert.Equal("Import Test", (format[id]?["en-US"] as ITranslationString)?.Value); - } - - [Fact] - public void NoMatchImportTest() - { - IFormat format = CreateAndReadFromFile("Res.en.resx", new FormatReadOptions { TargetLanguage = "en-US" }); - - const string id = "Not matching Import-Id"; - const string value = "Import Test"; - var imported = format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: value); - - Assert.Empty(imported); - } - - [Fact] - public void ImportEqualTranslationTest() - { - IFormat format = CreateAndReadFromFile("Res.en.resx", new FormatReadOptions { TargetLanguage = "en-US" }); - - const string id = "Button_RemoveAll"; - const string value = "Remove All"; - var imported = format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: value); - - Assert.Empty(imported); - } - - [Fact] - public async Task SimpleAssign() - { - var mock = MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits("en-US", "ID", "Hello World"); - var converted = await mock.ConvertToAsync(formatFactory, - new AssignOptions { TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }); - - Assert.NotNull(converted); - Assert.Single(converted); - Assert.Single(converted["ID"] ?? Enumerable.Empty()); - Assert.Equal("Hello World", (converted["ID"]?["en-US"] as ITranslationString)?.Value); - } - - [Fact] - public async Task ComplexAssign() - { - const string id = "peru.CSystem.CreateUniqueFileFailed"; - var mockFormat = new MockFormatWithTranslationUnits - { - { "en-US", id, "Error creating unique file name." }, - { "de-DE", id, "Fehler beim Erzeugen eines eindeutigen Dateinamens" } - }; - - var optionsEn = new AssignOptions { TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }; - var optionsDe = new AssignOptions { TargetLanguage = "de-DE", Filter = new DefaultTranslationFilter() }; - - var convertedEnUs = await mockFormat.ConvertToAsync(formatFactory, optionsEn); - var convertedDeDe = await mockFormat.ConvertToAsync(formatFactory, optionsDe); - - Assert.NotNull(convertedEnUs); - Assert.NotNull(convertedDeDe); - - - Assert.Equal("en-US", convertedEnUs.Header.TargetLanguage); - Assert.Equal("de-DE", convertedDeDe.Header.TargetLanguage); - - Assert.Equal("Error creating unique file name.", (convertedEnUs[id]?["en-US"] as ITranslationString)?.Value); - Assert.Equal("Fehler beim Erzeugen eines eindeutigen Dateinamens", - (convertedDeDe[id]?["de-DE"] as ITranslationString)?.Value); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.ResX/tests/Startup.cs b/src/Ashampoo.Translation.Systems.Formats.ResX/tests/Startup.cs deleted file mode 100644 index 787626a..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.ResX/tests/Startup.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using CommunityToolkit.Diagnostics; -using Microsoft.Extensions.DependencyInjection; - -namespace Ashampoo.Translation.Systems.Formats.ResX.Tests; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddFormatFactory().RegisterFormat(); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/Ashampoo.Translation.Systems.Formats.TsProj.csproj b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/Ashampoo.Translation.Systems.Formats.TsProj.csproj index 28cdb4c..0a26875 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/Ashampoo.Translation.Systems.Formats.TsProj.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/Ashampoo.Translation.Systems.Formats.TsProj.csproj @@ -1,25 +1,10 @@  - net7.0 + net8.0 enable enable - Ashampoo.Translation.Systems.Formats.TsProj - tjorvenK - ashampoo - Package containing the implementation for the TsProj format. - LICENSE - localization;translation;tsproj true - true - true - true - snupkg - ash-logo-icon-big-128x.png - v - true - prerelease.0 - README.md @@ -33,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/DependencyInjectionExtension.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/DependencyInjectionExtension.cs new file mode 100644 index 0000000..c9321dc --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/DependencyInjectionExtension.cs @@ -0,0 +1,31 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats.TsProj; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services for the Gengo format. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection AddTsProjFormatFeatures(this IServiceCollection services) + { + services.AddSingleton() + .AddSingleton>(sp => sp.GetRequiredService()) + .AddSingleton>(sp => sp.GetRequiredService()); + + return services; + } +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TranslationStringSource.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TranslationStringSource.cs index 8bceb0e..eb737cd 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TranslationStringSource.cs +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TranslationStringSource.cs @@ -1,45 +1,40 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; namespace Ashampoo.Translation.Systems.Formats.TsProj; /// -/// Special implementation of the interface, for source string for the . +/// Special implementation of the interface, for source string for the . /// -public class TranslationStringSource : ITranslationString +public class TranslationStringSource : ITranslation { - private readonly Element.Translation translationElement; + private readonly Element.Translation _translationElement; /// public string Value { - get => translationElement.Source ?? string.Empty; - set => translationElement.Source = value; + get => _translationElement.Source ?? string.Empty; + set => _translationElement.Source = value; } - - /// - public string Id => translationElement.Id; - + /// public string? Comment { - get => translationElement.Comment; - set => translationElement.Comment = value; + get => _translationElement.Comment; + set => _translationElement.Comment = value; } /// - public string Language { get; set; } = string.Empty; + public Language Language { get; set; } = Language.Empty; /// - /// Implementation of ITranslationString interface + /// Implementation of interface /// /// /// The element to be used as a source for the . /// public TranslationStringSource(Element.Translation translationElement) { - this.translationElement = translationElement; + _translationElement = translationElement; } - - /// - public bool IsEmpty => string.IsNullOrWhiteSpace(Value); } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TranslationStringTarget.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TranslationStringTarget.cs index a5bd370..069e3b0 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TranslationStringTarget.cs +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TranslationStringTarget.cs @@ -1,43 +1,41 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; namespace Ashampoo.Translation.Systems.Formats.TsProj; /// -/// Special implementation of the interface for the TsProj format. +/// Special implementation of the interface for the TsProj format. /// -public class TranslationStringTarget : ITranslationString +public class TranslationStringTarget : ITranslation { - private readonly Element.Translation translationElement; + private readonly Element.Translation _translationElement; /// public string Value { - get => translationElement.Value; - set => translationElement.Value = value; + get => _translationElement.Value; + set => _translationElement.Value = value; } - /// - public string Id => translationElement.Id; - /// public string? Comment { - get => translationElement.Comment; - set => translationElement.Comment = value; + get => _translationElement.Comment; + set => _translationElement.Comment = value; } /// - public string Language { get; set; } = string.Empty; + public Language Language { get; set; } = Language.Empty; /// - /// Implementation of ITranslationString interface + /// Implementation of interface /// /// /// /// The element to be used as a source for the . /// public TranslationStringTarget(Element.Translation translationElement) { - this.translationElement = translationElement; + _translationElement = translationElement; } /// diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormat.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormat.cs index 7b01497..61e0237 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormat.cs +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormat.cs @@ -1,16 +1,16 @@ using System.Xml; using System.Xml.Serialization; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.Formats.TsProj.Element; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.Formats.TsProj; /// /// Implementation of the interface for the TsProj format. /// -public class TsProjFormat : AbstractTranslationUnits, IFormat +public class TsProjFormat : IFormat { /// /// The project element for the xml structure. @@ -21,9 +21,14 @@ public class TsProjFormat : AbstractTranslationUnits, IFormat public IFormatHeader Header { get; init; } = new DefaultFormatHeader(); /// - public FormatLanguageCount LanguageCount => FormatLanguageCount.SourceAndTarget; + public LanguageSupport LanguageSupport => LanguageSupport.SourceAndTarget; /// + public ICollection TranslationUnits { get; } = new List(); + + /// + /// Default constructor for the class. + /// public TsProjFormat() { Project = new Project(); @@ -63,80 +68,70 @@ private void ReadComponents(List components) { if (component.Translations is null) continue; // No translations for this component - foreach (var (source, target) in ReadTranslations(component.Translations)) - { - var translationUnit = - new DefaultTranslationUnit(target.Id); // Create a new translation unit with the target id - - if (source is not null) translationUnit[source.Language] = source; - - translationUnit[target.Language] = target; - Add(translationUnit); - } + ReadRootTranslations(component.Translations); } } private void ReadRootTranslations(List translations) { - foreach (var (source, target) in ReadTranslations(translations)) + foreach (var unit in ReadTranslations(translations)) { - var translationUnit = - new DefaultTranslationUnit(target.Id); // Create a new translation unit with the id of the translation - - if (source is not null) - translationUnit[source.Language] = source; // Add the source translation if it exists - - translationUnit[target.Language] = target; // Add the target translation - Add(translationUnit); // Add the translation unit to the hash set of translation units + TranslationUnits.Add(unit); // Add the translation unit to the hash set of translation units } } - private IEnumerable<(ITranslation?, ITranslation)> ReadTranslations( + private IEnumerable ReadTranslations( IEnumerable translations) { return translations.Select(CreateTranslationString); } - private (ITranslation?, ITranslation) CreateTranslationString(Element.Translation translation) + private ITranslationUnit CreateTranslationString(Element.Translation translation) { TranslationStringSource? source = null; - if (!string.IsNullOrWhiteSpace(Header.SourceLanguage)) + if (!Header.SourceLanguage.IsNullOrWhitespace()) source = new TranslationStringSource(translation) { - Language = Header.SourceLanguage + Language = (Language)Header.SourceLanguage! }; // Create a source translation string, if a source language is specified in the header var target = new TranslationStringTarget(translation) // Create a target translation string { - Language = !string.IsNullOrWhiteSpace(Header.TargetLanguage) + Language = !Header.TargetLanguage.IsNullOrWhitespace() ? Header.TargetLanguage : throw new Exception("Target language is missing.") }; - return (source, target); + + var translationUnit = new DefaultTranslationUnit(translation.Id); + if (source is not null) + translationUnit.Translations.AddOrUpdateTranslation(source.Language, source); // Add the source translation if it exists + + translationUnit.Translations.AddOrUpdateTranslation(target.Language, target); + return translationUnit; } private async Task ConfigureHeader(FormatReadOptions? options) { if (!string.IsNullOrWhiteSpace(Project.SourceLanguage)) Header.SourceLanguage = - Project.SourceLanguage; // Set the source language if it is specified in the project file + Language.Parse(Project.SourceLanguage); // Set the source language if it is specified in the project file if (!string.IsNullOrWhiteSpace(Project.TargetLanguage)) Header.TargetLanguage = - Project.TargetLanguage; // Set the target language if it is specified in the project file + Language.Parse(Project.TargetLanguage); // Set the target language if it is specified in the project file - if (!string.IsNullOrWhiteSpace(Header.SourceLanguage) && - !string.IsNullOrWhiteSpace(Header.TargetLanguage)) + if (!Header.SourceLanguage.IsNullOrWhitespace() && + !Header.TargetLanguage.IsNullOrWhitespace()) return true; // If both source and target languages are specified, return true var setTargetLanguage = string.IsNullOrWhiteSpace(options - ?.TargetLanguage); // If the target language is not specified, ask the user to specify it + ?.TargetLanguage.Value); // If the target language is not specified, ask the user to specify it var setSourceLanguage = string.IsNullOrWhiteSpace(options - ?.SourceLanguage); // If the source language is not specified, ask the user to specify it + ?.SourceLanguage?.Value); // If the source language is not specified, ask the user to specify it if (setTargetLanguage || setSourceLanguage) { if (options?.FormatOptionsCallback is null) @@ -148,7 +143,7 @@ private async Task ConfigureHeader(FormatReadOptions? options) var sourceLanguageOption = new FormatStringOption("SourceLanguage", true); // Create a new option for the source language - List optionList = new(); + List optionList = []; if (setSourceLanguage) optionList.Add(sourceLanguageOption); if (setTargetLanguage) optionList.Add(targetLanguageOption); @@ -162,14 +157,14 @@ private async Task ConfigureHeader(FormatReadOptions? options) if (setSourceLanguage) Header.SourceLanguage = - sourceLanguageOption.Value; // Set the source language if it is specified in the options + Language.Parse(sourceLanguageOption.Value); // Set the source language if it is specified in the options if (setTargetLanguage) Header.TargetLanguage = - targetLanguageOption.Value; // Set the target language if it is specified in the options + Language.Parse(targetLanguageOption.Value); // Set the target language if it is specified in the options } else { - Header.TargetLanguage = options!.TargetLanguage!; + Header.TargetLanguage = (Language)options!.TargetLanguage!; Header.SourceLanguage = options.SourceLanguage; } @@ -209,14 +204,4 @@ public async Task WriteAsync(Stream stream) await writer.FlushAsync(); } - - /// - public Func BuildFormatProvider() - { - return builder => builder.SetId("tsproj") - .SetSupportedFileExtensions(new[] { ".tsproj" }) - .SetFormatType() - .SetFormatBuilder() - .Create(); - } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormatBuilder.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormatBuilder.cs index b1e30e8..c8ee2f6 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormatBuilder.cs +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormatBuilder.cs @@ -1,4 +1,5 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.Formats.TsProj.Element; using CommunityToolkit.Diagnostics; @@ -6,44 +7,44 @@ namespace Ashampoo.Translation.Systems.Formats.TsProj; /// -/// Implementation of the interface for the format. +/// Implementation of the interface for the format. /// -public class TsProjFormatBuilder : IFormatBuilderWithSourceAndTarget +public class TsProjFormatBuilder : IFormatBuilderWithSourceAndTarget { - private string? sourceLanguage; - private string? targetLanguage; - private readonly Dictionary translations = new(); - private Dictionary information = new(); + private Language? _sourceLanguage; + private Language? _targetLanguage; + private readonly Dictionary _translations = new(); + private Dictionary _information = new(); /// public void Add(string id, string source, string target) { - translations.Add(id, (source, target)); + _translations.Add(id, (source, target)); } /// - public IFormat Build() + public TsProjFormat Build() { - Guard.IsNotNullOrWhiteSpace(sourceLanguage, nameof(sourceLanguage)); // sourceLanguage is required - Guard.IsNotNullOrWhiteSpace(targetLanguage, nameof(targetLanguage)); // targetLanguage is required + Guard.IsNotNullOrWhiteSpace(_sourceLanguage?.Value, nameof(_sourceLanguage)); // sourceLanguage is required + Guard.IsNotNullOrWhiteSpace(_targetLanguage?.Value, nameof(_targetLanguage)); // targetLanguage is required //Create new TsProj format and add translations var tsProjFormat = new TsProjFormat(); var project = tsProjFormat.Project; - project.SourceLanguage = sourceLanguage; // Set source language for xml object - project.TargetLanguage = targetLanguage; // Set target language for xml object - tsProjFormat.Header.SourceLanguage = sourceLanguage; // Set source language for format object - tsProjFormat.Header.TargetLanguage = targetLanguage; // Set target language for format object + project.SourceLanguage = _sourceLanguage?.Value; // Set source language for xml object + project.TargetLanguage = _targetLanguage?.Value!; // Set target language for xml object + tsProjFormat.Header.SourceLanguage = _sourceLanguage; // Set source language for format object + tsProjFormat.Header.TargetLanguage = (Language)_targetLanguage!; // Set target language for format object // Add information to header - var nameFound = information.TryGetValue("Name", out var name); - var versionFound = information.TryGetValue("Version", out var version); - var authorFound = information.TryGetValue("Author", out var author); - var mailFound = information.TryGetValue("Mail", out var mail); - var creationToolFound = information.TryGetValue("CreationTool", out var creationTool); - var creationToolVersionFound = information.TryGetValue("CreationToolVersion", out var creationToolVersion); - var countryNameFound = information.TryGetValue("CountryName", out var countryName); + var nameFound = _information.TryGetValue("Name", out var name); + var versionFound = _information.TryGetValue("Version", out var version); + var authorFound = _information.TryGetValue("Author", out var author); + var mailFound = _information.TryGetValue("Mail", out var mail); + var creationToolFound = _information.TryGetValue("CreationTool", out var creationTool); + var creationToolVersionFound = _information.TryGetValue("CreationToolVersion", out var creationToolVersion); + var countryNameFound = _information.TryGetValue("CountryName", out var countryName); if (nameFound) project.Name = name; if (versionFound) project.Version = version; @@ -58,9 +59,9 @@ public IFormat Build() PluginGuid = "F0D8F625-2EE3-4C84-96EC-BFBDD4946878", // Guid for the TsProj format Translations = new List() }; - project.Components = new List { component }; // Add component to project + project.Components = [component]; // Add component to project - foreach (var keyValuePair in translations) + foreach (var keyValuePair in _translations) { var translationElement = new Element.Translation // Create new translation element for xml parsing { @@ -70,42 +71,45 @@ public IFormat Build() }; component.Translations.Add(translationElement); - var sourceTranslationString = new TranslationStringSource(translationElement) { Language = sourceLanguage }; // Create new source translation string - var targetTranslationString = new TranslationStringTarget(translationElement) { Language = targetLanguage }; // Create new target translation string + var sourceTranslationString = new TranslationStringSource(translationElement) { Language = (Language)_sourceLanguage! }; // Create new source translation string + var targetTranslationString = new TranslationStringTarget(translationElement) { Language = (Language)_targetLanguage }; // Create new target translation string var translationUnit = new DefaultTranslationUnit(keyValuePair.Key) // Create new translation unit { - sourceTranslationString, - targetTranslationString + Translations = + { + sourceTranslationString, + targetTranslationString + } }; - tsProjFormat.Add(translationUnit); + tsProjFormat.TranslationUnits.Add(translationUnit); } return tsProjFormat; } /// - public void SetSourceLanguage(string language) + public void SetSourceLanguage(Language language) { - sourceLanguage = language; + _sourceLanguage = language; } /// - public void SetTargetLanguage(string language) + public void SetTargetLanguage(Language language) { - targetLanguage = language; + _targetLanguage = language; } /// public void SetHeaderInformation(IFormatHeader header) { - information = new Dictionary(header); + _information = new Dictionary(header.AdditionalHeaders); } /// public void AddHeaderInformation(string key, string value) { - information.Add(key, value); + _information.Add(key, value); } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormatProvider.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormatProvider.cs new file mode 100644 index 0000000..4ca07be --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/src/TsProjFormatProvider.cs @@ -0,0 +1,25 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; + +namespace Ashampoo.Translation.Systems.Formats.TsProj; + +/// +public sealed class TsProjFormatProvider : IFormatProvider +{ + /// + public string Id { get; } = "tsproj"; + + /// + public TsProjFormat Create() => new(); + + /// + public bool SupportsFileName(string fileName) + { + return SupportedFileExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + } + + /// + public string[] SupportedFileExtensions { get; } = [".tsproj"]; + + /// + public IFormatBuilder GetFormatBuilder() => new TsProjFormatBuilder(); +} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/AshLangExportedTest.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/AshLangExportedTest.cs index a292afa..4b9a493 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/AshLangExportedTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/AshLangExportedTest.cs @@ -1,6 +1,8 @@ using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.TsProj.Tests @@ -10,14 +12,14 @@ public class AshLangExportedTest : FormatTestBase [Fact] public void TranslationTest() { - IFormat format = CreateAndReadFromFile("normalized_export_ashlang-de-DE.tsproj"); + var format = CreateAndReadFromFile("normalized_export_ashlang-de-DE.tsproj"); const string id = "peru.CFileNotFoundError.GeneralDesc"; - Assert.Equal("The file was not found.", - (format[id]?[format.Header.SourceLanguage ?? ""] as ITranslationString)?.Value); - Assert.Equal("Die Datei wurde nicht gefunden.", - (format[id]?[format.Header.TargetLanguage] as ITranslationString)?.Value); + format.TranslationUnits.GetTranslationUnit(id).Translations + .GetTranslation(format.Header.SourceLanguage ?? Language.Empty).Value.Should().Be("The file was not found."); + format.TranslationUnits.GetTranslationUnit(id).Translations.GetTranslation(format.Header.TargetLanguage) + .Value.Should().Be("Die Datei wurde nicht gefunden."); } } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/Ashampoo.Translation.Systems.Formats.TsProj.Tests.csproj b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/Ashampoo.Translation.Systems.Formats.TsProj.Tests.csproj index f72c910..b1116f8 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/Ashampoo.Translation.Systems.Formats.TsProj.Tests.csproj +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/Ashampoo.Translation.Systems.Formats.TsProj.Tests.csproj @@ -1,18 +1,14 @@  - net7.0 + net8.0 enable false - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/Startup.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/Startup.cs deleted file mode 100644 index 7c9d597..0000000 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/Startup.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using CommunityToolkit.Diagnostics; -using Microsoft.Extensions.DependencyInjection; - -namespace Ashampoo.Translation.Systems.Formats.TsProj.Tests; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddFormatFactory().RegisterFormat(); - } -} \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/TsProjFormatTest.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/TsProjFormatTest.cs index e5418ee..3719c1d 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/TsProjFormatTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/TsProjFormatTest.cs @@ -2,40 +2,25 @@ using System.IO; using System.Threading.Tasks; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using Ashampoo.Translation.Systems.Formats.Abstractions.TranslationFilter; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.TsProj.Tests; public class TsProjFormatTest : FormatTestBase { - private readonly IFormatFactory formatFactory; - - public TsProjFormatTest(IFormatFactory formatFactory) - { - this.formatFactory = formatFactory; - } - - [Fact] - public void IsAssignableFrom() - { - IFormat format = CreateFormat(); - - //provides translation units - Assert.IsAssignableFrom(format); - } - [Fact] public void NewFormat() { var format = CreateFormat(); - - Assert.NotNull(format); - Assert.Empty(format); - Assert.Null(format.Header.SourceLanguage); - Assert.Equal(string.Empty, format.Header.TargetLanguage); + + format.Should().NotBeNull(); + format.TranslationUnits.Should().BeEmpty(); + format.Header.SourceLanguage.Should().BeNull(); + format.Header.TargetLanguage.Value.Should().BeEmpty(); } [Fact] @@ -43,65 +28,16 @@ public void ReadFromFile() { IFormat format = CreateAndReadFromFile("normalized_export_ashlang-de-DE.tsproj"); - Assert.Equal(67, format.Count); - Assert.Equal("en-US", format.Header.SourceLanguage); - Assert.Equal("de-DE", format.Header.TargetLanguage); + format.TranslationUnits.Count.Should().Be(67); + format.Header.SourceLanguage.Should().Be(new Language("en-US")); + format.Header.TargetLanguage.Should().Be(new Language("de-DE")); //TODO: add author to tsproj const string id = "peru.CFileNotFoundError.Desc"; - Assert.Equal("The file '%FILE%' was not found.", (format[id]?["en-US"] as ITranslationString)?.Value); - Assert.Equal("Die Datei '%FILE%' wurde nicht gefunden.", - (format[id]?["de-DE"] as ITranslationString)?.Value); - } - - [Fact] - public void ImportSuccessTest() - { - IFormat format = CreateAndReadFromFile("normalized_export_ashlang-de-DE.tsproj"); - - - const string id = "peru.gui.CImageConversionError.Desc"; - const string valueSource = "Import Test Source"; - const string valueTarget = "Import Test Target"; - var importedWithUnits = - format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: valueSource); - - Assert.NotNull(importedWithUnits); - Assert.Single(importedWithUnits); - Assert.Equal("Import Test Source", (format[id]?["en-US"] as ITranslationString)?.Value); - - importedWithUnits = format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: valueTarget); - - Assert.NotNull(importedWithUnits); - Assert.Single(importedWithUnits); - Assert.Equal("Import Test Target", (format[id]?["de-DE"] as ITranslationString)?.Value); - } - - [Fact] - public void NoMatchImportTest() - { - IFormat format = CreateAndReadFromFile("normalized_export_ashlang-de-DE.tsproj"); - - const string id = "Not matching Import-Id"; - const string value = "Import Test"; - - var imported = format.ImportMockTranslationWithUnits(language: "en-US", id: id, value: value); - Assert.Empty(imported); - - imported = format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: value); - Assert.Empty(imported); - } - - [Fact] - public void ImportEqualTranslationTest() - { - var format = CreateAndReadFromFile("normalized_export_ashlang-de-DE.tsproj"); - - const string id = "peru.gui.CImageConversionError.Desc"; - const string value = "Fehler beim konvertieren der Bilddaten (%DETAILS%)."; - var imported = format.ImportMockTranslationWithUnits(language: "de-DE", id: id, value: value); - - Assert.Empty(imported); + format.TranslationUnits.GetTranslationUnit(id).Translations.GetTranslation(new Language("en-US")).Value.Should() + .Be("The file '%FILE%' was not found."); + format.TranslationUnits.GetTranslationUnit(id).Translations.GetTranslation(new Language("de-DE")).Value.Should() + .Be("Die Datei '%FILE%' wurde nicht gefunden."); } @@ -123,36 +59,7 @@ public async Task ReadAndWriteAshLangExportedFile() [Fact] public async Task ReadAndWriteNLangExportedFile() { - await Assert.ThrowsAsync(async () => - await CreateAndReadFromFileAsync("normalized_export_nlang-de-DE.tsproj")); - } - - [Fact] - public async Task ConvertTest() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits(language: "de-DE", - id: "Convert test", value: "Hallo Welt"); - var options = new AssignOptions - { SourceLanguage = "de-DE", TargetLanguage = "en-US", Filter = new DefaultTranslationFilter() }; - var convertedFormat = await mockFormat.ConvertToAsync(formatFactory, options); - - Assert.NotNull(convertedFormat); - Assert.Single(convertedFormat); - Assert.Equal("Hallo Welt", (convertedFormat["Convert test"]?["de-DE"] as ITranslationString)?.Value); - } - - [Fact] - public async Task AssignWithSimpleFilter() - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits(language: "de-DE", - id: "Convert test", value: "Hallo Welt"); - var options = new AssignOptions - { Filter = new IsEmptyTranslationFilter(), SourceLanguage = "de-DE", TargetLanguage = "en-US" }; - var assignedFormat = await mockFormat.ConvertToAsync(formatFactory, options); - - Assert.NotNull(assignedFormat); - Assert.Empty(assignedFormat); + await CreateAndReadFromFileAsync("normalized_export_nlang-de-DE.tsproj").Invoking(x => x).Should() + .ThrowAsync(); } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/WebExportedTest.cs b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/WebExportedTest.cs index 1ccb264..dad5883 100644 --- a/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/WebExportedTest.cs +++ b/src/Ashampoo.Translation.Systems.Formats.TsProj/tests/WebExportedTest.cs @@ -1,5 +1,8 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Ashampoo.Translation.Systems.TestBase; +using FluentAssertions; using Xunit; namespace Ashampoo.Translation.Systems.Formats.TsProj.Tests; @@ -14,7 +17,9 @@ public void TranslationTest() const string id = "textids.content.T_1904_SUBMIT_80"; const string value = "Auf Windows 11 Systemvoraussetzungen testen und Installation ermöglichen"; - Assert.Equal(value, (format[id]?[format.Header.SourceLanguage ?? ""] as ITranslationString)?.Value); - Assert.Equal("", (format[id]?[format.Header.TargetLanguage] as ITranslationString)?.Value); + format.TranslationUnits.GetTranslationUnit(id).Translations.GetTranslation((Language)format.Header.SourceLanguage!).Value + .Should().Be(value); + format.TranslationUnits.GetTranslationUnit(id).Translations.GetTranslation(format.Header.TargetLanguage).Value + .Should().BeEmpty(); } } \ No newline at end of file diff --git a/src/Ashampoo.Translation.Systems.Formats/src/Ashampoo.Translation.Systems.Formats.csproj b/src/Ashampoo.Translation.Systems.Formats/src/Ashampoo.Translation.Systems.Formats.csproj index d5eac5d..d0eeaed 100644 --- a/src/Ashampoo.Translation.Systems.Formats/src/Ashampoo.Translation.Systems.Formats.csproj +++ b/src/Ashampoo.Translation.Systems.Formats/src/Ashampoo.Translation.Systems.Formats.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable Ashampoo.Translation.Systems.Formats @@ -24,6 +24,7 @@ + @@ -41,7 +42,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Ashampoo.Translation.Systems.Formats/src/DependencyInjection.cs b/src/Ashampoo.Translation.Systems.Formats/src/DependencyInjection.cs new file mode 100644 index 0000000..ec329b3 --- /dev/null +++ b/src/Ashampoo.Translation.Systems.Formats/src/DependencyInjection.cs @@ -0,0 +1,47 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.AshLang; +using Ashampoo.Translation.Systems.Formats.Gengo; +using Ashampoo.Translation.Systems.Formats.JavaProperties; +using Ashampoo.Translation.Systems.Formats.Json; +using Ashampoo.Translation.Systems.Formats.NLang; +using Ashampoo.Translation.Systems.Formats.PO; +using Ashampoo.Translation.Systems.Formats.ResX; +using Ashampoo.Translation.Systems.Formats.TsProj; +using Microsoft.Extensions.DependencyInjection; + +namespace Ashampoo.Translation.Systems.Formats; + +/// +/// Static class that contains extension methods for . +/// +public static class DependencyInjection +{ + /// + /// Registers all necessary services and the format implementations. + /// + /// + /// The to register the services with. + /// + /// + /// The for chaining. + /// + /// + /// Thrown if something went wrong during the registration. + /// + public static IServiceCollection RegisterFormats(this IServiceCollection services) + { + services.AddSingleton(); + services + .AddAshLangFormatFeatures() + .AddGengoFormatFeatures() + .AddJavaPropertiesFormatFeatures() + .AddJsonFormatFeatures() + .AddNLangFormatFeatures() + .AddPOFormatFeatures() + .AddResXFormatFeatures() + .AddTsProjFormatFeatures(); + + return services; + } + +} \ No newline at end of file diff --git a/src/tests/Ashampoo.Translation.Systems.TestBase/Ashampoo.Translation.Systems.TestBase.csproj b/src/tests/Ashampoo.Translation.Systems.TestBase/Ashampoo.Translation.Systems.TestBase.csproj index 0fe45e2..db7dd5d 100644 --- a/src/tests/Ashampoo.Translation.Systems.TestBase/Ashampoo.Translation.Systems.TestBase.csproj +++ b/src/tests/Ashampoo.Translation.Systems.TestBase/Ashampoo.Translation.Systems.TestBase.csproj @@ -1,18 +1,17 @@ - net7.0 + net8.0 enable false - - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/tests/Ashampoo.Translation.Systems.TestBase/FormatTestBase.cs b/src/tests/Ashampoo.Translation.Systems.TestBase/FormatTestBase.cs index ba88fbb..45389a7 100644 --- a/src/tests/Ashampoo.Translation.Systems.TestBase/FormatTestBase.cs +++ b/src/tests/Ashampoo.Translation.Systems.TestBase/FormatTestBase.cs @@ -46,8 +46,6 @@ protected T CreateAndReadFromFile(string fileName, FormatReadOptions? options = format.Read(stream, options); stream.Close(); - stream.Dispose(); - return format; } diff --git a/src/tests/Ashampoo.Translation.Systems.TestBase/Helper.cs b/src/tests/Ashampoo.Translation.Systems.TestBase/Helper.cs index 9231060..20145f7 100644 --- a/src/tests/Ashampoo.Translation.Systems.TestBase/Helper.cs +++ b/src/tests/Ashampoo.Translation.Systems.TestBase/Helper.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; using System.IO; -using Ashampoo.Translation.Systems.Formats.Abstractions; -using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; using Xunit; namespace Ashampoo.Translation.Systems.TestBase; @@ -18,20 +15,11 @@ public static void MustBeEqualTo(this Stream lhStream, Stream rhStream) { Assert.Equal(lhStream.Length, rhStream.Length); - var lhByte = -1; - var rhByte = -1; + int lhByte; + int rhByte; while ((lhByte = lhStream.ReadByte()) != -1 && (rhByte = rhStream.ReadByte()) != -1) { Assert.Equal(lhByte, rhByte); } } - - public static IList ImportMockTranslationWithUnits(this IFormat format, string language, string id, - string value = "Import Test") - { - var mockFormat = - MockFormatWithTranslationUnits.CreateMockFormatWithTranslationUnits(language: language, id: id, - value: value); - return format.ImportFrom(mockFormat); - } } \ No newline at end of file diff --git a/src/tests/Ashampoo.Translation.Systems.TestBase/MockFormatWithTranslationUnits.cs b/src/tests/Ashampoo.Translation.Systems.TestBase/MockFormatWithTranslationUnits.cs index 5a80c56..b2a8378 100644 --- a/src/tests/Ashampoo.Translation.Systems.TestBase/MockFormatWithTranslationUnits.cs +++ b/src/tests/Ashampoo.Translation.Systems.TestBase/MockFormatWithTranslationUnits.cs @@ -1,52 +1,42 @@ using System; -using System.Collections; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; -using IFormatProvider = Ashampoo.Translation.Systems.Formats.Abstractions.IFormatProvider; namespace Ashampoo.Translation.Systems.TestBase; -public class MockFormatWithTranslationUnits : AbstractTranslationUnits, IFormat +public class MockFormatWithTranslationUnits : IFormat { - public static IFormat CreateMockFormatWithTranslationUnits(string language, string id, string value) + public static IFormat CreateMockFormatWithTranslationUnits(Language language, string id, string value) { return new MockFormatWithTranslationUnits(language, id, value); } - public MockFormatWithTranslationUnits() - { - } - - public MockFormatWithTranslationUnits(string language, string id, string value) + private MockFormatWithTranslationUnits(Language language, string id, string value) { var translationString = new MockTranslationString(id: id, value: value, language: language); var translationUnit = new MockTranslationUnit(id: id) { - [language] = translationString + Translations = + { + translationString + } }; - Add(translationUnit); + TranslationUnits.Add(translationUnit); } - public Func BuildFormatProvider() + public Task WriteAsync(Stream stream) { throw new NotImplementedException(); } public IFormatHeader Header { get; init; } = new MockHeader(); - public FormatLanguageCount LanguageCount => FormatLanguageCount.OnlyTarget; - - public void Add(string language, string id, string value) - { - var translationString = new MockTranslationString(id: id, value: value, language: language); - - var translationUnit = this[id] ?? new MockTranslationUnit(id: id); - translationUnit[language] = translationString; - - Add(translationUnit); - } + public LanguageSupport LanguageSupport => LanguageSupport.OnlyTarget; + public ICollection TranslationUnits { get; } = new List(); public void Read(Stream stream, FormatReadOptions? options = null) { @@ -58,11 +48,6 @@ public void Write(Stream stream) throw new NotImplementedException(); } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - public Task ReadAsync(Stream stream, FormatReadOptions? options = null) { throw new NotImplementedException(); diff --git a/src/tests/Ashampoo.Translation.Systems.TestBase/MockHeader.cs b/src/tests/Ashampoo.Translation.Systems.TestBase/MockHeader.cs index dbadde1..9b9d0f9 100644 --- a/src/tests/Ashampoo.Translation.Systems.TestBase/MockHeader.cs +++ b/src/tests/Ashampoo.Translation.Systems.TestBase/MockHeader.cs @@ -1,9 +1,12 @@ +using System.Collections.Generic; using Ashampoo.Translation.Systems.Formats.Abstractions; +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; namespace Ashampoo.Translation.Systems.TestBase; public class MockHeader : AbstractFormatHeader { - public override string? SourceLanguage { get; set; } = ""; - public override string TargetLanguage { get; set; } = ""; + public override Language? SourceLanguage { get; set; } = Language.Empty; + public override Dictionary AdditionalHeaders { get; set; } = new(); + public override Language TargetLanguage { get; set; } = Language.Empty; } \ No newline at end of file diff --git a/src/tests/Ashampoo.Translation.Systems.TestBase/MockTranslationString.cs b/src/tests/Ashampoo.Translation.Systems.TestBase/MockTranslationString.cs index 3e5e97a..4dd6c01 100644 --- a/src/tests/Ashampoo.Translation.Systems.TestBase/MockTranslationString.cs +++ b/src/tests/Ashampoo.Translation.Systems.TestBase/MockTranslationString.cs @@ -1,8 +1,9 @@ +using Ashampoo.Translation.Systems.Formats.Abstractions.Models; using Ashampoo.Translation.Systems.Formats.Abstractions.Translation; namespace Ashampoo.Translation.Systems.TestBase; -public class MockTranslationString : ITranslationString +public class MockTranslationString : ITranslation { public string Value { get; set; } @@ -12,9 +13,9 @@ public class MockTranslationString : ITranslationString public bool IsEmpty => string.IsNullOrWhiteSpace(Value); - public string Language { get; set; } + public Language Language { get; set; } - public MockTranslationString(string id, string value, string language = "") + public MockTranslationString(string id, string value, Language language = new()) { Id = id; Value = value;