2

In order to allow extracting all translatable messages from the source code, we want to use a specific 'semantic' Message class. Only Messages can be presented to the UI. This way we want to prevent too many untranslated string ending up in the UI.

But an occurence like Message.Create("a" + x) can not be extracted by the gettext extraction engine.

So is there a way in C# to assure that my function (GetText._ or the like) is only called with string literals?

Or should I revert to preprocessing somehow? Are there any libraries/tools that can do this out there?

xtofl
  • 40,723
  • 12
  • 105
  • 192
  • As opposed to what exactly? – Manfred Radlwimmer Apr 04 '18 at 07:22
  • @TheGeneral I'll elaborate on the 'why' – xtofl Apr 04 '18 at 07:24
  • What is the signature of your function? Your question only makes sense if you have *object* as function parameter, otherwise just accept *String*. – Rob Apr 04 '18 at 07:25
  • 2
    Maybe you should use enums, at least your weird requirement is clearly a [xy-problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Tim Schmelter Apr 04 '18 at 07:25
  • 1
    I'm voting to close this question as off-topic because this is clearly a xy-problem but OP hasn't provided enough details to suggest a different approach – Tim Schmelter Apr 04 '18 at 07:27
  • What still baffles me is why you would want to single out string **literals** only. How is the *"Why"* elaboration coming along? – Manfred Radlwimmer Apr 04 '18 at 07:29
  • Are you looking for a compile time restriction? If you are, the answer is no. – InBetween Apr 04 '18 at 07:34
  • @InBetween compile time would be great. Load time or run time are possible, too. AST manipulation, ... anything that does the job. – xtofl Apr 04 '18 at 07:38
  • Possible dupe, at least related, [here](https://stackoverflow.com/questions/31035933/check-if-a-string-is-a-literal-string-known-at-compile-time). – Rawling Apr 04 '18 at 07:41
  • 3
    "_extracting all translatable messages_" how about storing these messages in Resource file? You have them all in one place and translation requires changing only 1 line of code... – FCin Apr 04 '18 at 07:46
  • @FCin: See the comments on my answer. The OP appears to dislike the centralized approach :( – Jon Skeet Apr 04 '18 at 09:38

1 Answers1

2

There's nothing in the language to support an argument having to be a string literal, no. I think that would be an odd language "feature", personally.

What I would advise instead is to create a MessageTemplate class or something like that, and change your method to accept a MessageTemplate parameter.

The MessageTemplate class can then have a specific set of instances, which would contain all the strings you need. For example:

public sealed class MessageTemplate
{
    public static MessageTemplate InvalidFilter { get; } =
        new MessageTemplate("invalid_filter");

    public static MessageTemplate Success { get; } =
        new MessageTemplate("success");

    public string Name { get; }

    private MessageTemplate(string name) => Name = name;
}

Then the method calls would be like this:

Message.Create(MessageTemplate.Success);

Advantages:

  • The valid strings are all in a single place, rather than scattered through the codebase
  • The calling code can use a useful name (e.g. "Success") even if the underlying string name is less useful (e.g. "x"), which makes it easier to read
  • You can pass MessageTemplate references around safely, knowing that they are message templates rather than just arbitrary strings

If you really wanted to only accept string literals, your best bet would probably be to create an attribute for this purpose, and then write a Roslyn analyzer. That would check every method invocation, and validate that if the parameter had the attribute, the argument was a string literal.

Personally I think that has the undesirable effect of scattering these magic, brittle string constants all over the codebase, but it's the closest you'll get to the exact behavior you've requested. It will only have an effect when building with Roslyn, of course.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    *I think that would be an odd language "feature", personally* - exercise to the user: use Roslyn to alter the C# language so `string` literals *are* different somehow... – Rawling Apr 04 '18 at 07:45
  • 2
    @Rawling: I suspect it wouldn't be too hard to write a Roslyn analyzer so that you could add an attribute to a method parameter, and then check all calls to the method, raising a warning if the corresponding argument wasn't a string literal. I prefer the answer I've given here for this situation, of course :) – Jon Skeet Apr 04 '18 at 07:53
  • This is obviously a better approach compared to scattering calls with magic strings all over your code but its still punting the problem. At the end, nothing, except code review or some preprocessing tool, is assuring you that whatever message your helper class is returning is in fact a string literal. – InBetween Apr 04 '18 at 07:58
  • Instead of trying to enforce this weird rule I would look at a different solution to the problem. What happens when one of the messages has to be written based on a condition? – FCin Apr 04 '18 at 08:13
  • @InBetween: My understanding of the problem is not the fact that it's a string literal which is important, so much as that it's only a fixed set of strings that will be accepted. The template class enforces that only that fixed set of strings is used. Yes, that class needs reviewing to make sure the strings are correct, but that's all. – Jon Skeet Apr 04 '18 at 08:17
  • @FCin: Then you pick one `MessageTemplate` or other based on the condition - easy. – Jon Skeet Apr 04 '18 at 08:17
  • ... why move all translatable messages into a single spot? That would couple the whole code base to a single, often-changing class. – xtofl Apr 04 '18 at 08:40
  • @xtofl: It only needs to change when a new message is added - at which point you'll be changing other code anyway to use the message. The alternative of scattering magic string values around the codebase sounds *much* worse to me. How do you protect against typos in that scenario? Note that putting them all in one place makes it really easy to find all the existing messages as well, encouraging increased consistency: it makes it easy to reuse an existing message instead of creating a new one. – Jon Skeet Apr 04 '18 at 08:44
  • That statement goes for any coupled code. A message added to X requires a change in A and a recompile of an unrelated Y. The gathering of scattered messages is what `gettext` has solved 20 years ago, which is the reason I want to try reusing it. It solves most i18n problems, which 'just' a resource file does not. – xtofl Apr 04 '18 at 09:17
  • @xtofl: I think we'll have to agree to differ on what makes for maintainable code and idiomatic C#. If you really want to stick with potentially-slightly-inconsistent string literals all over the place, I suggest you write a Roslyn analyzer, but I'd still recommend this approach instead. – Jon Skeet Apr 04 '18 at 09:31
  • @xtofl Storing data in Resource files will not require recompilation. "_That would couple the whole code base_". Not if you abstract access to Resource files with custom class, e.g. `ResourceRepository`. Now you have interface dependency `IResourceRepository` which exposes `SetCulture(...)`, `GetString(...)`, etc. Going against the language and building confusing structures will quickly create a lot of code to maintain and understand. Simple class with abstraction is great for simple/easy to understand separation. – FCin Apr 04 '18 at 09:46
  • @FCin: If the OP were to use resource files and the generated resource code (which is the only way it would be nice and compile-time safe) it would require a rebuild to pick up new resource names. Personally I don't see that as being a significant problem though. – Jon Skeet Apr 04 '18 at 09:55
  • @DaisyShipton Why would anyone want to change variable names at runtime? If we are talking about values then they can be changed/added without recompilation, more [here](https://learn.microsoft.com/en-us/dotnet/framework/resources/packaging-and-deploying-resources-in-desktop-apps) – FCin Apr 04 '18 at 10:01
  • @FCin: I don't see where variable names come in. The point is that if you add a new message, you need to add that to the resource file (or the `MessageTemplate` in my answer). You regenerate the code, and then you can refer to that message in a compile-time-safe way. I'm not talking about adding a new translation for an existing message (that wouldn't require recompilation) - I'm talking about adding a completely new message. I fail to see how you'd use a new message without recompilation, but in a safe way. – Jon Skeet Apr 04 '18 at 10:09