Strings in JS files cannot be externalized in form of __(`xxx`)
Summary
bin/rake gettext:find
will detect the externalized strings in both backend and frontend. It calls gettext_i18n_rails_js
for detecting the externalized strings in Javascript. However, only __('xxx')
and __("xxx")
can be detected in JS. Some files in app/assets/javascripts/
contain invalid forms of externalized strings, which cannot be detected and will not be presented in the gitlab.pot
, so they cannot be translated later.
- Backtick quote
errorMsg.textContent = __(`
Cannot show preview. For previews on sketch files, they must have the file format
introduced by Sketch version 43 and above.
`);
or
<div slot="description">
{{ s__(`ClusterIntegration|Helm streamlines installing
and managing Kubernetes applications.
Tiller runs inside of your Kubernetes Cluster,
and manages releases of your charts.`) }}
</div>
- Put string in a variable
message =
this.items && this.items.length
? 'ClusterIntegration|To use a new project, first create one on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}.'
: 'ClusterIntegration|To create a cluster, first create a project on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}.';
return sprintf(
s__(message),
{
docsLinkEnd: ' <i class="fa fa-external-link" aria-hidden="true"></i></a>',
docsLinkStart: `<a href="${_.escape(
this.docsUrl,
)}" target="_blank" rel="noopener noreferrer">`,
},
false,
);
- New line after
__(
return s__(
'Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored.',
);
All three above forms cannot be detected by gettext_i18n_rails_js
, and I found many such cases in files under app/assets/javascripts
.
$ egrep "[^a-z][a-z]__\(([^'\"]|$)" -R app/assets/javascripts
app/assets/javascripts/notes/stores/collapse_utils.js: s__(`MergeRequest|
app/assets/javascripts/pipelines/components/empty_state.vue: {{ s__(`Pipelines|Continuous Integration can help
app/assets/javascripts/pipelines/components/pipelines.vue: :message="s__(`Pipelines|There was an error fetching the pipelines.
app/assets/javascripts/commit/pipelines/pipelines_table.vue: :message="s__(`Pipelines|There was an error fetching the pipelines.
app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue: {{ s__(`mrWidget|The pipeline for this merge request failed.
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue: {{ s__(`mrWidget|Fast-forward merge is not possible.
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue: {{ s__(`mrWidget|Resolve these conflicts or ask someone
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue: {{ s__(`mrWidget|Ready to be merged automatically.
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue: {{ s__(`mrWidget|Pipeline blocked.
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue: return n__(
app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue: {{ s__(`mrWidget|The source branch HEAD has recently changed.
app/assets/javascripts/groups/components/group_folder.vue: return n__(
app/assets/javascripts/groups/components/app.vue: this.groupLeaveConfirmationMessage = s__(
app/assets/javascripts/clusters/components/applications.vue: s__(
app/assets/javascripts/clusters/components/applications.vue: s__(
app/assets/javascripts/clusters/components/applications.vue: s__(
app/assets/javascripts/clusters/components/applications.vue: s__(
app/assets/javascripts/clusters/components/applications.vue: {{ s__(`ClusterIntegration|Helm streamlines installing
app/assets/javascripts/clusters/components/applications.vue: {{ s__(`ClusterIntegration|Ingress gives you a way to route
app/assets/javascripts/clusters/components/applications.vue: {{ s__(`ClusterIntegration|The IP address is in
app/assets/javascripts/clusters/components/applications.vue: {{ s__(`ClusterIntegration|Point a wildcard DNS to this
app/assets/javascripts/clusters/components/applications.vue: {{ s__(`ClusterIntegration|GitLab Runner connects to this
app/assets/javascripts/clusters/components/applications.vue: {{ s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns,
app/assets/javascripts/clusters/components/applications.vue: {{ s__(`ClusterIntegration|Replace this with your own hostname if you want.
app/assets/javascripts/badges/components/badge_settings.vue: return s__(
app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue: s__(
app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue: s__(message),
app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue: return s__(
app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue: s__(
app/assets/javascripts/projects/gke_cluster_dropdowns/constants.js:export const GCP_API_ERROR = s__(
app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue: :title="n__(
app/assets/javascripts/environments/components/empty_state.vue: {{ s__(`Environments|Environments are places where
app/assets/javascripts/sidebar/components/confidential/edit_form.vue: return s__(
app/assets/javascripts/sidebar/components/confidential/edit_form.vue: return s__(
app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue: const keepContributionsText = s__(`AdminArea|
app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue: const deleteContributionsText = s__(`AdminArea|
app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue: return sprintf(s__(`AdminProjects|
app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue: return sprintf(s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}.
app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue: s__(`Milestones|
app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue: s__(`Milestones|
app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue: return sprintf(s__(`Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}.
app/assets/javascripts/profile/account/components/update_username.vue: s__(`Profiles|
app/assets/javascripts/profile/account/components/delete_account_modal.vue: s__(`Profiles|
app/assets/javascripts/diffs/components/compare_versions_dropdown.vue: return n__(
Or, the files can be detected via regular expression: [^a-z][a-z]__\(([^'"]|$)
.
This is a known issue to gettext_i18n_rails_js
, https://github.com/webhippie/gettext_i18n_rails_js/issues/41.
To varify the issue, just find the above externalization form in .js
or .vue
file, and check whether locale/gitlab.pot
contains the externalized strings or not.