From 8b0fe5bc2426ae28cb27e78f5d0c68da08835c13 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Sat, 20 Dec 2025 22:34:24 +0100 Subject: [PATCH 1/6] Add Translations extensions and update docs Introduced new Translations extensions for ApplicationCommands, CommandsNext, and Manager, including supporting project files and extension methods. Updated documentation to include API references and table of contents for the new extensions. Made minor copyright year updates and added 'dcs-artifacts/' to .gitignore. --- .gitignore | 1 + .../index.md | 11 + .../toc.yml | 10 + .../index.md | 11 + .../toc.yml | 10 + .../index.md | 11 + .../toc.yml | 13 + .../index.md | 11 + .../toc.yml | 19 + DisCatSharp.Extensions.Docs/api/index.md | 4 + DisCatSharp.Extensions.Docs/docfx.json | 64 +++ .../EventArgs/AccessTokenRefreshEventArgs.cs | 2 +- .../EventArgs/AccessTokenRevokeEventArgs.cs | 2 +- .../AuthorizationCodeExchangeEventArgs.cs | 2 +- .../AuthorizationCodeReceiveEventArgs.cs | 2 +- .../EventArgs/DiscordOAuth2EventArgs.cs | 2 +- .../AuthorizationCodeEventWaiter.cs | 2 +- .../AuthorizationCodeCollectRequest.cs | 2 +- .../Requests/AuthorizationCodeMatchRequest.cs | 2 +- .../ExtensionMethods.cs | 2 +- .../OAuth2Result.cs | 2 +- .../OAuth2WebConfiguration.cs | 2 +- .../OAuth2WebExtension.cs | 2 +- .../OAuth2WebExtensionUtilities.cs | 2 +- .../ExtensionMethods.cs | 2 +- .../SimpleMusicCommandsExtension.cs | 2 +- ...ns.Translations.ApplicationCommands.csproj | 38 ++ .../ExtensionMethods.cs | 64 +++ ...xtensions.Translations.CommandsNext.csproj | 39 ++ .../ExtensionMethods.cs | 48 ++ .../AuditReport.cs | 158 ++++++ .../AuditReportProvider.cs | 122 +++++ ...arp.Extensions.Translations.Manager.csproj | 67 +++ .../DynamicUsage.cs | 25 + .../ITranslationsReloadHandler.cs | 67 +++ .../LocaleHelper.cs | 125 +++++ .../ScanResult.cs | 25 + .../Scanner.cs | 81 +++ .../StringsMutation.cs | 25 + .../StringsStore.cs | 57 ++ .../TranslationData.cs | 113 ++++ .../TranslationManager.cs | 163 ++++++ .../TranslationUsageWalker.cs | 141 +++++ .../TranslationsManagerConfiguration.cs | 100 ++++ .../UsageLocation.cs | 25 + .../wwwroot/app.js | 503 ++++++++++++++++++ .../wwwroot/favicon.ico | Bin 0 -> 432254 bytes .../wwwroot/index.html | 67 +++ .../wwwroot/styles.css | 359 +++++++++++++ ...DisCatSharp.Extensions.Translations.csproj | 47 ++ .../ExtensionMethods.cs | 117 ++++ .../InternalsVisibleTo.targets | 9 + .../TranslationEngine.cs | 211 ++++++++ .../TranslationsConfiguration.cs | 87 +++ .../TranslationsExtension.cs | 89 ++++ .../Entities/TwoFactorResponse.cs | 2 +- .../Enums/TwoFactorResult.cs | 2 +- .../ExtensionMethods.cs | 2 +- ...ommandRequireEnrolledTwoFactorAttribute.cs | 2 +- .../TwoFactorApplicationCommandExtension.cs | 2 +- ...ommandRequireEnrolledTwoFactorAttribute.cs | 2 +- .../TwoFactorCommandsNextExtension.cs | 2 +- .../Properties/AssemblyProperties.cs | 2 +- .../TwoFactorConfiguration.cs | 2 +- .../TwoFactorExtension.cs | 2 +- .../TwoFactorExtensionUtilities.cs | 2 +- DisCatSharp.Extensions.slnx | 5 + README.md | 15 +- Targets/Library.targets | 3 +- 69 files changed, 3179 insertions(+), 33 deletions(-) create mode 100644 DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.ApplicationCommands/index.md create mode 100644 DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.ApplicationCommands/toc.yml create mode 100644 DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.CommandsNext/index.md create mode 100644 DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.CommandsNext/toc.yml create mode 100644 DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.Manager/index.md create mode 100644 DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.Manager/toc.yml create mode 100644 DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations/index.md create mode 100644 DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations/toc.yml create mode 100644 DisCatSharp.Extensions.Translations.ApplicationCommands/DisCatSharp.Extensions.Translations.ApplicationCommands.csproj create mode 100644 DisCatSharp.Extensions.Translations.ApplicationCommands/ExtensionMethods.cs create mode 100644 DisCatSharp.Extensions.Translations.CommandsNext/DisCatSharp.Extensions.Translations.CommandsNext.csproj create mode 100644 DisCatSharp.Extensions.Translations.CommandsNext/ExtensionMethods.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/AuditReport.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/AuditReportProvider.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/DisCatSharp.Extensions.Translations.Manager.csproj create mode 100644 DisCatSharp.Extensions.Translations.Manager/DynamicUsage.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/ITranslationsReloadHandler.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/LocaleHelper.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/ScanResult.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/Scanner.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/StringsMutation.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/StringsStore.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/TranslationData.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/TranslationManager.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/TranslationUsageWalker.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/TranslationsManagerConfiguration.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/UsageLocation.cs create mode 100644 DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js create mode 100644 DisCatSharp.Extensions.Translations.Manager/wwwroot/favicon.ico create mode 100644 DisCatSharp.Extensions.Translations.Manager/wwwroot/index.html create mode 100644 DisCatSharp.Extensions.Translations.Manager/wwwroot/styles.css create mode 100644 DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj create mode 100644 DisCatSharp.Extensions.Translations/ExtensionMethods.cs create mode 100644 DisCatSharp.Extensions.Translations/InternalsVisibleTo.targets create mode 100644 DisCatSharp.Extensions.Translations/TranslationEngine.cs create mode 100644 DisCatSharp.Extensions.Translations/TranslationsConfiguration.cs create mode 100644 DisCatSharp.Extensions.Translations/TranslationsExtension.cs diff --git a/.gitignore b/.gitignore index 8429186..09cf028 100644 --- a/.gitignore +++ b/.gitignore @@ -309,6 +309,7 @@ dcs-docs-preview.zip docfx.zip DisCatSharp.Docs/_site/ _site +dcs-artifacts/ #Ignore thumbnails created by Windows Thumbs.db diff --git a/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.ApplicationCommands/index.md b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.ApplicationCommands/index.md new file mode 100644 index 0000000..7d3a18a --- /dev/null +++ b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.ApplicationCommands/index.md @@ -0,0 +1,11 @@ +--- +uid: api_discatsharp_extensions_translations_applicationcommands_index +title: DisCatSharp Translations ApplicationCommands Extension API Reference +author: DisCatSharp Team +--- + +# API Reference + +Welcome to the DisCatSharp Translations ApplicationCommands Extension API reference. + +To begin, select a namespace, then a class, from the table of contents on the left. diff --git a/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.ApplicationCommands/toc.yml b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.ApplicationCommands/toc.yml new file mode 100644 index 0000000..9c97191 --- /dev/null +++ b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.ApplicationCommands/toc.yml @@ -0,0 +1,10 @@ +### YamlMime:TableOfContent +items: +- uid: DisCatSharp.Extensions.Translations.ApplicationCommands + name: DisCatSharp.Extensions.Translations.ApplicationCommands + type: Namespace + items: + - uid: DisCatSharp.Extensions.Translations.ApplicationCommands.ExtensionMethods + name: ExtensionMethods + type: Class +memberLayout: SeparatePages diff --git a/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.CommandsNext/index.md b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.CommandsNext/index.md new file mode 100644 index 0000000..2f039af --- /dev/null +++ b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.CommandsNext/index.md @@ -0,0 +1,11 @@ +--- +uid: api_discatsharp_extensions_translations_commandsnext_index +title: DisCatSharp Translations CommandsNext Extension API Reference +author: DisCatSharp Team +--- + +# API Reference + +Welcome to the DisCatSharp Translations CommandsNext Extension API reference. + +To begin, select a namespace, then a class, from the table of contents on the left. diff --git a/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.CommandsNext/toc.yml b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.CommandsNext/toc.yml new file mode 100644 index 0000000..a150e35 --- /dev/null +++ b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.CommandsNext/toc.yml @@ -0,0 +1,10 @@ +### YamlMime:TableOfContent +items: +- uid: DisCatSharp.Extensions.Translations.CommandsNext + name: DisCatSharp.Extensions.Translations.CommandsNext + type: Namespace + items: + - uid: DisCatSharp.Extensions.Translations.CommandsNext.ExtensionMethods + name: ExtensionMethods + type: Class +memberLayout: SeparatePages diff --git a/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.Manager/index.md b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.Manager/index.md new file mode 100644 index 0000000..282e9fa --- /dev/null +++ b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.Manager/index.md @@ -0,0 +1,11 @@ +--- +uid: api_discatsharp_extensions_translations_manager_index +title: DisCatSharp Translations Manager Extension API Reference +author: DisCatSharp Team +--- + +# API Reference + +Welcome to the DisCatSharp Translations Manager Extension API reference. + +To begin, select a namespace, then a class, from the table of contents on the left. diff --git a/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.Manager/toc.yml b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.Manager/toc.yml new file mode 100644 index 0000000..bfbe9b5 --- /dev/null +++ b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations.Manager/toc.yml @@ -0,0 +1,13 @@ +### YamlMime:TableOfContent +items: +- uid: DisCatSharp.Extensions.Translations.Manager + name: DisCatSharp.Extensions.Translations.Manager + type: Namespace + items: + - uid: DisCatSharp.Extensions.Translations.Manager.TranslationManager + name: TranslationManager + type: Class + - uid: DisCatSharp.Extensions.Translations.Manager.TranslationsManagerConfiguration + name: TranslationsManagerConfiguration + type: Class +memberLayout: SeparatePages diff --git a/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations/index.md b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations/index.md new file mode 100644 index 0000000..1b1712a --- /dev/null +++ b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations/index.md @@ -0,0 +1,11 @@ +--- +uid: api_discatsharp_extensions_translations_index +title: DisCatSharp Translations Extension API Reference +author: DisCatSharp Team +--- + +# API Reference + +Welcome to the DisCatSharp Translations Extension API reference. + +To begin, select a namespace, then a class, from the table of contents on the left. diff --git a/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations/toc.yml b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations/toc.yml new file mode 100644 index 0000000..ab0a8c7 --- /dev/null +++ b/DisCatSharp.Extensions.Docs/api/DisCatSharp.Extensions.Translations/toc.yml @@ -0,0 +1,19 @@ +### YamlMime:TableOfContent +items: +- uid: DisCatSharp.Extensions.Translations + name: DisCatSharp.Extensions.Translations + type: Namespace + items: + - uid: DisCatSharp.Extensions.Translations.ExtensionMethods + name: ExtensionMethods + type: Class + - uid: DisCatSharp.Extensions.Translations.TranslationEngine + name: TranslationEngine + type: Class + - uid: DisCatSharp.Extensions.Translations.TranslationsConfiguration + name: TranslationsConfiguration + type: Class + - uid: DisCatSharp.Extensions.Translations.TranslationsExtension + name: TranslationsExtension + type: Class +memberLayout: SeparatePages diff --git a/DisCatSharp.Extensions.Docs/api/index.md b/DisCatSharp.Extensions.Docs/api/index.md index f68f68c..a559d32 100644 --- a/DisCatSharp.Extensions.Docs/api/index.md +++ b/DisCatSharp.Extensions.Docs/api/index.md @@ -10,3 +10,7 @@ Welcome to the DisCatSharp Extensions Global API reference. - [DisCatSharp.Extensions.TwoFactorCommands](xref:api_discatsharp_extensions_twofactorcommands_index) - [DisCatSharp.Extensions.OAuth2Web](xref:api_discatsharp_extensions_oauth2web_index) - [DisCatSharp.Extensions.SimpleMusicCommands](xref:api_discatsharp_extensions_simplemusiccommands_index) +- [DisCatSharp.Extensions.Translations](xref:api_discatsharp_extensions_translations_index) +- [DisCatSharp.Extensions.Translations.ApplicationCommands](xref:api_discatsharp_extensions_translations_applicationcommands_index) +- [DisCatSharp.Extensions.Translations.CommandsNext](xref:api_discatsharp_extensions_translations_commandsnext_index) +- [DisCatSharp.Extensions.Translations.Manager](xref:api_discatsharp_extensions_translations_manager_index) diff --git a/DisCatSharp.Extensions.Docs/docfx.json b/DisCatSharp.Extensions.Docs/docfx.json index b955147..9b54c5d 100644 --- a/DisCatSharp.Extensions.Docs/docfx.json +++ b/DisCatSharp.Extensions.Docs/docfx.json @@ -47,6 +47,70 @@ "namespaceLayout": "flattened", "enumSortOrder": "declaringOrder", "includeExplicitInterfaceImplementations": true + }, + { + "src": [ + { + "src": "../DisCatSharp.Extensions.Translations/", + "files": ["**.csproj"], + "exclude": ["**/obj/**", "**/bin/**"] + } + ], + "dest": "api/DisCatSharp.Extensions.Translations", + "filter": "filter_config.yml", + "disableDefaultFilter": false, + "memberLayout": "separatePages", + "namespaceLayout": "flattened", + "enumSortOrder": "declaringOrder", + "includeExplicitInterfaceImplementations": true + }, + { + "src": [ + { + "src": "../DisCatSharp.Extensions.Translations.ApplicationCommands/", + "files": ["**.csproj"], + "exclude": ["**/obj/**", "**/bin/**"] + } + ], + "dest": "api/DisCatSharp.Extensions.Translations.ApplicationCommands", + "filter": "filter_config.yml", + "disableDefaultFilter": false, + "memberLayout": "separatePages", + "namespaceLayout": "flattened", + "enumSortOrder": "declaringOrder", + "includeExplicitInterfaceImplementations": true + }, + { + "src": [ + { + "src": "../DisCatSharp.Extensions.Translations.CommandsNext/", + "files": ["**.csproj"], + "exclude": ["**/obj/**", "**/bin/**"] + } + ], + "dest": "api/DisCatSharp.Extensions.Translations.CommandsNext", + "filter": "filter_config.yml", + "disableDefaultFilter": false, + "memberLayout": "separatePages", + "namespaceLayout": "flattened", + "enumSortOrder": "declaringOrder", + "includeExplicitInterfaceImplementations": true + }, + { + "src": [ + { + "src": "../DisCatSharp.Extensions.Translations.Manager/", + "files": ["**.csproj"], + "exclude": ["**/obj/**", "**/bin/**"] + } + ], + "dest": "api/DisCatSharp.Extensions.Translations.Manager", + "filter": "filter_config.yml", + "disableDefaultFilter": false, + "memberLayout": "separatePages", + "namespaceLayout": "flattened", + "enumSortOrder": "declaringOrder", + "includeExplicitInterfaceImplementations": true } ], "build": { diff --git a/DisCatSharp.Extensions.OAuth2Web/EventArgs/AccessTokenRefreshEventArgs.cs b/DisCatSharp.Extensions.OAuth2Web/EventArgs/AccessTokenRefreshEventArgs.cs index 287aade..5963e6c 100644 --- a/DisCatSharp.Extensions.OAuth2Web/EventArgs/AccessTokenRefreshEventArgs.cs +++ b/DisCatSharp.Extensions.OAuth2Web/EventArgs/AccessTokenRefreshEventArgs.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/EventArgs/AccessTokenRevokeEventArgs.cs b/DisCatSharp.Extensions.OAuth2Web/EventArgs/AccessTokenRevokeEventArgs.cs index dca8114..1f7c202 100644 --- a/DisCatSharp.Extensions.OAuth2Web/EventArgs/AccessTokenRevokeEventArgs.cs +++ b/DisCatSharp.Extensions.OAuth2Web/EventArgs/AccessTokenRevokeEventArgs.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/EventArgs/AuthorizationCodeExchangeEventArgs.cs b/DisCatSharp.Extensions.OAuth2Web/EventArgs/AuthorizationCodeExchangeEventArgs.cs index 9d469e8..bfd0bd4 100644 --- a/DisCatSharp.Extensions.OAuth2Web/EventArgs/AuthorizationCodeExchangeEventArgs.cs +++ b/DisCatSharp.Extensions.OAuth2Web/EventArgs/AuthorizationCodeExchangeEventArgs.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/EventArgs/AuthorizationCodeReceiveEventArgs.cs b/DisCatSharp.Extensions.OAuth2Web/EventArgs/AuthorizationCodeReceiveEventArgs.cs index 723e7d7..def7a94 100644 --- a/DisCatSharp.Extensions.OAuth2Web/EventArgs/AuthorizationCodeReceiveEventArgs.cs +++ b/DisCatSharp.Extensions.OAuth2Web/EventArgs/AuthorizationCodeReceiveEventArgs.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/EventArgs/DiscordOAuth2EventArgs.cs b/DisCatSharp.Extensions.OAuth2Web/EventArgs/DiscordOAuth2EventArgs.cs index 799064b..a4b7ced 100644 --- a/DisCatSharp.Extensions.OAuth2Web/EventArgs/DiscordOAuth2EventArgs.cs +++ b/DisCatSharp.Extensions.OAuth2Web/EventArgs/DiscordOAuth2EventArgs.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/EventHandling/AuthorizationCodeEventWaiter.cs b/DisCatSharp.Extensions.OAuth2Web/EventHandling/AuthorizationCodeEventWaiter.cs index b162d9e..a134391 100644 --- a/DisCatSharp.Extensions.OAuth2Web/EventHandling/AuthorizationCodeEventWaiter.cs +++ b/DisCatSharp.Extensions.OAuth2Web/EventHandling/AuthorizationCodeEventWaiter.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/EventHandling/Requests/AuthorizationCodeCollectRequest.cs b/DisCatSharp.Extensions.OAuth2Web/EventHandling/Requests/AuthorizationCodeCollectRequest.cs index ef225f9..c0ad383 100644 --- a/DisCatSharp.Extensions.OAuth2Web/EventHandling/Requests/AuthorizationCodeCollectRequest.cs +++ b/DisCatSharp.Extensions.OAuth2Web/EventHandling/Requests/AuthorizationCodeCollectRequest.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/EventHandling/Requests/AuthorizationCodeMatchRequest.cs b/DisCatSharp.Extensions.OAuth2Web/EventHandling/Requests/AuthorizationCodeMatchRequest.cs index 233e54f..ecd35bd 100644 --- a/DisCatSharp.Extensions.OAuth2Web/EventHandling/Requests/AuthorizationCodeMatchRequest.cs +++ b/DisCatSharp.Extensions.OAuth2Web/EventHandling/Requests/AuthorizationCodeMatchRequest.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/ExtensionMethods.cs b/DisCatSharp.Extensions.OAuth2Web/ExtensionMethods.cs index 1a90b0a..699634e 100644 --- a/DisCatSharp.Extensions.OAuth2Web/ExtensionMethods.cs +++ b/DisCatSharp.Extensions.OAuth2Web/ExtensionMethods.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/OAuth2Result.cs b/DisCatSharp.Extensions.OAuth2Web/OAuth2Result.cs index 9c66e5d..ff5333c 100644 --- a/DisCatSharp.Extensions.OAuth2Web/OAuth2Result.cs +++ b/DisCatSharp.Extensions.OAuth2Web/OAuth2Result.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/OAuth2WebConfiguration.cs b/DisCatSharp.Extensions.OAuth2Web/OAuth2WebConfiguration.cs index 3697cb4..e267d7b 100644 --- a/DisCatSharp.Extensions.OAuth2Web/OAuth2WebConfiguration.cs +++ b/DisCatSharp.Extensions.OAuth2Web/OAuth2WebConfiguration.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/OAuth2WebExtension.cs b/DisCatSharp.Extensions.OAuth2Web/OAuth2WebExtension.cs index 0b8cbc6..9f19904 100644 --- a/DisCatSharp.Extensions.OAuth2Web/OAuth2WebExtension.cs +++ b/DisCatSharp.Extensions.OAuth2Web/OAuth2WebExtension.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.OAuth2Web/OAuth2WebExtensionUtilities.cs b/DisCatSharp.Extensions.OAuth2Web/OAuth2WebExtensionUtilities.cs index 2c57218..daa1525 100644 --- a/DisCatSharp.Extensions.OAuth2Web/OAuth2WebExtensionUtilities.cs +++ b/DisCatSharp.Extensions.OAuth2Web/OAuth2WebExtensionUtilities.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.SimpleMusicCommands/ExtensionMethods.cs b/DisCatSharp.Extensions.SimpleMusicCommands/ExtensionMethods.cs index 0e48c00..eb79fb5 100644 --- a/DisCatSharp.Extensions.SimpleMusicCommands/ExtensionMethods.cs +++ b/DisCatSharp.Extensions.SimpleMusicCommands/ExtensionMethods.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.SimpleMusicCommands/SimpleMusicCommandsExtension.cs b/DisCatSharp.Extensions.SimpleMusicCommands/SimpleMusicCommandsExtension.cs index 0550fe1..b2508c9 100644 --- a/DisCatSharp.Extensions.SimpleMusicCommands/SimpleMusicCommandsExtension.cs +++ b/DisCatSharp.Extensions.SimpleMusicCommands/SimpleMusicCommandsExtension.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.Translations.ApplicationCommands/DisCatSharp.Extensions.Translations.ApplicationCommands.csproj b/DisCatSharp.Extensions.Translations.ApplicationCommands/DisCatSharp.Extensions.Translations.ApplicationCommands.csproj new file mode 100644 index 0000000..26a33d2 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.ApplicationCommands/DisCatSharp.Extensions.Translations.ApplicationCommands.csproj @@ -0,0 +1,38 @@ + + + + + + + + + + + + DisCatSharp.Extensions.Translations.ApplicationCommands + DisCatSharp.Extensions.Translations.ApplicationCommands + + + + DisCatSharp.Extensions.Translations.ApplicationCommands + + DisCatSharp.Extensions.Translations.ApplicationCommands + + Extension making it easy to use localizations in ApplicationCommands commands. + + DisCatSharp,DisCatSharp Extension,Translations,Localization,ApplicationCommands,Discord,Bots,Discord Bots,AITSYS,Net8,Net9,Net10 + + + + + + + + + + + + + + + diff --git a/DisCatSharp.Extensions.Translations.ApplicationCommands/ExtensionMethods.cs b/DisCatSharp.Extensions.Translations.ApplicationCommands/ExtensionMethods.cs new file mode 100644 index 0000000..51339df --- /dev/null +++ b/DisCatSharp.Extensions.Translations.ApplicationCommands/ExtensionMethods.cs @@ -0,0 +1,64 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using DisCatSharp.ApplicationCommands.Context; + +namespace DisCatSharp.Extensions.Translations.ApplicationCommands; + +/// +/// Defines various extensions specific to Translator. +/// +public static class ExtensionMethods +{ + /// + /// Translates a key using the locale from the . + /// + /// The interaction context. + /// The translation key. + /// The placeholders to replace in the translation. + /// Whether to force the guild locale. + /// The translated string. + public static string T(this InteractionContext ctx, string key, object? placeholders = null, bool forceGuildLocale = false) + => ctx.Interaction.T(key, placeholders); + + /// + /// Translates a key using the locale from the . + /// + /// The context menu context. + /// The translation key. + /// The placeholders to replace in the translation. + /// Whether to force the guild locale. + /// The translated string. + public static string T(this ContextMenuContext ctx, string key, object? placeholders = null, bool forceGuildLocale = false) + => ctx.Interaction.T(key, placeholders); + + /// + /// Translates a key using the locale from the . + /// + /// The autocomplete context. + /// The translation key. + /// The placeholders to replace in the translation. + /// Whether to force the guild locale. + /// The translated string. + public static string T(this AutocompleteContext ctx, string key, object? placeholders = null, bool forceGuildLocale = false) + => ctx.Interaction.T(key, placeholders); +} diff --git a/DisCatSharp.Extensions.Translations.CommandsNext/DisCatSharp.Extensions.Translations.CommandsNext.csproj b/DisCatSharp.Extensions.Translations.CommandsNext/DisCatSharp.Extensions.Translations.CommandsNext.csproj new file mode 100644 index 0000000..23f76ad --- /dev/null +++ b/DisCatSharp.Extensions.Translations.CommandsNext/DisCatSharp.Extensions.Translations.CommandsNext.csproj @@ -0,0 +1,39 @@ + + + + + + + + + + + + DisCatSharp.Extensions.Translations.CommandsNext + DisCatSharp.Extensions.Translations.CommandsNext + + + + DisCatSharp.Extensions.Translations.CommandsNext + + DisCatSharp.Extensions.Translations.CommandsNext + + Extension making it easy to use localizations in CommandsNext commands. + + DisCatSharp,DisCatSharp Extension,Translations,Localization,CommandsNext,Discord,Bots,Discord Bots,AITSYS,Net8,Net9,Net10 + + + + + + + + + + + + + + + + diff --git a/DisCatSharp.Extensions.Translations.CommandsNext/ExtensionMethods.cs b/DisCatSharp.Extensions.Translations.CommandsNext/ExtensionMethods.cs new file mode 100644 index 0000000..ee4c434 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.CommandsNext/ExtensionMethods.cs @@ -0,0 +1,48 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using DisCatSharp.CommandsNext; + +namespace DisCatSharp.Extensions.Translations.CommandsNext; + +/// +/// Defines various extensions specific to Translator. +/// +public static class ExtensionMethods +{ + /// + /// Translates a key using the locale from the . + /// + /// The autocomplete context. + /// The translation key. + /// The placeholders to replace in the translation. + /// Whether to force the guild locale. + /// The translated string. + public static string T(this CommandContext ctx, string key, object? placeholders = null, bool forceGuildLocale = false) + { + var engine = ctx.Client.GetExtension()!.TranslationEngine; + var locale = engine.DefaultLocale; + if (forceGuildLocale || !string.IsNullOrWhiteSpace(ctx.Guild?.PreferredLocale)) + locale = ctx.Guild?.PreferredLocale; + return engine.TLocale(locale, key, placeholders); + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/AuditReport.cs b/DisCatSharp.Extensions.Translations.Manager/AuditReport.cs new file mode 100644 index 0000000..3aaa2f3 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/AuditReport.cs @@ -0,0 +1,158 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed record AuditReport +{ + public required DateTimeOffset GeneratedAt { get; init; } + public required string RepoRoot { get; init; } + public required string SourceDirectory { get; init; } + public required string StringsPath { get; init; } + public required int UsedKeysCount { get; init; } + public required int DefinedKeysCount { get; init; } + public required List MissingKeys { get; init; } + public required List UnusedKeys { get; init; } + public required List DuplicateKeys { get; init; } + public required Dictionary> DuplicateEnUsValues { get; init; } + public required Dictionary LocaleMissingCounts { get; init; } + public required List DynamicKeyUsages { get; init; } + public required Dictionary> KeyUsages { get; init; } + + public static AuditReport Build(TranslationData translations, ScanResult scan, TranslationsManagerConfiguration configuration, string stringsPath) + { + var repoRoot = Path.GetFullPath(configuration.RepoRoot); + var sourceDirectory = Path.GetFullPath(configuration.SourceDirectory); + var resolvedStringsPath = Path.GetFullPath(stringsPath); + var definedKeys = translations.Map.Keys.ToHashSet(StringComparer.Ordinal); + var missing = scan.Keys.Where(k => !definedKeys.Contains(k)).OrderBy(k => k).ToList(); + var dynamicPrefixes = BuildDynamicPrefixes(scan.DynamicUsages); + var unused = definedKeys + .Where(k => !scan.Keys.Contains(k) && !IsCoveredByDynamic(k, dynamicPrefixes)) + .OrderBy(k => k) + .ToList(); + + var usageLookup = scan.Usages + .GroupBy(u => u.Key, StringComparer.OrdinalIgnoreCase) + .ToDictionary(g => g.Key, g => g.OrderBy(u => u.File).ThenBy(u => u.Line).ToList(), StringComparer.OrdinalIgnoreCase); + + var enPairs = translations.Map + .Select(kv => kv.Value.TryGetValue("en-US", out var enValue) ? (kv.Key, Value: enValue) : (Key: null, Value: null)) + .Where(x => x.Key is not null && !string.IsNullOrWhiteSpace(x.Value)) + .Select(x => (Key: x.Key!, Value: x.Value!)); + + var enDuplicates = enPairs + .GroupBy(x => x.Value) + .Where(g => g.Count() > 1) + .ToDictionary(g => g.Key, g => g.Select(item => item.Key).OrderBy(x => x).ToList(), StringComparer.Ordinal); + + var localeMissing = BuildLocaleMissing(translations.Map); + + return new AuditReport + { + GeneratedAt = DateTimeOffset.UtcNow, + RepoRoot = repoRoot, + SourceDirectory = sourceDirectory, + StringsPath = resolvedStringsPath, + UsedKeysCount = scan.Keys.Count, + DefinedKeysCount = translations.Map.Count, + MissingKeys = missing, + UnusedKeys = unused, + DuplicateKeys = [.. translations.DuplicateKeys], + DuplicateEnUsValues = enDuplicates, + LocaleMissingCounts = localeMissing, + DynamicKeyUsages = [.. scan.DynamicUsages], + KeyUsages = usageLookup + }; + } + + private static Dictionary BuildLocaleMissing(Dictionary> map) + { + var locales = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var value in map.Values) + { + foreach (var locale in value.Keys) + locales.Add(locale); + } + + var missing = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var locale in locales) + { + var count = map.Count(kv => !kv.Value.TryGetValue(locale, out var text) || string.IsNullOrWhiteSpace(text)); + missing[locale] = count; + } + + return missing; + } + + private static IReadOnlyList BuildDynamicPrefixes(IReadOnlyList dynamicUsages) + { + var prefixes = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var dyn in dynamicUsages) + { + var prefix = ExtractPrefix(dyn.Expression); + if (!string.IsNullOrWhiteSpace(prefix)) + prefixes.Add(prefix); + } + + return prefixes.ToList(); + } + + private static bool IsCoveredByDynamic(string key, IReadOnlyList prefixes) + { + foreach (var prefix in prefixes) + { + if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + return true; + } + return false; + } + + private static string ExtractPrefix(string expression) + { + if (string.IsNullOrWhiteSpace(expression)) + return string.Empty; + + // Trim leading interpolation markers + var expr = expression.Trim(); + if (expr.StartsWith("$") && expr.Length > 1) + expr = expr.Substring(1); + if (expr.StartsWith("@$") && expr.Length > 2) + expr = expr.Substring(2); + expr = expr.TrimStart('"'); + + var brace = expr.IndexOf('{'); + if (brace >= 0) + expr = expr.Substring(0, brace); + + // Strip trailing quote if still present + expr = expr.TrimEnd('"'); + + // Keep only prefixes that look like translation key prefixes (must contain a dot) + return expr.Contains('.') ? expr : string.Empty; + } +} \ No newline at end of file diff --git a/DisCatSharp.Extensions.Translations.Manager/AuditReportProvider.cs b/DisCatSharp.Extensions.Translations.Manager/AuditReportProvider.cs new file mode 100644 index 0000000..359c704 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/AuditReportProvider.cs @@ -0,0 +1,122 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to do so. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed class AuditReportProvider +{ + private readonly TranslationsManagerConfiguration _configuration; + private readonly StringsStore _store; + private readonly ILogger _logger; + private readonly SemaphoreSlim _gate = new(1, 1); + + private AuditReport? _cached; + private DateTimeOffset _cacheExpiresAt; + + public AuditReportProvider(TranslationsManagerConfiguration configuration, StringsStore store, ILogger logger) + { + this._configuration = configuration; + this._store = store; + this._logger = logger; + } + + public async Task GetReportAsync(CancellationToken cancellationToken = default) + { + await this._gate.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + if (this._cached is not null && DateTimeOffset.UtcNow < this._cacheExpiresAt) + return this._cached; + + var translations = this.LoadTranslations(); + var scan = Scanner.ScanSource(this.ResolveSourceDirectory(), this.ResolveRepoRoot()); + var report = AuditReport.Build(translations, scan, this._configuration, this._store.Path); + + if (this._configuration.WriteAuditToDisk) + this.WriteReport(report); + + this._cached = report; + this._cacheExpiresAt = DateTimeOffset.UtcNow.Add(this._configuration.ReportCacheDuration); + return report; + } + catch (Exception ex) + { + this._logger.LogError(ex, "Failed to build translations audit report."); + throw; + } + finally + { + this._gate.Release(); + } + } + + private TranslationData LoadTranslations() + { + return !File.Exists(this._store.Path) + ? new TranslationData( + new Dictionary>(StringComparer.OrdinalIgnoreCase), + [], + new Dictionary(StringComparer.OrdinalIgnoreCase)) + : TranslationData.Load(this._store.Path); + } + + private string ResolveRepoRoot() + => Path.GetFullPath(this._configuration.RepoRoot); + + private string ResolveSourceDirectory() + { + var src = this._configuration.SourceDirectory; + if (!Path.IsPathRooted(src)) + src = Path.Combine(this.ResolveRepoRoot(), src); + return Path.GetFullPath(src); + } + + private string ResolveAuditOutputPath() + { + var output = this._configuration.AuditOutputPath; + if (!Path.IsPathRooted(output)) + output = Path.Combine(this.ResolveRepoRoot(), output); + return Path.GetFullPath(output); + } + + private void WriteReport(AuditReport report) + { + var outputPath = this.ResolveAuditOutputPath(); + var directory = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + var json = JsonSerializer.Serialize(report, new JsonSerializerOptions + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }); + File.WriteAllText(outputPath, json, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/DisCatSharp.Extensions.Translations.Manager.csproj b/DisCatSharp.Extensions.Translations.Manager/DisCatSharp.Extensions.Translations.Manager.csproj new file mode 100644 index 0000000..c445bce --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/DisCatSharp.Extensions.Translations.Manager.csproj @@ -0,0 +1,67 @@ + + + + + + + + + + + + DisCatSharp.Extensions.Translations.Manager + DisCatSharp.Extensions.Translations.Manager + + + + DisCatSharp.Extensions.Translations.Manager + + DisCatSharp.Extensions.Translations.Manager + + Extention for DisCatSharp.Extensions.Translation to manage translations for your Discord + bot with ease. + + Provides an easy-to-use webinterface for managing translations, loading from various + sources, and integrating with DisCatSharp-based Discord bots. + + DisCatSharp,DisCatSharp Extension,Translations,Localization,Web,Translation + Manager,Discord,Bots,Discord Bots,AITSYS,Net8,Net9,Net10 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DisCatSharp.Extensions.Translations.Manager/DynamicUsage.cs b/DisCatSharp.Extensions.Translations.Manager/DynamicUsage.cs new file mode 100644 index 0000000..ebfc7dd --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/DynamicUsage.cs @@ -0,0 +1,25 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed record DynamicUsage(string File, int Line, string Method, string Reason, string Expression); diff --git a/DisCatSharp.Extensions.Translations.Manager/ITranslationsReloadHandler.cs b/DisCatSharp.Extensions.Translations.Manager/ITranslationsReloadHandler.cs new file mode 100644 index 0000000..c7918c3 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/ITranslationsReloadHandler.cs @@ -0,0 +1,67 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to do so. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Threading; +using System.Threading.Tasks; + +namespace DisCatSharp.Extensions.Translations.Manager; + +/// +/// Provides a hook to refresh translations after the backing store changes. +/// +internal interface ITranslationsReloadHandler +{ + Task ReloadAsync(CancellationToken cancellationToken = default); +} + +internal sealed class NoOpTranslationsReloadHandler : ITranslationsReloadHandler +{ + public Task ReloadAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; +} + +internal sealed class DiscordTranslationsReloadHandler : ITranslationsReloadHandler +{ + private readonly DiscordClient? _client; + private readonly DiscordShardedClient? _shardedClient; + + public DiscordTranslationsReloadHandler(DiscordClient client) + { + this._client = client; + } + + public DiscordTranslationsReloadHandler(DiscordShardedClient client) + { + this._shardedClient = client; + } + + public async Task ReloadAsync(CancellationToken cancellationToken = default) + { + if (this._client is not null) + { + var extension = this._client.GetTranslationsExtension(); + extension?.TranslationEngine.Reload(); + } + + if (this._shardedClient is not null) + { + var extensions = await this._shardedClient.GetTranslationsExtension().ConfigureAwait(false); + foreach (var kvp in extensions) + kvp.Value?.TranslationEngine.Reload(); + } + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/LocaleHelper.cs b/DisCatSharp.Extensions.Translations.Manager/LocaleHelper.cs new file mode 100644 index 0000000..95ce893 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/LocaleHelper.cs @@ -0,0 +1,125 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Reflection; + +using DisCatSharp.Entities; + +using Microsoft.CodeAnalysis; + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal static class LocaleHelper +{ + internal static string[] GetValidLocales() + { + var values = new List(); + + foreach (var field in typeof(DiscordLocales).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var value = ToLocaleString(field.GetValue(null)); + if (!string.IsNullOrWhiteSpace(value)) + values.Add(value); + } + + foreach (var prop in typeof(DiscordLocales).GetProperties(BindingFlags.Public | BindingFlags.Static)) + { + if (!prop.CanRead) + continue; + var value = ToLocaleString(prop.GetValue(null)); + if (!string.IsNullOrWhiteSpace(value)) + values.Add(value); + } + + values.AddRange(s_fallbackLocales); + + return values + .Where(v => !string.IsNullOrWhiteSpace(v)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(v => v, Comparer.Create((l, r) => PrimaryFirstThenAlpha(l, r, "en-US"))) + .ToArray(); + } + + private static string? ToLocaleString(object? value) => value switch + { + null => null, + string s => s, + _ => value.ToString() + }; + + private static readonly string[] s_fallbackLocales = + [ + "da", "de", "en-GB", "en-US", "es-ES", "fr", "hr", "it", "lt", "hu", "nl", "no", + "pl", "pt-BR", "ro", "fi", "sv-SE", "vi", "tr", "cs", "el", "bg", "ru", "uk", + "hi", "th", "zh-CN", "ja", "zh-TW", "ko" + ]; + + private static int PrimaryFirstThenAlpha(string? left, string? right, string primary = "en-US") + { + var l = left ?? string.Empty; + var r = right ?? string.Empty; + return string.Equals(l, r, StringComparison.OrdinalIgnoreCase) + ? 0 + : string.Equals(l, primary, StringComparison.OrdinalIgnoreCase) + ? -1 + : string.Equals(r, primary, StringComparison.OrdinalIgnoreCase) ? 1 : string.Compare(l, r, StringComparison.OrdinalIgnoreCase); + } + + internal static bool ValidateLocales(Dictionary? translations, string[] allowed, out string? error) + { + if (translations is null) + { + error = null; + return true; + } + + if (!translations.ContainsKey("en-US")) + { + error = "Primary locale en-US is required."; + return false; + } + + var set = new HashSet(allowed, StringComparer.OrdinalIgnoreCase); + foreach (var locale in translations.Keys) + { + if (!set.Contains(locale)) + { + error = $"Invalid locale: {locale}"; + return false; + } + } + + // optional deeper validation via DiscordApplicationCommandLocalization + var localization = new DiscordApplicationCommandLocalization(); + foreach (var locale in translations.Keys) + { + if (!localization.Validate(locale)) + { + error = $"Invalid locale: {locale}"; + return false; + } + } + + error = null; + return true; + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/ScanResult.cs b/DisCatSharp.Extensions.Translations.Manager/ScanResult.cs new file mode 100644 index 0000000..e9d831e --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/ScanResult.cs @@ -0,0 +1,25 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed record ScanResult(HashSet Keys, IReadOnlyList DynamicUsages, IReadOnlyList Usages, int FilesScanned); diff --git a/DisCatSharp.Extensions.Translations.Manager/Scanner.cs b/DisCatSharp.Extensions.Translations.Manager/Scanner.cs new file mode 100644 index 0000000..e5768aa --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/Scanner.cs @@ -0,0 +1,81 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.CodeAnalysis.CSharp; + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal static class Scanner +{ + public static ScanResult ScanSource(string sourceDir, string repoRoot) + { + var usedKeys = new HashSet(StringComparer.Ordinal); + var dynamicUsages = new List(); + var usages = new List(); + if (!Directory.Exists(sourceDir)) + return new ScanResult(usedKeys, dynamicUsages, usages, 0); + + var options = new EnumerationOptions + { + RecurseSubdirectories = true, + IgnoreInaccessible = true, + MatchCasing = MatchCasing.CaseInsensitive + }; + var files = Directory.EnumerateFiles(sourceDir, "*.cs", options); + var processed = 0; + + foreach (var file in files) + { + if (IsIgnored(file)) + continue; + + processed++; + // Skip the translator definition file to avoid self-references showing as dynamic keys + if (string.Equals(Path.GetFileName(file), "TranslationEngine.cs", StringComparison.OrdinalIgnoreCase)) + continue; + + if (string.Equals(Path.GetFileName(file), "ExtensionMethods.cs", StringComparison.OrdinalIgnoreCase)) + continue; + + var text = File.ReadAllText(file); + var tree = CSharpSyntaxTree.ParseText(text, new CSharpParseOptions(LanguageVersion.Preview)); + var walker = new TranslationUsageWalker(file, repoRoot, usedKeys, dynamicUsages, usages); + walker.Visit(tree.GetRoot()); + } + + return new ScanResult(usedKeys, dynamicUsages, usages, processed); + } + + private static bool IsIgnored(string filePath) + { + static bool ContainsSegment(string path, string segment) => path.Contains($"{Path.DirectorySeparatorChar}{segment}{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase); + + return ContainsSegment(filePath, "bin") + || ContainsSegment(filePath, "obj") + || ContainsSegment(filePath, ".git") + || ContainsSegment(filePath, "node_modules"); + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/StringsMutation.cs b/DisCatSharp.Extensions.Translations.Manager/StringsMutation.cs new file mode 100644 index 0000000..9e7b1d0 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/StringsMutation.cs @@ -0,0 +1,25 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed record StringsMutation(string Key, Dictionary? Translations); diff --git a/DisCatSharp.Extensions.Translations.Manager/StringsStore.cs b/DisCatSharp.Extensions.Translations.Manager/StringsStore.cs new file mode 100644 index 0000000..ff6e4db --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/StringsStore.cs @@ -0,0 +1,57 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed class StringsStore(string path, string file, ITranslationsReloadHandler reloadHandler) +{ + private readonly object _gate = new(); + private readonly ITranslationsReloadHandler _reloadHandler = reloadHandler; + public string Path { get; } = System.IO.Path.Join(path, file); + + public Dictionary> Load() + { + lock (this._gate) + { + if (!File.Exists(this.Path)) + return new Dictionary>(StringComparer.OrdinalIgnoreCase); + var data = TranslationData.Load(this.Path); + return data.Map; + } + } + + public async Task SaveAsync(Dictionary> map, bool sort = false, string? primaryLocale = null, CancellationToken cancellationToken = default) + { + lock (this._gate) + { + TranslationData.Save(this.Path, map, sort, primaryLocale); + } + + await this._reloadHandler.ReloadAsync(cancellationToken).ConfigureAwait(false); + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/TranslationData.cs b/DisCatSharp.Extensions.Translations.Manager/TranslationData.cs new file mode 100644 index 0000000..7b83946 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/TranslationData.cs @@ -0,0 +1,113 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed record TranslationData( + Dictionary> Map, + IReadOnlyList DuplicateKeys, + IReadOnlyDictionary KeyCounts) +{ + public static TranslationData Load(string path) + { + var text = File.ReadAllText(path); + var bytes = Encoding.UTF8.GetBytes(text); + var duplicateKeys = new List(); + var keyCounts = new Dictionary(StringComparer.Ordinal); + + var reader = new Utf8JsonReader(bytes, new JsonReaderOptions + { + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip + }); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.PropertyName && reader.CurrentDepth == 1) + { + var key = reader.GetString() ?? string.Empty; + if (keyCounts.TryGetValue(key, out var count)) + { + keyCounts[key] = count + 1; + if (count == 1) + duplicateKeys.Add(key); + } + else + { + keyCounts[key] = 1; + } + } + } + + var map = JsonSerializer.Deserialize>>(text, new JsonSerializerOptions + { + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }) ?? new Dictionary>(StringComparer.OrdinalIgnoreCase); + + return new TranslationData(new Dictionary>(map, StringComparer.OrdinalIgnoreCase), duplicateKeys, keyCounts); + } + + public static void Save(string path, Dictionary> map, bool sort = false, string? primaryLocale = null) + { + IEnumerable>> outer = map; + if (sort) + outer = outer.OrderBy(k => k.Key, StringComparer.OrdinalIgnoreCase); + + var ordered = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var pair in outer) + { + IEnumerable> inner = pair.Value; + if (primaryLocale is not null) + inner = inner.OrderBy(p => p.Key, Comparer.Create((l, r) => PrimaryFirstThenAlpha(l, r, primaryLocale))); + else if (sort) + inner = inner.OrderBy(p => p.Key, StringComparer.OrdinalIgnoreCase); + + ordered[pair.Key] = inner.ToDictionary(p => p.Key, p => p.Value, StringComparer.OrdinalIgnoreCase); + } + + var json = JsonSerializer.Serialize(ordered, new JsonSerializerOptions + { + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + DefaultIgnoreCondition = JsonIgnoreCondition.Never + }); + + File.WriteAllText(path, json, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + + private static int PrimaryFirstThenAlpha(string? left, string? right, string primary = "en-US") + { + var l = left ?? string.Empty; + var r = right ?? string.Empty; + return string.Equals(l, r, StringComparison.OrdinalIgnoreCase) + ? 0 + : string.Equals(l, primary, StringComparison.OrdinalIgnoreCase) + ? -1 + : string.Equals(r, primary, StringComparison.OrdinalIgnoreCase) ? 1 : string.Compare(l, r, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/TranslationManager.cs b/DisCatSharp.Extensions.Translations.Manager/TranslationManager.cs new file mode 100644 index 0000000..2f58c91 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/TranslationManager.cs @@ -0,0 +1,163 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Text.Json; + +using DisCatSharp; + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DisCatSharp.Extensions.Translations.Manager; + +public sealed class TranslationManager : IAsyncDisposable +{ + private readonly TranslationsManagerConfiguration _configuration; + private readonly ITranslationsReloadHandler _reloadHandler; + private readonly WebApplication _app; + private readonly ILogger _logger; + + public TranslationManager(DiscordClient client, TranslationsManagerConfiguration configuration) + : this(configuration, new DiscordTranslationsReloadHandler(client)) + { } + + public TranslationManager(DiscordShardedClient client, TranslationsManagerConfiguration configuration) + : this(configuration, new DiscordTranslationsReloadHandler(client)) + { } + + internal TranslationManager(TranslationsManagerConfiguration configuration, ITranslationsReloadHandler? reloadHandler = null) + { + this._configuration = configuration; + this._reloadHandler = reloadHandler ?? new NoOpTranslationsReloadHandler(); + + var webRoot = Path.Combine(AppContext.BaseDirectory, "wwwroot"); + var translationsPath = this._configuration.TranslationsFolder; + if (!Path.IsPathRooted(translationsPath)) + translationsPath = Path.Combine(this._configuration.RepoRoot, translationsPath); + + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + ContentRootPath = AppContext.BaseDirectory, + WebRootPath = webRoot + }); + builder.WebHost.UseUrls($"http://localhost:{this._configuration.ServerPort}"); + builder.Services.AddSingleton(this._configuration); + builder.Services.AddSingleton(this._reloadHandler); + builder.Services.AddSingleton(sp => new StringsStore(translationsPath, this._configuration.TranslationsFileName, this._reloadHandler)); + builder.Services.AddSingleton(LocaleHelper.GetValidLocales()); + builder.Services.AddSingleton(); + builder.Services.ConfigureHttpJsonOptions(o => o.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase); + + this._app = builder.Build(); + this._logger = this._app.Services.GetRequiredService>(); + this.ConfigureEndpoints(); + } + + private void ConfigureEndpoints() + { + this._app.UseDefaultFiles(); + this._app.UseStaticFiles(); + + this._app.MapGet("/api/report", async (AuditReportProvider provider, CancellationToken cancellationToken) => + { + var report = await provider.GetReportAsync(cancellationToken).ConfigureAwait(false); + return Results.Json(report); + }); + + this._app.MapGet("/api/strings", (StringsStore store) => Results.Json(store.Load())); + + this._app.MapGet("/api/locales", (string[] locales) => Results.Json(locales)); + + this._app.MapPost("/api/strings", async (StringsStore store, StringsMutation payload, string[] locales, CancellationToken cancellationToken) => + { + if (payload is null || string.IsNullOrWhiteSpace(payload.Key)) + return Results.BadRequest(new { message = "Key is required." }); + if (!LocaleHelper.ValidateLocales(payload.Translations, locales, out var error)) + return Results.BadRequest(new { message = error }); + + var map = store.Load(); + if (map.ContainsKey(payload.Key)) + return Results.Conflict(new { message = "Key already exists." }); + map[payload.Key] = payload.Translations ?? []; + await store.SaveAsync(map, cancellationToken: cancellationToken).ConfigureAwait(false); + return Results.Ok(); + }); + + this._app.MapPut("/api/strings/{key}", async (StringsStore store, string key, StringsMutation payload, string[] locales, CancellationToken cancellationToken) => + { + if (payload is null) + return Results.BadRequest(new { message = "Payload required." }); + if (!LocaleHelper.ValidateLocales(payload.Translations, locales, out var error)) + return Results.BadRequest(new { message = error }); + + var map = store.Load(); + map[key] = payload.Translations ?? []; + await store.SaveAsync(map, cancellationToken: cancellationToken).ConfigureAwait(false); + return Results.Ok(); + }); + + this._app.MapPost("/api/strings/format", async (StringsStore store, CancellationToken cancellationToken) => + { + var map = store.Load(); + await store.SaveAsync(map, sort: false, primaryLocale: this._configuration.DefaultLocale, cancellationToken: cancellationToken).ConfigureAwait(false); + return Results.Ok(); + }); + + this._app.MapDelete("/api/strings/{key}", async (StringsStore store, string key, CancellationToken cancellationToken) => + { + var map = store.Load(); + if (!map.Remove(key)) + return Results.NotFound(); + await store.SaveAsync(map, cancellationToken: cancellationToken).ConfigureAwait(false); + return Results.Ok(); + }); + } + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + this._logger.LogInformation("Serving translation UI at http://localhost:{Port}", this._configuration.ServerPort); + await this._app.StartAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task RunAsync(CancellationToken cancellationToken = default) + { + await this.StartAsync(cancellationToken).ConfigureAwait(false); + await this._app.WaitForShutdownAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await this._app.StopAsync(cancellationToken).ConfigureAwait(false); + this._logger.LogInformation("Translation UI server stopped."); + } + + public async ValueTask DisposeAsync() + => await this._app.DisposeAsync(); +} diff --git a/DisCatSharp.Extensions.Translations.Manager/TranslationUsageWalker.cs b/DisCatSharp.Extensions.Translations.Manager/TranslationUsageWalker.cs new file mode 100644 index 0000000..2484dfb --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/TranslationUsageWalker.cs @@ -0,0 +1,141 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Text; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed class TranslationUsageWalker(string filePath, string repoRoot, HashSet keys, List dynamicUsages, List usages) : CSharpSyntaxWalker +{ + private readonly string _filePath = filePath; + private readonly string _repoRoot = repoRoot; + private readonly HashSet _keys = keys; + private readonly List _dynamicUsages = dynamicUsages; + private readonly List _usages = usages; + + public override void VisitInvocationExpression(InvocationExpressionSyntax node) + { + var methodName = GetMethodName(node.Expression); + if (methodName is "T" or "TLocale") + this.HandleTranslationCall(methodName, node); + + base.VisitInvocationExpression(node); + } + + private void HandleTranslationCall(string methodName, InvocationExpressionSyntax node) + { + var args = node.ArgumentList?.Arguments; + if (args is null || args.Value.Count is 0) + { + this.RecordDynamic(node, methodName, "no arguments", null); + return; + } + + ExpressionSyntax? keyExpr = null; + if (string.Equals(methodName, "TLocale", StringComparison.Ordinal)) + { + var member = node.Expression as MemberAccessExpressionSyntax; + var receiverIsTranslator = member?.Expression is IdentifierNameSyntax id && string.Equals(id.Identifier.ValueText, "Translator", StringComparison.Ordinal); + var index = receiverIsTranslator && args.Value.Count > 1 ? 1 : 0; + if (index < args.Value.Count) + keyExpr = args.Value[index].Expression; + } + else + { + keyExpr = args.Value[0].Expression; + } + + if (keyExpr is null) + { + this.RecordDynamic(node, methodName, "dynamic key", null); + return; + } + + if (TryEvaluateString(keyExpr, out var key)) + { + this._keys.Add(key); + this.RecordUsage(node, methodName, key); + return; + } + + this.RecordDynamic(node, methodName, "dynamic key", keyExpr.ToString()); + } + + private static bool TryEvaluateString(ExpressionSyntax expr, out string value) + { + switch (expr) + { + case LiteralExpressionSyntax literal when literal.IsKind(SyntaxKind.StringLiteralExpression): + value = literal.Token.ValueText; + return true; + case InterpolatedStringExpressionSyntax interpolated: + if (interpolated.Contents.All(c => c is InterpolatedStringTextSyntax)) + { + var sb = new StringBuilder(); + foreach (var part in interpolated.Contents.OfType()) + sb.Append(part.TextToken.ValueText); + value = sb.ToString(); + return true; + } + break; + case ParenthesizedExpressionSyntax paren: + return TryEvaluateString(paren.Expression, out value); + case BinaryExpressionSyntax binary when binary.IsKind(SyntaxKind.AddExpression): + if (TryEvaluateString(binary.Left, out var left) && TryEvaluateString(binary.Right, out var right)) + { + value = left + right; + return true; + } + break; + } + + value = string.Empty; + return false; + } + + private static string? GetMethodName(ExpressionSyntax expression) => expression switch + { + IdentifierNameSyntax id => id.Identifier.ValueText, + MemberAccessExpressionSyntax member => member.Name.Identifier.ValueText, + _ => null + }; + + private void RecordDynamic(SyntaxNode node, string methodName, string reason, string? expression) + { + var span = node.GetLocation().GetLineSpan(); + var line = span.StartLinePosition.Line + 1; + var relative = Path.GetRelativePath(this._repoRoot, this._filePath); + this._dynamicUsages.Add(new DynamicUsage(relative, line, methodName, reason, expression ?? string.Empty)); + } + + private void RecordUsage(SyntaxNode node, string methodName, string key) + { + var span = node.GetLocation().GetLineSpan(); + var line = span.StartLinePosition.Line + 1; + var relative = Path.GetRelativePath(this._repoRoot, this._filePath); + this._usages.Add(new UsageLocation(key, relative, line, methodName)); + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/TranslationsManagerConfiguration.cs b/DisCatSharp.Extensions.Translations.Manager/TranslationsManagerConfiguration.cs new file mode 100644 index 0000000..0bed7c5 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/TranslationsManagerConfiguration.cs @@ -0,0 +1,100 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.IO; + +using Microsoft.Extensions.DependencyInjection; + +namespace DisCatSharp.Extensions.Translations.Manager; + +/// +/// Represents a configuration for . +/// +public sealed class TranslationsManagerConfiguration : TranslationsConfiguration +{ + /// + /// Creates a new instance of . + /// + [ActivatorUtilitiesConstructor] + public TranslationsManagerConfiguration() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The service provider. + [ActivatorUtilitiesConstructor] + public TranslationsManagerConfiguration(IServiceProvider provider) + { + this.ServiceProvider = provider; + } + + /// + /// Creates a new instance of , copying the properties of another configuration. + /// + /// Configuration the properties of which are to be copied. + public TranslationsManagerConfiguration(TranslationsManagerConfiguration other) + { + this.TranslationsFolder = other.TranslationsFolder; + this.TranslationsFileName = other.TranslationsFileName; + this.ServiceProvider = other.ServiceProvider; + this.DefaultLocale = other.DefaultLocale; + this.ServerPort = other.ServerPort; + this.RepoRoot = other.RepoRoot; + this.SourceDirectory = other.SourceDirectory; + this.AuditOutputPath = other.AuditOutputPath; + this.ReportCacheDuration = other.ReportCacheDuration; + this.WriteAuditToDisk = other.WriteAuditToDisk; + } + + /// + /// Sets the port the translation server will run on. Defaults to 5000. + /// + public int ServerPort { get; set; } = 5000; + + /// + /// Sets the repository root used for usage scanning and opening files from the UI. + /// Defaults to the current working directory. + /// + public string RepoRoot { get; set; } = Directory.GetCurrentDirectory(); + + /// + /// Sets the source directory scanned for translation usages. Defaults to "src" under . + /// + public string SourceDirectory { get; set; } = Path.Combine(Directory.GetCurrentDirectory(), "src"); + + /// + /// Optional path where the generated audit report is written. Defaults to translations folder. + /// + public string AuditOutputPath { get; set; } = Path.Combine("data", "translation_audit.json"); + + /// + /// Duration to cache audit reports before rescanning the repository. + /// + public TimeSpan ReportCacheDuration { get; set; } = TimeSpan.FromSeconds(30); + + /// + /// When true, writes the audit report to on each request. + /// + public bool WriteAuditToDisk { get; set; } = false; +} diff --git a/DisCatSharp.Extensions.Translations.Manager/UsageLocation.cs b/DisCatSharp.Extensions.Translations.Manager/UsageLocation.cs new file mode 100644 index 0000000..749a9fe --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/UsageLocation.cs @@ -0,0 +1,25 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DisCatSharp.Extensions.Translations.Manager; + +internal sealed record UsageLocation(string Key, string File, int Line, string Method); diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js b/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js new file mode 100644 index 0000000..ac208f5 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js @@ -0,0 +1,503 @@ +(() => { + const PRIMARY_LOCALE = "en-US"; + const FALLBACK_LOCALES = [ + "da", "de", "en-GB", "en-US", "es-ES", "fr", "hr", "it", "lt", "hu", "nl", "no", + "pl", "pt-BR", "ro", "fi", "sv-SE", "vi", "tr", "cs", "el", "bg", "ru", "uk", + "hi", "th", "zh-CN", "ja", "zh-TW", "ko" + ]; + const params = new URLSearchParams(location.search); + const storedScheme = localStorage.getItem("vscodeScheme"); + let vscodeScheme = params.get("vscodeScheme") + || (storedScheme === "vscode-insiders" || storedScheme === "vscode" + ? storedScheme + : (navigator.userAgent.includes("Insider") ? "vscode-insiders" : "vscode")); + + const defaultRepoBlobBase = "https://github.com/Aiko-IT-Systems/DisCatSharp.Extensions/blob/main"; + const storedRepoBlobBase = localStorage.getItem("repoBlobBase"); + let repoBlobBase = params.get("gitBase") || storedRepoBlobBase || defaultRepoBlobBase; + localStorage.setItem("repoBlobBase", repoBlobBase); + + const storedVscodeBase = localStorage.getItem("vscodeBase"); + let vscodeBase = params.get("vscodeBase") || storedVscodeBase; + let customVscodeBase = !!vscodeBase; + if (!vscodeBase) + vscodeBase = `${vscodeScheme}://file/`; + const state = { + report: null, + strings: {}, + locales: [], + selected: null, + }; + + const el = (id) => document.getElementById(id); + const keyList = el("keyList"); + const details = el("detailsBody"); + const summary = el("summary"); + const scheme = el("scheme"); + const linkConfigBtn = document.createElement("button"); + linkConfigBtn.id = "linkConfig"; + linkConfigBtn.textContent = "Link Settings"; + linkConfigBtn.className = "secondary"; + scheme?.parentElement?.append(linkConfigBtn); + const search = el("search"); + const filter = el("filter"); + const beautifyBtn = el("beautify"); + const downloadBtn = el("downloadStrings"); + const modalHost = el("modalHost"); + + document.getElementById("refresh").addEventListener("click", refreshAll); + document.getElementById("addKey").addEventListener("click", onAddKey); + document.getElementById("beautify").addEventListener("click", beautify); + downloadBtn.addEventListener("click", downloadStrings); + scheme.addEventListener("change", () => { + vscodeScheme = scheme.value; + localStorage.setItem("vscodeScheme", vscodeScheme); + if (!customVscodeBase) + { + vscodeBase = `${vscodeScheme}://file/`; + localStorage.setItem("vscodeBase", vscodeBase); + } + renderDetails(state.selected); + }); + + linkConfigBtn.addEventListener("click", configureLinks); + + // initialize scheme selector based on detection + scheme.value = vscodeScheme; + search.addEventListener("input", renderKeyList); + filter.addEventListener("change", renderKeyList); + + async function loadAll() { + const [report, strings, locales] = await Promise.all([ + fetchJson("/api/report"), + fetchJson("/api/strings"), + fetchJson("/api/locales"), + ]); + state.report = report; + state.strings = strings; + state.locales = (locales && locales.length > 0) ? locales : FALLBACK_LOCALES; + summary.textContent = `Used ${report.usedKeysCount} / Defined ${report.definedKeysCount} | Missing ${report.missingKeys.length} | Unused ${report.unusedKeys.length} | Dynamic ${report.dynamicKeyUsages.length}`; + renderKeyList(); + renderDetails(state.selected); + } + + function renderKeyList() { + if (!state.report) return; + const query = search.value.toLowerCase(); + const filterValue = filter.value; + + const translationKeys = Object.keys(state.strings); + const dynamicKeys = (state.report.dynamicKeyUsages || []) + .map((d) => keyFromDynamic(d)) + .filter((k) => k && k.includes(".")); + const keys = Array.from(new Set([...translationKeys, ...dynamicKeys])).sort((a, b) => a.localeCompare(b)); + keyList.innerHTML = ""; + const template = document.getElementById("key-item-template"); + + keys.forEach((key) => { + const translations = state.strings[key] || {}; + const valuesText = Object.values(translations) + .join(" ") + .toLowerCase(); + if ( + query && + !key.toLowerCase().includes(query) && + !valuesText.includes(query) + ) + return; + + const isUnused = state.report.unusedKeys.includes(key); + const isMissing = Object.values(translations).some((v) => !v); + const isDynamic = state.report.dynamicKeyUsages.some( + (d) => keyFromDynamic(d) === key + ); + const isDynamicOnly = isDynamic && !state.report.keyUsages[key]; + + if (filterValue === "missing" && !isMissing) return; + if (filterValue === "unused" && !isUnused) return; + if (filterValue === "dynamic" && !isDynamic) return; + + const node = template.content.firstElementChild.cloneNode(true); + node.querySelector(".key-text").textContent = key; + const badges = node.querySelector(".badges"); + if (isUnused) badges.append(makeBadge("Unused", "warn")); + if (isMissing) badges.append(makeBadge("Missing", "danger")); + if (isDynamic) badges.append(makeBadge("Dynamic", "success")); + node.addEventListener("click", () => renderDetails(key)); + if (state.selected === key) node.classList.add("selected"); + keyList.append(node); + }); + } + + function renderDetails(key) { + state.selected = key; + if (!key) { + details.className = "details-empty"; + details.textContent = "Select a key to edit"; + return; + } + const translations = state.strings[key] || {}; + const locales = orderLocales(Object.keys(translations)); + + const wrapper = document.createElement("div"); + wrapper.className = "details"; + const title = document.createElement("div"); + title.innerHTML = `${key}`; + wrapper.append(title); + + const transWrap = document.createElement("div"); + transWrap.className = "translations"; + locales.forEach((locale) => { + transWrap.append(makeLocaleRow(locale, translations[locale] ?? "")); + }); + const addLocaleRow = document.createElement("div"); + addLocaleRow.className = "translation-add-row"; + const addSelect = document.createElement("select"); + const availableLocales = orderLocales(state.locales); + const existing = new Set(locales); + const options = availableLocales.filter((l) => !existing.has(l)); + addSelect.innerHTML = `${options + .map((l) => ``) + .join("")}`; + const addButton = document.createElement("button"); + addButton.textContent = "Add locale"; + addButton.className = "secondary"; + addButton.onclick = () => { + const loc = addSelect.value; + if (!loc) return; + if (transWrap.querySelector(`[data-locale="${loc}"]`)) return; + transWrap.insertBefore(makeLocaleRow(loc, ""), addLocaleRow); + addSelect.value = ""; + }; + addLocaleRow.append(addSelect, addButton); + transWrap.append(addLocaleRow); + wrapper.append(transWrap); + + const usageSection = document.createElement("div"); + usageSection.className = "usage"; + usageSection.innerHTML = "Usages"; + const usageList = document.createElement("div"); + const usages = state.report.keyUsages[key] || []; + const dynUsages = (state.report.dynamicKeyUsages || []).filter( + (d) => keyFromDynamic(d) === key + ); + const usageTemplate = document.getElementById("usage-row-template"); + const repoRoot = state.report.repoRoot.replace(/\\/g, "/"); + if (usages.length > 0) { + usages.forEach((u) => { + const row = + usageTemplate.content.firstElementChild.cloneNode(true); + row.querySelector( + ".usage-file" + ).textContent = `${u.file} : ${u.line}`; + const fullPath = `${repoRoot}/${u.file}`.replace(/\\/g, "/"); + const openBtn = ensureUsageOpen(row); + openBtn.onclick = () => { + const codeUrl = `${vscodeBase}${encodeURI(fullPath)}:${u.line}`; + window.open(codeUrl, "_blank"); + }; + const gitBtn = row.querySelector(".usage-copy"); + const gitUrl = `${repoBlobBase}/${u.file.replace(/\\/g, "/")}#L${u.line}`; + gitBtn.onclick = () => copyText(gitUrl, "Git link copied"); + usageList.append(row); + }); + } + if (dynUsages.length > 0) { + dynUsages.forEach((d) => { + const row = usageTemplate.content.firstElementChild.cloneNode(true); + row.querySelector(".usage-file").textContent = `${d.file} : ${d.line} (dynamic: ${d.reason})`; + const fullPath = `${repoRoot}/${d.file}`.replace(/\\/g, "/"); + const openBtn = ensureUsageOpen(row); + openBtn.onclick = () => { + const codeUrl = `${vscodeBase}${encodeURI(fullPath)}:${d.line}`; + window.open(codeUrl, "_blank"); + }; + const gitBtn = row.querySelector(".usage-copy"); + const gitUrl = `${repoBlobBase}/${d.file.replace(/\\/g, "/")}#L${d.line}`; + gitBtn.onclick = () => copyText(gitUrl, "Git link copied"); + usageList.append(row); + }); + } + if (usages.length === 0 && dynUsages.length === 0) { + const none = document.createElement("div"); + none.textContent = "No usages found."; + none.className = "details-empty"; + usageList.append(none); + } + usageSection.append(usageList); + wrapper.append(usageSection); + + const actions = document.createElement("div"); + actions.className = "footer-actions"; + const saveBtn = document.createElement("button"); + saveBtn.textContent = "Save"; + saveBtn.classList.add("success"); + saveBtn.onclick = () => saveKey(key, transWrap); + const deleteBtn = document.createElement("button"); + deleteBtn.textContent = "Delete"; + deleteBtn.className = "danger"; + deleteBtn.onclick = () => deleteKey(key); + actions.append(saveBtn, deleteBtn); + wrapper.append(actions); + + details.className = ""; + details.innerHTML = ""; + details.append(wrapper); + } + + function makeLocaleRow(locale, value) { + const row = document.createElement("div"); + row.className = "translation-row"; + row.dataset.locale = locale; + const input = document.createElement("input"); + input.value = locale; + input.disabled = true; + const textarea = document.createElement("textarea"); + textarea.value = value ?? ""; + autosizeText(textarea); + textarea.addEventListener("input", () => autosizeText(textarea)); + const remove = document.createElement("button"); + remove.textContent = "Remove"; + remove.className = "danger"; + remove.onclick = () => { + if (locale === PRIMARY_LOCALE) return; + row.remove(); + }; + if (locale === PRIMARY_LOCALE) { + remove.disabled = true; + remove.textContent = "Primary"; + } + row.append(input, textarea, remove); + return row; + } + + function autosizeText(textarea) { + textarea.style.height = "auto"; + textarea.style.height = `${textarea.scrollHeight}px`; + } + + function orderLocales(locales) { + return [...locales].sort((a, b) => { + if (a === b) return 0; + if (a === PRIMARY_LOCALE) return -1; + if (b === PRIMARY_LOCALE) return 1; + return a.localeCompare(b); + }); + } + + async function saveKey(key, transWrap) { + const ok = await confirmModal("Save changes?", "This will write updates to strings.json."); + if (!ok) return; + const translations = {}; + transWrap.querySelectorAll(".translation-row").forEach((row) => { + const loc = row.dataset.locale; + const text = row.querySelector("textarea").value; + if (loc) translations[loc] = text; + }); + const res = await fetch(`/api/strings/${encodeURIComponent(key)}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ key, translations }), + }); + if (!res.ok) { + const msg = await readError(res); + alert(`Save failed: ${msg}`); + return; + } + await loadAll(); + renderDetails(key); + showToast("Saved"); + } + + async function deleteKey(key) { + const ok = await confirmModal("Delete key?", `This removes '${key}' from strings.json.`); + if (!ok) return; + const res = await fetch(`/api/strings/${encodeURIComponent(key)}`, { method: "DELETE" }); + if (!res.ok) { + const msg = await readError(res); + alert(`Delete failed: ${msg}`); + return; + } + await loadAll(); + renderDetails(null); + showToast("Deleted"); + } + + async function onAddKey() { + const key = prompt("New translation key"); + if (!key) return; + const res = await fetch("/api/strings", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ key, translations: { "en-US": "" } }), + }); + if (!res.ok) { + const msg = await readError(res); + alert(`Add failed: ${msg}`); + return; + } + await loadAll(); + renderDetails(key); + } + + async function beautify() { + const ok = await confirmModal("Beautify strings.json?", "This will rewrite strings.json with formatting."); + if (!ok) return; + const res = await fetch('/api/strings/format', { method: 'POST' }); + if (!res.ok) { + const msg = await readError(res); + alert(`Beautify failed: ${msg}`); + return; + } + await loadAll(); + showToast('Formatted'); + } + + function makeBadge(text, tone) { + const span = document.createElement("span"); + span.className = `badge ${tone}`; + span.textContent = text; + return span; + } + + function keyFromDynamic(dyn) { + if (!dyn) return ""; + const raw = dyn.expression || ""; + const normalized = normalizeDynamicKey(raw); + return normalized || raw || ""; + } + + function normalizeDynamicKey(expr) { + let s = expr.trim(); + // Remove leading interpolation markers + if (s.startsWith("@$")) s = s.slice(2); + else if (s.startsWith("$")) s = s.slice(1); + // Strip surrounding quotes + s = s.replace(/^"/, "").replace(/"$/, ""); + // Cut at first interpolation + const brace = s.indexOf("{"); + if (brace >= 0) s = s.slice(0, brace); + return s.trim(); + } + + function ensureUsageOpen(row) { + let btn = row.querySelector(".usage-open"); + if (!btn) { + btn = document.createElement("button"); + btn.className = "usage-open"; + btn.textContent = "Open in VS Code"; + const actions = row.querySelector(".usage-actions") || row; + actions.prepend(btn); + } + return btn; + } + + async function refreshAll() { + try { + await loadAll(); + showToast("Refreshed"); + } catch (err) { + const message = err?.message || err; + alert(`Refresh failed: ${message}`); + } + } + + async function downloadStrings() { + try { + const res = await fetch("/api/strings"); + if (!res.ok) throw new Error(await readError(res)); + const data = await res.json(); + const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "strings.json"; + a.click(); + URL.revokeObjectURL(url); + showToast("Downloaded strings.json"); + } catch (err) { + alert(`Download failed: ${err}`); + } + } + + function copyText(text, toastMessage) { + navigator.clipboard?.writeText(text).then(() => showToast(toastMessage)); + } + + function configureLinks() { + const gitInput = prompt("Git blob base (e.g. https://github.com/org/repo/blob/main)", repoBlobBase); + if (gitInput) { + repoBlobBase = gitInput.trim().replace(/\/$/, ""); + localStorage.setItem("repoBlobBase", repoBlobBase); + } + + const vscodeInput = prompt("VS Code URI base (leave blank to follow scheme)", customVscodeBase ? vscodeBase : ""); + if (vscodeInput !== null) { + const trimmed = vscodeInput.trim(); + if (trimmed) { + vscodeBase = trimmed.endsWith("/") ? trimmed : `${trimmed}/`; + customVscodeBase = true; + localStorage.setItem("vscodeBase", vscodeBase); + } else { + customVscodeBase = false; + vscodeBase = `${vscodeScheme}://file/`; + localStorage.setItem("vscodeBase", vscodeBase); + } + } + + renderDetails(state.selected); + } + + function showToast(message) { + const toast = document.createElement("div"); + toast.className = "toast"; + toast.textContent = message; + document.body.append(toast); + setTimeout(() => toast.remove(), 2000); + } + + function confirmModal(title, message) { + return new Promise((resolve) => { + const backdrop = document.createElement("div"); + backdrop.className = "modal-backdrop"; + const modal = document.createElement("div"); + modal.className = "modal"; + modal.innerHTML = `

