├── assets
├── icon.jpeg
├── logo.jpeg
└── panel_screenshot.png
├── translord
├── icon.jpeg
├── ITranslationsCache.cs
├── ILanguageTranslator.cs
├── ITranslationsStore.cs
├── Models
│ └── Translation.cs
├── ITranslator.cs
├── TranslatorConfiguration.cs
├── translord.csproj
├── TranslordServiceCollectionExtensions.cs
├── README.md
├── Core
│ ├── Translator.cs
│ └── FileStore.cs
└── Enums
│ └── Language.cs
├── examples
├── WebApiWithUI
│ ├── my-ui-app
│ │ ├── .eslintrc.json
│ │ ├── src
│ │ │ ├── app
│ │ │ │ ├── favicon.ico
│ │ │ │ ├── images
│ │ │ │ │ └── icon.jpeg
│ │ │ │ ├── api
│ │ │ │ │ └── axiosInstance.ts
│ │ │ │ ├── i18n
│ │ │ │ │ ├── settings.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── globals.css
│ │ │ │ └── [lng]
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ └── page.tsx
│ │ │ └── middleware.ts
│ │ ├── next.config.mjs
│ │ ├── postcss.config.mjs
│ │ ├── next-env.d.ts
│ │ ├── tailwind.config.ts
│ │ ├── public
│ │ │ ├── vercel.svg
│ │ │ └── next.svg
│ │ ├── tsconfig.json
│ │ ├── package.json
│ │ └── README.md
│ ├── WebApi
│ │ ├── appsettings.json
│ │ ├── WebApi.http
│ │ ├── appsettings.Development.json
│ │ ├── translations
│ │ │ ├── translations.en-gb.json
│ │ │ └── translations.pl.json
│ │ ├── CustomTranslationsCache.cs
│ │ ├── Properties
│ │ │ └── launchSettings.json
│ │ ├── WebApi.csproj
│ │ ├── CustomTranslationsStore.cs
│ │ └── Program.cs
│ └── README.md
└── ConsoleApp
│ ├── README.md
│ ├── ConsoleApp.csproj
│ ├── translations
│ └── translations.pl.json
│ └── Program.cs
├── translord.DeepL
├── icon.jpeg
├── TranslordDeepLTranslatorServiceCollectionExtensions.cs
├── README.md
├── translord.DeepL.csproj
└── DeepLTranslator.cs
├── translord.Manager
├── icon.jpeg
├── Components
│ ├── Account
│ │ ├── Pages
│ │ │ ├── _Imports.razor
│ │ │ ├── Manage
│ │ │ │ ├── _Imports.razor
│ │ │ │ ├── PersonalData.razor
│ │ │ │ ├── ResetAuthenticator.razor
│ │ │ │ ├── Disable2fa.razor
│ │ │ │ ├── GenerateRecoveryCodes.razor
│ │ │ │ ├── Index.razor
│ │ │ │ ├── DeletePersonalData.razor
│ │ │ │ ├── SetPassword.razor
│ │ │ │ ├── TwoFactorAuthentication.razor
│ │ │ │ ├── ChangePassword.razor
│ │ │ │ ├── Email.razor
│ │ │ │ └── ExternalLogins.razor
│ │ │ ├── InvalidUser.razor
│ │ │ ├── InvalidPasswordReset.razor
│ │ │ ├── AccessDenied.razor
│ │ │ ├── Lockout.razor
│ │ │ ├── ForgotPasswordConfirmation.razor
│ │ │ ├── ResetPasswordConfirmation.razor
│ │ │ ├── ConfirmEmail.razor
│ │ │ ├── ConfirmEmailChange.razor
│ │ │ ├── RegisterConfirmation.razor
│ │ │ ├── ResendEmailConfirmation.razor
│ │ │ ├── ForgotPassword.razor
│ │ │ ├── LoginWithRecoveryCode.razor
│ │ │ ├── LoginWith2fa.razor
│ │ │ ├── ResetPassword.razor
│ │ │ └── Login.razor
│ │ ├── Shared
│ │ │ ├── RedirectToLogin.razor
│ │ │ ├── ManageLayout.razor
│ │ │ ├── ShowRecoveryCodes.razor
│ │ │ ├── AccountLayout.razor
│ │ │ ├── StatusMessage.razor
│ │ │ ├── ManageNavMenu.razor
│ │ │ └── ExternalLoginPicker.razor
│ │ ├── IdentityUserAccessor.cs
│ │ ├── IdentityNoOpEmailSender.cs
│ │ ├── IdentityRevalidatingAuthenticationStateProvider.cs
│ │ ├── IdentityRedirectManager.cs
│ │ └── IdentityComponentsEndpointRouteBuilderExtensions.cs
│ ├── Pages
│ │ ├── Translations.razor
│ │ ├── Home.razor
│ │ └── Error.razor
│ ├── Routes.razor
│ ├── _Imports.razor
│ ├── Layout
│ │ ├── MainLayout.razor
│ │ ├── MainLayout.razor.css
│ │ └── NavMenu.razor
│ ├── App.razor
│ └── Translation
│ │ ├── TranslationsStatistics.razor
│ │ ├── ImportTranslationsDialog.razor
│ │ └── TranslationDialog.razor
├── wwwroot
│ ├── favicon.png
│ └── app.css
├── Data
│ ├── ApplicationUser.cs
│ └── TranslordManagerDbContext.cs
├── Models
│ └── GroupedTranslations.cs
├── README.md
├── translord.Manager.csproj
└── TranslordManagerExtensions.cs
├── translord.RedisCache
├── icon.jpeg
├── README.md
├── TranslordRedisCache.cs
├── translord.RedisCache.csproj
└── TranslordRedisCacheServiceCollectionExtensions.cs
├── translord.EntityFramework
├── icon.jpeg
├── README.md
├── TranslordEntityFrameworkServiceCollectionExtensions.cs
├── Data
│ ├── Configurations
│ │ └── TranslationConfiguration.cs
│ └── TranslationsDbContext.cs
├── translord.EntityFramework.csproj
└── EfStore.cs
├── translord.EntityFramework.Postgres
├── icon.jpeg
├── TranslationsPostgresDbContext.cs
├── README.md
├── TranslordEntityFrameworkPostgresServiceCollectionExtensions.cs
├── Migrations
│ ├── 20240316093826_InitialCreate.cs
│ ├── TranslationsPostgresDbContextModelSnapshot.cs
│ └── 20240316093826_InitialCreate.Designer.cs
└── translord.EntityFramework.Postgres.csproj
├── .vscode
├── launch.json
└── tasks.json
├── LICENSE
└── translord.sln
/assets/icon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/assets/icon.jpeg
--------------------------------------------------------------------------------
/assets/logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/assets/logo.jpeg
--------------------------------------------------------------------------------
/translord/icon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/translord/icon.jpeg
--------------------------------------------------------------------------------
/examples/WebApiWithUI/my-ui-app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/translord.DeepL/icon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/translord.DeepL/icon.jpeg
--------------------------------------------------------------------------------
/assets/panel_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/assets/panel_screenshot.png
--------------------------------------------------------------------------------
/translord.Manager/icon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/translord.Manager/icon.jpeg
--------------------------------------------------------------------------------
/translord.RedisCache/icon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/translord.RedisCache/icon.jpeg
--------------------------------------------------------------------------------
/translord.EntityFramework/icon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/translord.EntityFramework/icon.jpeg
--------------------------------------------------------------------------------
/translord.Manager/Components/Account/Pages/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using translord.Manager.Components.Account.Shared
2 | @layout AccountLayout
--------------------------------------------------------------------------------
/translord.Manager/wwwroot/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/translord.Manager/wwwroot/favicon.png
--------------------------------------------------------------------------------
/translord.EntityFramework.Postgres/icon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/translord.EntityFramework.Postgres/icon.jpeg
--------------------------------------------------------------------------------
/translord.Manager/Components/Account/Pages/Manage/_Imports.razor:
--------------------------------------------------------------------------------
1 | @layout ManageLayout
2 | @attribute [Microsoft.AspNetCore.Authorization.Authorize]
--------------------------------------------------------------------------------
/examples/WebApiWithUI/my-ui-app/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/examples/WebApiWithUI/my-ui-app/src/app/favicon.ico
--------------------------------------------------------------------------------
/examples/WebApiWithUI/my-ui-app/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 |
3 | const nextConfig = {};
4 |
5 | export default nextConfig;
6 |
--------------------------------------------------------------------------------
/examples/WebApiWithUI/my-ui-app/src/app/images/icon.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/margosmat/translord/HEAD/examples/WebApiWithUI/my-ui-app/src/app/images/icon.jpeg
--------------------------------------------------------------------------------
/translord.Manager/Components/Account/Pages/InvalidUser.razor:
--------------------------------------------------------------------------------
1 | @page "/Account/InvalidUser"
2 |
3 |
7 | The password reset link is invalid. 8 |
-------------------------------------------------------------------------------- /examples/WebApiWithUI/my-ui-app/next-env.d.ts: -------------------------------------------------------------------------------- 1 | ///You do not have access to this resource.
8 |This account has been locked out, please try again later.
8 |7 | Please check your email to reset your password. 8 |
-------------------------------------------------------------------------------- /translord.Manager/Models/GroupedTranslations.cs: -------------------------------------------------------------------------------- 1 | using translord.Models; 2 | 3 | namespace translord.Manager.Models; 4 | 5 | public class GroupedTranslations 6 | { 7 | public required string Key { get; set; } 8 | 9 | public List6 | Your password has been reset. Please click here to log in. 7 |
-------------------------------------------------------------------------------- /translord.Manager/Components/Account/Shared/RedirectToLogin.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager NavigationManager 2 | 3 | @code { 4 | 5 | protected override void OnInitialized() 6 | { 7 | NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /translord.Manager/Data/TranslordManagerDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace translord.Manager.Data; 5 | 6 | public class TranslordManagerDbContext(DbContextOptionsLog in to see your translations statistics.
17 |5 | Put these codes in a safe place. 6 |
7 |8 | If you lose your device and don't have the recovery codes you will lose access to your account. 9 |
10 |@recoveryCode
17 | Loading...
8 | } 9 | else 10 | { 11 | @Body 12 | } 13 | 14 | @code { 15 | [CascadingParameter] private HttpContext? HttpContext { get; set; } 16 | 17 | protected override void OnParametersSet() 18 | { 19 | if (HttpContext is null) 20 | { 21 | // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext. 22 | // The identity pages need to set cookies, so they require an HttpContext. To achieve this we 23 | // must transition back from interactive mode to a server-rendered page. 24 | NavigationManager.Refresh(forceReload: true); 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /translord/ITranslator.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using translord.Enums; 3 | using translord.Models; 4 | 5 | namespace translord; 6 | 7 | public interface ITranslator 8 | { 9 | bool IsTranslationSupported { get; } 10 | TaskYour account contains personal data that you have given us. This page allows you to download or delete that data.
13 |14 | Deleting this data will permanently remove your account, and this cannot be recovered. 15 |
16 | 20 |21 | Delete 22 |
23 |
12 | Request ID: @RequestId
13 |
18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |
20 |21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |
26 | 27 | @code{ 28 | [CascadingParameter] private HttpContext? HttpContext { get; set; } 29 | 30 | private string? RequestId { get; set; } 31 | private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 32 | 33 | protected override void OnInitialized() => 34 | RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; 35 | 36 | } -------------------------------------------------------------------------------- /translord.Manager/Components/Account/IdentityNoOpEmailSender.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.AspNetCore.Identity.UI.Services; 3 | using translord.Manager.Data; 4 | 5 | namespace translord.Manager.Components.Account; 6 | 7 | // Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation. 8 | internal sealed class IdentityNoOpEmailSender : IEmailSenderTranslations statistics:
5 | @if (_baseNumber == 0) 6 | { 7 |Start adding translations to see statistics.
8 | } 9 | else 10 | { 11 | @foreach (var (lang, count) in _translationsCount) 12 | { 13 |12 | There are no external authentication services configured. See this 13 | 14 | article 15 | about setting up this ASP.NET application to support logging in via external services 16 | . 17 |
18 |18 | 19 | If you reset your authenticator key your authenticator app will not work until you reconfigure it. 20 |
21 |22 | This process disables 2FA until you verify your authenticator app. 23 | If you do not complete your authenticator app configuration you may lose access to your account. 24 |
25 |18 | This action only disables 2FA. 19 |
20 |21 | Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key 22 | used in an authenticator app you should reset your authenticator keys. 23 |
24 |22 | This app does not currently have a real email sender registered, see these docs for how to configure a real email sender. 23 | Normally this would be emailed: Click here to confirm your account 24 |
25 | } 26 | else 27 | { 28 |Please check your email to confirm your account.
29 | } 30 | 31 | @code { 32 | private string? emailConfirmationLink; 33 | private string? statusMessage; 34 | 35 | [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; 36 | 37 | [SupplyParameterFromQuery] private string? Email { get; set; } 38 | 39 | [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } 40 | 41 | protected override async Task OnInitializedAsync() 42 | { 43 | if (Email is null) 44 | { 45 | RedirectManager.RedirectTo(""); 46 | } 47 | 48 | var user = await UserManager.FindByEmailAsync(Email); 49 | if (user is null) 50 | { 51 | HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; 52 | statusMessage = "Error finding user for unspecified email"; 53 | } 54 | else if (EmailSender is IdentityNoOpEmailSender) 55 | { 56 | // Once you add a real email sender, you should remove this code that lets you confirm the account 57 | var userId = await UserManager.GetUserIdAsync(user); 58 | var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); 59 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); 60 | emailConfirmationLink = NavigationManager.GetUriWithQueryParameters( 61 | NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, 62 | new Dictionary22 | 23 | Put these codes in a safe place. 24 |
25 |26 | If you lose your device and don't have the recovery codes you will lose access to your account. 27 |
28 |29 | Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key 30 | used in an authenticator app you should reset your authenticator keys. 31 |
32 |21 | Deleting this data will permanently remove your account, and this cannot be recovered. 22 |
23 |18 | You have requested to log in with a recovery code. This login will not be remembered until you provide 19 | an authenticator app code at log in or disable 2FA and log in again. 20 |
21 |17 | You do not have a local username/password for this site. Add a local 18 | account so you can log in without an external login. 19 |
20 |Your login is protected with an authenticator app. Enter your authenticator code below.
18 |43 | Don't have access to your authenticator device? You can 44 | log in with a recovery code. 45 |
46 | 47 | @code { 48 | private string? message; 49 | private ApplicationUser user = default!; 50 | 51 | [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); 52 | 53 | [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } 54 | 55 | [SupplyParameterFromQuery] private bool RememberMe { get; set; } 56 | 57 | protected override async Task OnInitializedAsync() 58 | { 59 | // Ensure the user has gone through the username & password screen first 60 | user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? 61 | throw new InvalidOperationException("Unable to load two-factor authentication user."); 62 | } 63 | 64 | private async Task OnValidSubmitAsync() 65 | { 66 | var authenticatorCode = Input.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty); 67 | var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, RememberMe, Input.RememberMachine); 68 | var userId = await UserManager.GetUserIdAsync(user); 69 | 70 | if (result.Succeeded) 71 | { 72 | Logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId); 73 | RedirectManager.RedirectTo(ReturnUrl); 74 | } 75 | else if (result.IsLockedOut) 76 | { 77 | Logger.LogWarning("User with ID '{UserId}' account locked out.", userId); 78 | RedirectManager.RedirectTo("Account/Lockout"); 79 | } 80 | else 81 | { 82 | Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId); 83 | message = "Error: Invalid authenticator code."; 84 | } 85 | } 86 | 87 | private sealed class InputModel 88 | { 89 | [Required] 90 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 91 | [DataType(DataType.Text)] 92 | [Display(Name = "Authenticator code")] 93 | public string? TwoFactorCode { get; set; } 94 | 95 | [Display(Name = "Remember this machine")] 96 | public bool RememberMachine { get; set; } 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /translord/README.md: -------------------------------------------------------------------------------- 1 | # translord 2 | 3 | translord - simple TMS to get your translations up and running in no time. 4 | 5 | 🚧 Project still in the early stages of development 6 | 7 | What this tool aims to achieve? To be a central place in your project that handles all things related to your translations: 8 | - storing 9 | - translating 10 | - delivering 11 | - management 12 | - revision 13 | 14 | ## Packages structure 15 | - translord 16 | - The core library 17 | - [translord.DeepL](https://github.com/margosmat/translord/tree/main/translord.DeepL) 18 | - Library containing [DeepL API](https://www.deepl.com/pro-api?cta=header-pro-api) configuration for texts translation in translord. 19 | - [translord.EntityFramework](https://github.com/margosmat/translord/tree/main/translord.EntityFramework) 20 | - Library containing configuration data that uses EntityFramework as its database abstraction. 21 | - [translord.EntityFramework.Postgres](https://github.com/margosmat/translord/tree/main/translord.EntityFramework.Postgres) 22 | - Library extending the `translord.EntityFramework` library with Postgres configuration. 23 | - [translord.Manager](https://github.com/margosmat/translord/tree/main/translord.Manager) 24 | - Library containing the TMS admin panel allowing for translations editing/management/translation. 25 | - [translord.RedisCache](https://github.com/margosmat/translord/tree/main/translord.RedisCache) 26 | - Library containing configuration for Redis as the cache for translord. 27 | 28 | ## Configuration examples 29 | 30 | ### WebApp with FileStore 31 | ```c# 32 | builder.Services.AddTranslordFileStore(options => 33 | { 34 | options.TranslationsPath = Path.Combine(Directory.GetCurrentDirectory(), "translations"); 35 | }); 36 | builder.Services.AddTranslord(o => 37 | { 38 | ListYou must generate a new set of recovery codes before you can log in with a recovery code.
25 |You can generate a new set of recovery codes.
32 |You should generate a new set of recovery codes.
39 |You must accept the policy before you can enable two factor authentication.
70 |46 | Forgot your password? 47 |
48 |49 | { ["ReturnUrl"] = ReturnUrl }))">Register as a new user 50 |
51 |52 | Resend email confirmation 53 |
54 || @login.ProviderDisplayName | 25 |26 | @if (showRemoveButton) 27 | { 28 | 36 | } 37 | else 38 | { 39 | @: 40 | } 41 | | 42 |