angular-gettext icon indicating copy to clipboard operation
angular-gettext copied to clipboard

New "translate-attr" directive describing which attribute(s) value(s) must be translated

Open varadero opened this issue 9 years ago • 3 comments

Hi,

I encountered a problem translating attribute values like title and placeholder. Currently we could convert this: <input type="password" placeholder="Password" .../> to this: <input type="password" placeholder="{{'Password' | translate}}" .../>

But this adds unnecessary obfuscation of the html attribute itself. My proposal is to create new directive say translate-attr which will be used to point to the name of an attribute which should be translated so the above could look like this: <input type="text" placeholder="Password" translate-attr="placeholder" ...>

It could greatly simplify elements which have more than one attribute for translation like this: <img alt="{{'Tree' | translate}}" title="{{'An old tree' | translate}}" ... /> to this: <img alt="Tree" title="An old tree" translate-attr="alt" translate-attr="title" ... /> or even combine all attribute names: <img alt="Tree" title="An old tree" translate-attr="alt,title" ... />

It looks easier to read by a human, it removes "logic" from attribute values so non-programmer could easily find the text that will be translated reducing the number of possible errors, looks more "HTML-ish". And also I suppose it is easier for parser to extract text compared to filter approach.

varadero avatar Oct 06 '15 13:10 varadero

:+1:

crissdev avatar Oct 06 '15 17:10 crissdev

I found the following "custom annotations" ability of angular-gettext at https://angular-gettext.rocketeer.be/dev-guide/custom-annotations/ which could be used in this situation. But I also found that it only works for elements without content (so the sample with placeholder attribute of an input element is not good enough). If you for example try to do the same for title attribute of an label element that have some text content, grunt-angular-gettext will extract the label text, not the title attribute value. So if you have:

<label for="someId" title="Label tooltip">Label text content</label>

Extraction will extract only "Label text content".

So I have modified the node_modules\grunt-angular-gettext\node_modules\angular-gettext-tools\lib\extract.js file in order to support extracting attribute values, not element content.

I have changed the following block in Extractor.prototype.extractHtml method:

                for (var attr in node.attr()) {
                    attr = attr.replace(/^data-/, '');

                    if (possibleAttributes.indexOf(attr) > -1) {
                        var attrValue = extracted[attr];
                        str = node.html(); // this shouldn't be necessary, but it is    
                        self.addString(reference(n.startIndex), str || getAttr(attr) || '', attrValue.plural, attrValue.extractedComment, attrValue.context);
                    } else if (matches = noDelimRegex.exec(node.attr(attr))) {
                        str = matches[2].replace(/\\\'/g, '\'');
                        self.addString(reference(n.startIndex), str);
                        noDelimRegex.lastIndex = 0;
                    }
                }

To:

                var selfContentOnlyAttrs = self.options.selfContentOnlyAttributes;
                for (var attr in node.attr()) {
                    attr = attr.replace(/^data-/, '');

                    if (possibleAttributes.indexOf(attr) > -1) {
                        var attrValue = extracted[attr];
                        if (selfContentOnlyAttrs && selfContentOnlyAttrs.indexOf(attr) > -1) {
                            // This attribute is found in selfContentOnlyAttrs array - so we should get only attribute's value, not its element html()
                            str = node.attr(attr);
                        } else {
                            // Not found in selfContentOnlyAttrs - get its element html
                            str = node.html(); // this shouldn't be necessary, but it is    
                        }
                        //str = node.html(); // this shouldn't be necessary, but it is
                        self.addString(reference(n.startIndex), str || getAttr(attr) || '', attrValue.plural, attrValue.extractedComment, attrValue.context);
                    } else if (matches = noDelimRegex.exec(node.attr(attr))) {
                        str = matches[2].replace(/\\\'/g, '\'');
                        self.addString(reference(n.startIndex), str);
                        noDelimRegex.lastIndex = 0;
                    }
                }

After that the usage in Gruntfile.js (for attributes title and placeolder) would be:

        nggettext_extract: {
            pot: {
                options: {
                    attributes: ["placeholder", "title"],
                    selfContentOnlyAttributes: ["placeholder", "title"]
                },
                files: {
                    'po/template.pot': ['src/views/*.html']
                }
            },
        },

It is obvious that including these specific attributes both in attributes and selfContentOnlyAttributes arrays is not good solution, so I could create pull request for review by the author and eventually change it to something more robust.

Note: I didn't started any tests after the change of extract.js

varadero avatar Oct 16 '15 09:10 varadero

I must confirm issue posted by @varadero.

If you have

<foo bar="abc">

nggettext_extract output is:

msgid "abc"

same for:

<foo bar="abc"></foo>

but:

<foo bar="abc">xyz</foo>

produces:

msgid "xyz"

The only way here is to use translate filter like:

<foo bar="{{'abc'|translate}}">xyz</foo>

maciejlew avatar May 26 '16 13:05 maciejlew