How to remove irrelevant languages for categories which are assigned to a sales channel? - shopware6

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.

Related

Retrieve config value from sales channel in plugin settings

How can I retrieve values from the plugin config for a specific sales channel? I need to validate an API-Token, but can only retrieve the value stored for all sales channels and don't know how my custom admin component could even know about its current sales channel.
Currently, I am retrieving values via the following code, which follows the example plugin from Shopware, but they too only retrieve the global value.
Component.register('my-component', {
computed: {
getMyKey() {
return this.pluginConfig['MyPlugin.config.myKey'];
},
pluginConfig() {
let $parent = this.$parent;
while ($parent.actualConfigData === undefined) {
$parent = $parent.$parent;
}
return $parent.actualConfigData.null;
}
}
}
You may want to inject systemConfigApiService and use it to retrieve the config values. getValues takes a second argument for the sales channel id.
Component.register('my-component', {
inject: ['systemConfigApiService'],
methods: {
getConfig(salesChannelId) {
const values = this.systemConfigApiService
.getValues('MyPlugin.config', salesChannelId);
return values.myKey;
},
},
}

How Do I Display Product Images In Shopware 6 Administration

I am working on a Shopware 6 Administrative plugin but displaying product images has been a big headache. I went through shopware repositories of 'product_media', 'media', and generally all folder repositories related to media.
I have not been able to decipher how image linking works since I can not get hold of the exact folder names.
How then do I go about this. Note: I have been able to get correct image names and relevant image data like id, folder id etc.
Below is my module/component idex.js file codes
import template from './images-page.html.twig';
const { Component, Context } = Shopware;
const { Criteria } = Shopware.Data;
Component.register('images', {
template,
inject: ['repositoryFactory', 'mediaService', 'acl'],
metaInfo() {
return {
title: 'images'
};
},
computed: {
/**productMediaRepository() {
return this.repositoryFactory.create(this.product.media.entity);
},*/
productRepository() {
return this.repositoryFactory.create('product');
},
mediaFolderRepository() {
return this.repositoryFactory.create('media_folder');
},
mediaRepository() {
return this.repositoryFactory.create('media');
},
rootFolder() {
const root = this.mediaFolderRepository.create(Context.api);
root.name = this.$tc('sw-media.index.rootFolderName');
root.id = null;
return root;
},
logRep(){
console.log(this.productRepository);
// console.log(this.mediaFolderRepository);
// console.log(this.mediaRepository);
// console.log(this.rootFolder);
}
},
methods: {
logProducts(){
const criteria = new Criteria();
this.productRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result[0]);
});
},
logMediaFolders(){
const criteria = new Criteria();
this.mediaFolderRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result);
});
}
},
created(){
this.logMediaFolders();
}
});
here's the twig template (nothing really here)
<sw-card title="Watermark">
<img src="" alt="Product Image" />
</sw-card>
The media elements of the products are associations that are not loaded automatically, but you can configure the criteria, so that those associations will be loaded directly when you fetch the products. Refer to the official docs for detailed infos.
In you case that means to load the cover image / or all product images, you would have to adjust the criteria you use for fetching the products the following way
logProducts(){
const criteria = new Criteria();
criteria.addAssociation('cover.media');
criteria.addAssociation('media.media');
this.productRepository
.search(criteria, Shopware.Context.api)
.then(result => {
console.log(result[0]);
});
},
Then to link to the cover image you can use:
<sw-card title="Watermark">
<img src="product.cover.media.url" alt="Product Image" />
</sw-card>
You find all the media elements of the product as an array under product.media and can also use product.media[0].media.url to link to those images.

How to directly modify v-model value using directives?

I've got form with dynamic number of input fields and i need to transliterate data, passed to this fields in 'live'. I wrote custom directive which do all job, but there is an a error -> it converts all chars except last one (should be привет->privet, while привет->priveт). This is my source code
directives: {
transliterate: {
update(element, binding) {
element.value = tr(element.value)
}
}
}
This is PUG (Jade)
input(v-model='requestHotels.travellers[index].first_name', v-transliterate='true')
tr - just function, which transliterate from ru to en
I knew why this happening, but i can't solve it by myself. Any ideas?
1) Consider using computed property instead of directive. Personally, I don't like directives because they can add alot of useless complexity to your code. But there are some complex cases where they can be really useful. But this one is not one of them.
export default {
data: () => ({
tranliteratedValue: ""
}),
computed: {
vModelValue: {
get() {
return this.tranliteratedValue;
},
set(value) {
this.tranliteratedValue = transl.transform(value);
}
}
}
};
Full example: https://codesandbox.io/s/039vvo13yv?module=%2Fsrc%2Fcomponents%2FComputedProperty.vue
2) You can use filter and transliterate during render
filters: {
transliterate(value) {
return transl.transform(value);
}
}
Then in your template:
<p>{{ value | transliterate }}</p>
Full example: https://codesandbox.io/s/039vvo13yv?module=%2Fsrc%2Fcomponents%2FFilter.vue
3) Transparent wrapper technique (using custom component)
The idea behind transparent wrapper is that you should create custom component that behave as build-in input (and accepts the same arguments) but you can intercept events and change behaviour as you'd like. In your example - tranliterate input text.
<textarea
v-bind="$attrs"
:value="value"
v-on="listeners"
/>
computed: {
listeners() {
return {
...this.$listeners,
input: event => {
const value = transl.transform(event.target.value + "");
this.$emit("input", value);
}
};
}
}
Full example: https://codesandbox.io/s/039vvo13yv?module=%2Fsrc%2Fcomponents%2Finc%2FTransliteratedInput.vue
Read more about Transparent wrapper technique here https://github.com/chrisvfritz/7-secret-patterns/blob/master/slides-2018-03-03-spotlight-export.pdf
You can check all 3 working approaches here https://codesandbox.io/s/039vvo13yv

Auto-suggest entities using the Wikipedia API

I want to provide an auto-suggest feature to my users, where they can choose from a list of known "things" from a semantic entity database.
I'm looking at using the Wikipedia Media API instead of setting up my own:
https://www.mediawiki.org/wiki/API:Main_page
There is an API tool for testing requests:
https://www.mediawiki.org/wiki/Special:ApiSandbox
For example if a user likes cats:
https://www.wikidata.org/wiki/Q146
The requests would be:
https://en.wikipedia.org/w/api.php?action=query&format=jsonfm&prop=pageterms&list=&meta=&titles=C
https://en.wikipedia.org/w/api.php?action=query&format=jsonfm&prop=pageterms&list=&meta=&titles=Ca
https://en.wikipedia.org/w/api.php?action=query&format=jsonfm&prop=pageterms&list=&meta=&titles=Cat
The user would select Cat from the dropdown, and I would save the ID.
Is this a good approach? How could I improve it?
I managed to load autosuggestions using the Wikipedia REST api:
https://en.wikipedia.org/w/api.php?action=query&format=json&gsrlimit=15&generator=search&origin=*&gsrsearch=
The Angular code:
this.resultsCtrl = new FormControl();
this.resultsCtrl.valueChanges
.debounceTime(400)
.subscribe(name => {
if (name) {
this.filteredResults = this.filterResults(name);
}
});
filterResults(name: string) {
return Observable.create(obs => {
const displaySuggestions = function (response) {
if (!response) {
obs.error(status);
} else {
obs.next(Object.keys(response.query.pages).map(key => response.query.pages[key]));
obs.complete();
}
};
this.http.get('https://en.wikipedia.org/w/api.php?action=query&format=json&gsrlimit=15&generator=search&origin=*&gsrsearch='
+ encodeURI(name))
.subscribe(displaySuggestions);
});
}

Redux - Filterable category with products - Actions & Filter

My state looks exactly like this:
{
categories: {
"unique-category-id" {
"data": {
// All kind of data (name, description ,etc )
"products": [
{
// Products that belong to this category with the current filter
}
]
},
"readyState": "CATEGORY_FETCHED",
"query": { // This object holds all the params that the filter allows to choose from
"brands": [],
"options": {
"usb": true
"minPrice": 200
}
}
}
}
}
This works and fetches data correctly. Now it's time to implement the filtering funcionality. I am trying to make it work like this, not sure if this is the "redux" way of doing things.
1.- On each filter on the category page we assign a click handler:
<li onClick={this.toggleFilterField} key={item.VALUE}>{item.NAME}</li>
toggleFilterField is passed into the container via mapDispatchToProps:
const mapDispatchToProps = (dispatch) => {
return {
toggleFilterField: (category, filter) => {
dispatch(toggleFilterField(category, filter))
}
}
}
export { CustomTagFilter };
export default connect(null, mapDispatchToProps)(CustomTagFilter);
2.- My toogleFilterField should just return an action like this one:
return {
type: CHANGE_FILTER,
category,
filter
}
Then my category_reducer.js should handle this action and update the following state key
state.categories[unique-category-id].query
Now this is OK, but now, after applying the filter to the state I would have to refetch the data from the category.
How and where do I fire a new action based on a previous action success?
Is this the correct way of handling filtered elements in redux that come from a paginated API?