UNPKG

8.31 kBMarkdownView Raw
1## Overview
2
3The `GlSprintf` component lets you do `sprintf`-style string interpolation with
4child components. Each placeholder in the translated string, provided via the
5`message` prop, becomes a slot that you can use to insert any components or
6markup in the rendered output.
7
8> NOTE: `gl-sprintf` does not translate the message for you; you must provide
9> it already translated. In the following examples, it is assumed that
10> a `gettext`-style `__` translation function is available in your Vue
11> templates.
12
13## Displaying messages with text between placeholders (e.g., links, buttons)
14
15Sentences should not be split up into different messages, otherwise they may
16not be translatable into certain languages. To help with this, `GlSprintf`
17interprets placeholders suffixed with `Start` and `End` to indicate the
18boundaries of a component to display within the message. Any text between
19them is passed, via the `content` scoped slot property, to the slot name common
20to the placeholders.
21
22For example, using `linkStart` and `linkEnd` placeholders in a message defines
23a `link` scoped slot:
24
25```html
26<div>
27 <gl-sprintf :message="__('Learn more about %{linkStart}zones%{linkEnd}')">
28 <template #link="{ content }">
29 <gl-link
30 href="https://cloud.google.com/compute/docs/regions-zones/regions-zones"
31 target="_blank"
32 >{{ content }}</gl-link>
33 </template>
34 </gl-sprintf>
35</div>
36```
37
38will render as:
39
40```html
41<div>
42 Learn more about
43 <a
44 href="https://cloud.google.com/compute/docs/regions-zones/regions-zones"
45 target="_blank"
46 rel="noopener noreferrer"
47 >zones</a>
48</div>
49```
50
51Note that _any_ arbitrary HTML tags or Vue component(s) can be used within
52a scoped slot, and that the content passed to it can be used in any way at all;
53for instance, as regular text, or in component attributes or slots.
54
55Here's a more complex example, which `<gl-sprintf>` lets you do in a breeze:
56
57```html
58<div>
59 <gl-sprintf :message="__('Written by %{authorStart}someone%{authorEnd}')">
60 <template #author="{ content }">
61 <my-vue-component v-gl-tooltip="content" @event="handleEvent(content)">
62 {{ content }}
63 </my-vue-component>
64 <p>
65 {{ content }}
66 <div>{{ content }}</div>
67 </p>
68 </template>
69 </gl-sprintf>
70</div>
71```
72
73This is not feasible in a JS-only solution, since arbitrary Vue components
74cannot easily be used. In addition, a JS-only solution is more likely to be
75prone to XSS attacks, as the Vue compiler isn't available to help protect
76against them.
77
78### Customizing start/end placeholders
79
80You can customize the start and end placeholders that `GlSprintf` looks for
81using the `placeholders` prop. For instance:
82
83```html
84<div>
85 <gl-sprintf
86 :message="__('Learn more about %{my_custom_start}zones%{my_custom_end}')"
87 :placeholders="{ link: ['my_custom_start', 'my_custom_end'] }"
88 >
89 <template #link="{ content }">
90 <gl-link
91 href="https://cloud.google.com/compute/docs/regions-zones/regions-zones"
92 target="_blank"
93 >{{ content }}</gl-link>
94 </template>
95 </gl-sprintf>
96</div>
97```
98
99This can be useful if you are migrating an existing string to `GlSprintf` that
100uses different placeholder naming conventions, and don't want invalidate
101existing translations.
102
103## Displaying components within a message
104
105Use slots to replace placeholders in the message with the slots' contents.
106There is a slot for every placeholder in the message. For example, the `author`
107slot name can be used when there is an `%{author}` placeholder in the message:
108
109```html
110<script>
111export default {
112 data() {
113 return {
114 authorName: 'Some author',
115 };
116 },
117};
118</script>
119
120<template>
121 <div>
122 <gl-sprintf :message="__('Written by %{author}')">
123 <template #author>
124 <span>{{ authorName }}</span>
125 </template>
126 </gl-sprintf>
127 </div>
128</template>
129```
130
131The example above renders to this HTML:
132
133```html
134<div>Written by <span>Some author</span></div>
135```
136
137## Usage caveats
138
139### White space
140
141`GlSprintf` does not handle white space in scoped slots specially; it is passed
142through and rendered just like regular text. This means that white space in the
143scoped slot templates *themselves*, including newlines and indentation, are
144passed through untouched (assuming the template compiler you're using doesn't
145trim text nodes at compile time; `vue-template-compiler` preserves white space
146by default, for instance).
147
148Most of the time you don't need to worry about this, since
149[browsers normalize white space][1] automatically, but here's an example, using
150punctuation, where you might want to be conscious of the white space in the
151template:
152
153```html
154<div>
155 <gl-sprintf :message="__('Foo %{boldStart}bar%{boldEnd}!')">
156 <template #bold="{ content }">
157 <b>
158 {{ content }}
159 </b>
160 </template>
161 </gl-sprintf>
162</div>
163```
164
165As written, the literal markup rendered would be:
166
167```html
168<div> Foo <b>
169 bar
170 </b>!
171</div>
172```
173
174where the white space (including newlines) before and after `bar` is exactly
175the newlines and indentation in the source template. The browser will render
176this as:
177
178<div> Foo <b>
179 bar
180 </b>!
181</div>
182
183Note the single space between `bar` and `!`. To avoid that, remove the
184white space in the template, or use `v-text`:
185
186```html
187<div>
188 <gl-sprintf :message="__('Foo %{boldStart}bar%{boldEnd}!')">
189 <template #bold="{ content }">
190 <b>{{ content }}</b>
191 <!-- OR -->
192 <b v-text="content" />
193 </template>
194 </gl-sprintf>
195</div>
196```
197
198### Miscellaneous
199
200While there are a lot of caveats here, you don't need to worry about reading
201them _unless_ you find `GlSprintf` isn't rendering what you'd expect.
202
203- Since `GlSprintf` typically renders multiple elements, it can't be used as
204 a component's root, it must be wrapped with at least one other root element,
205 otherwise Vue will throw a `Multiple root nodes returned from render
206 function` error.
207- If a slot for a given placeholder _isn't_ provided, the placeholder
208 will be rendered as-is, e.g., literally `Written by %{author}` if the
209 `author` slot _isn't_ provided, or literally `%{linkStart}foo%{linkEnd}` if
210 the `link` slot isn't provided.
211- Content between `Start` and `End` placeholders is effectively thrown away if
212 the scoped slot of the correct name doesn't consume the `content` property in
213 some way, though the slot's components should still be rendered.
214- If there's no placeholder in the message for a provided named slot, the
215 content of that slot is silently thrown away.
216- If only one of the `Start` or `End` placeholders is in the message, or they
217 are in the wrong order, they are treated as plain slots, i.e., it is assumed
218 there is no text to extract and pass to the scoped slot. This allows you to
219 use plain slots whose names end in `Start` or `End`, e.g., `backEnd`, or
220 `fromStart` in isolation, without their `Start`/`End` counterparts.
221- Text extraction between `Start` and `End` placeholders is only done one level
222 deep. This is intentional, so as to avoid building complex sprintf messages
223 that would better be implemented in components. As an example,
224 `${linkStart}test%{icon}%{linkEnd}`, if provided both the `link` and `icon`
225 slots, would pass `test%{icon}` as a literal string as content to the `link`
226 scoped slot.
227- For more examples and edge cases, please see the test suite for `GlSprintf`.
228- To be successfully used in `GlSprintf`, slot names should:
229 - start with a letter (`[A-Za-z]`)
230 - only contain alpha-numeric characters (`[A-Za-z0-9]`), underscore (`_`) and
231 dash (`-`),
232 - should not end with underscore (`_`) or dash (`-`) So for example:
233 `%{author}`, `%{author_name}`, `%{authorName}` or `%{author-name-100}` are
234 all valid placeholders.
235
236## Internet Explorer 11
237
238This component uses [`String.prototype.startsWith()`] and [`String.prototype.endsWith()`] under the
239hood. Make sure those methods are polyfilled if you plan on using the component on IE11.
240
241[1]: https://www.w3.org/TR/css-text-3/#white-space-phase-1
242[`String.prototype.startsWith()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
243[`String.prototype.endsWith()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith