I'm currently trying to use react-i18next for the following use case: an application built for different companies and targeted toward the user company. The company name for users differs but they will reuse the same UI components. The translations object will look like this:
{
"companyX": {
"homeFeature": {
"title": "Sint est in mollit ",
"description": "Incididunt quis mollit id excepteur amet ipsum."
}
},
"companyY": {
"homeFeature": {
"title": "Dolor esse eiusm ut."
}
}
}
I have tried this to load the specific key in the translation JSON (eg: companyX) and added it to the i18next config and it works.
const translationsJson = {
en: {
translation: en["companyX"],
},
de: {
translation: de["companyX"],
},
};
I would like to know what is the correct way to load the resource based on the user's company.
I'd be happy to hear your thoughts on that! Let me know if I left things unclear or can be of any assistance!
You could try something like this:
This will init i18next with the resources for the company specified in the company variable.
const translationsJson = {
en: {
translation: en["companyX"],
},
de: {
translation: de["companyX"],
},
};
const company = "companyX";
const i18n = i18next.init({
lng: "en",
resources: {
en: {
translation: translationsJson.en.translation[company],
},
de: {
translation: translationsJson.de.translation[company],
},
},
});
The creators of i18next do offer a nice setup that may cover your needs.
You can create company-specific projects based on your main project. All translations are anticipated for the company-specific project and can be overridden, if necessary.
But the anticipated translations are not just copied, so in case you need to correct a translation in your main project, it will be automatically populated to your company-specific projects, if not overridden.
More information about this setup can be found here.
Related
We set up a Shopware 6.4.16.0 site for Germany with the languages de_DE and en_GB.
Now we want to setup an American sales channel with only English content.
We selected only English as a language for that sales channel, duplicated the category tree and assigned the new root category to the American sales channel.
Sales channel details look like this:
Unfortunately, in the categories all languages are selectable, which is confusing:
Should the irrelevant languages be hidden or is this working as intended. I did not find any GitHub issue for this yet. Or is this intended?
Probably we need to extend the admin UI to determine which languages are relevant for a (sub)category (via the sales channel assignment of the root category) and hide the irrelevant languages? What is a good starting point for this?
The language dropdown in your second screenshot in fact works as expected. The languages are globally.
In theory there are two different translations systems inside shopware:
Content translation:
Every entity that you can edit in the admin in different languages uses this translation mechanism. The values in the different languages are stored in the DB and the correct value will be read according to the language in the context. This translation system is used in the second screenshot for the categories.
UI translation:
This translation system is used for the "snippet" system in the storefront, where you provide translations for text snippets that are displayed in the storefront. This is what your first screenshot shows.
Both translation systems are only loosely coupled in a way that the content will be displayed with the same locale as the rest of the UI translations whereever content from the DB is displayed in the storefront.
With the admin extension of a plugin you could override both of the corresponding components and restrict the criteria for fetching the available languages in the language switcher.
const { Component } = Shopware;
const { Criteria } = Shopware.Data;
Component.override('sw-language-switch', {
props: {
salesChannelIds: {
type: Array,
default: () => {
return [];
},
},
},
computed: {
languageCriteria() {
if (this.salesChannelIds.length) {
const criteria = this.$super('languageCriteria');
criteria.addFilter(Criteria.equalsAny(
'salesChannels.id',
this.salesChannelIds
));
return criteria;
}
return this.$super('languageCriteria');
}
}
});
Component.override('sw-category-detail', {
template: `{% block sw_category_language_switch %}
<sw-language-switch
:key="rootCategory?.id"
:sales-channel-ids="salesChannelIds"
:save-changes-function="saveOnLanguageChange"
:abort-change-function="abortOnLanguageChange"
:disabled="landingPageId === 'create'"
#on-change="onChangeLanguage"
/>
{% endblock %}`,
data() {
return {
rootCategoryId: null,
rootCategory: null,
};
},
computed: {
salesChannelIds() {
if (!this.rootCategory) {
return [];
}
return this.rootCategory.navigationSalesChannels.map((salesChannel) => {
return salesChannel.id;
});
}
},
watch: {
category(category) {
if (!category || !category.path) {
this.rootCategoryId = null;
return;
}
const parentIds = category.path.split('|');
this.rootCategoryId = parentIds[1];
},
rootCategoryId(rootCategoryId) {
if (!rootCategoryId) {
this.rootCategory = null;
return;
}
const criteria = new Criteria();
criteria.addAssociation('navigationSalesChannels');
this.categoryRepository.get(rootCategoryId, Shopware.Context.api, criteria)
.then((rootCategory) => {
this.rootCategory = rootCategory;
});
},
},
});
You could still tweak this a little. For example when you click on a category in the tree that won't feature the currently selected language, you could automatically switch to the relevant language instead. For now this example just restricts the options in the dropdown on category-by-category basis.
What is the best approach for accessing a single nested record in Ember?
The JSON response which we are trying to manipulate looks gets returned as the following: (the attribute being targeted is the tradeIdentifier property)
trade:
tradeIdentifier:"83f3f561-62af-11e7-958b-028c04d7e8f9"
tradeName:"Plumber"
userEmail:"test#gmail.com"
The project-user model looks partially like:
email: attr('string'),
trade:attr(),
tradeId: attr(),
The project-user serializer looks partially like:
export default UndefinedOmitted.extend(EmbeddedRecordsMixin, {
primaryKey: 'userRoleId',
attrs: {
'email': { key: 'userEmail' },
'trade': { key: 'trade' },
'tradeId': { key: 'tradeIdentifier' },
},
});
The trade attr here is a placeholder to make sure that the data was accessible.
I would like to be able to access the tradeIdentifier without having to do the following in the component:
const trade = get(formRole, 'trade');
if (trade) {
set(formProps, 'tradeId', trade.tradeIdentifier);
}
Have tested creating a trade-id transform (referenced via tradeId: attr('trade-id')), however to no avail.
export default Transform.extend({
deserialize(val) {
const trade = val;
const tradeId = val.tradeIdentifier;
return tradeId;
},
serialize(val) {
return val;
},
});
Can anyone suggest where I'm going wrong?
A transform seems a bit overkill for what I'm trying to achieve here, however it does the job. Managed to get it working by modifying the following:
In serializers/project-user.js:
'tradeId': { key: 'trade' },
Note that this references the property in the payload to transform, not the property being targeted (which was my mistake).
In models/project-user.js:
tradeId: attr('trade-id'),
Attribute references the transform.
In transform/trade-id.js:
export default Transform.extend({
deserialize(val) {
let tradeId = val
if (tradeId) {
tradeId = val.tradeIdentifier;
}
return tradeId;
},
serialize(val) {
return val;
},
});
If there's a simpler solution outside of transforms, I would still be open to suggestions.
PayPal's Express Checkout documentation says that you can customize the checkout using the Experience API. And when you go to the Experience API documentation, you see the ability to set a custom name, logo_image, and more.
In our implementation, hiding the shipping fields (no_shipping: 1) works - and that uses the Experience API - but setting the name and logo_image does not.
Code below. Does anyone know if there's a way to set name and/or logo_image?
payment: function(data, actions) {
return actions.payment.create({
payment: {
transactions: [
{
amount: { total: '9.99', currency: 'USD' }
}
]
},
experience: {
name: 'Custom Name',
presentation: {
logo_image: 'https://i.imgur.com/customimage.png'
},
input_fields: {
no_shipping: 1
}
}
});
},
So, I will go straight to the point. I am getting such data from api:
[
{
id: 123,
email: asd#asd.com
},
{
id: 456,
email: asdasd.com
},
{
id: 789,
email: asd#asd
},
...
]
and I should validate email and show this all info in a list, something like this:
asd#asd.com - valid
asdasd.com - invalid
asd#asd - invalid
...
My question is what is the best way to store validation data in a store? Is it better to have something like "isValid" property by each email? I mean like this:
store = {
emailsById: [
123: {
value: asd#asd.com,
isValid: true
},
456: {
value: asdasd.com,
isValid: false
},
789: {
value: asd#asd,
isValid: false
}
...
]
}
or something like this:
store = {
emailsById: [
123: {
value: asd#asd.com
},
456: {
value: asdasd.com
},
789: {
value: asd#asd
}
...
],
inValidIds: ['456', '789']
}
which one is better? Or maybe there is some another better way to have such data in store? Have in mind that there can be thousands emails in a list :)
Thanks in advance for the answers ;)
I recommend reading the article "Avoiding Accidental Complexity When Structuring Your App State" by Tal Kol which answers exactly your problem: https://hackernoon.com/avoiding-accidental-complexity-when-structuring-your-app-state-6e6d22ad5e2a
Your example is quite simplistic and everything really depends on your needs but personally I would go with something like this (based on linked article):
var store = {
emailsById: {
123: {
value: '123#example.com',
},
456: {
value: '456#example.com',
},
789: {
value: '789#example.com',
},
// ...
},
validEmailsMap: {
456: true, // true when valid
789: false, // false when invalid
},
};
So your best option would be to create a separate file that will contain all your validations methods. Import that into the component you're using and then when you want to use the logic for valid/invalid.
If its something that you feel you want to put in the store from the beginning and the data will never be in a transient state you could parse your DTO through an array map in your reducer when you get the response from your API.
export default function (state = initialState, action) {
const {type, response} = action
switch (type) {
case DATA_RECIEVED_SUCCESS:
const items = []
for (var i = 0; i < response.emailsById.length; i++) {
var email = response.emailsById[i];
email.isValid = checkEmailValid(email)
items.push(email)
}
return {
...state,
items
}
}
}
However my preference would be to always check at the last moment you need to. It makes it a safer design in case you find you need to change you design in the future. Also separating the validation logic out will make it more testable
First of all, the way you defined an array in javascript is wrong.
What you need is an array of objects like,
emails : [
{
id: '1',
email: 'abc#abc.com',
isValid: true
},
{
id: '2',
email: 'abc.com',
isValid: false;
}
];
if you need do access email based on an id, you can add an id property along with email and isValid. uuid is a good way to go about it.
In conclusion, it depends upon your use case.
I believe, the above example is a good way to keep data in store because it's simple.
What you described in your second example is like maintaining two different states. I would not recommend that.
How can I make i18next load all languages from just one file?
I managed to do it by putting each language in a seperate file (translation-en.json, translation-no.json, etc), and also managed to input languages with the resStore option, but putting it all in a seperate .json file is really not documented anywhere (I've searched for 4 hours+ now)
My js code:
i18n.init({
debug: true,
lng: 'en',
resGetPath: 'translation.json'
},
function(t) {
console.log(t('test'));
});
My translation.json file:
{
en: {
translation: {
test: "some string"
}
},
no: {
translation: {
test: "litt tekst"
}
}
}
Ok, so I managed to "hack" it byt putting an object into a seperate .js file, include it in a script tag and loading it using resStore, but that just can't be the best way to use this lib.
Assume that your translation.json has loaded and assigned to a variable named resStore:
var resStore = {
en: {
translation: {
test: "some string"
}
},
no: {
translation: {
test: "litt tekst"
}
}
};
Next, you can override default ajax loading functionality with your customLoad function. An example might look like this:
var options = {
lng: 'en',
load: 'current',
lowerCaseLng: true,
fallbackLng: false,
resGetPath: 'i18n/__lng__/__ns__.json',
customLoad: function(lng, ns, options, loadComplete) {
var data = resStore[lng][ns];
loadComplete(null, data); // or loadComplete('some error'); if failed
},
ns: {
namespaces: ['translation'],
defaultNs: 'translation'
}
};
i18n.init(options, function(t) {
t('test'); // will get "some string"
});
new update on Mar 20, 2015
You can simply pass your resource store with the resStore option:
i18n.init({ resStore: resources });