turquoiseowl / i18n

Smart internationalization for ASP.NET
Other
556 stars 156 forks source link

Nuggets in nuggets #198

Open fduman opened 9 years ago

fduman commented 9 years ago

Hi,

Why aren't you support these?

var s = "[[[Field [[[Name]]] is required]]]

or this

var s = "[[[Field %0 is required|||[[[Name]]]]]]

In this sample 'Name' is a DisplayName attribute's parameter and concat like this in my validation code. I have to replace the Name's nugget tokens with parameter tokens ((( and ))) or it doesn't work.

turquoiseowl commented 9 years ago

Honest answer, I haven't needed that myself.

I take it this isn't a bug, but a feature request?

fduman commented 9 years ago

Actually, I surprised when this usage didn't work but we may say this is a feature request. I hope that you can support this usage. It's really annoying replacing the nugget tokens with the parameter one's.

Thanks Martin.

turquoiseowl commented 9 years ago

Okay, I've marked this as a feature request then. It might involve some sexy regex...

gitsno commented 9 years ago

I'm not sure this is a good idea, I see what you want to do but although it's a little more work (or as you said "It's really annoying"), I think that having to replace the nugget tokens with parameters is important because then you keep a clear delineation between nuggets and parameters.

The problem with what you're suggesting is that it will result in problems for the translators, in your example, if this was supported then [[[Field %0 is required|||[[[Name]]]]]] will result in msgids in the POT for "Field %0 is required", and "Name" and the translators will put translations of those in your PO files. Nuggets get translated, but I don't think you want a translation of "Name", you want translations of various different names that can be inserted into your translated string, so the translated string should be [[[Field %0 is required|||{0}]]], and you should put the correct value into the parameter. How you translate the names then depends on how you are storing the names, but can be done in the code, see https://github.com/turquoiseowl/i18n#how-to-get-a-translation-of-a-nugget-in-your-c-code.

In fact I think it would be true to say that [[[Field %0 is required|||[[[Name]]]]]] and [[[Field Name is required]]] are effectively exactly the same, which is not what you want. This is because, within a given translation [[[Name]]] can only be translated one way and therefore you would always be substituting that one value into [[[Field %0 is required]]], resulting in effectively [[[Field Name is required]]]. So as I said before, what you want is parameterization and there isn't a shortcut to it, even if it is annoying :-)

fduman commented 9 years ago

Hi gitsno,

I want to explain more to clarify. I think that parameter section can be more flexible. Unfortunately I cannot use the HttpContext.GetText or something, because our validation code not in that context or process. It is in another layer of application.

In fact our validation engine has error strings like this: [[[%0 is required|||{1}]]]

So {1} which replace with string.Format might be a property name or better a DisplayName attribute value. Also I want to translate the DisplayName attribute values.

This is some of the validation engine's code. I cut some for brevity:

    private string FindMemberComment(Type type, string propertyName)
    {
                ///  ......................
                DisplayNameAttribute attr = ReflectionFactory.RetrieveSingleAttribute<DisplayNameAttribute>(memberInfoList[0]);
                if (attr == null)
                    currentPrefix = "**" + type.Name + "." + prefixPropertyName + "**";
                else
                    currentPrefix = attr.DisplayName;
                /// .........................

I prepare the exception message:

var exceptionMessage = string.Format("[[[%0 is required|||{1}]]]", FindMemberComment(.....));

At last I have an exception message which I have to replace the Surename's nugget tokens with parameter tokens: "[[[%0 is required|||[[[Surename]]]"

If parameter section can be more flexible I don't have to replace the nugget tokens. This will not be a problem for translators.

gitsno commented 9 years ago

Thinking about it some more I can see that it would be a valid feature to add - I would add though that the idea of nuggets with nuggets is two very different things, each of which would need to be separate new features.

The first, which is what you are asking for is to support nuggets within nugget parameters. Your example - [[[Field %0 is required|||[[[Name]]]]]] So the nugget [[[Name]]] needs to be translated first and then used as the nugget parameter in the main nugget.

The second case, which you mentioned in your original post is different, that is support for automatic nugget parameterization, your example [[[Field [[[Name]]] is required]]] cannot be used as is so it would need to first be parsed into the parameterized equivalent that uses nuggets within nugget parameters.

jonnybee commented 9 years ago

Well - The issue here is how to write a regex that can recursively parse tokens or a rewrite of the parsing method in I18N. Which is the reason this functionality was added with separate parsing tokens to be used for extracting parameter tokens.

So essentially you now have to do this: Ex:

 var s = "[[[Field %0 is required|||(((Name)))]]]

Which kind of makes sense as you cannot do recursive parsing within a parameter.

What I did was to use FluentValidation and create my own standard set of "i18N" based error messages. This allowed me to just write:

[Validator(typeof(CountryValidator))]
public class CountryViewModel
{
    // with context - and we use 
    // MessageContextEnabledFromComment = true 
    // in Web.Config
    [DisplayName("[[[CountryCode///CountryViewModel]]]")]
    public string CountryCode { get; set; }

    [DisplayName("[[[TwoLetterIsoCode///CountryViewModel]]]")]
    public string TwoLetterIsoCode { get; set; }

and validator:

public class CountryValidator : AbstractValidator<CountryViewModel>
{
    public CountryValidator()
    {
        RuleFor(c => c.CountryCode).NotEmpty().Length(1, 11);
        // can also override error message
        RuleFor(c => c.TwoLetterIsoCode)
            .NotNull().WithMessage("[[[%0 is required to have value!|||{PropertyName}///CountryValidator]]]")
            .Length(1, 2);

So now the DisplayName of properties belongs to context CountryViewModel and the rule message is in context CountryValidator. My translation tokens are split per ViewModel (when I wish to do so) so I have speparate translations for each type. Plus - I have a default set of error messages that support parameter substitution from DisplayName.

You can loook at my sample project at https://github.com/jonnybee/fluent_i18n_demo