New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
varadero opened this Issue Oct 6, 2015 · 3 comments

Comments

Projects
None yet
3 participants
@varadero

varadero commented Oct 6, 2015

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.

@crissdev

This comment has been minimized.

Show comment
Hide comment
@crissdev

crissdev Oct 6, 2015

Contributor

👍

Contributor

crissdev commented Oct 6, 2015

👍

@varadero

This comment has been minimized.

Show comment
Hide comment
@varadero

varadero Oct 16, 2015

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

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

@maciejlew

This comment has been minimized.

Show comment
Hide comment
@maciejlew

maciejlew May 26, 2016

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>

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment