-
-
Notifications
You must be signed in to change notification settings - Fork 861
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
Feature Request: Use Vue's scoped slots for component interpolation #668
Comments
Here's the simplest prototype I could come up with: export default {
functional: true,
props: {
tag: { type: String, default: undefined },
path: { type: String, required: true },
locale: { type: String, default: undefined },
},
render(h, { data, parent, props, slots }) {
const { $i18n } = parent
if (!$i18n) throw new Error("Cannot find `vue-i18n` instance.")
const params = slots()
const children = $i18n.i(
props.path,
props.locale,
onlyHasDefaultPlace(params) ? params.default : params
)
const { tag } = props
return tag ? h(tag, data, children) : children
},
}
function onlyHasDefaultPlace(params) {
const places = Object.keys(params)
return places.length === 1 && places[0] === "default"
} Turned out to be much simpler than I thought thanks to Feel free to use and modify as desired :) |
@sirlancelot As you say, Using a slot is a similar syntax of Vue and is easier to learn. |
Makes sense, I'll see if I can prototype a backwards-compatible version. |
I made an improved version below. It is also backwards-compatible with both import { warn } from "../util"
export default {
name: "i18n",
functional: true,
props: {
locale: { type: String, default: undefined },
path: { type: String, required: true },
places: { type: [Array, Object], default: undefined },
tag: { type: String, default: undefined },
},
render(h, { data, parent, props, slots }) {
const { $i18n } = parent
if (!$i18n) throw new Error("Cannot find `vue-i18n` instance.")
const params = slots()
const children = $i18n.i(
props.path,
props.locale,
onlyHasDefaultPlace(params)
? useLegacyPlaces(params.default, props.places)
: params
)
const { tag } = props
return tag ? h(tag, data, children) : children
},
}
function onlyHasDefaultPlace(params) {
var prop
for (prop in params) if (prop != "default") return false
return Boolean(prop)
}
function useLegacyPlaces(children, places) {
const params = places ? createParamsFromPlaces(places) : {}
const everyPlace = children.every(vnodeHasPlaceAttribute)
if (process.env.NODE_ENV !== "production" && everyPlace)
warn("`place` attribute is deprecated. Please switch to Vue slots.")
return children.reduce(
everyPlace ? assignChildPlace : assignChildIndex,
params
)
}
function createParamsFromPlaces(places) {
if (process.env.NODE_ENV !== "production")
warn("`places` prop is deprecated. Please switch to Vue slots.")
return Array.isArray(places)
? places.reduce(assignChildIndex, {})
: Object.assign({}, places)
}
function assignChildPlace(params, child) {
params[child.data.attrs.place] = child
return params
}
function assignChildIndex(params, child, key) {
params[key] = child
return params
}
function vnodeHasPlaceAttribute(vnode) {
return Boolean(vnode.data && vnode.data.attrs && vnode.data.attrs.place)
} As always, feel free to use and modify as desired :) |
close due to merge #685 |
One of my biggest struggles as I was learning vue-i18n's usages was its peculiar syntax for component interpolation. I see a lot of overlap between the
place
attribute and Vue's own slots mechanism. This suggestion is a breaking change, but I would like to see this library adopt Vue's own Scoped Slots in its next major version rather than continue forward with the homegrownplace
solution.Basic usage example (single key replacement):
<!-- Current --> <i18n path="term" tag="span"> <a :href="url" target="_blank">{{ $t('tos') }}</a> </i18n> <!-- Proposed --> <i18n path="term" tag="span" v-slot> <a :href="url" target="_blank">{{ $t('tos') }}</a> </i18n>
The library would need to replace
{0}
in messages with thedefault
scoped slot.Advanced usage example (multiple key replacement):
<!-- Current --> <i18n path="info" tag="p"> <span place="limit">{{ changeLimit }}</span> <a place="action" :href="changeUrl">{{ $t('change') }}</a> </i18n> <!-- Proposed --> <i18n path="info" tag="p"> <template #limit> <span>{{ changeLimit }}</span> </template> <template #action> <a :href="changeUrl">{{ $t('change') }}</a> </template> </i18n>
The library would need to replace
{limit}
&{action}
in messages with the respectively named scoped slot.Replace usage of the
places
prop:<!-- Current --> <i18n path="info" tag="p" :places="{ limit: refundLimit }"> <a place="action" :href="refundUrl">{{ $t('refund') }}</a> </i18n> <!-- Proposed --> <i18n path="info" tag="p"> <template #action><a :href="refundUrl">{{ $t('refund') }}</a></template> <template #limit>{{ refundLimit }}</template> </i18n>
I propose removing the
places="{}"
prop but strictly speaking the two can live and work together.The proposed syntax is admittedly more verbose than the current syntax, but I believe it will feel more natural. It is more consistent with Vue's own syntax so developers will not need to learn or remember a special syntax for this library.
The text was updated successfully, but these errors were encountered: