Skip to content

Commit 4418988

Browse files
HandyS11claude
andauthored
refactor: consolidate localization into shared .resx + duplication quick-wins (#26)
* style: harden editorconfig style rules from suggestion to warning Promote dotnet/csharp style + naming rules from :suggestion to :warning so redundant style smells fail the build instead of accumulating silently. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: resolve build errors surfaced by editorconfig hardening Promoting style rules to :warning under TreatWarningsAsErrors surfaced 5 latent violations. Two clean fixes (IDE0130 test namespace, IDE0028 collection expressions); IDE0060 folded into the existing MapIcons RCS1163 suppression. IDE0045 (AlarmEmbedRenderer) and IDE0290 (PairingSupervisor.Handle) are suppressed at the line with documented justification: their "fixes" conflict with RCS1238/S3358 (nested ternary) and CA2000 (CTS on a primary ctor). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(localization): add shared Strings.resx (en/fr) generated from catalogs 193 keys, globally unique across all 6 feature catalogs, generated by reflection from the compiled *LocalizationCatalog.Default values so emoji/accents/{0} are byte-exact. NeutralResourcesLanguage("en") makes the neutral set the English fallback; verified ResourceManager round-trip resolves en/fr, falls back de->en, and walks fr-FR->fr. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(localization): add ResxLocalizer + AddRustPlusBotLocalization DI extension ResxLocalizer resolves per-call culture via ResourceManager.GetString over the shared Strings resource set, preserving the ILocalizer contract: en fallback, fr-FR->fr normalization, key returned when missing, per-culture format provider. AddRustPlusBotLocalization registers it idempotently (TryAddSingleton). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(localization): add en/fr resource parity + key-count guard Enumerates the neutral (en, invariant-keyed) and fr resource sets directly so a dropped or renamed key fails the build. Asserts 193 keys with matching sets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(commands): use shared ILocalizer/ResxLocalizer, drop command catalog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(workspace): use shared ILocalizer/ResxLocalizer, drop workspace catalog * refactor(players): use shared ILocalizer/ResxLocalizer, drop player catalog * refactor(events): use shared ILocalizer/ResxLocalizer, drop event catalog * refactor(switches): use shared ILocalizer/ResxLocalizer, drop switch catalog * refactor(alarms): use shared ILocalizer/ResxLocalizer, drop alarm catalog * refactor(localization): remove obsolete DictionaryLocalizer All 6 features now resolve the shared ILocalizer via ResxLocalizer, so the dictionary-backed implementation and its tests are dead code. Behavioral equivalence with the old per-feature catalogs spot-checked across feature prefixes in both cultures (emoji, accents, placeholders byte-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(commands): share marker-event reply helper across cargo/heli/chinook The cargo/heli/chinook handlers differed only in MarkerKind and key prefix. Extract MarkerReply.For(state, context, kind, prefix, localizer, clock), mirroring the existing RigReply helper; each handler now delegates in one call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 019d717 commit 4418988

112 files changed

Lines changed: 1659 additions & 1157 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 67 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ trim_trailing_whitespace = true
2020
[*.cs]
2121

2222
# Organize usings
23-
dotnet_separate_import_directive_groups = false:suggestion
24-
dotnet_sort_system_directives_first = false:suggestion
25-
csharp_using_directive_placement = outside_namespace:suggestion
23+
dotnet_separate_import_directive_groups = false:warning
24+
dotnet_sort_system_directives_first = false:warning
25+
csharp_using_directive_placement = outside_namespace:warning
2626
file_header_template = unset
2727

2828
# this. and Me. preferences
@@ -45,40 +45,40 @@ dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:sil
4545
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
4646

4747
# Expression-level preferences
48-
dotnet_style_coalesce_expression = true:suggestion
49-
dotnet_style_collection_initializer = true:suggestion
50-
dotnet_style_explicit_tuple_names = true:suggestion
51-
dotnet_style_namespace_match_folder = true:suggestion
52-
dotnet_style_null_propagation = true:suggestion
53-
dotnet_style_object_initializer = true:suggestion
48+
dotnet_style_coalesce_expression = true:warning
49+
dotnet_style_collection_initializer = true:warning
50+
dotnet_style_explicit_tuple_names = true:warning
51+
dotnet_style_namespace_match_folder = true:warning
52+
dotnet_style_null_propagation = true:warning
53+
dotnet_style_object_initializer = true:warning
5454
dotnet_style_operator_placement_when_wrapping = beginning_of_line
55-
dotnet_style_prefer_auto_properties = true:suggestion
56-
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
57-
dotnet_style_prefer_compound_assignment = true:suggestion
58-
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
55+
dotnet_style_prefer_auto_properties = true:warning
56+
dotnet_style_prefer_collection_expression = when_types_loosely_match:warning
57+
dotnet_style_prefer_compound_assignment = true:warning
58+
dotnet_style_prefer_conditional_expression_over_assignment = true:warning
5959
dotnet_style_prefer_conditional_expression_over_return = true:silent
60-
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion
61-
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
62-
dotnet_style_prefer_inferred_tuple_names = true:suggestion
60+
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:warning
61+
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
62+
dotnet_style_prefer_inferred_tuple_names = true:warning
6363
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
64-
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
65-
dotnet_style_prefer_simplified_interpolation = true:suggestion
64+
dotnet_style_prefer_simplified_boolean_expressions = true:warning
65+
dotnet_style_prefer_simplified_interpolation = true:warning
6666

6767
# Field preferences
6868
dotnet_style_readonly_field = true:warning
6969

7070
# Parameter preferences
71-
dotnet_code_quality_unused_parameters = all:suggestion
71+
dotnet_code_quality_unused_parameters = all:warning
7272

7373
# Suppression preferences
7474
dotnet_remove_unnecessary_suppression_exclusions = none
7575

7676
#### C# Coding Conventions ####
7777

7878
# var preferences
79-
csharp_style_var_elsewhere = true:suggestion
80-
csharp_style_var_for_built_in_types = true:suggestion
81-
csharp_style_var_when_type_is_apparent = true:suggestion
79+
csharp_style_var_elsewhere = true:warning
80+
csharp_style_var_for_built_in_types = true:warning
81+
csharp_style_var_when_type_is_apparent = true:warning
8282

8383
# Expression-bodied members
8484
csharp_style_expression_bodied_accessors = true:silent
@@ -91,44 +91,44 @@ csharp_style_expression_bodied_operators = false:silent
9191
csharp_style_expression_bodied_properties = true:silent
9292

9393
# Pattern matching preferences
94-
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
95-
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
96-
csharp_style_prefer_extended_property_pattern = true:suggestion
97-
csharp_style_prefer_not_pattern = true:suggestion
94+
csharp_style_pattern_matching_over_as_with_null_check = true:warning
95+
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
96+
csharp_style_prefer_extended_property_pattern = true:warning
97+
csharp_style_prefer_not_pattern = true:warning
9898
csharp_style_prefer_pattern_matching = true:silent
99-
csharp_style_prefer_switch_expression = true:suggestion
99+
csharp_style_prefer_switch_expression = true:warning
100100

101101
# Null-checking preferences
102-
csharp_style_conditional_delegate_call = true:suggestion
102+
csharp_style_conditional_delegate_call = true:warning
103103

104104
# Modifier preferences
105-
csharp_prefer_static_anonymous_function = true:suggestion
105+
csharp_prefer_static_anonymous_function = true:warning
106106
csharp_prefer_static_local_function = true:warning
107-
csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async:suggestion
108-
csharp_style_prefer_readonly_struct = true:suggestion
109-
csharp_style_prefer_readonly_struct_member = true:suggestion
107+
csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async:warning
108+
csharp_style_prefer_readonly_struct = true:warning
109+
csharp_style_prefer_readonly_struct_member = true:warning
110110

111111
# Code-block preferences
112-
csharp_prefer_braces = true:suggestion
113-
csharp_prefer_simple_using_statement = true:suggestion
114-
csharp_style_namespace_declarations = file_scoped:suggestion
112+
csharp_prefer_braces = true:warning
113+
csharp_prefer_simple_using_statement = true:warning
114+
csharp_style_namespace_declarations = file_scoped:warning
115115
csharp_style_prefer_method_group_conversion = true:silent
116-
csharp_style_prefer_primary_constructors = true:suggestion
116+
csharp_style_prefer_primary_constructors = true:warning
117117
csharp_style_prefer_top_level_statements = true:silent
118118

119119
# Expression-level preferences
120-
csharp_prefer_simple_default_expression = true:suggestion
121-
csharp_style_deconstructed_variable_declaration = true:suggestion
122-
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
123-
csharp_style_inlined_variable_declaration = true:suggestion
124-
csharp_style_prefer_index_operator = true:suggestion
125-
csharp_style_prefer_local_over_anonymous_function = true:suggestion
126-
csharp_style_prefer_null_check_over_type_check = true:suggestion
127-
csharp_style_prefer_range_operator = true:suggestion
128-
csharp_style_prefer_tuple_swap = true:suggestion
129-
csharp_style_prefer_utf8_string_literals = true:suggestion
130-
csharp_style_throw_expression = true:suggestion
131-
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
120+
csharp_prefer_simple_default_expression = true:warning
121+
csharp_style_deconstructed_variable_declaration = true:warning
122+
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
123+
csharp_style_inlined_variable_declaration = true:warning
124+
csharp_style_prefer_index_operator = true:warning
125+
csharp_style_prefer_local_over_anonymous_function = true:warning
126+
csharp_style_prefer_null_check_over_type_check = true:warning
127+
csharp_style_prefer_range_operator = true:warning
128+
csharp_style_prefer_tuple_swap = true:warning
129+
csharp_style_prefer_utf8_string_literals = true:warning
130+
csharp_style_throw_expression = true:warning
131+
csharp_style_unused_value_assignment_preference = discard_variable:warning
132132
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
133133

134134
#### C# Formatting Rules ####
@@ -182,79 +182,79 @@ csharp_preserve_single_line_statements = false
182182

183183
# Naming rules
184184

185-
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
185+
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = warning
186186
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
187187
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
188188

189-
dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
189+
dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = warning
190190
dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
191191
dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
192192

193-
dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
193+
dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = warning
194194
dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
195195
dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
196196

197-
dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
197+
dotnet_naming_rule.methods_should_be_pascalcase.severity = warning
198198
dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
199199
dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
200200

201-
dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
201+
dotnet_naming_rule.properties_should_be_pascalcase.severity = warning
202202
dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
203203
dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
204204

205-
dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
205+
dotnet_naming_rule.events_should_be_pascalcase.severity = warning
206206
dotnet_naming_rule.events_should_be_pascalcase.symbols = events
207207
dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
208208

209-
dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
209+
dotnet_naming_rule.local_variables_should_be_camelcase.severity = warning
210210
dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
211211
dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
212212

213-
dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
213+
dotnet_naming_rule.local_constants_should_be_camelcase.severity = warning
214214
dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
215215
dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
216216

217-
dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
217+
dotnet_naming_rule.parameters_should_be_camelcase.severity = warning
218218
dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
219219
dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
220220

221-
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
221+
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = warning
222222
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
223223
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
224224

225-
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
225+
dotnet_naming_rule.private_fields_should_be__camelcase.severity = warning
226226
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
227227
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
228228

229-
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
229+
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = warning
230230
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
231231
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = _camelcase
232232

233-
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
233+
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = warning
234234
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
235235
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
236236

237-
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
237+
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = warning
238238
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
239239
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
240240

241-
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
241+
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = warning
242242
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
243243
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
244244

245-
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
245+
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = warning
246246
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
247247
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
248248

249-
dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
249+
dotnet_naming_rule.enums_should_be_pascalcase.severity = warning
250250
dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
251251
dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
252252

253-
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
253+
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = warning
254254
dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
255255
dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
256256

257-
dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
257+
dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = warning
258258
dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
259259
dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
260260

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<PackageVersion Include="Microsoft.AspNetCore.DataProtection" Version="10.0.9" />
99
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9" />
1010
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.9" />
11+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
1112
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.9" />
1213
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.9" />
1314
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />

src/RustPlusBot.Features.Alarms/AlarmServiceCollectionExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using RustPlusBot.Features.Alarms.Posting;
66
using RustPlusBot.Features.Alarms.Relaying;
77
using RustPlusBot.Features.Alarms.Rendering;
8+
using RustPlusBot.Localization;
89

910
namespace RustPlusBot.Features.Alarms;
1011

@@ -18,7 +19,7 @@ public static IServiceCollection AddAlarms(this IServiceCollection services)
1819
{
1920
ArgumentNullException.ThrowIfNull(services);
2021

21-
services.AddSingleton<IAlarmLocalizer>(new AlarmLocalizer(AlarmLocalizationCatalog.Default));
22+
services.AddRustPlusBotLocalization();
2223
services.AddSingleton<AlarmEmbedRenderer>();
2324
services.AddSingleton<IAlarmChannelPoster, DiscordAlarmChannelPoster>();
2425
services.AddSingleton<IAlarmRefresher, AlarmRefresher>();

src/RustPlusBot.Features.Alarms/Relaying/AlarmStateRelay.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using RustPlusBot.Features.Alarms.Rendering;
88
using RustPlusBot.Features.Connections.Listening;
99
using RustPlusBot.Features.Workspace.Locating;
10+
using RustPlusBot.Localization;
1011
using RustPlusBot.Persistence.Alarms;
1112
using RustPlusBot.Persistence.Connections;
1213
using RustPlusBot.Persistence.Workspace;
@@ -36,7 +37,7 @@ internal sealed partial class AlarmStateRelay(
3637
IServiceScopeFactory scopeFactory,
3738
IAlarmRefresher refresher,
3839
AlarmRelayChannels channels,
39-
IAlarmLocalizer localizer,
40+
ILocalizer localizer,
4041
IClock clock,
4142
ILogger<AlarmStateRelay> logger)
4243
{

src/RustPlusBot.Features.Alarms/Rendering/AlarmEmbedRenderer.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
using Discord;
33
using RustPlusBot.Abstractions.Time;
44
using RustPlusBot.Domain.Alarms;
5+
using RustPlusBot.Localization;
56

67
namespace RustPlusBot.Features.Alarms.Rendering;
78

89
/// <summary>Renders a Smart Alarm as a Discord embed + control row, and the pairing-prompt embed + row. Pure.</summary>
910
/// <param name="localizer">The alarm localizer.</param>
1011
/// <param name="clock">The clock used to compute relative trigger times.</param>
11-
internal sealed class AlarmEmbedRenderer(IAlarmLocalizer localizer, IClock clock)
12+
internal sealed class AlarmEmbedRenderer(ILocalizer localizer, IClock clock)
1213
{
1314
/// <summary>Renders the alarm embed and its control buttons.</summary>
1415
/// <param name="alarm">The alarm to render.</param>
@@ -20,6 +21,7 @@ internal sealed class AlarmEmbedRenderer(IAlarmLocalizer localizer, IClock clock
2021
ArgumentNullException.ThrowIfNull(alarm);
2122

2223
string statusKey;
24+
#pragma warning disable IDE0045 // Collapsing to a nested ternary trips RCS1238/S3358 (nested conditional); the if/else chain is intentional.
2325
if (unreachable)
2426
{
2527
statusKey = "alarm.status.unreachable";
@@ -32,6 +34,7 @@ internal sealed class AlarmEmbedRenderer(IAlarmLocalizer localizer, IClock clock
3234
{
3335
statusKey = "alarm.status.armed";
3436
}
37+
#pragma warning restore IDE0045
3538

3639
var triggered = alarm.LastTriggeredUtc is { } t
3740
? localizer.Get("alarm.embed.lasttriggered", culture, CompactDuration(clock.UtcNow - t))

0 commit comments

Comments
 (0)