Polyconseil / vue-gettext

Translate your Vue.js applications with gettext.
MIT License
278 stars 55 forks source link

xgettext fails with some .vue files [was: makefile ignore strings] #28

Open thujone25 opened 7 years ago

thujone25 commented 7 years ago

Hi! I have a Vue component file which are ignored by Makefile. What do I make wrong? The text appears in the view but doesn't appear in .po files after make makemessages

screenshot from 2017-06-22 17-01-53

thujone25 commented 7 years ago

I've found out that everything works good if I remove line №7 or №8 But I can't understand why.

kemar commented 7 years ago

Which one is line 7? (it's difficult to help you when the only thing I have is a screenshot)

thujone25 commented 7 years ago
<template>
  <form class="auth-form"
        :class="{'auth-form--password-pages': (forgotPass || newPass)}">
    <h1 class="auth-form__title">
      <span>{{ headerText }}</span>
    </h1>
    <special-error class="auth-form__errors-block"></special-error>
    <p class="auth-form__forgot-pass-text" v-if="forgotPass">{{ text3 }}</p>
    <slot name="emailField"></slot>
    <slot name="passwordField"></slot>
    <slot name="submitBtn"></slot>
    <em class="auth-form__password-text"
        v-if="newPass"></em>
    <router-link :to="{name: 'SignIn'}" 
                 v-if="forgotPass"
                 class="tt-link tt-link--destroy-action auth-form__forgot-pass-link">{{ text4 }}</router-link>
    <div class="auth-form__remember-forgot-cont" v-if="rememberSection">
      <div class="auth-form__remember-forgot-section">
        <slot name="rememberCheckbox"></slot>
      </div>
      <div class="auth-form__remember-forgot-section auth-form__remember-forgot-section--link">
        <router-link class="tt-link"
                     :to="{name: 'RestorePassword'}">{{ text1 }}</router-link>
      </div>
    </div>
    <social-links :hr-text="text2"
                  v-if="socialLinks"></social-links>
  </form>
</template>

<script>
  export default {
    props: [
      'headerText',
      'paddingRight',
      'rememberSection',
      'socialLinks',
      'forgotPass',
      'newPass'
    ],
    computed: {
      text1() {return this.$gettext('Forgot your password?');},
      text2() {return this.$gettext('or join in with');},
      text3() {return this.$gettext('Enter the e-mail address associated with your account, and we will send you a link to reset your password.');},
      text4() {return this.$gettext('Cancel');},
      text5() {return this.$gettext('8 characters minimum');},
    }
  }
</script>

Lines:

<special-error class="auth-form__errors-block"></special-error>
    <p class="auth-form__forgot-pass-text" v-if="forgotPass">{{ text3 }}</p>
kemar commented 7 years ago

Ok, now I can reproduce and confirm your issue.

I think that xgettext fails during extraction (here). I don't know exactly why but I think this is related to the way xgettext is parsing your .vue file.

By default xgettext cannot extracts strings directly from .vue files. I used a trick in order to make it think it's parsing a JavaScript file.

However, I can see that it's not working for you :'(

Unfortunately I have no easy solution for this problem yet. The ideal solution could be to code a custom extractor for .vue files (see also https://github.com/Polyconseil/vue-gettext/issues/9) but I don't have much time yet to implement it.

thujone25 commented 7 years ago

Hi. I've spotted that wrapping template markup with parentheses solves the problem. Also it allows us to use Inline expression. from #9 Unfortunately I've not found a solution to use Inline expression for props (<component :prop-name="$gettext('some text')"></component>) yet. I hope that this small trick with parentheses can help. So instead of

<template>
  <translate>text 1</translate>
  <p v-translate>text 2</p>
  <p>{{ text3 }}</p>
</template>

<script>
  export default {
    computed: {
      text3() {return this.$gettext('Text 3');}
    }
  }
</script>

we should wrap <template>

(<template>
  <translate>text 1</translate>
  <p v-translate>text 2</p>
  <!-- Inline expression. -->
  <p>{{ $gettext('Text 3') }}</p>
</template>)

It's hack so some testing is needed.

GUI commented 6 years ago

As one other potential option for xgettext parsing of Vue files, I've created gettext-vue which integrates with xgettext-template to do some pretty basic parsing of Vue files.

The parsing is just done with some basic regexes (based on gettext-ejs) that might not handle more complicated parsing situations, but it seems to work well for our use-case. The parsing doesn't take any context into account, so it's just looking for any function calls named $t, $gettext, or $ngettext by default (although those keywords can be configured with xgettext-template). But this simplistic approach does mean it picks up references in both the <template> and <script> sections of Vue files, and it should also pick up the various syntaxes in the templates (eg, {{ $t('Hello') }} or <div v-bind:placeholder="$t('World')">).

So while this might not cover all parsing situations, hopefully it might be useful to others. And if we can improve the parsing for other situations, I'm open to pull requests or feedback.

kemar commented 6 years ago

@GUI thanx for letting us know. This is really interesting.

bart-1990 commented 6 years ago

I did find one bug in the xgettext parsing of .vue files. It has something to do with the amount of '/' the file contains.

Suppose you have the following test.vue file:

<template>
    <div>
        <div></div>
    </div>
</template>

<script>
    export default {
        computed: {
            zoomLabel() {
                return this.$gettext('Zoom');
            }
        }
</script>

Running the command xgettext --language=JavaScript --keyword=npgettext:1c,2,3 --from-code=utf-8 --join-existing --no-wrap --output translations.pot test.vue will not extract the Zoom string in the translations.pot file.

However when you change the file to one of the following 2 templates, it will work!

<template>
    <div></div>
</template>

<script>
    export default {
        computed: {
            zoomLabel() {
                return this.$gettext('Zoom');
            }
        }
</script>

--> 2 / now because of the removal of one <div>

<template>
    <div>
        <div></div>
    </div>
</template>

<script>
    import foo from './foo';

    export default {
        computed: {
            zoomLabel() {
                return this.$gettext('Zoom');
            }
        }
</script>

--> 4 / now because of the import statement

Do mind I'm not counting the / in </script>.

@thujone25 If I counted well, your template also contains an odd number of /, so I think adding an element would probably resolve the issue, not only removing line 7 or 8. Of course it's not an elegant solution to add extra elements but I don't know anything about xgettext parsers.

thujone25 commented 6 years ago

@bart-1990 actually I wrap every template with () and I've not faced with problems yet. So I think that () better than counting /

bart-1990 commented 6 years ago

It's still a bug that needs solving. :) Wrapping all my templates in () is not something I'm particularly fond of but indeed it helps.

narrowtux commented 6 years ago

For webpack users that want to use "$gettext" etc in slots: I've created a webpack loader that plugs behind vue-loader, gets the compiled vue source code, runs GNU xgettext on that source code and emits a pot file for each vue component file. You can also use this for other files that are transpiled to javascript.

https://gist.github.com/narrowtux/6b745e9346db97cec7f122f5bdf9bba4

lajosbencz commented 5 years ago

Putting the Githubissues.

  • Githubissues is a development platform for aggregating issues.