${title}

${message}

`; + const actions = document.createElement("div"); + actions.className = "modal-actions"; + const cancel = document.createElement("button"); + cancel.className = "ghost"; + cancel.textContent = "Cancel"; + const ok = document.createElement("button"); + ok.className = "danger"; + ok.textContent = "Confirm"; + cancel.onclick = () => { backdrop.remove(); resolve(false); }; + ok.onclick = () => { backdrop.remove(); resolve(true); }; + actions.append(cancel, ok); + modal.append(actions); + backdrop.append(modal); + modalHost.append(backdrop); + }); + } + + async function fetchJson(url) { + const res = await fetch(url); + if (!res.ok) { + const msg = await readError(res); + throw new Error(msg); + } + return res.json(); + } + + async function readError(res) { + try { + const data = await res.json(); + if (data?.message) return data.message; + return `${res.status} ${res.statusText}`; + } catch { + return `${res.status} ${res.statusText}`; + } + } + + loadAll(); +})(); diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/favicon.ico b/DisCatSharp.Extensions.Translations.Manager/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..45800b2372bd92bd5095e2fe89235670688a2961 GIT binary patch literal 432254 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�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 z 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 literal 0 HcmV?d00001 diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/index.html b/DisCatSharp.Extensions.Translations.Manager/wwwroot/index.html new file mode 100644 index 0000000..11d4406 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/wwwroot/index.html @@ -0,0 +1,67 @@ + + + + + + + Translation Tool + + + + + +
+
Translation Tool
+
+ + + + + +
+ +
+
+
+ + +
+
+
+ +
+
Select a key to edit
+
+
+ + + + + +
+ + + + + diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/styles.css b/DisCatSharp.Extensions.Translations.Manager/wwwroot/styles.css new file mode 100644 index 0000000..1a09926 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/wwwroot/styles.css @@ -0,0 +1,359 @@ +* { + box-sizing: border-box; +} + +:root { + --bg: #0f172a; + --panel: #111827; + --border: #1f2937; + --text: #e5e7eb; + --muted: #9ca3af; + --accent: #22d3ee; + --warn: #fbbf24; + --danger: #f87171; + --success: #34d399; + font-family: "Segoe UI", sans-serif; +} + +body { + margin: 0; + background: var(--bg); + color: var(--text); + min-height: 100vh; +} + +.topbar { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + border-bottom: 1px solid var(--border); + background: #0b1220; +} + +.title { + font-weight: 700; + letter-spacing: 0.5px; +} + +.summary { + color: var(--muted); + flex: 1; + font-size: 14px; +} + +button { + background: var(--accent); + color: #0b1220; + border: none; + border-radius: 6px; + padding: 8px 12px; + cursor: pointer; + font-weight: 600; +} + +button.ghost { + background: transparent; + color: var(--text); + border: 1px solid var(--border); +} + +button.danger { + background: var(--danger); + color: #0b1220; +} + +button.warn { + background: #fbbf24; + color: #0b1220; +} + +button.success { + background: var(--success); + color: #0b1220; +} + +button.warn { + background: #fbbf24; + color: #0b1220; +} + +button[disabled] { + opacity: 0.6; + cursor: not-allowed; +} + +button.secondary { + background: var(--border); + color: var(--text); +} + +input, +select, +textarea { + background: #0b1220; + color: var(--text); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 10px; + font-size: 14px; +} + +.layout { + display: grid; + grid-template-columns: 380px 1fr; + gap: 12px; + padding: 12px; + height: calc(100vh - 64px); +} + +.panel { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 10px; + padding: 12px; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.panel-header { + display: flex; + gap: 8px; + margin-bottom: 10px; +} + +#search { + flex: 1; +} + +.list { + overflow-y: auto; + border: 1px solid var(--border); + border-radius: 8px; + flex: 1; +} + +.key-item { + padding: 10px 12px; + border-bottom: 1px solid var(--border); + display: flex; + justify-content: space-between; + align-items: center; + gap: 6px; + cursor: pointer; +} + +.key-item:hover { + background: #0c182c; +} + +.key-item.selected { + background: #0c182c; + border-left: 2px solid var(--accent); +} + +.key-text { + font-size: 14px; + word-break: break-all; +} + +.badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 999px; + font-size: 12px; + border: 1px solid var(--border); + color: var(--muted); +} + +.badge.warn { + color: var(--warn); + border-color: var(--warn); +} + +.badge.danger { + color: var(--danger); + border-color: var(--danger); +} + +.badge.success { + color: var(--success); + border-color: var(--success); +} + +.details-empty { + color: var(--muted); +} + +.details { + display: flex; + flex-direction: column; + gap: 12px; + overflow: auto; +} + +.translations { + display: flex; + flex-direction: column; + gap: 8px; +} + +.translation-row { + display: flex; + gap: 8px; + align-items: center; +} + +.translation-row input { + flex: 0 0 120px; +} + +.translation-row textarea { + flex: 1; + min-height: 60px; +} + +.translation-row button { + flex: 0 0 auto; +} + +.translation-add-row { + display: flex; + gap: 8px; + align-items: center; +} + +.usage { + border-top: 1px solid var(--border); + padding-top: 8px; +} + +.usage-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 0; + font-size: 13px; +} + +.usage-actions { + display: flex; + gap: 6px; +} + +.usage-open { + background: var(--accent); + color: #0b1220; + border: none; +} + +.usage-copy { + background: var(--warn); + color: #0b1220; + border: none; +} + +#downloadStrings { + background: var(--success); + color: #0b1220; + border: none; +} + +#refresh { + background: var(--accent); + color: #0b1220; + border: none; +} + +.footer-actions { + display: flex; + gap: 8px; +} + +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 10px; + padding: 16px; + max-width: 420px; + width: 90%; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); +} + +.modal h3 { + margin: 0 0 8px 0; +} + +.modal p { + margin: 0 0 16px 0; + color: var(--muted); +} + +.modal-actions { + display: flex; + gap: 8px; + justify-content: flex-end; +} + +.toast { + position: fixed; + bottom: 16px; + right: 16px; + background: var(--panel); + border: 1px solid var(--success); + color: var(--text); + padding: 10px 14px; + border-radius: 8px; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35); + opacity: 0; + transform: translateY(10px); + animation: toast-in 200ms ease-out forwards, + toast-out 200ms ease-in forwards 1600ms; + z-index: 1100; +} + +@keyframes toast-in { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes toast-out { + from { + opacity: 1; + transform: translateY(0); + } + + to { + opacity: 0; + transform: translateY(10px); + } +} + +@media (max-width: 960px) { + .layout { + grid-template-columns: 1fr; + } +} diff --git a/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj b/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj new file mode 100644 index 0000000..a1848d4 --- /dev/null +++ b/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj @@ -0,0 +1,47 @@ + + + + + + + + + + + + + DisCatSharp.Extensions.Translations + DisCatSharp.Extensions.Translations + + + + DisCatSharp.Extensions.Translations + + DisCatSharp.Extensions.Translations + + Core extension for DisCatSharp making it easy to use localizations in your bot. + + DisCatSharp,DisCatSharp Extension,Translations,Localization,Discord,Bots,Discord Bots,AITSYS,Net8,Net9,Net10 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/DisCatSharp.Extensions.Translations/ExtensionMethods.cs b/DisCatSharp.Extensions.Translations/ExtensionMethods.cs new file mode 100644 index 0000000..147539f --- /dev/null +++ b/DisCatSharp.Extensions.Translations/ExtensionMethods.cs @@ -0,0 +1,117 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; + +using DisCatSharp.Entities; + +namespace DisCatSharp.Extensions.Translations; + +/// +/// Defines various extensions specific to TranslationsExtension. +/// +public static class ExtensionMethods +{ + /// + /// Enables TranslationsExtension module on this . + /// + /// Client to enable TranslationsExtension for. + /// Lavalink configuration to use. + /// Created . + public static TranslationsExtension UseTranslations(this DiscordClient client, TranslationsConfiguration? cfg = null) + { + if (client.GetExtension() != null) + throw new InvalidOperationException("TranslationsExtension is already enabled for that client."); + + cfg ??= new(); + + var smc = new TranslationsExtension(cfg); + client.AddExtension(smc); + return smc; + } + + /// + /// Enables TranslationsExtension module on all shards in this . + /// + /// Client to enable TranslationsExtension for. + /// Lavalink configuration to use. + /// A dictionary of created , indexed by shard id. + public static async Task> UseTranslationsAsync(this DiscordShardedClient client, TranslationsConfiguration? cfg = null) + { + var modules = new Dictionary(); + await client.InitializeShardsAsync().ConfigureAwait(false); + + foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value)) + { + var smc = shard.GetExtension(); + smc ??= shard.UseTranslations(cfg); + modules[shard.ShardId] = smc; + } + + return new ReadOnlyDictionary(modules); + } + + /// + /// Gets the active TranslationsExtension module for this client. + /// + /// Client to get TranslationsExtension module from. + /// The module, or null if not activated. + public static TranslationsExtension? GetTranslationsExtension(this DiscordClient client) + => client.GetExtension(); + + /// + /// Gets the active TranslationsExtension modules for all shards in this client. + /// + /// Client to get TranslationsExtension instances from. + /// A dictionary of the modules, indexed by shard id. + public static async Task> GetTranslationsExtension(this DiscordShardedClient client) + { + await client.InitializeShardsAsync().ConfigureAwait(false); + var extensions = client.ShardClients.Select(xkvp => xkvp.Value).ToDictionary(shard => shard.ShardId, shard => shard.GetExtension()); + + return new ReadOnlyDictionary(extensions); + } + + /// + /// Translates a key using the locale from the . + /// + /// The interaction. + /// The translation key. + /// The placeholders to replace in the translation. + /// Whether to force the guild locale. + /// The translated string. + public static string T(this DiscordInteraction interaction, string key, object? placeholders = null, bool forceGuildLocale = false) + { + var locale = "en-US"; + if (forceGuildLocale && !string.IsNullOrWhiteSpace(interaction.GuildLocale)) + locale = interaction.GuildLocale; + else if (!string.IsNullOrWhiteSpace(interaction.Locale)) + locale = interaction.Locale; + else if (!string.IsNullOrWhiteSpace(interaction.GuildLocale)) + locale = interaction.GuildLocale; + return (interaction.Discord as DiscordClient)!.GetTranslationsExtension()!.TranslationEngine.TLocale(locale, key, placeholders); + } +} diff --git a/DisCatSharp.Extensions.Translations/InternalsVisibleTo.targets b/DisCatSharp.Extensions.Translations/InternalsVisibleTo.targets new file mode 100644 index 0000000..9466261 --- /dev/null +++ b/DisCatSharp.Extensions.Translations/InternalsVisibleTo.targets @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/DisCatSharp.Extensions.Translations/TranslationEngine.cs b/DisCatSharp.Extensions.Translations/TranslationEngine.cs new file mode 100644 index 0000000..969333c --- /dev/null +++ b/DisCatSharp.Extensions.Translations/TranslationEngine.cs @@ -0,0 +1,211 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Text.RegularExpressions; +using System.Text.Json; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System; +using System.Collections.Concurrent; + +namespace DisCatSharp.Extensions.Translations; + +/// +/// Represents a translation engine. +/// +/// The translations configuration. +public partial class TranslationEngine(TranslationsConfiguration configuration) +{ + /// + /// Guard for initialization. + /// + private readonly Lock _initLock = new(); + + /// + /// Whether the translations have been initialized. + /// + private volatile bool _initialized; + + /// + /// Provides a mapping of translation keys to localized string values for multiple languages. + /// + /// Each entry in the outer dictionary represents a language, identified by its language code (such as + /// "en" or "fr"). The inner dictionary maps translation keys to their corresponding localized strings for that + /// language. + internal ConcurrentDictionary> Translations { get; private set; } = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets the default locale. + /// + internal string DefaultLocale { get; } = configuration.DefaultLocale ?? "en-US"; + + /// + /// Gets or sets the path to the translations file. + /// + internal string? PATH { get; set; } + + /// + /// Gets the configuration. + /// + internal readonly TranslationsConfiguration Configuration = configuration; + + /// + /// Ensures that the translations are loaded from the file. + /// + private void EnsureLoaded() + { + if (this._initialized) + return; + lock (this._initLock) + { + if (this._initialized) + return; + if (this.PATH is null) + return; + try + { + if (!File.Exists(this.PATH)) + { + this.Translations = new(StringComparer.OrdinalIgnoreCase); + this._initialized = true; + return; + } + + var json = File.ReadAllText(this.PATH); + var doc = JsonSerializer.Deserialize>>(json) ?? []; + this.Translations = new(doc, StringComparer.OrdinalIgnoreCase); + } + catch + { + this.Translations = new(StringComparer.OrdinalIgnoreCase); + } + finally + { + this._initialized = true; + } + } + } + + /// + /// Translates a key using the specified locale. + /// + /// The locale to use for translation. + /// The translation key. + /// The placeholders to replace in the translation. + /// The translated string. + public string TLocale(string? locale, string key, object? placeholders = null) + { + this.EnsureLoaded(); + locale ??= "en-US"; + return !this.Translations.TryGetValue(key, out var langs) + ? this.ApplyPlaceholders(key, placeholders) + : langs.TryGetValue(locale, out var text) + ? this.ApplyPlaceholders(text, placeholders) + : langs.TryGetValue("en-US", out var fallback) + ? this.ApplyPlaceholders(fallback, placeholders) + : this.ApplyPlaceholders(key, placeholders); + } + + /// + /// Applies placeholders to the template string. + /// + /// The template string. + /// The placeholders to replace in the template. + /// The template string with placeholders replaced. + private string ApplyPlaceholders(string template, object? placeholders) + { + if (placeholders is null) + return template; + + if (placeholders is not Dictionary values) + { + values = new(StringComparer.OrdinalIgnoreCase); + var props = placeholders.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + foreach (var p in props) + { + try + { + values[p.Name] = p.GetValue(placeholders); + } + catch + { + values[p.Name] = null; + } + } + } + + var result = this.TemplateRegex().Replace(template, m => + { + var name = m.Groups[1].Value; + return values.TryGetValue(name, out var val) && val is not null ? val.ToString()! : string.Empty; + }); + + return result; + } + + /// + /// Reloads the translations from the file. + /// + public void Reload() + { + lock (this._initLock) + { + this._initialized = false; + this.EnsureLoaded(); + } + } + + /// + /// Allows overriding the file path used for translations and loads from it immediately. + /// + public void UseFile(string filePath) + { + lock (this._initLock) + { + this.PATH = filePath; + this._initialized = false; + this.EnsureLoaded(); + } + } + + /// + /// Template regex for matching placeholders. + /// + [GeneratedRegex(@"\{\s*([a-zA-Z0-9_]+)\s*\}", RegexOptions.Compiled)] + private partial Regex TemplateRegex(); + + /// + /// Initializes the translations engine. + /// + internal void Initialize() + { + if (!string.IsNullOrWhiteSpace(this.Configuration.TranslationsFolder) && !string.IsNullOrWhiteSpace(this.Configuration.TranslationsFileName)) + { + var path = Path.Combine(this.Configuration.TranslationsFolder, this.Configuration.TranslationsFileName); + if (File.Exists(path)) + this.UseFile(path); + else + throw new FileNotFoundException("Translations file not found at the specified path.", path); + } + } +} diff --git a/DisCatSharp.Extensions.Translations/TranslationsConfiguration.cs b/DisCatSharp.Extensions.Translations/TranslationsConfiguration.cs new file mode 100644 index 0000000..8676373 --- /dev/null +++ b/DisCatSharp.Extensions.Translations/TranslationsConfiguration.cs @@ -0,0 +1,87 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; + +using Microsoft.Extensions.DependencyInjection; + +namespace DisCatSharp.Extensions.Translations; + +/// +/// Represents a configuration for . +/// +public class TranslationsConfiguration +{ + /// + /// Creates a new instance of . + /// + [ActivatorUtilitiesConstructor] + public TranslationsConfiguration() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The service provider. + [ActivatorUtilitiesConstructor] + public TranslationsConfiguration(IServiceProvider provider) + { + this.ServiceProvider = provider; + } + + /// + /// Creates a new instance of , copying the properties of another configuration. + /// + /// Configuration the properties of which are to be copied. + public TranslationsConfiguration(TranslationsConfiguration other) + { + this.TranslationsFolder = other.TranslationsFolder; + this.TranslationsFileName = other.TranslationsFileName; + this.ServiceProvider = other.ServiceProvider; + this.DefaultLocale = other.DefaultLocale; + } + + /// + /// Sets the service provider for this Translations instance. + /// + /// Objects in this provider are used when instantiating command modules. This allows passing data around without + /// resorting to static members. + /// + /// Defaults to an empty service provider. + /// + public IServiceProvider ServiceProvider { internal get; set; } = new ServiceCollection().BuildServiceProvider(true); + + /// + /// Sets the folder where translation files are stored. Defaults to data/translations. + /// + public string TranslationsFolder { internal get; set; } = "data/translations"; + + /// + /// Sets the name of the translations file. Defaults to strings.json. + /// + public string TranslationsFileName { internal get; set; } = "strings.json"; + + /// + /// Gets or sets the default locale used for culture-specific operations. + /// + public string DefaultLocale { internal get; set; } = "en-US"; +} diff --git a/DisCatSharp.Extensions.Translations/TranslationsExtension.cs b/DisCatSharp.Extensions.Translations/TranslationsExtension.cs new file mode 100644 index 0000000..4bc423f --- /dev/null +++ b/DisCatSharp.Extensions.Translations/TranslationsExtension.cs @@ -0,0 +1,89 @@ +// This file is part of the DisCatSharp project. +// +// Copyright (c) 2021-2025 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Reflection; + +namespace DisCatSharp.Extensions.Translations; + +/// +/// Represents a . +/// +public sealed class TranslationsExtension : BaseExtension +{ + /// + /// Initializes a new instance of the class. + /// + /// The config. + internal TranslationsExtension(TranslationsConfiguration? configuration = null) + { + configuration ??= new(); + this.TranslationEngine = new(configuration); + } + + /// + /// Gets the translation engine. + /// + public TranslationEngine TranslationEngine { get; private set; } + + /// + /// Gets the discord client. + /// + internal new DiscordClient Client { get; private set; } + + /// + /// DO NOT USE THIS MANUALLY. + /// + /// DO NOT USE THIS MANUALLY. + /// + protected internal override void Setup(DiscordClient client) + { + if (this.Client != null) + throw new InvalidOperationException("What did I tell you?"); + + this.Client = client; + + var a = typeof(TranslationsExtension).GetTypeInfo().Assembly; + + var iv = a.GetCustomAttribute(); + if (iv != null) + this.VersionString = iv.InformationalVersion; + else + { + var v = a.GetName().Version; + var vs = v!.ToString(3); + + if (v.Revision > 0) + this.VersionString = $"{vs}, CI build {v.Revision}"; + } + + this.HasVersionCheckSupport = true; + + this.RepositoryOwner = "Aiko-IT-Systems"; + + this.Repository = "DisCatSharp.Extensions"; + + this.PackageId = "DisCatSharp.Extensions.Translations"; + + this.TranslationEngine.Initialize(); + } +} diff --git a/DisCatSharp.Extensions.TwoFactorCommands/Entities/TwoFactorResponse.cs b/DisCatSharp.Extensions.TwoFactorCommands/Entities/TwoFactorResponse.cs index 6c7204b..044de15 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/Entities/TwoFactorResponse.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/Entities/TwoFactorResponse.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/Enums/TwoFactorResult.cs b/DisCatSharp.Extensions.TwoFactorCommands/Enums/TwoFactorResult.cs index 24bc721..bbcfa1f 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/Enums/TwoFactorResult.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/Enums/TwoFactorResult.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/ExtensionMethods.cs b/DisCatSharp.Extensions.TwoFactorCommands/ExtensionMethods.cs index a9c7849..e9d465c 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/ExtensionMethods.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/ExtensionMethods.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/Extensions/ApplicationCommands/ApplicationCommandRequireEnrolledTwoFactorAttribute.cs b/DisCatSharp.Extensions.TwoFactorCommands/Extensions/ApplicationCommands/ApplicationCommandRequireEnrolledTwoFactorAttribute.cs index f8db251..8303a60 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/Extensions/ApplicationCommands/ApplicationCommandRequireEnrolledTwoFactorAttribute.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/Extensions/ApplicationCommands/ApplicationCommandRequireEnrolledTwoFactorAttribute.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/Extensions/ApplicationCommands/TwoFactorApplicationCommandExtension.cs b/DisCatSharp.Extensions.TwoFactorCommands/Extensions/ApplicationCommands/TwoFactorApplicationCommandExtension.cs index a2c1bd5..0edb856 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/Extensions/ApplicationCommands/TwoFactorApplicationCommandExtension.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/Extensions/ApplicationCommands/TwoFactorApplicationCommandExtension.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/Extensions/CommandsNext/CommandRequireEnrolledTwoFactorAttribute.cs b/DisCatSharp.Extensions.TwoFactorCommands/Extensions/CommandsNext/CommandRequireEnrolledTwoFactorAttribute.cs index 806ef98..1e55eeb 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/Extensions/CommandsNext/CommandRequireEnrolledTwoFactorAttribute.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/Extensions/CommandsNext/CommandRequireEnrolledTwoFactorAttribute.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/Extensions/CommandsNext/TwoFactorCommandsNextExtension.cs b/DisCatSharp.Extensions.TwoFactorCommands/Extensions/CommandsNext/TwoFactorCommandsNextExtension.cs index ccb33b2..c6031b4 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/Extensions/CommandsNext/TwoFactorCommandsNextExtension.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/Extensions/CommandsNext/TwoFactorCommandsNextExtension.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/Properties/AssemblyProperties.cs b/DisCatSharp.Extensions.TwoFactorCommands/Properties/AssemblyProperties.cs index f91d345..81ecc1e 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/Properties/AssemblyProperties.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/Properties/AssemblyProperties.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorConfiguration.cs b/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorConfiguration.cs index f753141..38a5368 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorConfiguration.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorConfiguration.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorExtension.cs b/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorExtension.cs index 7388cb8..b9ca53c 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorExtension.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorExtension.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorExtensionUtilities.cs b/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorExtensionUtilities.cs index adad3da..e5d3e39 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorExtensionUtilities.cs +++ b/DisCatSharp.Extensions.TwoFactorCommands/TwoFactorExtensionUtilities.cs @@ -1,6 +1,6 @@ // This file is part of the DisCatSharp project. // -// Copyright (c) 2021-2023 AITSYS +// Copyright (c) 2021-2025 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/DisCatSharp.Extensions.slnx b/DisCatSharp.Extensions.slnx index 52f089a..ebf1479 100644 --- a/DisCatSharp.Extensions.slnx +++ b/DisCatSharp.Extensions.slnx @@ -35,4 +35,9 @@ + + + + + diff --git a/README.md b/README.md index c4f132a..757de54 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # DisCatSharp.Extensions -Official extensions for [DisCatSharp](https://github.com/Aiko-IT-Systems/DisCatSharp). +Official extensions for [DisCatSharp](https://github.com/Aiko-IT-Systems/DisCatSharp). ## Installing @@ -21,8 +21,11 @@ The documentation is available at [ext-docs.dcs.aitsys.dev](https://ext-docs.dcs ## NuGet Packages -| Package | Stable | Nightly | -| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| DisCatSharp.Extensions.TwoFactorCommands | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.TwoFactorCommands.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.TwoFactorCommands.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | -| DisCatSharp.Extensions.OAuth2Web | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.OAuth2Web.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.OAuth2Web.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | -| DisCatSharp.Extensions.SimpleMusicCommands | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.SimpleMusicCommands.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.SimpleMusicCommands.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | +| Package | Stable | Nightly | +| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DisCatSharp.Extensions.TwoFactorCommands | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.TwoFactorCommands.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.TwoFactorCommands.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | +| DisCatSharp.Extensions.OAuth2Web | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.OAuth2Web.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.OAuth2Web.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | +| DisCatSharp.Extensions.SimpleMusicCommands | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.SimpleMusicCommands.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.SimpleMusicCommands.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | +| DisCatSharp.Extensions.Translations | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.Translations.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.Translations.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | +| DisCatSharp.Extensions.Translations.ApplicationCommands | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.Translations.ApplicationCommands.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.Translations.ApplicationCommands.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | +| DisCatSharp.Extensions.Translations.CommandsNext | ![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Extensions.Translations.CommandsNext.svg?label=&logo=nuget&style=flat-square) | ![NuGet](https://img.shields.io/nuget/vpre/DisCatSharp.Extensions.Translations.CommandsNext.svg?label=&logo=nuget&style=flat-square&color=%23ff1493) | diff --git a/Targets/Library.targets b/Targets/Library.targets index 7935a82..2cc312f 100644 --- a/Targets/Library.targets +++ b/Targets/Library.targets @@ -6,5 +6,6 @@ net8.0;net9.0;net10.0 enable latest + enable - + \ No newline at end of file From 08ce9ac4322d59a38e807591226540e2e3176bff Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Tue, 30 Dec 2025 19:09:21 +0100 Subject: [PATCH 2/6] switch to typescript for frontend code --- .../build.cmd | 5 + .../package-lock.json | 144 ++++ .../package.json | 11 + .../wwwroot/app.js | 505 +------------- .../wwwroot/app.js.map | 1 + .../wwwroot/app.ts | 653 ++++++++++++++++++ .../wwwroot/jsconfig.json | 9 + .../wwwroot/tsconfig.json | 18 + 8 files changed, 843 insertions(+), 503 deletions(-) create mode 100644 DisCatSharp.Extensions.Translations.Manager/build.cmd create mode 100644 DisCatSharp.Extensions.Translations.Manager/package-lock.json create mode 100644 DisCatSharp.Extensions.Translations.Manager/package.json create mode 100644 DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js.map create mode 100644 DisCatSharp.Extensions.Translations.Manager/wwwroot/app.ts create mode 100644 DisCatSharp.Extensions.Translations.Manager/wwwroot/jsconfig.json create mode 100644 DisCatSharp.Extensions.Translations.Manager/wwwroot/tsconfig.json diff --git a/DisCatSharp.Extensions.Translations.Manager/build.cmd b/DisCatSharp.Extensions.Translations.Manager/build.cmd new file mode 100644 index 0000000..37c05a6 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/build.cmd @@ -0,0 +1,5 @@ +@echo off +setlocal +pushd %~dp0 +npm run build +popd diff --git a/DisCatSharp.Extensions.Translations.Manager/package-lock.json b/DisCatSharp.Extensions.Translations.Manager/package-lock.json new file mode 100644 index 0000000..7f9a4ba --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/package-lock.json @@ -0,0 +1,144 @@ +{ + "name": "DisCatSharp.Extensions.Translations.Manager", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "terser": "^5.16.1", + "typescript": "5.9.3" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/package.json b/DisCatSharp.Extensions.Translations.Manager/package.json new file mode 100644 index 0000000..a9867a3 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/package.json @@ -0,0 +1,11 @@ +{ + "devDependencies": { + "typescript": "5.9.3", + "terser": "^5.16.1" + }, + "scripts": { + "build:ts": "npx tsc -p wwwroot/tsconfig.json", + "build:minify": "npx terser wwwroot/app.js -b --rename -c -m --source-map \"content=wwwroot/app.js.map,url=app.js.map\" -o wwwroot/app.js", + "build": "npm run build:ts && npm run build:minify" + } +} diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js b/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js index ac208f5..8bc386b 100644 --- a/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js +++ b/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js @@ -1,503 +1,2 @@ -(() => { - const PRIMARY_LOCALE = "en-US"; - const FALLBACK_LOCALES = [ - "da", "de", "en-GB", "en-US", "es-ES", "fr", "hr", "it", "lt", "hu", "nl", "no", - "pl", "pt-BR", "ro", "fi", "sv-SE", "vi", "tr", "cs", "el", "bg", "ru", "uk", - "hi", "th", "zh-CN", "ja", "zh-TW", "ko" - ]; - const params = new URLSearchParams(location.search); - const storedScheme = localStorage.getItem("vscodeScheme"); - let vscodeScheme = params.get("vscodeScheme") - || (storedScheme === "vscode-insiders" || storedScheme === "vscode" - ? storedScheme - : (navigator.userAgent.includes("Insider") ? "vscode-insiders" : "vscode")); - - const defaultRepoBlobBase = "https://github.com/Aiko-IT-Systems/DisCatSharp.Extensions/blob/main"; - const storedRepoBlobBase = localStorage.getItem("repoBlobBase"); - let repoBlobBase = params.get("gitBase") || storedRepoBlobBase || defaultRepoBlobBase; - localStorage.setItem("repoBlobBase", repoBlobBase); - - const storedVscodeBase = localStorage.getItem("vscodeBase"); - let vscodeBase = params.get("vscodeBase") || storedVscodeBase; - let customVscodeBase = !!vscodeBase; - if (!vscodeBase) - vscodeBase = `${vscodeScheme}://file/`; - const state = { - report: null, - strings: {}, - locales: [], - selected: null, - }; - - const el = (id) => document.getElementById(id); - const keyList = el("keyList"); - const details = el("detailsBody"); - const summary = el("summary"); - const scheme = el("scheme"); - const linkConfigBtn = document.createElement("button"); - linkConfigBtn.id = "linkConfig"; - linkConfigBtn.textContent = "Link Settings"; - linkConfigBtn.className = "secondary"; - scheme?.parentElement?.append(linkConfigBtn); - const search = el("search"); - const filter = el("filter"); - const beautifyBtn = el("beautify"); - const downloadBtn = el("downloadStrings"); - const modalHost = el("modalHost"); - - document.getElementById("refresh").addEventListener("click", refreshAll); - document.getElementById("addKey").addEventListener("click", onAddKey); - document.getElementById("beautify").addEventListener("click", beautify); - downloadBtn.addEventListener("click", downloadStrings); - scheme.addEventListener("change", () => { - vscodeScheme = scheme.value; - localStorage.setItem("vscodeScheme", vscodeScheme); - if (!customVscodeBase) - { - vscodeBase = `${vscodeScheme}://file/`; - localStorage.setItem("vscodeBase", vscodeBase); - } - renderDetails(state.selected); - }); - - linkConfigBtn.addEventListener("click", configureLinks); - - // initialize scheme selector based on detection - scheme.value = vscodeScheme; - search.addEventListener("input", renderKeyList); - filter.addEventListener("change", renderKeyList); - - async function loadAll() { - const [report, strings, locales] = await Promise.all([ - fetchJson("/api/report"), - fetchJson("/api/strings"), - fetchJson("/api/locales"), - ]); - state.report = report; - state.strings = strings; - state.locales = (locales && locales.length > 0) ? locales : FALLBACK_LOCALES; - summary.textContent = `Used ${report.usedKeysCount} / Defined ${report.definedKeysCount} | Missing ${report.missingKeys.length} | Unused ${report.unusedKeys.length} | Dynamic ${report.dynamicKeyUsages.length}`; - renderKeyList(); - renderDetails(state.selected); - } - - function renderKeyList() { - if (!state.report) return; - const query = search.value.toLowerCase(); - const filterValue = filter.value; - - const translationKeys = Object.keys(state.strings); - const dynamicKeys = (state.report.dynamicKeyUsages || []) - .map((d) => keyFromDynamic(d)) - .filter((k) => k && k.includes(".")); - const keys = Array.from(new Set([...translationKeys, ...dynamicKeys])).sort((a, b) => a.localeCompare(b)); - keyList.innerHTML = ""; - const template = document.getElementById("key-item-template"); - - keys.forEach((key) => { - const translations = state.strings[key] || {}; - const valuesText = Object.values(translations) - .join(" ") - .toLowerCase(); - if ( - query && - !key.toLowerCase().includes(query) && - !valuesText.includes(query) - ) - return; - - const isUnused = state.report.unusedKeys.includes(key); - const isMissing = Object.values(translations).some((v) => !v); - const isDynamic = state.report.dynamicKeyUsages.some( - (d) => keyFromDynamic(d) === key - ); - const isDynamicOnly = isDynamic && !state.report.keyUsages[key]; - - if (filterValue === "missing" && !isMissing) return; - if (filterValue === "unused" && !isUnused) return; - if (filterValue === "dynamic" && !isDynamic) return; - - const node = template.content.firstElementChild.cloneNode(true); - node.querySelector(".key-text").textContent = key; - const badges = node.querySelector(".badges"); - if (isUnused) badges.append(makeBadge("Unused", "warn")); - if (isMissing) badges.append(makeBadge("Missing", "danger")); - if (isDynamic) badges.append(makeBadge("Dynamic", "success")); - node.addEventListener("click", () => renderDetails(key)); - if (state.selected === key) node.classList.add("selected"); - keyList.append(node); - }); - } - - function renderDetails(key) { - state.selected = key; - if (!key) { - details.className = "details-empty"; - details.textContent = "Select a key to edit"; - return; - } - const translations = state.strings[key] || {}; - const locales = orderLocales(Object.keys(translations)); - - const wrapper = document.createElement("div"); - wrapper.className = "details"; - const title = document.createElement("div"); - title.innerHTML = `${key}`; - wrapper.append(title); - - const transWrap = document.createElement("div"); - transWrap.className = "translations"; - locales.forEach((locale) => { - transWrap.append(makeLocaleRow(locale, translations[locale] ?? "")); - }); - const addLocaleRow = document.createElement("div"); - addLocaleRow.className = "translation-add-row"; - const addSelect = document.createElement("select"); - const availableLocales = orderLocales(state.locales); - const existing = new Set(locales); - const options = availableLocales.filter((l) => !existing.has(l)); - addSelect.innerHTML = `${options - .map((l) => ``) - .join("")}`; - const addButton = document.createElement("button"); - addButton.textContent = "Add locale"; - addButton.className = "secondary"; - addButton.onclick = () => { - const loc = addSelect.value; - if (!loc) return; - if (transWrap.querySelector(`[data-locale="${loc}"]`)) return; - transWrap.insertBefore(makeLocaleRow(loc, ""), addLocaleRow); - addSelect.value = ""; - }; - addLocaleRow.append(addSelect, addButton); - transWrap.append(addLocaleRow); - wrapper.append(transWrap); - - const usageSection = document.createElement("div"); - usageSection.className = "usage"; - usageSection.innerHTML = "Usages"; - const usageList = document.createElement("div"); - const usages = state.report.keyUsages[key] || []; - const dynUsages = (state.report.dynamicKeyUsages || []).filter( - (d) => keyFromDynamic(d) === key - ); - const usageTemplate = document.getElementById("usage-row-template"); - const repoRoot = state.report.repoRoot.replace(/\\/g, "/"); - if (usages.length > 0) { - usages.forEach((u) => { - const row = - usageTemplate.content.firstElementChild.cloneNode(true); - row.querySelector( - ".usage-file" - ).textContent = `${u.file} : ${u.line}`; - const fullPath = `${repoRoot}/${u.file}`.replace(/\\/g, "/"); - const openBtn = ensureUsageOpen(row); - openBtn.onclick = () => { - const codeUrl = `${vscodeBase}${encodeURI(fullPath)}:${u.line}`; - window.open(codeUrl, "_blank"); - }; - const gitBtn = row.querySelector(".usage-copy"); - const gitUrl = `${repoBlobBase}/${u.file.replace(/\\/g, "/")}#L${u.line}`; - gitBtn.onclick = () => copyText(gitUrl, "Git link copied"); - usageList.append(row); - }); - } - if (dynUsages.length > 0) { - dynUsages.forEach((d) => { - const row = usageTemplate.content.firstElementChild.cloneNode(true); - row.querySelector(".usage-file").textContent = `${d.file} : ${d.line} (dynamic: ${d.reason})`; - const fullPath = `${repoRoot}/${d.file}`.replace(/\\/g, "/"); - const openBtn = ensureUsageOpen(row); - openBtn.onclick = () => { - const codeUrl = `${vscodeBase}${encodeURI(fullPath)}:${d.line}`; - window.open(codeUrl, "_blank"); - }; - const gitBtn = row.querySelector(".usage-copy"); - const gitUrl = `${repoBlobBase}/${d.file.replace(/\\/g, "/")}#L${d.line}`; - gitBtn.onclick = () => copyText(gitUrl, "Git link copied"); - usageList.append(row); - }); - } - if (usages.length === 0 && dynUsages.length === 0) { - const none = document.createElement("div"); - none.textContent = "No usages found."; - none.className = "details-empty"; - usageList.append(none); - } - usageSection.append(usageList); - wrapper.append(usageSection); - - const actions = document.createElement("div"); - actions.className = "footer-actions"; - const saveBtn = document.createElement("button"); - saveBtn.textContent = "Save"; - saveBtn.classList.add("success"); - saveBtn.onclick = () => saveKey(key, transWrap); - const deleteBtn = document.createElement("button"); - deleteBtn.textContent = "Delete"; - deleteBtn.className = "danger"; - deleteBtn.onclick = () => deleteKey(key); - actions.append(saveBtn, deleteBtn); - wrapper.append(actions); - - details.className = ""; - details.innerHTML = ""; - details.append(wrapper); - } - - function makeLocaleRow(locale, value) { - const row = document.createElement("div"); - row.className = "translation-row"; - row.dataset.locale = locale; - const input = document.createElement("input"); - input.value = locale; - input.disabled = true; - const textarea = document.createElement("textarea"); - textarea.value = value ?? ""; - autosizeText(textarea); - textarea.addEventListener("input", () => autosizeText(textarea)); - const remove = document.createElement("button"); - remove.textContent = "Remove"; - remove.className = "danger"; - remove.onclick = () => { - if (locale === PRIMARY_LOCALE) return; - row.remove(); - }; - if (locale === PRIMARY_LOCALE) { - remove.disabled = true; - remove.textContent = "Primary"; - } - row.append(input, textarea, remove); - return row; - } - - function autosizeText(textarea) { - textarea.style.height = "auto"; - textarea.style.height = `${textarea.scrollHeight}px`; - } - - function orderLocales(locales) { - return [...locales].sort((a, b) => { - if (a === b) return 0; - if (a === PRIMARY_LOCALE) return -1; - if (b === PRIMARY_LOCALE) return 1; - return a.localeCompare(b); - }); - } - - async function saveKey(key, transWrap) { - const ok = await confirmModal("Save changes?", "This will write updates to strings.json."); - if (!ok) return; - const translations = {}; - transWrap.querySelectorAll(".translation-row").forEach((row) => { - const loc = row.dataset.locale; - const text = row.querySelector("textarea").value; - if (loc) translations[loc] = text; - }); - const res = await fetch(`/api/strings/${encodeURIComponent(key)}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ key, translations }), - }); - if (!res.ok) { - const msg = await readError(res); - alert(`Save failed: ${msg}`); - return; - } - await loadAll(); - renderDetails(key); - showToast("Saved"); - } - - async function deleteKey(key) { - const ok = await confirmModal("Delete key?", `This removes '${key}' from strings.json.`); - if (!ok) return; - const res = await fetch(`/api/strings/${encodeURIComponent(key)}`, { method: "DELETE" }); - if (!res.ok) { - const msg = await readError(res); - alert(`Delete failed: ${msg}`); - return; - } - await loadAll(); - renderDetails(null); - showToast("Deleted"); - } - - async function onAddKey() { - const key = prompt("New translation key"); - if (!key) return; - const res = await fetch("/api/strings", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ key, translations: { "en-US": "" } }), - }); - if (!res.ok) { - const msg = await readError(res); - alert(`Add failed: ${msg}`); - return; - } - await loadAll(); - renderDetails(key); - } - - async function beautify() { - const ok = await confirmModal("Beautify strings.json?", "This will rewrite strings.json with formatting."); - if (!ok) return; - const res = await fetch('/api/strings/format', { method: 'POST' }); - if (!res.ok) { - const msg = await readError(res); - alert(`Beautify failed: ${msg}`); - return; - } - await loadAll(); - showToast('Formatted'); - } - - function makeBadge(text, tone) { - const span = document.createElement("span"); - span.className = `badge ${tone}`; - span.textContent = text; - return span; - } - - function keyFromDynamic(dyn) { - if (!dyn) return ""; - const raw = dyn.expression || ""; - const normalized = normalizeDynamicKey(raw); - return normalized || raw || ""; - } - - function normalizeDynamicKey(expr) { - let s = expr.trim(); - // Remove leading interpolation markers - if (s.startsWith("@$")) s = s.slice(2); - else if (s.startsWith("$")) s = s.slice(1); - // Strip surrounding quotes - s = s.replace(/^"/, "").replace(/"$/, ""); - // Cut at first interpolation - const brace = s.indexOf("{"); - if (brace >= 0) s = s.slice(0, brace); - return s.trim(); - } - - function ensureUsageOpen(row) { - let btn = row.querySelector(".usage-open"); - if (!btn) { - btn = document.createElement("button"); - btn.className = "usage-open"; - btn.textContent = "Open in VS Code"; - const actions = row.querySelector(".usage-actions") || row; - actions.prepend(btn); - } - return btn; - } - - async function refreshAll() { - try { - await loadAll(); - showToast("Refreshed"); - } catch (err) { - const message = err?.message || err; - alert(`Refresh failed: ${message}`); - } - } - - async function downloadStrings() { - try { - const res = await fetch("/api/strings"); - if (!res.ok) throw new Error(await readError(res)); - const data = await res.json(); - const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = "strings.json"; - a.click(); - URL.revokeObjectURL(url); - showToast("Downloaded strings.json"); - } catch (err) { - alert(`Download failed: ${err}`); - } - } - - function copyText(text, toastMessage) { - navigator.clipboard?.writeText(text).then(() => showToast(toastMessage)); - } - - function configureLinks() { - const gitInput = prompt("Git blob base (e.g. https://github.com/org/repo/blob/main)", repoBlobBase); - if (gitInput) { - repoBlobBase = gitInput.trim().replace(/\/$/, ""); - localStorage.setItem("repoBlobBase", repoBlobBase); - } - - const vscodeInput = prompt("VS Code URI base (leave blank to follow scheme)", customVscodeBase ? vscodeBase : ""); - if (vscodeInput !== null) { - const trimmed = vscodeInput.trim(); - if (trimmed) { - vscodeBase = trimmed.endsWith("/") ? trimmed : `${trimmed}/`; - customVscodeBase = true; - localStorage.setItem("vscodeBase", vscodeBase); - } else { - customVscodeBase = false; - vscodeBase = `${vscodeScheme}://file/`; - localStorage.setItem("vscodeBase", vscodeBase); - } - } - - renderDetails(state.selected); - } - - function showToast(message) { - const toast = document.createElement("div"); - toast.className = "toast"; - toast.textContent = message; - document.body.append(toast); - setTimeout(() => toast.remove(), 2000); - } - - function confirmModal(title, message) { - return new Promise((resolve) => { - const backdrop = document.createElement("div"); - backdrop.className = "modal-backdrop"; - const modal = document.createElement("div"); - modal.className = "modal"; - modal.innerHTML = `

${title}

${message}

`; - const actions = document.createElement("div"); - actions.className = "modal-actions"; - const cancel = document.createElement("button"); - cancel.className = "ghost"; - cancel.textContent = "Cancel"; - const ok = document.createElement("button"); - ok.className = "danger"; - ok.textContent = "Confirm"; - cancel.onclick = () => { backdrop.remove(); resolve(false); }; - ok.onclick = () => { backdrop.remove(); resolve(true); }; - actions.append(cancel, ok); - modal.append(actions); - backdrop.append(modal); - modalHost.append(backdrop); - }); - } - - async function fetchJson(url) { - const res = await fetch(url); - if (!res.ok) { - const msg = await readError(res); - throw new Error(msg); - } - return res.json(); - } - - async function readError(res) { - try { - const data = await res.json(); - if (data?.message) return data.message; - return `${res.status} ${res.statusText}`; - } catch { - return `${res.status} ${res.statusText}`; - } - } - - loadAll(); -})(); +"use strict";(()=>{const e="en-US",t=["da","de","en-GB","en-US","es-ES","fr","hr","it","lt","hu","nl","no","pl","pt-BR","ro","fi","sv-SE","vi","tr","cs","el","bg","ru","uk","hi","th","zh-CN","ja","zh-TW","ko"],n=localStorage.getItem("vscodeScheme");let s="vscode-insiders"===n||"vscode"===n?n:navigator.userAgent.includes("Insider")?"vscode-insiders":"vscode";const a="https://github.com/Aiko-IT-Systems/ScWikeloGrind/blob/main",o={report:null,strings:{},locales:[],selected:null,realKeys:new Set},c=e=>document.getElementById(e),i=c("keyList"),r=c("detailsBody"),l=c("summary"),d=c("scheme"),u=c("search"),m=c("filter"),p=c("downloadStrings"),f=c("modalHost");async function g(){const[n,s,a]=await Promise.all([L("/api/report"),L("/api/strings"),L("/api/locales")]);o.report=n,o.realKeys=new Set(Object.keys(s)),o.strings={...s};(n?.missingKeys??[]).forEach(t=>{Object.prototype.hasOwnProperty.call(o.strings,t)||(o.strings[t]={[e]:""})}),o.locales=a&&a.length>0?a:t,l.textContent=`Used ${n.usedKeysCount} / Defined ${n.definedKeysCount} | Missing ${n.missingKeys.length} | Unused ${n.unusedKeys.length} | Dynamic ${n.dynamicKeyUsages.length}`,y(),h(o.selected)}function y(){if(!o.report)return;const t=u.value.toLowerCase(),n=m.value,s=function(t){const n=new Set;return n.add(e),Object.values(t).forEach(e=>{Object.keys(e||{}).forEach(e=>n.add(e))}),n}(o.strings),a=Object.keys(o.strings),c=o.report?.missingKeys??[],r=(o.report.dynamicKeyUsages||[]).map(e=>k(e)).filter(e=>e&&e.includes(".")),l=Array.from(new Set([...a,...r,...c])).sort((e,t)=>e.localeCompare(t));i.innerHTML="";const d=document.getElementById("key-item-template");l.forEach(a=>{const r=o.strings[a]||{},l=Object.values(r).join(" ").toLowerCase();if(t&&!a.toLowerCase().includes(t)&&!l.includes(t))return;const u=c.includes(a),m=o.report.dynamicKeyUsages.some(e=>k(e)===a),p=!m&&(!o.realKeys.has(a)||u),f=o.report.unusedKeys.includes(a),g=!r[e]||0===r[e].trim().length,y=!p&&!m&&(g||function(e,t){for(const n of t){const t=e?.[n]??"";if(!t||0===t.trim().length)return!0}return!1}(r,s)),v=m;if("kmissing"===n&&!p)return;if("lmissing"===n&&!y)return;if("unused"===n&&!f)return;if("dynamic"===n&&!v)return;const E=d.content.firstElementChild.cloneNode(!0);E.querySelector(".key-text").textContent=a;const w=E.querySelector(".badges");w.style.flexWrap="wrap",p&&w.append($("Missing Key","danger")),f&&w.append($("Unused","warn")),!p&&y&&w.append($("Missing Locale","danger")),v&&w.append($("Dynamic","success")),E.addEventListener("click",()=>h(a)),o.selected===a&&E.classList.add("selected"),i.append(E)})}function h(t){if(o.selected=t,!t)return r.className="details-empty",void(r.textContent="Select a key to edit");const n=o.realKeys.has(t),c=o.report?.dynamicKeyUsages.some(e=>k(e)===t),i=o.report?.dynamicKeyCandidates?.[t]??[],l=o.strings[t]||{[e]:""},d=w(Object.keys(l)),u=document.createElement("div");u.className="details";const m=document.createElement("div");if(m.innerHTML=`${t}${n||c?"":' Missing Key'}`,u.append(m),!n&&!c){const e=document.createElement("div");e.className="details-empty",e.textContent="This key is missing from strings.json. Fill in values and Save to create it.",u.append(e)}if(c){const e=document.createElement("div");e.className="details-empty",e.textContent="Dynamic key: manage concrete keys instead.",u.append(e)}else{const e=document.createElement("div");e.className="translations",d.forEach(t=>{e.append(v(t,l[t]??""))});const n=document.createElement("div");n.className="translation-add-row";const s=document.createElement("select"),a=w(o.locales),c=new Set(d),i=a.filter(e=>!c.has(e));s.innerHTML=`${i.map(e=>``).join("")}`;const r=document.createElement("button");r.textContent="Add locale",r.className="secondary",r.onclick=()=>{const t=s.value;t&&(e.querySelector(`[data-locale="${t}"]`)||(e.insertBefore(v(t,""),n),s.value=""))};const m=document.createElement("div");m.className="footer-actions";const p=document.createElement("button");p.textContent="Save",p.classList.add("success"),p.onclick=()=>async function(e,t){if(!await N("Save changes?","This will write updates to strings.json."))return;const n={};t.querySelectorAll(".translation-row").forEach(e=>{const t=e.dataset.locale,s=e.querySelector("textarea").value;t&&(n[t]=s)});const s=await fetch(`/api/strings/${encodeURIComponent(e)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:e,translations:n})});if(!s.ok){const e=await x(s);return void alert(`Save failed: ${e}`)}await g(),h(e),b("Saved")}(t,e);const f=document.createElement("button");f.textContent="Delete",f.className="danger",f.onclick=()=>async function(e){const t=await N("Delete key?",`This removes '${e}' from strings.json.`);if(!t)return;const n=await fetch(`/api/strings/${encodeURIComponent(e)}`,{method:"DELETE"});if(!n.ok){const e=await x(n);return void alert(`Delete failed: ${e}`)}await g(),h(null),b("Deleted")}(t),m.append(p,f),n.append(s,r,m),e.append(n),u.append(e)}const p=document.createElement("div");p.className="usage",p.innerHTML="Usages";const f=document.createElement("div"),y=o.report?.keyUsages[t]||[],E=(o.report?.dynamicKeyUsages||[]).filter(e=>k(e)===t),$=document.getElementById("usage-row-template"),L=(o.report?.repoRoot||"").replace(/\\/g,"/");if(y.length>0&&y.forEach(e=>{const t=$.content.firstElementChild.cloneNode(!0);t.querySelector(".usage-file").textContent=`${e.file} : ${e.line}`;const n=`${L}/${e.file}`.replace(/\\/g,"/");S(t).onclick=()=>{const t=`${s}://file/${encodeURI(n)}:${e.line}`;window.open(t,"_blank")};const o=t.querySelector(".usage-copy"),c=`${a}/${e.file.replace(/\\/g,"/")}#L${e.line}`;o.onclick=()=>C(c,"Git link copied"),f.append(t)}),E.length>0&&E.forEach(e=>{const t=$.content.firstElementChild.cloneNode(!0);t.querySelector(".usage-file").textContent=`${e.file} : ${e.line} (dynamic: ${e.reason??""})`;const n=`${L}/${e.file}`.replace(/\\/g,"/");S(t).onclick=()=>{const t=`${s}://file/${encodeURI(n)}:${e.line}`;window.open(t,"_blank")};const o=t.querySelector(".usage-copy"),c=`${a}/${e.file.replace(/\\/g,"/")}#L${e.line}`;o.onclick=()=>C(c,"Git link copied"),f.append(t)}),0===y.length&&0===E.length){const e=document.createElement("div");e.textContent="No usages found.",e.className="details-empty",f.append(e)}if(p.append(f),u.append(p),c&&i.length>0){const e=document.createElement("div");e.className="usage",e.innerHTML="Candidate keys";const t=document.createElement("div");t.className="candidate-list",i.forEach(e=>{const n=document.createElement("div");n.className="details-empty",n.textContent=e,n.style.cursor="pointer",n.onclick=()=>h(e),t.append(n)}),e.append(t),u.append(e)}r.className="",r.innerHTML="",r.append(u)}function v(t,n){const s=document.createElement("div");s.className="translation-row",s.dataset.locale=t;const a=document.createElement("input");a.value=t,a.disabled=!0;const o=document.createElement("textarea");o.value=n??"",E(o),o.addEventListener("input",()=>E(o));const c=document.createElement("button");return c.textContent="Remove",c.className="danger",c.onclick=()=>{t!==e&&s.remove()},t===e&&(c.disabled=!0,c.textContent="Primary"),s.append(a,o,c),s}function E(e){e.style.height="auto",e.style.height=`${e.scrollHeight}px`}function w(t){return[...t].sort((t,n)=>t===n?0:t===e?-1:n===e?1:t.localeCompare(n))}function $(e,t){const n=document.createElement("span");return n.className=`badge ${t}`,n.textContent=e,n}function k(e){if(!e)return"";const t=e.expression||"";return function(e){let t=e.trim();t.startsWith("@$")?t=t.slice(2):t.startsWith("$")&&(t=t.slice(1));t=t.replace(/^"/,"").replace(/"$/,"");const n=t.indexOf("{");n>=0&&(t=t.slice(0,n));return t.trim()}(t)||t||""}function S(e){let t=e.querySelector(".usage-open");if(!t){t=document.createElement("button"),t.className="usage-open",t.textContent="Open in VS Code";(e.querySelector(".usage-actions")||e).prepend(t)}return t}function C(e,t){navigator.clipboard?.writeText(e).then(()=>b(t))}function b(e){const t=document.createElement("div");t.className="toast",t.textContent=e,document.body.append(t),setTimeout(()=>t.remove(),2e3)}function N(e,t){return new Promise(n=>{const s=document.createElement("div");s.className="modal-backdrop";const a=document.createElement("div");a.className="modal",a.innerHTML=`

${e}

${t}

`;const o=document.createElement("div");o.className="modal-actions";const c=document.createElement("button");c.className="ghost",c.textContent="Cancel";const i=document.createElement("button");i.className="danger",i.textContent="Confirm",c.onclick=()=>{s.remove(),n(!1)},i.onclick=()=>{s.remove(),n(!0)},o.append(c,i),a.append(o),s.append(a),f.append(s)})}async function L(e){const t=await fetch(e);if(!t.ok){const e=await x(t);throw new Error(e)}return t.json()}async function x(e){try{const t=await e.json();return t?.message?t.message:`${e.status} ${e.statusText}`}catch{return`${e.status} ${e.statusText}`}}c("refresh").addEventListener("click",async function(){try{await g(),b("Refreshed")}catch(e){const t=e?.message||String(e);alert(`Refresh failed: ${t}`)}}),c("addKey").addEventListener("click",async function(){const t=prompt("New translation key");if(!t)return;const n=await fetch("/api/strings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:t,translations:{[e]:""}})});if(!n.ok){const e=await x(n);return void alert(`Add failed: ${e}`)}await g(),h(t)}),c("beautify").addEventListener("click",async function(){if(!await N("Beautify strings.json?","This will rewrite strings.json with formatting."))return;const e=await fetch("/api/strings/format",{method:"POST"});if(!e.ok){const t=await x(e);return void alert(`Beautify failed: ${t}`)}await g(),b("Formatted")}),p.addEventListener("click",async function(){try{const e=await fetch("/api/strings");if(!e.ok)throw new Error(await x(e));const t=await e.json(),n=new Blob([JSON.stringify(t,null,2)],{type:"application/json"}),s=URL.createObjectURL(n),a=document.createElement("a");a.href=s,a.download="strings.json",a.click(),URL.revokeObjectURL(s),b("Downloaded strings.json")}catch(e){alert(`Download failed: ${e}`)}}),d.addEventListener("change",()=>{s=d.value,localStorage.setItem("vscodeScheme",s),h(o.selected)}),d.value=s,u.addEventListener("input",y),m.addEventListener("change",y),g()})(); +//# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js.map b/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js.map new file mode 100644 index 0000000..d916ab9 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.js.map @@ -0,0 +1 @@ +{"version":3,"names":["PRIMARY_LOCALE","FALLBACK_LOCALES","storedScheme","localStorage","getItem","vscodeScheme","navigator","userAgent","includes","REPO_BLOB_BASE","state","report","strings","locales","selected","realKeys","Set","el","id","document","getElementById","keyList","details","summary","scheme","search","filter","downloadBtn","modalHost","async","loadAll","Promise","all","fetchJson","Object","keys","missingKeys","forEach","key","prototype","hasOwnProperty","call","length","textContent","usedKeysCount","definedKeysCount","unusedKeys","dynamicKeyUsages","renderKeyList","renderDetails","query","value","toLowerCase","filterValue","expectedLocales","set","add","values","map","loc","collectExpectedLocales","translationKeys","dynamicKeys","d","keyFromDynamic","k","Array","from","sort","a","b","localeCompare","innerHTML","template","translations","valuesText","join","isReportedMissing","isDynamic","some","isMissingKey","has","isUnused","primaryMissing","trim","isMissingLocale","expected","text","hasMissingLocale","isDynamicBadge","node","content","firstElementChild","cloneNode","querySelector","badges","style","flexWrap","append","makeBadge","addEventListener","classList","className","existsInStrings","isDynamicKey","dynamicCandidates","dynamicKeyCandidates","orderLocales","wrapper","createElement","title","hint","dynNote","transWrap","locale","makeLocaleRow","addLocaleRow","addSelect","availableLocales","existing","options","l","addButton","onclick","insertBefore","actions","saveBtn","confirmModal","querySelectorAll","row","dataset","res","fetch","encodeURIComponent","method","headers","body","JSON","stringify","ok","msg","readError","alert","showToast","saveKey","deleteBtn","deleteKey","usageSection","usageList","usages","keyUsages","dynUsages","usageTemplate","repoRoot","replace","u","file","line","fullPath","ensureUsageOpen","codeUrl","encodeURI","window","open","gitBtn","gitUrl","copyText","reason","none","candidateSection","candidateList","cand","cursor","input","disabled","textarea","autosizeText","remove","height","scrollHeight","tone","span","dyn","raw","expression","expr","s","startsWith","slice","brace","indexOf","normalizeDynamicKey","btn","prepend","toastMessage","clipboard","writeText","then","message","toast","setTimeout","resolve","backdrop","modal","cancel","url","Error","json","data","status","statusText","err","String","prompt","blob","Blob","type","URL","createObjectURL","href","download","click","revokeObjectURL","setItem"],"sources":["https://translate-scwg.admin.aitsys.dev/app.ts"],"sourcesContent":["(() => {\r\n\ttype Locale = string;\r\n\ttype TranslationMap = Record;\r\n\ttype StringsMap = Record;\r\n\r\n\ttype AuditUsage = {\r\n\t\tfile: string;\r\n\t\tline: number;\r\n\t\treason?: string;\r\n\t\texpression?: string;\r\n\t};\r\n\r\n\ttype AuditReport = {\r\n\t\tusedKeysCount: number;\r\n\t\tdefinedKeysCount: number;\r\n\t\tmissingKeys: string[];\r\n\t\tunusedKeys: string[];\r\n\t\tdynamicKeyUsages: AuditUsage[];\r\n\t\tdynamicKeyCandidates?: Record;\r\n\t\tkeyUsages: Record;\r\n\t\trepoRoot: string;\r\n\t};\r\n\r\n\ttype State = {\r\n\t\treport: AuditReport | null;\r\n\t\tstrings: StringsMap;\r\n\t\tlocales: Locale[];\r\n\t\tselected: string | null;\r\n\t\trealKeys: Set;\r\n\t};\r\n\r\n\tconst PRIMARY_LOCALE = \"en-US\";\r\n\tconst FALLBACK_LOCALES: Locale[] = [\r\n\t\t\"da\",\r\n\t\t\"de\",\r\n\t\t\"en-GB\",\r\n\t\t\"en-US\",\r\n\t\t\"es-ES\",\r\n\t\t\"fr\",\r\n\t\t\"hr\",\r\n\t\t\"it\",\r\n\t\t\"lt\",\r\n\t\t\"hu\",\r\n\t\t\"nl\",\r\n\t\t\"no\",\r\n\t\t\"pl\",\r\n\t\t\"pt-BR\",\r\n\t\t\"ro\",\r\n\t\t\"fi\",\r\n\t\t\"sv-SE\",\r\n\t\t\"vi\",\r\n\t\t\"tr\",\r\n\t\t\"cs\",\r\n\t\t\"el\",\r\n\t\t\"bg\",\r\n\t\t\"ru\",\r\n\t\t\"uk\",\r\n\t\t\"hi\",\r\n\t\t\"th\",\r\n\t\t\"zh-CN\",\r\n\t\t\"ja\",\r\n\t\t\"zh-TW\",\r\n\t\t\"ko\",\r\n\t];\r\n\r\n\tconst storedScheme = localStorage.getItem(\"vscodeScheme\");\r\n\tlet vscodeScheme =\r\n\t\tstoredScheme === \"vscode-insiders\" || storedScheme === \"vscode\"\r\n\t\t\t? storedScheme\r\n\t\t\t: navigator.userAgent.includes(\"Insider\")\r\n\t\t\t? \"vscode-insiders\"\r\n\t\t\t: \"vscode\";\r\n\tconst REPO_BLOB_BASE =\r\n\t\t\"https://github.com/Aiko-IT-Systems/ScWikeloGrind/blob/main\";\r\n\r\n\tconst state: State = {\r\n\t\treport: null,\r\n\t\tstrings: {},\r\n\t\tlocales: [],\r\n\t\tselected: null,\r\n\t\trealKeys: new Set(),\r\n\t};\r\n\r\n\tconst el = (id: string) => document.getElementById(id)!;\r\n\tconst keyList = el(\"keyList\");\r\n\tconst details = el(\"detailsBody\");\r\n\tconst summary = el(\"summary\");\r\n\tconst scheme = el(\"scheme\") as HTMLSelectElement;\r\n\tconst search = el(\"search\") as HTMLInputElement;\r\n\tconst filter = el(\"filter\") as HTMLSelectElement;\r\n\tconst downloadBtn = el(\"downloadStrings\");\r\n\tconst modalHost = el(\"modalHost\");\r\n\r\n\tel(\"refresh\").addEventListener(\"click\", refreshAll);\r\n\tel(\"addKey\").addEventListener(\"click\", onAddKey);\r\n\tel(\"beautify\").addEventListener(\"click\", beautify);\r\n\tdownloadBtn.addEventListener(\"click\", downloadStrings);\r\n\tscheme.addEventListener(\"change\", () => {\r\n\t\tvscodeScheme = scheme.value;\r\n\t\tlocalStorage.setItem(\"vscodeScheme\", vscodeScheme);\r\n\t\trenderDetails(state.selected);\r\n\t});\r\n\r\n\tscheme.value = vscodeScheme;\r\n\tsearch.addEventListener(\"input\", renderKeyList);\r\n\tfilter.addEventListener(\"change\", renderKeyList);\r\n\r\n\tasync function loadAll() {\r\n\t\tconst [report, strings, locales] = await Promise.all([\r\n\t\t\tfetchJson(\"/api/report\"),\r\n\t\t\tfetchJson(\"/api/strings\"),\r\n\t\t\tfetchJson(\"/api/locales\"),\r\n\t\t]);\r\n\r\n\t\tstate.report = report;\r\n\t\tstate.realKeys = new Set(Object.keys(strings));\r\n\t\tstate.strings = { ...strings };\r\n\r\n\t\tconst missingKeys = report?.missingKeys ?? [];\r\n\t\tmissingKeys.forEach((key) => {\r\n\t\t\tif (!Object.prototype.hasOwnProperty.call(state.strings, key)) {\r\n\t\t\t\tstate.strings[key] = { [PRIMARY_LOCALE]: \"\" };\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tstate.locales =\r\n\t\t\tlocales && locales.length > 0 ? locales : FALLBACK_LOCALES;\r\n\t\tsummary.textContent = `Used ${report.usedKeysCount} / Defined ${report.definedKeysCount} | Missing ${report.missingKeys.length} | Unused ${report.unusedKeys.length} | Dynamic ${report.dynamicKeyUsages.length}`;\r\n\t\trenderKeyList();\r\n\t\trenderDetails(state.selected);\r\n\t}\r\n\r\n\tfunction renderKeyList() {\r\n\t\tif (!state.report) return;\r\n\t\tconst query = search.value.toLowerCase();\r\n\t\tconst filterValue = filter.value;\r\n\t\tconst expectedLocales = collectExpectedLocales(state.strings);\r\n\r\n\t\tconst translationKeys = Object.keys(state.strings);\r\n\t\tconst missingKeys = state.report?.missingKeys ?? [];\r\n\t\tconst dynamicKeys = (state.report.dynamicKeyUsages || [])\r\n\t\t\t.map((d) => keyFromDynamic(d))\r\n\t\t\t.filter((k) => k && k.includes(\".\"));\r\n\t\tconst keys = Array.from(\r\n\t\t\tnew Set([...translationKeys, ...dynamicKeys, ...missingKeys])\r\n\t\t).sort((a, b) => a.localeCompare(b));\r\n\r\n\t\tkeyList.innerHTML = \"\";\r\n\t\tconst template = document.getElementById(\r\n\t\t\t\"key-item-template\"\r\n\t\t) as HTMLTemplateElement;\r\n\r\n\t\tkeys.forEach((key) => {\r\n\t\t\tconst translations = state.strings[key] || {};\r\n\t\t\tconst valuesText = Object.values(translations)\r\n\t\t\t\t.join(\" \")\r\n\t\t\t\t.toLowerCase();\r\n\t\t\tif (\r\n\t\t\t\tquery &&\r\n\t\t\t\t!key.toLowerCase().includes(query) &&\r\n\t\t\t\t!valuesText.includes(query)\r\n\t\t\t)\r\n\t\t\t\treturn;\r\n\r\n\t\t\tconst isReportedMissing = missingKeys.includes(key);\r\n\t\t\tconst isDynamic = state.report!.dynamicKeyUsages.some(\r\n\t\t\t\t(d) => keyFromDynamic(d) === key\r\n\t\t\t);\r\n\t\t\tconst isMissingKey = !isDynamic && (!state.realKeys.has(key) || isReportedMissing);\r\n\t\t\tconst isUnused = state.report!.unusedKeys.includes(key);\r\n\t\t\tconst primaryMissing =\r\n\t\t\t\t!translations[PRIMARY_LOCALE] ||\r\n\t\t\t\ttranslations[PRIMARY_LOCALE].trim().length === 0;\r\n\t\t\tconst isMissingLocale =\r\n\t\t\t\t!isMissingKey &&\r\n\t\t\t\t!isDynamic &&\r\n\t\t\t\t(primaryMissing || hasMissingLocale(translations, expectedLocales));\r\n\t\t\tconst isDynamicBadge = isDynamic;\r\n\r\n\t\t\tif (filterValue === \"kmissing\" && !isMissingKey) return;\r\n\t\t\tif (filterValue === \"lmissing\" && !isMissingLocale) return;\r\n\t\t\tif (filterValue === \"unused\" && !isUnused) return;\r\n\t\t\tif (filterValue === \"dynamic\" && !isDynamicBadge) return;\r\n\r\n\t\t\tconst node = template.content.firstElementChild!.cloneNode(\r\n\t\t\t\ttrue\r\n\t\t\t) as HTMLElement;\r\n\t\t\tnode.querySelector(\".key-text\")!.textContent = key;\r\n\t\t\tconst badges = node.querySelector(\".badges\")!;\r\n\t\t\tbadges.style.flexWrap = \"wrap\";\r\n\t\t\tif (isMissingKey) badges.append(makeBadge(\"Missing Key\", \"danger\"));\r\n\t\t\tif (isUnused) badges.append(makeBadge(\"Unused\", \"warn\"));\r\n\t\t\tif (!isMissingKey && isMissingLocale)\r\n\t\t\t\tbadges.append(makeBadge(\"Missing Locale\", \"danger\"));\r\n\t\t\tif (isDynamicBadge) badges.append(makeBadge(\"Dynamic\", \"success\"));\r\n\t\t\tnode.addEventListener(\"click\", () => renderDetails(key));\r\n\t\t\tif (state.selected === key) node.classList.add(\"selected\");\r\n\t\t\tkeyList.append(node);\r\n\t\t});\r\n\t}\r\n\r\n\tfunction collectExpectedLocales(strings: StringsMap) {\r\n\t\tconst set = new Set();\r\n\t\tset.add(PRIMARY_LOCALE);\r\n\t\tObject.values(strings).forEach((map) => {\r\n\t\t\tObject.keys(map || {}).forEach((loc) => set.add(loc));\r\n\t\t});\r\n\t\treturn set;\r\n\t}\r\n\r\n\tfunction hasMissingLocale(translations: TranslationMap, expected: Set) {\r\n\t\tfor (const loc of expected) {\r\n\t\t\tconst text = translations?.[loc] ?? \"\";\r\n\t\t\tif (!text || text.trim().length === 0) return true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tfunction renderDetails(key: string | null) {\r\n\t\tstate.selected = key;\r\n\t\tif (!key) {\r\n\t\t\tdetails.className = \"details-empty\";\r\n\t\t\tdetails.textContent = \"Select a key to edit\";\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst existsInStrings = state.realKeys.has(key);\r\n\t\tconst isDynamicKey = state.report?.dynamicKeyUsages.some(\r\n\t\t\t(d) => keyFromDynamic(d) === key\r\n\t\t);\r\n\t\tconst dynamicCandidates = state.report?.dynamicKeyCandidates?.[key] ?? [];\r\n\t\tconst translations = state.strings[key] || { [PRIMARY_LOCALE]: \"\" };\r\n\t\tconst locales = orderLocales(Object.keys(translations));\r\n\r\n\t\tconst wrapper = document.createElement(\"div\");\r\n\t\twrapper.className = \"details\";\r\n\t\tconst title = document.createElement(\"div\");\r\n\t\ttitle.innerHTML = `${key}${\r\n\t\t\texistsInStrings || isDynamicKey\r\n\t\t\t\t? \"\"\r\n\t\t\t\t: ' Missing Key'\r\n\t\t}`;\r\n\t\twrapper.append(title);\r\n\t\tif (!existsInStrings && !isDynamicKey) {\r\n\t\t\tconst hint = document.createElement(\"div\");\r\n\t\t\thint.className = \"details-empty\";\r\n\t\t\thint.textContent =\r\n\t\t\t\t\"This key is missing from strings.json. Fill in values and Save to create it.\";\r\n\t\t\twrapper.append(hint);\r\n\t\t}\r\n\r\n\t\tif (isDynamicKey) {\r\n\t\t\tconst dynNote = document.createElement(\"div\");\r\n\t\t\tdynNote.className = \"details-empty\";\r\n\t\t\tdynNote.textContent = \"Dynamic key: manage concrete keys instead.\";\r\n\t\t\twrapper.append(dynNote);\r\n\t\t} else {\r\n\t\t\tconst transWrap = document.createElement(\"div\");\r\n\t\t\ttransWrap.className = \"translations\";\r\n\t\t\tlocales.forEach((locale) => {\r\n\t\t\t\ttransWrap.append(makeLocaleRow(locale, translations[locale] ?? \"\"));\r\n\t\t\t});\r\n\r\n\t\t\tconst addLocaleRow = document.createElement(\"div\");\r\n\t\t\taddLocaleRow.className = \"translation-add-row\";\r\n\t\t\tconst addSelect = document.createElement(\"select\");\r\n\t\t\tconst availableLocales = orderLocales(state.locales);\r\n\t\t\tconst existing = new Set(locales);\r\n\t\t\tconst options = availableLocales.filter((l) => !existing.has(l));\r\n\t\t\taddSelect.innerHTML = `${options\r\n\t\t\t\t.map((l) => ``)\r\n\t\t\t\t.join(\"\")}`;\r\n\t\t\tconst addButton = document.createElement(\"button\");\r\n\t\t\taddButton.textContent = \"Add locale\";\r\n\t\t\taddButton.className = \"secondary\";\r\n\t\t\taddButton.onclick = () => {\r\n\t\t\t\tconst loc = addSelect.value;\r\n\t\t\t\tif (!loc) return;\r\n\t\t\t\tif (transWrap.querySelector(`[data-locale=\"${loc}\"]`)) return;\r\n\t\t\t\ttransWrap.insertBefore(makeLocaleRow(loc, \"\"), addLocaleRow);\r\n\t\t\t\taddSelect.value = \"\";\r\n\t\t\t};\r\n\t\t\tconst actions = document.createElement(\"div\");\r\n\t\t\tactions.className = \"footer-actions\";\r\n\t\t\tconst saveBtn = document.createElement(\"button\");\r\n\t\t\tsaveBtn.textContent = \"Save\";\r\n\t\t\tsaveBtn.classList.add(\"success\");\r\n\t\t\tsaveBtn.onclick = () => saveKey(key, transWrap);\r\n\t\t\tconst deleteBtn = document.createElement(\"button\");\r\n\t\t\tdeleteBtn.textContent = \"Delete\";\r\n\t\t\tdeleteBtn.className = \"danger\";\r\n\t\t\tdeleteBtn.onclick = () => deleteKey(key);\r\n\t\t\tactions.append(saveBtn, deleteBtn);\r\n\t\t\taddLocaleRow.append(addSelect, addButton, actions);\r\n\t\t\ttransWrap.append(addLocaleRow);\r\n\t\t\twrapper.append(transWrap);\r\n\t\t}\r\n\r\n\t\tconst usageSection = document.createElement(\"div\");\r\n\t\tusageSection.className = \"usage\";\r\n\t\tusageSection.innerHTML = \"Usages\";\r\n\t\tconst usageList = document.createElement(\"div\");\r\n\t\tconst usages = state.report?.keyUsages[key] || [];\r\n\t\tconst dynUsages = (state.report?.dynamicKeyUsages || []).filter(\r\n\t\t\t(d) => keyFromDynamic(d) === key\r\n\t\t);\r\n\t\tconst usageTemplate = document.getElementById(\r\n\t\t\t\"usage-row-template\"\r\n\t\t) as HTMLTemplateElement;\r\n\t\tconst repoRoot = (state.report?.repoRoot || \"\").replace(/\\\\/g, \"/\");\r\n\t\tif (usages.length > 0) {\r\n\t\t\tusages.forEach((u) => {\r\n\t\t\t\tconst row = usageTemplate.content.firstElementChild!.cloneNode(\r\n\t\t\t\t\ttrue\r\n\t\t\t\t) as HTMLElement;\r\n\t\t\t\trow.querySelector(\r\n\t\t\t\t\t\".usage-file\"\r\n\t\t\t\t)!.textContent = `${u.file} : ${u.line}`;\r\n\t\t\t\tconst fullPath = `${repoRoot}/${u.file}`.replace(/\\\\/g, \"/\");\r\n\t\t\t\tconst openBtn = ensureUsageOpen(row);\r\n\t\t\t\topenBtn.onclick = () => {\r\n\t\t\t\t\tconst codeUrl = `${vscodeScheme}://file/${encodeURI(\r\n\t\t\t\t\t\tfullPath\r\n\t\t\t\t\t)}:${u.line}`;\r\n\t\t\t\t\twindow.open(codeUrl, \"_blank\");\r\n\t\t\t\t};\r\n\t\t\t\tconst gitBtn =\r\n\t\t\t\t\trow.querySelector(\".usage-copy\")!;\r\n\t\t\t\tconst gitUrl = `${REPO_BLOB_BASE}/${u.file.replace(\r\n\t\t\t\t\t/\\\\/g,\r\n\t\t\t\t\t\"/\"\r\n\t\t\t\t)}#L${u.line}`;\r\n\t\t\t\tgitBtn.onclick = () => copyText(gitUrl, \"Git link copied\");\r\n\t\t\t\tusageList.append(row);\r\n\t\t\t});\r\n\t\t}\r\n\t\tif (dynUsages.length > 0) {\r\n\t\t\tdynUsages.forEach((d) => {\r\n\t\t\t\tconst row = usageTemplate.content.firstElementChild!.cloneNode(\r\n\t\t\t\t\ttrue\r\n\t\t\t\t) as HTMLElement;\r\n\t\t\t\trow.querySelector(\".usage-file\")!.textContent = `${\r\n\t\t\t\t\td.file\r\n\t\t\t\t} : ${d.line} (dynamic: ${d.reason ?? \"\"})`;\r\n\t\t\t\tconst fullPath = `${repoRoot}/${d.file}`.replace(/\\\\/g, \"/\");\r\n\t\t\t\tconst openBtn = ensureUsageOpen(row);\r\n\t\t\t\topenBtn.onclick = () => {\r\n\t\t\t\t\tconst codeUrl = `${vscodeScheme}://file/${encodeURI(\r\n\t\t\t\t\t\tfullPath\r\n\t\t\t\t\t)}:${d.line}`;\r\n\t\t\t\t\twindow.open(codeUrl, \"_blank\");\r\n\t\t\t\t};\r\n\t\t\t\tconst gitBtn =\r\n\t\t\t\t\trow.querySelector(\".usage-copy\")!;\r\n\t\t\t\tconst gitUrl = `${REPO_BLOB_BASE}/${d.file.replace(\r\n\t\t\t\t\t/\\\\/g,\r\n\t\t\t\t\t\"/\"\r\n\t\t\t\t)}#L${d.line}`;\r\n\t\t\t\tgitBtn.onclick = () => copyText(gitUrl, \"Git link copied\");\r\n\t\t\t\tusageList.append(row);\r\n\t\t\t});\r\n\t\t}\r\n\t\tif (usages.length === 0 && dynUsages.length === 0) {\r\n\t\t\tconst none = document.createElement(\"div\");\r\n\t\t\tnone.textContent = \"No usages found.\";\r\n\t\t\tnone.className = \"details-empty\";\r\n\t\t\tusageList.append(none);\r\n\t\t}\r\n\t\tusageSection.append(usageList);\r\n\t\twrapper.append(usageSection);\r\n\r\n\t\tif (isDynamicKey && dynamicCandidates.length > 0) {\r\n\t\t\tconst candidateSection = document.createElement(\"div\");\r\n\t\t\tcandidateSection.className = \"usage\";\r\n\t\t\tcandidateSection.innerHTML = \"Candidate keys\";\r\n\t\t\tconst candidateList = document.createElement(\"div\");\r\n\t\t\tcandidateList.className = \"candidate-list\";\r\n\t\t\tdynamicCandidates.forEach((cand) => {\r\n\t\t\t\tconst row = document.createElement(\"div\");\r\n\t\t\t\trow.className = \"details-empty\";\r\n\t\t\t\trow.textContent = cand;\r\n\t\t\t\trow.style.cursor = \"pointer\";\r\n\t\t\t\trow.onclick = () => renderDetails(cand);\r\n\t\t\t\tcandidateList.append(row);\r\n\t\t\t});\r\n\t\t\tcandidateSection.append(candidateList);\r\n\t\t\twrapper.append(candidateSection);\r\n\t\t}\r\n\r\n\t\tdetails.className = \"\";\r\n\t\tdetails.innerHTML = \"\";\r\n\t\tdetails.append(wrapper);\r\n\t}\r\n\r\n\tfunction makeLocaleRow(locale: string, value: string) {\r\n\t\tconst row = document.createElement(\"div\");\r\n\t\trow.className = \"translation-row\";\r\n\t\trow.dataset.locale = locale;\r\n\t\tconst input = document.createElement(\"input\");\r\n\t\tinput.value = locale;\r\n\t\tinput.disabled = true;\r\n\t\tconst textarea = document.createElement(\"textarea\");\r\n\t\ttextarea.value = value ?? \"\";\r\n\t\tautosizeText(textarea);\r\n\t\ttextarea.addEventListener(\"input\", () => autosizeText(textarea));\r\n\t\tconst remove = document.createElement(\"button\");\r\n\t\tremove.textContent = \"Remove\";\r\n\t\tremove.className = \"danger\";\r\n\t\tremove.onclick = () => {\r\n\t\t\tif (locale === PRIMARY_LOCALE) return;\r\n\t\t\trow.remove();\r\n\t\t};\r\n\t\tif (locale === PRIMARY_LOCALE) {\r\n\t\t\tremove.disabled = true;\r\n\t\t\tremove.textContent = \"Primary\";\r\n\t\t}\r\n\t\trow.append(input, textarea, remove);\r\n\t\treturn row;\r\n\t}\r\n\r\n\tfunction autosizeText(textarea: HTMLTextAreaElement) {\r\n\t\ttextarea.style.height = \"auto\";\r\n\t\ttextarea.style.height = `${textarea.scrollHeight}px`;\r\n\t}\r\n\r\n\tfunction orderLocales(locales: string[]) {\r\n\t\treturn [...locales].sort((a, b) => {\r\n\t\t\tif (a === b) return 0;\r\n\t\t\tif (a === PRIMARY_LOCALE) return -1;\r\n\t\t\tif (b === PRIMARY_LOCALE) return 1;\r\n\t\t\treturn a.localeCompare(b);\r\n\t\t});\r\n\t}\r\n\r\n\tasync function saveKey(key: string, transWrap: HTMLElement) {\r\n\t\tconst ok = await confirmModal(\r\n\t\t\t\"Save changes?\",\r\n\t\t\t\"This will write updates to strings.json.\"\r\n\t\t);\r\n\t\tif (!ok) return;\r\n\t\tconst translations: TranslationMap = {};\r\n\t\ttransWrap\r\n\t\t\t.querySelectorAll(\".translation-row\")\r\n\t\t\t.forEach((row) => {\r\n\t\t\t\tconst loc = row.dataset.locale;\r\n\t\t\t\tconst text = (\r\n\t\t\t\t\trow.querySelector(\"textarea\") as HTMLTextAreaElement\r\n\t\t\t\t).value;\r\n\t\t\t\tif (loc) translations[loc] = text;\r\n\t\t\t});\r\n\t\tconst res = await fetch(`/api/strings/${encodeURIComponent(key)}`, {\r\n\t\t\tmethod: \"PUT\",\r\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\r\n\t\t\tbody: JSON.stringify({ key, translations }),\r\n\t\t});\r\n\t\tif (!res.ok) {\r\n\t\t\tconst msg = await readError(res);\r\n\t\t\talert(`Save failed: ${msg}`);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tawait loadAll();\r\n\t\trenderDetails(key);\r\n\t\tshowToast(\"Saved\");\r\n\t}\r\n\r\n\tasync function deleteKey(key: string) {\r\n\t\tconst ok = await confirmModal(\r\n\t\t\t\"Delete key?\",\r\n\t\t\t`This removes '${key}' from strings.json.`\r\n\t\t);\r\n\t\tif (!ok) return;\r\n\t\tconst res = await fetch(`/api/strings/${encodeURIComponent(key)}`, {\r\n\t\t\tmethod: \"DELETE\",\r\n\t\t});\r\n\t\tif (!res.ok) {\r\n\t\t\tconst msg = await readError(res);\r\n\t\t\talert(`Delete failed: ${msg}`);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tawait loadAll();\r\n\t\trenderDetails(null);\r\n\t\tshowToast(\"Deleted\");\r\n\t}\r\n\r\n\tasync function onAddKey() {\r\n\t\tconst key = prompt(\"New translation key\");\r\n\t\tif (!key) return;\r\n\t\tconst res = await fetch(\"/api/strings\", {\r\n\t\t\tmethod: \"POST\",\r\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tkey,\r\n\t\t\t\ttranslations: { [PRIMARY_LOCALE]: \"\" },\r\n\t\t\t}),\r\n\t\t});\r\n\t\tif (!res.ok) {\r\n\t\t\tconst msg = await readError(res);\r\n\t\t\talert(`Add failed: ${msg}`);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tawait loadAll();\r\n\t\trenderDetails(key);\r\n\t}\r\n\r\n\tasync function beautify() {\r\n\t\tconst ok = await confirmModal(\r\n\t\t\t\"Beautify strings.json?\",\r\n\t\t\t\"This will rewrite strings.json with formatting.\"\r\n\t\t);\r\n\t\tif (!ok) return;\r\n\t\tconst res = await fetch(\"/api/strings/format\", { method: \"POST\" });\r\n\t\tif (!res.ok) {\r\n\t\t\tconst msg = await readError(res);\r\n\t\t\talert(`Beautify failed: ${msg}`);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tawait loadAll();\r\n\t\tshowToast(\"Formatted\");\r\n\t}\r\n\r\n\tfunction makeBadge(text: string, tone: \"warn\" | \"danger\" | \"success\") {\r\n\t\tconst span = document.createElement(\"span\");\r\n\t\tspan.className = `badge ${tone}`;\r\n\t\tspan.textContent = text;\r\n\t\treturn span;\r\n\t}\r\n\r\n\tfunction keyFromDynamic(dyn: AuditUsage | null | undefined) {\r\n\t\tif (!dyn) return \"\";\r\n\t\tconst raw = dyn.expression || \"\";\r\n\t\tconst normalized = normalizeDynamicKey(raw);\r\n\t\treturn normalized || raw || \"\";\r\n\t}\r\n\r\n\tfunction normalizeDynamicKey(expr: string) {\r\n\t\tlet s = expr.trim();\r\n\t\tif (s.startsWith(\"@$\")) s = s.slice(2);\r\n\t\telse if (s.startsWith(\"$\")) s = s.slice(1);\r\n\t\ts = s.replace(/^\"/, \"\").replace(/\"$/, \"\");\r\n\t\tconst brace = s.indexOf(\"{\");\r\n\t\tif (brace >= 0) s = s.slice(0, brace);\r\n\t\treturn s.trim();\r\n\t}\r\n\r\n\tfunction ensureUsageOpen(row: HTMLElement) {\r\n\t\tlet btn = row.querySelector(\".usage-open\");\r\n\t\tif (!btn) {\r\n\t\t\tbtn = document.createElement(\"button\");\r\n\t\t\tbtn.className = \"usage-open\";\r\n\t\t\tbtn.textContent = \"Open in VS Code\";\r\n\t\t\tconst actions =\r\n\t\t\t\trow.querySelector(\".usage-actions\") || row;\r\n\t\t\tactions.prepend(btn);\r\n\t\t}\r\n\t\treturn btn;\r\n\t}\r\n\r\n\tasync function refreshAll() {\r\n\t\ttry {\r\n\t\t\tawait loadAll();\r\n\t\t\tshowToast(\"Refreshed\");\r\n\t\t} catch (err: unknown) {\r\n\t\t\tconst message = (err as Error)?.message || String(err);\r\n\t\t\talert(`Refresh failed: ${message}`);\r\n\t\t}\r\n\t}\r\n\r\n\tasync function downloadStrings() {\r\n\t\ttry {\r\n\t\t\tconst res = await fetch(\"/api/strings\");\r\n\t\t\tif (!res.ok) throw new Error(await readError(res));\r\n\t\t\tconst data = await res.json();\r\n\t\t\tconst blob = new Blob([JSON.stringify(data, null, 2)], {\r\n\t\t\t\ttype: \"application/json\",\r\n\t\t\t});\r\n\t\t\tconst url = URL.createObjectURL(blob);\r\n\t\t\tconst a = document.createElement(\"a\");\r\n\t\t\ta.href = url;\r\n\t\t\ta.download = \"strings.json\";\r\n\t\t\ta.click();\r\n\t\t\tURL.revokeObjectURL(url);\r\n\t\t\tshowToast(\"Downloaded strings.json\");\r\n\t\t} catch (err: unknown) {\r\n\t\t\talert(`Download failed: ${err}`);\r\n\t\t}\r\n\t}\r\n\r\n\tfunction copyText(text: string, toastMessage: string) {\r\n\t\tnavigator.clipboard\r\n\t\t\t?.writeText(text)\r\n\t\t\t.then(() => showToast(toastMessage));\r\n\t}\r\n\r\n\tfunction showToast(message: string) {\r\n\t\tconst toast = document.createElement(\"div\");\r\n\t\ttoast.className = \"toast\";\r\n\t\ttoast.textContent = message;\r\n\t\tdocument.body.append(toast);\r\n\t\tsetTimeout(() => toast.remove(), 2000);\r\n\t}\r\n\r\n\tfunction confirmModal(title: string, message: string) {\r\n\t\treturn new Promise((resolve) => {\r\n\t\t\tconst backdrop = document.createElement(\"div\");\r\n\t\t\tbackdrop.className = \"modal-backdrop\";\r\n\t\t\tconst modal = document.createElement(\"div\");\r\n\t\t\tmodal.className = \"modal\";\r\n\t\t\tmodal.innerHTML = `

${title}

${message}

`;\r\n\t\t\tconst actions = document.createElement(\"div\");\r\n\t\t\tactions.className = \"modal-actions\";\r\n\t\t\tconst cancel = document.createElement(\"button\");\r\n\t\t\tcancel.className = \"ghost\";\r\n\t\t\tcancel.textContent = \"Cancel\";\r\n\t\t\tconst ok = document.createElement(\"button\");\r\n\t\t\tok.className = \"danger\";\r\n\t\t\tok.textContent = \"Confirm\";\r\n\t\t\tcancel.onclick = () => {\r\n\t\t\t\tbackdrop.remove();\r\n\t\t\t\tresolve(false);\r\n\t\t\t};\r\n\t\t\tok.onclick = () => {\r\n\t\t\t\tbackdrop.remove();\r\n\t\t\t\tresolve(true);\r\n\t\t\t};\r\n\t\t\tactions.append(cancel, ok);\r\n\t\t\tmodal.append(actions);\r\n\t\t\tbackdrop.append(modal);\r\n\t\t\tmodalHost.append(backdrop);\r\n\t\t});\r\n\t}\r\n\r\n\tasync function fetchJson(url: string) {\r\n\t\tconst res = await fetch(url);\r\n\t\tif (!res.ok) {\r\n\t\t\tconst msg = await readError(res);\r\n\t\t\tthrow new Error(msg);\r\n\t\t}\r\n\t\treturn res.json() as Promise;\r\n\t}\r\n\r\n\tasync function readError(res: Response) {\r\n\t\ttry {\r\n\t\t\tconst data = await res.json();\r\n\t\t\tif ((data as { message?: string })?.message)\r\n\t\t\t\treturn (data as { message: string }).message;\r\n\t\t\treturn `${res.status} ${res.statusText}`;\r\n\t\t} catch {\r\n\t\t\treturn `${res.status} ${res.statusText}`;\r\n\t\t}\r\n\t}\r\n\r\n\tloadAll();\r\n})();\r\n"],"mappings":"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","ignoreList":[]} \ No newline at end of file diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.ts b/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.ts new file mode 100644 index 0000000..5615958 --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/wwwroot/app.ts @@ -0,0 +1,653 @@ +(() => { + type Locale = string; + type TranslationMap = Record; + type StringsMap = Record; + + type AuditUsage = { + file: string; + line: number; + reason?: string; + expression?: string; + }; + + type AuditReport = { + usedKeysCount: number; + definedKeysCount: number; + missingKeys: string[]; + unusedKeys: string[]; + dynamicKeyUsages: AuditUsage[]; + dynamicKeyCandidates?: Record; + keyUsages: Record; + repoRoot: string; + }; + + type State = { + report: AuditReport | null; + strings: StringsMap; + locales: Locale[]; + selected: string | null; + realKeys: Set; + }; + + const PRIMARY_LOCALE = "en-US"; + const FALLBACK_LOCALES: Locale[] = [ + "da", + "de", + "en-GB", + "en-US", + "es-ES", + "fr", + "hr", + "it", + "lt", + "hu", + "nl", + "no", + "pl", + "pt-BR", + "ro", + "fi", + "sv-SE", + "vi", + "tr", + "cs", + "el", + "bg", + "ru", + "uk", + "hi", + "th", + "zh-CN", + "ja", + "zh-TW", + "ko", + ]; + + const storedScheme = localStorage.getItem("vscodeScheme"); + let vscodeScheme = + storedScheme === "vscode-insiders" || storedScheme === "vscode" + ? storedScheme + : navigator.userAgent.includes("Insider") + ? "vscode-insiders" + : "vscode"; + const REPO_BLOB_BASE = + "https://github.com/Aiko-IT-Systems/ScWikeloGrind/blob/main"; + + const state: State = { + report: null, + strings: {}, + locales: [], + selected: null, + realKeys: new Set(), + }; + + const el = (id: string) => document.getElementById(id)!; + const keyList = el("keyList"); + const details = el("detailsBody"); + const summary = el("summary"); + const scheme = el("scheme") as HTMLSelectElement; + const search = el("search") as HTMLInputElement; + const filter = el("filter") as HTMLSelectElement; + const downloadBtn = el("downloadStrings"); + const modalHost = el("modalHost"); + + el("refresh").addEventListener("click", refreshAll); + el("addKey").addEventListener("click", onAddKey); + el("beautify").addEventListener("click", beautify); + downloadBtn.addEventListener("click", downloadStrings); + scheme.addEventListener("change", () => { + vscodeScheme = scheme.value; + localStorage.setItem("vscodeScheme", vscodeScheme); + renderDetails(state.selected); + }); + + scheme.value = vscodeScheme; + search.addEventListener("input", renderKeyList); + filter.addEventListener("change", renderKeyList); + + async function loadAll() { + const [report, strings, locales] = await Promise.all([ + fetchJson("/api/report"), + fetchJson("/api/strings"), + fetchJson("/api/locales"), + ]); + + state.report = report; + state.realKeys = new Set(Object.keys(strings)); + state.strings = { ...strings }; + + const missingKeys = report?.missingKeys ?? []; + missingKeys.forEach((key) => { + if (!Object.prototype.hasOwnProperty.call(state.strings, key)) { + state.strings[key] = { [PRIMARY_LOCALE]: "" }; + } + }); + + state.locales = + locales && locales.length > 0 ? locales : FALLBACK_LOCALES; + summary.textContent = `Used ${report.usedKeysCount} / Defined ${report.definedKeysCount} | Missing ${report.missingKeys.length} | Unused ${report.unusedKeys.length} | Dynamic ${report.dynamicKeyUsages.length}`; + renderKeyList(); + renderDetails(state.selected); + } + + function renderKeyList() { + if (!state.report) return; + const query = search.value.toLowerCase(); + const filterValue = filter.value; + const expectedLocales = collectExpectedLocales(state.strings); + + const translationKeys = Object.keys(state.strings); + const missingKeys = state.report?.missingKeys ?? []; + const dynamicKeys = (state.report.dynamicKeyUsages || []) + .map((d) => keyFromDynamic(d)) + .filter((k) => k && k.includes(".")); + const keys = Array.from( + new Set([...translationKeys, ...dynamicKeys, ...missingKeys]) + ).sort((a, b) => a.localeCompare(b)); + + keyList.innerHTML = ""; + const template = document.getElementById( + "key-item-template" + ) as HTMLTemplateElement; + + keys.forEach((key) => { + const translations = state.strings[key] || {}; + const valuesText = Object.values(translations) + .join(" ") + .toLowerCase(); + if ( + query && + !key.toLowerCase().includes(query) && + !valuesText.includes(query) + ) + return; + + const isReportedMissing = missingKeys.includes(key); + const isDynamic = state.report!.dynamicKeyUsages.some( + (d) => keyFromDynamic(d) === key + ); + const isMissingKey = !isDynamic && (!state.realKeys.has(key) || isReportedMissing); + const isUnused = state.report!.unusedKeys.includes(key); + const primaryMissing = + !translations[PRIMARY_LOCALE] || + translations[PRIMARY_LOCALE].trim().length === 0; + const isMissingLocale = + !isMissingKey && + !isDynamic && + (primaryMissing || hasMissingLocale(translations, expectedLocales)); + const isDynamicBadge = isDynamic; + + if (filterValue === "kmissing" && !isMissingKey) return; + if (filterValue === "lmissing" && !isMissingLocale) return; + if (filterValue === "unused" && !isUnused) return; + if (filterValue === "dynamic" && !isDynamicBadge) return; + + const node = template.content.firstElementChild!.cloneNode( + true + ) as HTMLElement; + node.querySelector(".key-text")!.textContent = key; + const badges = node.querySelector(".badges")!; + badges.style.flexWrap = "wrap"; + if (isMissingKey) badges.append(makeBadge("Missing Key", "danger")); + if (isUnused) badges.append(makeBadge("Unused", "warn")); + if (!isMissingKey && isMissingLocale) + badges.append(makeBadge("Missing Locale", "danger")); + if (isDynamicBadge) badges.append(makeBadge("Dynamic", "success")); + node.addEventListener("click", () => renderDetails(key)); + if (state.selected === key) node.classList.add("selected"); + keyList.append(node); + }); + } + + function collectExpectedLocales(strings: StringsMap) { + const set = new Set(); + set.add(PRIMARY_LOCALE); + Object.values(strings).forEach((map) => { + Object.keys(map || {}).forEach((loc) => set.add(loc)); + }); + return set; + } + + function hasMissingLocale(translations: TranslationMap, expected: Set) { + for (const loc of expected) { + const text = translations?.[loc] ?? ""; + if (!text || text.trim().length === 0) return true; + } + return false; + } + + function renderDetails(key: string | null) { + state.selected = key; + if (!key) { + details.className = "details-empty"; + details.textContent = "Select a key to edit"; + return; + } + + const existsInStrings = state.realKeys.has(key); + const isDynamicKey = state.report?.dynamicKeyUsages.some( + (d) => keyFromDynamic(d) === key + ); + const dynamicCandidates = state.report?.dynamicKeyCandidates?.[key] ?? []; + const translations = state.strings[key] || { [PRIMARY_LOCALE]: "" }; + const locales = orderLocales(Object.keys(translations)); + + const wrapper = document.createElement("div"); + wrapper.className = "details"; + const title = document.createElement("div"); + title.innerHTML = `${key}${ + existsInStrings || isDynamicKey + ? "" + : ' Missing Key' + }`; + wrapper.append(title); + if (!existsInStrings && !isDynamicKey) { + const hint = document.createElement("div"); + hint.className = "details-empty"; + hint.textContent = + "This key is missing from strings.json. Fill in values and Save to create it."; + wrapper.append(hint); + } + + if (isDynamicKey) { + const dynNote = document.createElement("div"); + dynNote.className = "details-empty"; + dynNote.textContent = "Dynamic key: manage concrete keys instead."; + wrapper.append(dynNote); + } else { + const transWrap = document.createElement("div"); + transWrap.className = "translations"; + locales.forEach((locale) => { + transWrap.append(makeLocaleRow(locale, translations[locale] ?? "")); + }); + + const addLocaleRow = document.createElement("div"); + addLocaleRow.className = "translation-add-row"; + const addSelect = document.createElement("select"); + const availableLocales = orderLocales(state.locales); + const existing = new Set(locales); + const options = availableLocales.filter((l) => !existing.has(l)); + addSelect.innerHTML = `${options + .map((l) => ``) + .join("")}`; + const addButton = document.createElement("button"); + addButton.textContent = "Add locale"; + addButton.className = "secondary"; + addButton.onclick = () => { + const loc = addSelect.value; + if (!loc) return; + if (transWrap.querySelector(`[data-locale="${loc}"]`)) return; + transWrap.insertBefore(makeLocaleRow(loc, ""), addLocaleRow); + addSelect.value = ""; + }; + const actions = document.createElement("div"); + actions.className = "footer-actions"; + const saveBtn = document.createElement("button"); + saveBtn.textContent = "Save"; + saveBtn.classList.add("success"); + saveBtn.onclick = () => saveKey(key, transWrap); + const deleteBtn = document.createElement("button"); + deleteBtn.textContent = "Delete"; + deleteBtn.className = "danger"; + deleteBtn.onclick = () => deleteKey(key); + actions.append(saveBtn, deleteBtn); + addLocaleRow.append(addSelect, addButton, actions); + transWrap.append(addLocaleRow); + wrapper.append(transWrap); + } + + const usageSection = document.createElement("div"); + usageSection.className = "usage"; + usageSection.innerHTML = "Usages"; + const usageList = document.createElement("div"); + const usages = state.report?.keyUsages[key] || []; + const dynUsages = (state.report?.dynamicKeyUsages || []).filter( + (d) => keyFromDynamic(d) === key + ); + const usageTemplate = document.getElementById( + "usage-row-template" + ) as HTMLTemplateElement; + const repoRoot = (state.report?.repoRoot || "").replace(/\\/g, "/"); + if (usages.length > 0) { + usages.forEach((u) => { + const row = usageTemplate.content.firstElementChild!.cloneNode( + true + ) as HTMLElement; + row.querySelector( + ".usage-file" + )!.textContent = `${u.file} : ${u.line}`; + const fullPath = `${repoRoot}/${u.file}`.replace(/\\/g, "/"); + const openBtn = ensureUsageOpen(row); + openBtn.onclick = () => { + const codeUrl = `${vscodeScheme}://file/${encodeURI( + fullPath + )}:${u.line}`; + window.open(codeUrl, "_blank"); + }; + const gitBtn = + row.querySelector(".usage-copy")!; + const gitUrl = `${REPO_BLOB_BASE}/${u.file.replace( + /\\/g, + "/" + )}#L${u.line}`; + gitBtn.onclick = () => copyText(gitUrl, "Git link copied"); + usageList.append(row); + }); + } + if (dynUsages.length > 0) { + dynUsages.forEach((d) => { + const row = usageTemplate.content.firstElementChild!.cloneNode( + true + ) as HTMLElement; + row.querySelector(".usage-file")!.textContent = `${ + d.file + } : ${d.line} (dynamic: ${d.reason ?? ""})`; + const fullPath = `${repoRoot}/${d.file}`.replace(/\\/g, "/"); + const openBtn = ensureUsageOpen(row); + openBtn.onclick = () => { + const codeUrl = `${vscodeScheme}://file/${encodeURI( + fullPath + )}:${d.line}`; + window.open(codeUrl, "_blank"); + }; + const gitBtn = + row.querySelector(".usage-copy")!; + const gitUrl = `${REPO_BLOB_BASE}/${d.file.replace( + /\\/g, + "/" + )}#L${d.line}`; + gitBtn.onclick = () => copyText(gitUrl, "Git link copied"); + usageList.append(row); + }); + } + if (usages.length === 0 && dynUsages.length === 0) { + const none = document.createElement("div"); + none.textContent = "No usages found."; + none.className = "details-empty"; + usageList.append(none); + } + usageSection.append(usageList); + wrapper.append(usageSection); + + if (isDynamicKey && dynamicCandidates.length > 0) { + const candidateSection = document.createElement("div"); + candidateSection.className = "usage"; + candidateSection.innerHTML = "Candidate keys"; + const candidateList = document.createElement("div"); + candidateList.className = "candidate-list"; + dynamicCandidates.forEach((cand) => { + const row = document.createElement("div"); + row.className = "details-empty"; + row.textContent = cand; + row.style.cursor = "pointer"; + row.onclick = () => renderDetails(cand); + candidateList.append(row); + }); + candidateSection.append(candidateList); + wrapper.append(candidateSection); + } + + details.className = ""; + details.innerHTML = ""; + details.append(wrapper); + } + + function makeLocaleRow(locale: string, value: string) { + const row = document.createElement("div"); + row.className = "translation-row"; + row.dataset.locale = locale; + const input = document.createElement("input"); + input.value = locale; + input.disabled = true; + const textarea = document.createElement("textarea"); + textarea.value = value ?? ""; + autosizeText(textarea); + textarea.addEventListener("input", () => autosizeText(textarea)); + const remove = document.createElement("button"); + remove.textContent = "Remove"; + remove.className = "danger"; + remove.onclick = () => { + if (locale === PRIMARY_LOCALE) return; + row.remove(); + }; + if (locale === PRIMARY_LOCALE) { + remove.disabled = true; + remove.textContent = "Primary"; + } + row.append(input, textarea, remove); + return row; + } + + function autosizeText(textarea: HTMLTextAreaElement) { + textarea.style.height = "auto"; + textarea.style.height = `${textarea.scrollHeight}px`; + } + + function orderLocales(locales: string[]) { + return [...locales].sort((a, b) => { + if (a === b) return 0; + if (a === PRIMARY_LOCALE) return -1; + if (b === PRIMARY_LOCALE) return 1; + return a.localeCompare(b); + }); + } + + async function saveKey(key: string, transWrap: HTMLElement) { + const ok = await confirmModal( + "Save changes?", + "This will write updates to strings.json." + ); + if (!ok) return; + const translations: TranslationMap = {}; + transWrap + .querySelectorAll(".translation-row") + .forEach((row) => { + const loc = row.dataset.locale; + const text = ( + row.querySelector("textarea") as HTMLTextAreaElement + ).value; + if (loc) translations[loc] = text; + }); + const res = await fetch(`/api/strings/${encodeURIComponent(key)}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ key, translations }), + }); + if (!res.ok) { + const msg = await readError(res); + alert(`Save failed: ${msg}`); + return; + } + await loadAll(); + renderDetails(key); + showToast("Saved"); + } + + async function deleteKey(key: string) { + const ok = await confirmModal( + "Delete key?", + `This removes '${key}' from strings.json.` + ); + if (!ok) return; + const res = await fetch(`/api/strings/${encodeURIComponent(key)}`, { + method: "DELETE", + }); + if (!res.ok) { + const msg = await readError(res); + alert(`Delete failed: ${msg}`); + return; + } + await loadAll(); + renderDetails(null); + showToast("Deleted"); + } + + async function onAddKey() { + const key = prompt("New translation key"); + if (!key) return; + const res = await fetch("/api/strings", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + key, + translations: { [PRIMARY_LOCALE]: "" }, + }), + }); + if (!res.ok) { + const msg = await readError(res); + alert(`Add failed: ${msg}`); + return; + } + await loadAll(); + renderDetails(key); + } + + async function beautify() { + const ok = await confirmModal( + "Beautify strings.json?", + "This will rewrite strings.json with formatting." + ); + if (!ok) return; + const res = await fetch("/api/strings/format", { method: "POST" }); + if (!res.ok) { + const msg = await readError(res); + alert(`Beautify failed: ${msg}`); + return; + } + await loadAll(); + showToast("Formatted"); + } + + function makeBadge(text: string, tone: "warn" | "danger" | "success") { + const span = document.createElement("span"); + span.className = `badge ${tone}`; + span.textContent = text; + return span; + } + + function keyFromDynamic(dyn: AuditUsage | null | undefined) { + if (!dyn) return ""; + const raw = dyn.expression || ""; + const normalized = normalizeDynamicKey(raw); + return normalized || raw || ""; + } + + function normalizeDynamicKey(expr: string) { + let s = expr.trim(); + if (s.startsWith("@$")) s = s.slice(2); + else if (s.startsWith("$")) s = s.slice(1); + s = s.replace(/^"/, "").replace(/"$/, ""); + const brace = s.indexOf("{"); + if (brace >= 0) s = s.slice(0, brace); + return s.trim(); + } + + function ensureUsageOpen(row: HTMLElement) { + let btn = row.querySelector(".usage-open"); + if (!btn) { + btn = document.createElement("button"); + btn.className = "usage-open"; + btn.textContent = "Open in VS Code"; + const actions = + row.querySelector(".usage-actions") || row; + actions.prepend(btn); + } + return btn; + } + + async function refreshAll() { + try { + await loadAll(); + showToast("Refreshed"); + } catch (err: unknown) { + const message = (err as Error)?.message || String(err); + alert(`Refresh failed: ${message}`); + } + } + + async function downloadStrings() { + try { + const res = await fetch("/api/strings"); + if (!res.ok) throw new Error(await readError(res)); + const data = await res.json(); + const blob = new Blob([JSON.stringify(data, null, 2)], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "strings.json"; + a.click(); + URL.revokeObjectURL(url); + showToast("Downloaded strings.json"); + } catch (err: unknown) { + alert(`Download failed: ${err}`); + } + } + + function copyText(text: string, toastMessage: string) { + navigator.clipboard + ?.writeText(text) + .then(() => showToast(toastMessage)); + } + + function showToast(message: string) { + const toast = document.createElement("div"); + toast.className = "toast"; + toast.textContent = message; + document.body.append(toast); + setTimeout(() => toast.remove(), 2000); + } + + function confirmModal(title: string, message: string) { + return new Promise((resolve) => { + const backdrop = document.createElement("div"); + backdrop.className = "modal-backdrop"; + const modal = document.createElement("div"); + modal.className = "modal"; + modal.innerHTML = `

${title}

${message}

`; + const actions = document.createElement("div"); + actions.className = "modal-actions"; + const cancel = document.createElement("button"); + cancel.className = "ghost"; + cancel.textContent = "Cancel"; + const ok = document.createElement("button"); + ok.className = "danger"; + ok.textContent = "Confirm"; + cancel.onclick = () => { + backdrop.remove(); + resolve(false); + }; + ok.onclick = () => { + backdrop.remove(); + resolve(true); + }; + actions.append(cancel, ok); + modal.append(actions); + backdrop.append(modal); + modalHost.append(backdrop); + }); + } + + async function fetchJson(url: string) { + const res = await fetch(url); + if (!res.ok) { + const msg = await readError(res); + throw new Error(msg); + } + return res.json() as Promise; + } + + async function readError(res: Response) { + try { + const data = await res.json(); + if ((data as { message?: string })?.message) + return (data as { message: string }).message; + return `${res.status} ${res.statusText}`; + } catch { + return `${res.status} ${res.statusText}`; + } + } + + loadAll(); +})(); diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/jsconfig.json b/DisCatSharp.Extensions.Translations.Manager/wwwroot/jsconfig.json new file mode 100644 index 0000000..6cace4e --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/wwwroot/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "checkJs": false + }, + "exclude": [ + "app.js", + "app.js.map" + ] +} diff --git a/DisCatSharp.Extensions.Translations.Manager/wwwroot/tsconfig.json b/DisCatSharp.Extensions.Translations.Manager/wwwroot/tsconfig.json new file mode 100644 index 0000000..5aafd6d --- /dev/null +++ b/DisCatSharp.Extensions.Translations.Manager/wwwroot/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "none", + "outFile": "app.js", + "lib": ["DOM", "ES2020"], + "strict": true, + "noEmitOnError": true, + "moduleResolution": "classic", + "allowJs": false, + "removeComments": true, + "forceConsistentCasingInFileNames": true, + "sourceMap": true, + "inlineSources": true, + "sourceRoot": "http://localhost:5000/" + }, + "files": ["app.ts"] +} From 06a108de8de4d382329065f35134bb023f7e2531 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Sun, 1 Mar 2026 18:47:08 +0100 Subject: [PATCH 3/6] chore(deps): bump dcs min version --- .../DisCatSharp.Extensions.OAuth2Web.csproj | 2 +- .../DisCatSharp.Extensions.SimpleMusicCommands.csproj | 8 ++++---- .../DisCatSharp.Extensions.TwoFactorCommands.csproj | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj b/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj index b274fb4..8cb9165 100644 --- a/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj +++ b/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj @@ -42,7 +42,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj b/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj index 25713e9..7f56423 100644 --- a/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj +++ b/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj @@ -24,17 +24,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + diff --git a/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj b/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj index dc2627b..e94b93c 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj +++ b/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj @@ -29,16 +29,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + From 90795b0a4503bac64105d9b3e392c0ab9cab74e3 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Sun, 1 Mar 2026 18:47:20 +0100 Subject: [PATCH 4/6] chore(deps): bump dcs min version 2 --- ...Sharp.Extensions.Translations.ApplicationCommands.csproj | 2 +- .../DisCatSharp.Extensions.Translations.CommandsNext.csproj | 4 ++-- .../DisCatSharp.Extensions.Translations.Manager.csproj | 6 +++--- .../DisCatSharp.Extensions.Translations.csproj | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DisCatSharp.Extensions.Translations.ApplicationCommands/DisCatSharp.Extensions.Translations.ApplicationCommands.csproj b/DisCatSharp.Extensions.Translations.ApplicationCommands/DisCatSharp.Extensions.Translations.ApplicationCommands.csproj index 26a33d2..77272a2 100644 --- a/DisCatSharp.Extensions.Translations.ApplicationCommands/DisCatSharp.Extensions.Translations.ApplicationCommands.csproj +++ b/DisCatSharp.Extensions.Translations.ApplicationCommands/DisCatSharp.Extensions.Translations.ApplicationCommands.csproj @@ -24,7 +24,7 @@ - + diff --git a/DisCatSharp.Extensions.Translations.CommandsNext/DisCatSharp.Extensions.Translations.CommandsNext.csproj b/DisCatSharp.Extensions.Translations.CommandsNext/DisCatSharp.Extensions.Translations.CommandsNext.csproj index 23f76ad..2b1924b 100644 --- a/DisCatSharp.Extensions.Translations.CommandsNext/DisCatSharp.Extensions.Translations.CommandsNext.csproj +++ b/DisCatSharp.Extensions.Translations.CommandsNext/DisCatSharp.Extensions.Translations.CommandsNext.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/DisCatSharp.Extensions.Translations.Manager/DisCatSharp.Extensions.Translations.Manager.csproj b/DisCatSharp.Extensions.Translations.Manager/DisCatSharp.Extensions.Translations.Manager.csproj index c445bce..a87d6e3 100644 --- a/DisCatSharp.Extensions.Translations.Manager/DisCatSharp.Extensions.Translations.Manager.csproj +++ b/DisCatSharp.Extensions.Translations.Manager/DisCatSharp.Extensions.Translations.Manager.csproj @@ -29,14 +29,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -64,4 +64,4 @@ PackagePath="wwwroot/%(RecursiveDir)%(Filename)%(Extension)" /> -
\ No newline at end of file + diff --git a/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj b/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj index a1848d4..586ee1e 100644 --- a/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj +++ b/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj @@ -25,7 +25,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -33,7 +33,7 @@ - + From 6c48ef0bb8bf3fd76be730f9a1bc3d345abd0510 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Sun, 1 Mar 2026 18:56:03 +0100 Subject: [PATCH 5/6] chore(deps): adjust other deps to core required versions --- .../DisCatSharp.Extensions.OAuth2Web.csproj | 12 ++++++------ ...DisCatSharp.Extensions.SimpleMusicCommands.csproj | 10 +++++----- .../DisCatSharp.Extensions.TwoFactorCommands.csproj | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj b/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj index 8cb9165..5fd60f9 100644 --- a/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj +++ b/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj @@ -1,4 +1,4 @@ - + @@ -37,7 +37,7 @@ - + @@ -48,10 +48,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + diff --git a/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj b/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj index 7f56423..5cb1921 100644 --- a/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj +++ b/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj @@ -1,4 +1,4 @@ - + @@ -29,15 +29,15 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + diff --git a/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj b/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj index e94b93c..33871c1 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj +++ b/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj @@ -1,4 +1,4 @@ - + @@ -24,7 +24,7 @@ - + @@ -40,8 +40,8 @@ - - + + From 1ec114fe5d691d2f86b3e4d3e32db36cfbcdeff5 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Sun, 1 Mar 2026 18:57:29 +0100 Subject: [PATCH 6/6] chore(deps): adjust other deps to core required versions 2 --- .../DisCatSharp.Extensions.OAuth2Web.csproj | 2 +- .../DisCatSharp.Extensions.SimpleMusicCommands.csproj | 2 +- .../DisCatSharp.Extensions.Translations.csproj | 10 +++++----- .../DisCatSharp.Extensions.TwoFactorCommands.csproj | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj b/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj index 5fd60f9..436e731 100644 --- a/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj +++ b/DisCatSharp.Extensions.OAuth2Web/DisCatSharp.Extensions.OAuth2Web.csproj @@ -56,7 +56,7 @@ - + diff --git a/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj b/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj index 5cb1921..7fbe4f5 100644 --- a/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj +++ b/DisCatSharp.Extensions.SimpleMusicCommands/DisCatSharp.Extensions.SimpleMusicCommands.csproj @@ -43,7 +43,7 @@ - + diff --git a/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj b/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj index 586ee1e..4bebcb9 100644 --- a/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj +++ b/DisCatSharp.Extensions.Translations/DisCatSharp.Extensions.Translations.csproj @@ -1,4 +1,4 @@ - + @@ -30,13 +30,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + diff --git a/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj b/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj index 33871c1..92e2118 100644 --- a/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj +++ b/DisCatSharp.Extensions.TwoFactorCommands/DisCatSharp.Extensions.TwoFactorCommands.csproj @@ -49,7 +49,7 @@ - +