@plentymarkets/shop-core 1.13.4 → 1.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.d.mts +4 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +69 -6
- package/dist/runtime/composables/localization/helpers.d.ts +21 -0
- package/dist/runtime/composables/localization/helpers.js +157 -0
- package/dist/runtime/composables/localization/useEditorLocalizationKeys.d.ts +20 -0
- package/dist/runtime/composables/localization/useEditorLocalizationKeys.js +176 -0
- package/dist/runtime/composables/localization/useEditorLocalizationLocales.d.ts +50 -0
- package/dist/runtime/composables/localization/useEditorLocalizationLocales.js +67 -0
- package/dist/runtime/composables/useT.d.ts +1 -0
- package/dist/runtime/composables/useT.js +9 -0
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/types/localization.d.ts +42 -0
- package/dist/runtime/types/localization.js +0 -0
- package/dist/types.d.mts +1 -1
- package/package.json +14 -7
package/dist/module.d.mts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
export { Cookie, CookieGroup, CookieGroupFromNuxtConfig, Events, JsonCookie, Notification, PaymentButtonComponent, PaymentButtonComponentProps } from '../dist/runtime/types/index.js';
|
|
2
|
+
export { Cookie, CookieGroup, CookieGroupFromNuxtConfig, Events, JsonCookie, LocalizationMessage, Notification, PaymentButtonComponent, PaymentButtonComponentProps } from '../dist/runtime/types/index.js';
|
|
3
3
|
|
|
4
4
|
interface ModuleOptions {
|
|
5
5
|
apiUrl: string;
|
|
6
|
+
apiEndpoint: string;
|
|
7
|
+
configId: number;
|
|
8
|
+
securityToken: string;
|
|
6
9
|
}
|
|
7
10
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
8
11
|
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,15 +1,63 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addImports } from '@nuxt/kit';
|
|
2
|
+
import { ofetch } from 'ofetch';
|
|
3
|
+
import { writeFile } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
2
5
|
|
|
3
|
-
const
|
|
6
|
+
const fetchAllTranslations = async (url, configId, securityToken) => {
|
|
7
|
+
return await ofetch(
|
|
8
|
+
`${url}/rest/storefront/translations/${configId}/complete`,
|
|
9
|
+
{
|
|
10
|
+
method: "GET",
|
|
11
|
+
headers: {
|
|
12
|
+
"X-Security-Token": securityToken,
|
|
13
|
+
"Content-Type": "application/json"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const module$1 = defineNuxtModule({
|
|
4
20
|
meta: {
|
|
5
|
-
name: "shop-core",
|
|
21
|
+
name: "@plentymarkets/shop-core",
|
|
6
22
|
configKey: "shopCore"
|
|
7
23
|
},
|
|
8
|
-
// Default configuration options of the Nuxt module
|
|
9
24
|
defaults: {},
|
|
10
|
-
|
|
25
|
+
moduleDependencies: {
|
|
26
|
+
"@nuxtjs/i18n": {
|
|
27
|
+
version: ">=9.5.4"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
async setup(_options, nuxt) {
|
|
11
31
|
const resolver = createResolver(import.meta.url);
|
|
12
|
-
|
|
32
|
+
nuxt.options.runtimeConfig.public.shopCore = {
|
|
33
|
+
apiUrl: _options.apiUrl,
|
|
34
|
+
configId: _options.configId
|
|
35
|
+
};
|
|
36
|
+
const overridesDir = resolver.resolve("./runtime/lang/overrides");
|
|
37
|
+
const allLocalesForOverride = [];
|
|
38
|
+
if (_options.apiEndpoint && _options.configId && _options.securityToken) {
|
|
39
|
+
try {
|
|
40
|
+
const { data } = await fetchAllTranslations(_options.apiEndpoint, _options.configId, _options.securityToken);
|
|
41
|
+
for (const key of Object.keys(data)) {
|
|
42
|
+
allLocalesForOverride.push(key);
|
|
43
|
+
await writeFile(join(overridesDir, `${key}.json`), JSON.stringify({ translated: data[key] }, null, 2));
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.warn("Failed to fetch translations: ", error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
nuxt.hook("i18n:registerModule", (register) => {
|
|
50
|
+
if (allLocalesForOverride.length > 0) {
|
|
51
|
+
register({
|
|
52
|
+
langDir: resolver.resolve("./runtime/lang/overrides"),
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
locales: allLocalesForOverride.map((locale) => ({
|
|
55
|
+
code: locale,
|
|
56
|
+
file: `${locale}.json`
|
|
57
|
+
}))
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
13
61
|
addImports({
|
|
14
62
|
name: "useSdk",
|
|
15
63
|
as: "useSdk",
|
|
@@ -55,7 +103,22 @@ const module = defineNuxtModule({
|
|
|
55
103
|
as: "useCartStockReservation",
|
|
56
104
|
from: resolver.resolve("./runtime/composables/useCartStockReservation")
|
|
57
105
|
});
|
|
106
|
+
addImports({
|
|
107
|
+
name: "useEditorLocalizationKeys",
|
|
108
|
+
as: "useEditorLocalizationKeys",
|
|
109
|
+
from: resolver.resolve("./runtime/composables/localization/useEditorLocalizationKeys")
|
|
110
|
+
});
|
|
111
|
+
addImports({
|
|
112
|
+
name: "useEditorLocalizationLocales",
|
|
113
|
+
as: "useEditorLocalizationLocales",
|
|
114
|
+
from: resolver.resolve("./runtime/composables/localization/useEditorLocalizationLocales")
|
|
115
|
+
});
|
|
116
|
+
addImports({
|
|
117
|
+
name: "t",
|
|
118
|
+
as: "t",
|
|
119
|
+
from: resolver.resolve("./runtime/composables/useT")
|
|
120
|
+
});
|
|
58
121
|
}
|
|
59
122
|
});
|
|
60
123
|
|
|
61
|
-
export { module as default };
|
|
124
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LocalizationMessage, I18nInstance } from '../../types/index.js';
|
|
2
|
+
export declare const unflattenTranslations: (flat: Record<string, string>) => Record<string, any>;
|
|
3
|
+
/**
|
|
4
|
+
* Load default keys from i18n messages (PWA)
|
|
5
|
+
* @param keysMap
|
|
6
|
+
* @param $i18n
|
|
7
|
+
*/
|
|
8
|
+
export declare const loadDefaultKeys: (keysMap: Map<string, Record<string, LocalizationMessage>>, $i18n: I18nInstance) => Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Load translated keys from i18n messages (Module Overrides)
|
|
11
|
+
* @param keysMap
|
|
12
|
+
* @param $i18n
|
|
13
|
+
*/
|
|
14
|
+
export declare const loadTranslatedKeys: (keysMap: Map<string, Record<string, LocalizationMessage>>, $i18n: I18nInstance) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Load all translations from the backend
|
|
17
|
+
* @param keysMap
|
|
18
|
+
* @param $i18n
|
|
19
|
+
* @param backendLanguages
|
|
20
|
+
*/
|
|
21
|
+
export declare const loadKeysFromBackend: (keysMap: Map<string, Record<string, LocalizationMessage>>, $i18n: I18nInstance, backendLanguages?: Set<string>) => Promise<void>;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "nuxt/app";
|
|
2
|
+
import { useSdk } from "#imports";
|
|
3
|
+
function flattenTranslations(data) {
|
|
4
|
+
const result = {};
|
|
5
|
+
function flattenObject(obj, prefix = "") {
|
|
6
|
+
const flattened = {};
|
|
7
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
8
|
+
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
9
|
+
if (typeof value === "string") {
|
|
10
|
+
flattened[newKey] = value;
|
|
11
|
+
} else {
|
|
12
|
+
Object.assign(flattened, flattenObject(value, newKey));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return flattened;
|
|
16
|
+
}
|
|
17
|
+
for (const [lang, modules] of Object.entries(data)) {
|
|
18
|
+
result[lang] = flattenObject(modules);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
export const unflattenTranslations = (flat) => {
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const [key, value] of Object.entries(flat)) {
|
|
25
|
+
const parts = key.split(".");
|
|
26
|
+
let current = result;
|
|
27
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
28
|
+
const part = parts[i];
|
|
29
|
+
if (!current[part]) current[part] = {};
|
|
30
|
+
current = current[part];
|
|
31
|
+
}
|
|
32
|
+
const lastPart = parts[parts.length - 1];
|
|
33
|
+
current[lastPart] = value;
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
const isCompiledMessage = (value) => {
|
|
38
|
+
return typeof value === "object" && value !== null && ("loc" in value && typeof value.loc === "object" && value.loc !== null && "source" in value.loc && typeof value.loc.source === "string" || "b" in value && typeof value.b === "object" && value.b !== null && "s" in value.b && typeof value.b.s === "string");
|
|
39
|
+
};
|
|
40
|
+
const createEmptyMessage = (availableLocales) => {
|
|
41
|
+
let data = {};
|
|
42
|
+
for (const locale of availableLocales) {
|
|
43
|
+
data = {
|
|
44
|
+
...data,
|
|
45
|
+
[locale]: {
|
|
46
|
+
value: "",
|
|
47
|
+
input: "",
|
|
48
|
+
isDeployed: true
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return data;
|
|
53
|
+
};
|
|
54
|
+
const getTranslationValue = (value) => {
|
|
55
|
+
if (isCompiledMessage(value) && value) {
|
|
56
|
+
const data = value;
|
|
57
|
+
return data?.loc?.source ?? data?.b?.s ?? "";
|
|
58
|
+
}
|
|
59
|
+
if (typeof value === "string") {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
};
|
|
64
|
+
const isNestedObject = (value) => {
|
|
65
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !isCompiledMessage(value);
|
|
66
|
+
};
|
|
67
|
+
const extractKeysWithValues = (obj, prefix = "") => {
|
|
68
|
+
const result = {};
|
|
69
|
+
for (const key in obj) {
|
|
70
|
+
let fullKey = prefix ? `${prefix}.${key}` : key;
|
|
71
|
+
if (fullKey.endsWith(".body.static")) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (fullKey.endsWith(".loc.source")) {
|
|
75
|
+
fullKey = fullKey.substring(0, fullKey.length - ".loc.source".length);
|
|
76
|
+
}
|
|
77
|
+
const value = obj[key];
|
|
78
|
+
if (isNestedObject(value)) {
|
|
79
|
+
Object.assign(result, extractKeysWithValues(value, fullKey));
|
|
80
|
+
} else {
|
|
81
|
+
const translationValue = getTranslationValue(value);
|
|
82
|
+
if (translationValue)
|
|
83
|
+
result[fullKey] = {
|
|
84
|
+
value: translationValue,
|
|
85
|
+
default: translationValue,
|
|
86
|
+
input: translationValue,
|
|
87
|
+
isDeployed: true
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
export const loadDefaultKeys = async (keysMap, $i18n) => {
|
|
94
|
+
for (const locale of $i18n.availableLocales) {
|
|
95
|
+
await $i18n.loadLocaleMessages(locale);
|
|
96
|
+
const messages = $i18n.getLocaleMessage(locale);
|
|
97
|
+
const keysWithValues = extractKeysWithValues(messages);
|
|
98
|
+
for (const [key, value] of Object.entries(keysWithValues)) {
|
|
99
|
+
if (key.startsWith("translated.")) continue;
|
|
100
|
+
if (!keysMap.has(key)) {
|
|
101
|
+
keysMap.set(key, createEmptyMessage($i18n.availableLocales));
|
|
102
|
+
}
|
|
103
|
+
const translations = keysMap.get(key);
|
|
104
|
+
translations[locale] = value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
export const loadTranslatedKeys = (keysMap, $i18n) => {
|
|
109
|
+
for (const locale of $i18n.availableLocales) {
|
|
110
|
+
const messages = $i18n.getLocaleMessage(locale);
|
|
111
|
+
const translatedMessages = messages["translated"];
|
|
112
|
+
if (!translatedMessages) continue;
|
|
113
|
+
const keysWithValues = extractKeysWithValues(translatedMessages);
|
|
114
|
+
for (const [key, value] of Object.entries(keysWithValues)) {
|
|
115
|
+
if (!keysMap.has(key)) {
|
|
116
|
+
keysMap.set(key, createEmptyMessage($i18n.availableLocales));
|
|
117
|
+
}
|
|
118
|
+
const translations = keysMap.get(key);
|
|
119
|
+
if (translations[locale]) {
|
|
120
|
+
translations[locale].value = value.value;
|
|
121
|
+
translations[locale].input = value.input;
|
|
122
|
+
} else {
|
|
123
|
+
translations[locale] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
export const loadKeysFromBackend = async (keysMap, $i18n, backendLanguages) => {
|
|
129
|
+
try {
|
|
130
|
+
const config = useRuntimeConfig().public.shopCore;
|
|
131
|
+
const configId = config?.configId;
|
|
132
|
+
if (!configId) return;
|
|
133
|
+
const { data } = await useSdk().plentysystems.getAllTranslations({ configId });
|
|
134
|
+
const flattened = flattenTranslations(data);
|
|
135
|
+
for (const [lang, translations] of Object.entries(flattened)) {
|
|
136
|
+
if (backendLanguages && Object.keys(translations).length > 0) backendLanguages.add(lang);
|
|
137
|
+
for (const [key, value] of Object.entries(translations)) {
|
|
138
|
+
if (!keysMap.has(key)) {
|
|
139
|
+
keysMap.set(key, createEmptyMessage($i18n.availableLocales));
|
|
140
|
+
}
|
|
141
|
+
const localeTranslations = keysMap.get(key);
|
|
142
|
+
if (localeTranslations[lang]) {
|
|
143
|
+
localeTranslations[lang].isDeployed = localeTranslations[lang].value === value;
|
|
144
|
+
localeTranslations[lang].value = value;
|
|
145
|
+
localeTranslations[lang].input = value;
|
|
146
|
+
} else {
|
|
147
|
+
localeTranslations[lang] = {
|
|
148
|
+
value,
|
|
149
|
+
input: value,
|
|
150
|
+
isDeployed: false
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MultilingualKeysState } from '../../types/index.js';
|
|
2
|
+
export declare const useEditorLocalizationKeys: () => {
|
|
3
|
+
updateTranslationInput: (key: string, locale: string, newInput: string) => void;
|
|
4
|
+
checkHasUnsavedChanges: () => boolean;
|
|
5
|
+
collectChangedTranslations: () => Record<string, Record<string, string>>;
|
|
6
|
+
loadKeys: () => Promise<void>;
|
|
7
|
+
getTranslatedCount: (locale: string) => {
|
|
8
|
+
translated: number;
|
|
9
|
+
undeployed: number;
|
|
10
|
+
total: number;
|
|
11
|
+
};
|
|
12
|
+
getCategoryFromKey: (key: string) => string;
|
|
13
|
+
getKeyFromFullKey: (key: string) => string;
|
|
14
|
+
saveLocalizations: () => Promise<void>;
|
|
15
|
+
keys: import("vue").Ref<MultilingualKeysState[], MultilingualKeysState[]>;
|
|
16
|
+
drawerOpen: import("vue").Ref<boolean, boolean>;
|
|
17
|
+
hasChanges: import("vue").Ref<boolean, boolean>;
|
|
18
|
+
backendLanguages: import("vue").Ref<Set<string>, Set<string>>;
|
|
19
|
+
loading: import("vue").Ref<boolean, boolean>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { useState, useNuxtApp, useRuntimeConfig } from "nuxt/app";
|
|
2
|
+
import { toRefs } from "vue";
|
|
3
|
+
import { loadDefaultKeys, loadTranslatedKeys, loadKeysFromBackend, unflattenTranslations } from "./helpers.js";
|
|
4
|
+
import { useSdk, useNotification, useEditorLocalizationLocales } from "#imports";
|
|
5
|
+
import { allLanguages } from "./useEditorLocalizationLocales.js";
|
|
6
|
+
export const useEditorLocalizationKeys = () => {
|
|
7
|
+
const state = useState("useEditorLocalizationKeys", () => ({
|
|
8
|
+
keys: [],
|
|
9
|
+
drawerOpen: false,
|
|
10
|
+
hasChanges: false,
|
|
11
|
+
backendLanguages: /* @__PURE__ */ new Set(),
|
|
12
|
+
loading: false
|
|
13
|
+
}));
|
|
14
|
+
const loadKeys = async () => {
|
|
15
|
+
if (state.value.loading) return;
|
|
16
|
+
const $i18n = useNuxtApp().$i18n;
|
|
17
|
+
state.value.loading = true;
|
|
18
|
+
state.value.keys = [];
|
|
19
|
+
state.value.backendLanguages = /* @__PURE__ */ new Set();
|
|
20
|
+
const keysMap = /* @__PURE__ */ new Map();
|
|
21
|
+
await loadDefaultKeys(keysMap, $i18n);
|
|
22
|
+
loadTranslatedKeys(keysMap, $i18n);
|
|
23
|
+
await loadKeysFromBackend(keysMap, $i18n, state.value.backendLanguages);
|
|
24
|
+
state.value.keys = Array.from(keysMap.entries()).map(([key, translations]) => ({
|
|
25
|
+
key,
|
|
26
|
+
translations
|
|
27
|
+
})).sort((a, b) => a.key.localeCompare(b.key));
|
|
28
|
+
state.value.loading = false;
|
|
29
|
+
state.value.drawerOpen = true;
|
|
30
|
+
};
|
|
31
|
+
const getTranslatedCount = (locale) => {
|
|
32
|
+
let translatedCount = 0;
|
|
33
|
+
let undeployedCount = 0;
|
|
34
|
+
let totalCount = state.value.keys.length;
|
|
35
|
+
for (const keyState of state.value.keys) {
|
|
36
|
+
if (keyState.translations[locale] && keyState.translations[locale].value.length > 0) {
|
|
37
|
+
translatedCount++;
|
|
38
|
+
if (!keyState.translations[locale].isDeployed) {
|
|
39
|
+
undeployedCount++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
translated: translatedCount,
|
|
45
|
+
undeployed: undeployedCount,
|
|
46
|
+
total: totalCount
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const getCategoryFromKey = (key) => {
|
|
50
|
+
const parts = key.split(".");
|
|
51
|
+
const category = (parts.length > 1 ? parts[0] : "uncategorized") ?? "";
|
|
52
|
+
return category.replace(/([A-Z])/g, " $1").replace(/^./, (match) => match.toUpperCase()).trim();
|
|
53
|
+
};
|
|
54
|
+
const getKeyFromFullKey = (key) => {
|
|
55
|
+
const parts = key.split(".");
|
|
56
|
+
return parts.length > 1 ? parts.slice(1).join(".") : key;
|
|
57
|
+
};
|
|
58
|
+
const collectChangedTranslations = () => {
|
|
59
|
+
const { selectedLocales } = useEditorLocalizationLocales();
|
|
60
|
+
if (!selectedLocales.value || selectedLocales.value.length === 0) return {};
|
|
61
|
+
const translationsByLocale = {};
|
|
62
|
+
const selectedLocalesSet = new Set(selectedLocales.value);
|
|
63
|
+
for (let i = 0; i < state.value.keys.length; i++) {
|
|
64
|
+
const keyState = state.value.keys[i];
|
|
65
|
+
for (const locale in keyState.translations) {
|
|
66
|
+
if (!selectedLocalesSet.has(locale)) continue;
|
|
67
|
+
const translation = keyState.translations[locale];
|
|
68
|
+
if (translation && translation.input && translation.input !== translation.value) {
|
|
69
|
+
if (!translationsByLocale[locale]) translationsByLocale[locale] = {};
|
|
70
|
+
translationsByLocale[locale][keyState.key] = translation.input;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return translationsByLocale;
|
|
75
|
+
};
|
|
76
|
+
const checkHasUnsavedChanges = () => {
|
|
77
|
+
const { selectedLocales } = useEditorLocalizationLocales();
|
|
78
|
+
if (!selectedLocales.value || selectedLocales.value.length === 0) return false;
|
|
79
|
+
const selectedLocalesSet = new Set(selectedLocales.value);
|
|
80
|
+
for (const keyState of state.value.keys) {
|
|
81
|
+
for (const locale in keyState.translations) {
|
|
82
|
+
if (!selectedLocalesSet.has(locale)) continue;
|
|
83
|
+
const translation = keyState.translations[locale];
|
|
84
|
+
if (translation && translation.input && translation.input !== translation.value) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
};
|
|
91
|
+
const updateTranslationInput = (key, locale, newInput) => {
|
|
92
|
+
const keyState = state.value.keys.find((k) => k.key === key);
|
|
93
|
+
if (!keyState || !keyState.translations[locale]) return;
|
|
94
|
+
const translation = keyState.translations[locale];
|
|
95
|
+
translation.input = newInput;
|
|
96
|
+
if (newInput !== translation.value) {
|
|
97
|
+
state.value.hasChanges = true;
|
|
98
|
+
} else if (state.value.hasChanges) {
|
|
99
|
+
state.value.hasChanges = checkHasUnsavedChanges();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const saveLanguageTranslations = async (language, configId, translations) => {
|
|
103
|
+
try {
|
|
104
|
+
const nestedTranslations = unflattenTranslations(translations);
|
|
105
|
+
const languageExistsInBackend = state.value.backendLanguages.has(language);
|
|
106
|
+
const sdk = useSdk().plentysystems;
|
|
107
|
+
const saveMethod = languageExistsInBackend ? sdk.patchMergeTranslations : sdk.doCreateTranslations;
|
|
108
|
+
await saveMethod({ configId, language, translations: nestedTranslations });
|
|
109
|
+
if (!languageExistsInBackend) state.value.backendLanguages.add(language);
|
|
110
|
+
return { language, success: true };
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return { language, success: false, error };
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const updateLocalStateAfterSave = (language, savedTranslations) => {
|
|
116
|
+
for (let i = 0; i < state.value.keys.length; i++) {
|
|
117
|
+
const keyState = state.value.keys[i];
|
|
118
|
+
const translation = keyState.translations[language];
|
|
119
|
+
if (translation && savedTranslations[keyState.key] !== void 0) {
|
|
120
|
+
translation.isDeployed = true;
|
|
121
|
+
translation.value = translation.input;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const notifySaveResults = (results) => {
|
|
126
|
+
const { send } = useNotification();
|
|
127
|
+
const successful = results.filter((r) => r.success).map((r) => allLanguages[r.language] || r.language.toUpperCase());
|
|
128
|
+
const failed = results.filter((r) => !r.success).map((r) => allLanguages[r.language] || r.language.toUpperCase());
|
|
129
|
+
if (successful.length > 0) {
|
|
130
|
+
send({
|
|
131
|
+
message: `Translations for ${successful.join(", ")} saved successfully`,
|
|
132
|
+
type: "positive"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (failed.length > 0) {
|
|
136
|
+
send({
|
|
137
|
+
message: `Failed to save translations for ${failed.join(", ")}`,
|
|
138
|
+
type: "negative",
|
|
139
|
+
persist: true
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const saveLocalizations = async () => {
|
|
144
|
+
const config = useRuntimeConfig().public.shopCore;
|
|
145
|
+
const configId = config?.configId;
|
|
146
|
+
if (!configId) return;
|
|
147
|
+
const translationsByLocale = collectChangedTranslations();
|
|
148
|
+
const localesToSave = Object.keys(translationsByLocale);
|
|
149
|
+
if (localesToSave.length === 0) return;
|
|
150
|
+
state.value.loading = true;
|
|
151
|
+
try {
|
|
152
|
+
const savePromises = localesToSave.map(
|
|
153
|
+
(language) => saveLanguageTranslations(language, configId, translationsByLocale[language])
|
|
154
|
+
);
|
|
155
|
+
const results = await Promise.all(savePromises);
|
|
156
|
+
results.forEach((result) => {
|
|
157
|
+
if (result.success) updateLocalStateAfterSave(result.language, translationsByLocale[result.language]);
|
|
158
|
+
});
|
|
159
|
+
notifySaveResults(results);
|
|
160
|
+
state.value.hasChanges = false;
|
|
161
|
+
} finally {
|
|
162
|
+
state.value.loading = false;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
...toRefs(state.value),
|
|
167
|
+
updateTranslationInput,
|
|
168
|
+
checkHasUnsavedChanges,
|
|
169
|
+
collectChangedTranslations,
|
|
170
|
+
loadKeys,
|
|
171
|
+
getTranslatedCount,
|
|
172
|
+
getCategoryFromKey,
|
|
173
|
+
getKeyFromFullKey,
|
|
174
|
+
saveLocalizations
|
|
175
|
+
};
|
|
176
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const allLanguages: {
|
|
2
|
+
de: string;
|
|
3
|
+
en: string;
|
|
4
|
+
bg: string;
|
|
5
|
+
fr: string;
|
|
6
|
+
it: string;
|
|
7
|
+
es: string;
|
|
8
|
+
tr: string;
|
|
9
|
+
nl: string;
|
|
10
|
+
pl: string;
|
|
11
|
+
pt: string;
|
|
12
|
+
no: string;
|
|
13
|
+
ro: string;
|
|
14
|
+
da: string;
|
|
15
|
+
sv: string;
|
|
16
|
+
cs: string;
|
|
17
|
+
ru: string;
|
|
18
|
+
sk: string;
|
|
19
|
+
zh: string;
|
|
20
|
+
vi: string;
|
|
21
|
+
};
|
|
22
|
+
export declare const useEditorLocalizationLocales: () => {
|
|
23
|
+
allLanguages: {
|
|
24
|
+
de: string;
|
|
25
|
+
en: string;
|
|
26
|
+
bg: string;
|
|
27
|
+
fr: string;
|
|
28
|
+
it: string;
|
|
29
|
+
es: string;
|
|
30
|
+
tr: string;
|
|
31
|
+
nl: string;
|
|
32
|
+
pl: string;
|
|
33
|
+
pt: string;
|
|
34
|
+
no: string;
|
|
35
|
+
ro: string;
|
|
36
|
+
da: string;
|
|
37
|
+
sv: string;
|
|
38
|
+
cs: string;
|
|
39
|
+
ru: string;
|
|
40
|
+
sk: string;
|
|
41
|
+
zh: string;
|
|
42
|
+
vi: string;
|
|
43
|
+
};
|
|
44
|
+
selectLocale: (locale: string) => void;
|
|
45
|
+
removeLocale: (locale: string) => void;
|
|
46
|
+
toggleLocale: (locale: string) => void;
|
|
47
|
+
initializeLocales: () => void;
|
|
48
|
+
selectedLocales: import("vue").Ref<string[], string[]>;
|
|
49
|
+
loading: import("vue").Ref<boolean, boolean>;
|
|
50
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useState } from "#app";
|
|
2
|
+
import { toRefs } from "vue";
|
|
3
|
+
export const allLanguages = {
|
|
4
|
+
de: "German (default)",
|
|
5
|
+
en: "English (default)",
|
|
6
|
+
bg: "Bulgarian",
|
|
7
|
+
fr: "French",
|
|
8
|
+
it: "Italian",
|
|
9
|
+
es: "Spanish",
|
|
10
|
+
tr: "Turkish",
|
|
11
|
+
nl: "Dutch",
|
|
12
|
+
pl: "Polish",
|
|
13
|
+
pt: "Portuguese",
|
|
14
|
+
no: "Norwegian",
|
|
15
|
+
ro: "Romanian",
|
|
16
|
+
da: "Danish",
|
|
17
|
+
sv: "Swedish",
|
|
18
|
+
cs: "Czech",
|
|
19
|
+
ru: "Russian",
|
|
20
|
+
sk: "Slovak",
|
|
21
|
+
zh: "Chinese",
|
|
22
|
+
vi: "Vietnamese"
|
|
23
|
+
};
|
|
24
|
+
export const useEditorLocalizationLocales = () => {
|
|
25
|
+
const state = useState("useEditorLocalizationLocales", () => ({
|
|
26
|
+
selectedLocales: [],
|
|
27
|
+
loading: false
|
|
28
|
+
}));
|
|
29
|
+
const selectLocale = (locale) => {
|
|
30
|
+
if (!state.value.selectedLocales.includes(locale)) {
|
|
31
|
+
state.value.selectedLocales.push(locale);
|
|
32
|
+
saveLocales();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const removeLocale = (locale) => {
|
|
36
|
+
state.value.selectedLocales = state.value.selectedLocales.filter((l) => l !== locale);
|
|
37
|
+
saveLocales();
|
|
38
|
+
};
|
|
39
|
+
const toggleLocale = (locale) => {
|
|
40
|
+
if (state.value.selectedLocales.includes(locale)) {
|
|
41
|
+
removeLocale(locale);
|
|
42
|
+
} else {
|
|
43
|
+
selectLocale(locale);
|
|
44
|
+
}
|
|
45
|
+
saveLocales();
|
|
46
|
+
};
|
|
47
|
+
const initializeLocales = () => {
|
|
48
|
+
const selectedLocales = localStorage.getItem("editorLocalizationSelectedLocales");
|
|
49
|
+
if (selectedLocales) {
|
|
50
|
+
const parsedLocales = JSON.parse(selectedLocales);
|
|
51
|
+
state.value.selectedLocales = parsedLocales.filter((l) => allLanguages.hasOwnProperty(l));
|
|
52
|
+
} else {
|
|
53
|
+
state.value.selectedLocales = ["de", "en"];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const saveLocales = () => {
|
|
57
|
+
localStorage.setItem("editorLocalizationSelectedLocales", JSON.stringify(state.value.selectedLocales));
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
...toRefs(state.value),
|
|
61
|
+
allLanguages,
|
|
62
|
+
selectLocale,
|
|
63
|
+
removeLocale,
|
|
64
|
+
toggleLocale,
|
|
65
|
+
initializeLocales
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const t: (key: string, listOrNamed?: Record<string, unknown>) => string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useNuxtApp } from "nuxt/app";
|
|
2
|
+
export const t = (key, listOrNamed) => {
|
|
3
|
+
const $i18n = useNuxtApp().$i18n;
|
|
4
|
+
const prefix = "translated.";
|
|
5
|
+
if ($i18n.te(prefix + key)) {
|
|
6
|
+
return $i18n.t(prefix + key, listOrNamed ?? {});
|
|
7
|
+
}
|
|
8
|
+
return $i18n.t(key, listOrNamed ?? {});
|
|
9
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type MultilingualKeysState = {
|
|
2
|
+
key: string;
|
|
3
|
+
translations: {
|
|
4
|
+
[locale: string]: LocalizationMessage;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
export type CompiledMessage = {
|
|
8
|
+
type?: number;
|
|
9
|
+
loc?: {
|
|
10
|
+
source: string;
|
|
11
|
+
start: {
|
|
12
|
+
line: number;
|
|
13
|
+
column: number;
|
|
14
|
+
offset: number;
|
|
15
|
+
};
|
|
16
|
+
end: {
|
|
17
|
+
line: number;
|
|
18
|
+
column: number;
|
|
19
|
+
offset: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
b?: {
|
|
23
|
+
s: string;
|
|
24
|
+
};
|
|
25
|
+
start?: number;
|
|
26
|
+
end?: number;
|
|
27
|
+
body?: unknown;
|
|
28
|
+
items?: unknown[];
|
|
29
|
+
};
|
|
30
|
+
export type LocalizationMessage = {
|
|
31
|
+
value: string;
|
|
32
|
+
input: string;
|
|
33
|
+
default?: string;
|
|
34
|
+
isDeployed: boolean;
|
|
35
|
+
};
|
|
36
|
+
export interface I18nInstance {
|
|
37
|
+
availableLocales: string[];
|
|
38
|
+
loadLocaleMessages: (locale: string) => Promise<void>;
|
|
39
|
+
getLocaleMessage: (locale: string) => Record<string, unknown>;
|
|
40
|
+
te: (key: string) => boolean;
|
|
41
|
+
t: (key: string, params?: Record<string, unknown>) => string;
|
|
42
|
+
}
|
|
File without changes
|
package/dist/types.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type Cookie, type CookieGroup, type CookieGroupFromNuxtConfig, type Events, type JsonCookie, type Notification, type PaymentButtonComponent, type PaymentButtonComponentProps } from '../dist/runtime/types/index.js'
|
|
1
|
+
export { type Cookie, type CookieGroup, type CookieGroupFromNuxtConfig, type Events, type JsonCookie, type LocalizationMessage, type Notification, type PaymentButtonComponent, type PaymentButtonComponentProps } from '../dist/runtime/types/index.js'
|
|
2
2
|
|
|
3
3
|
export { default } from './module.mjs'
|
|
4
4
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plentymarkets/shop-core",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.5",
|
|
4
4
|
"description": "Core module for PlentyONE Shop",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"type": "module",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20.0.0",
|
|
18
|
+
"npm": ">=10.0.0"
|
|
19
|
+
},
|
|
16
20
|
"exports": {
|
|
17
21
|
".": {
|
|
18
22
|
"require": "./dist/module.mjs",
|
|
@@ -38,20 +42,21 @@
|
|
|
38
42
|
"test": "vitest run",
|
|
39
43
|
"test:coverage": "vitest run --coverage",
|
|
40
44
|
"test:watch": "vitest watch",
|
|
41
|
-
"test:types": "vue-tsc --noEmit
|
|
45
|
+
"test:types": "vue-tsc --noEmit"
|
|
42
46
|
},
|
|
43
47
|
"dependencies": {
|
|
44
|
-
"@plentymarkets/shop-api": "^0.
|
|
48
|
+
"@plentymarkets/shop-api": "^0.143.0",
|
|
45
49
|
"@vue-storefront/sdk": "^3.4.1",
|
|
46
50
|
"cookie": "^1.0.2",
|
|
47
51
|
"js-sha256": "^0.11.0",
|
|
48
|
-
"resolve": "^1.22.10"
|
|
52
|
+
"resolve": "^1.22.10",
|
|
53
|
+
"ofetch": "^1.5.1"
|
|
49
54
|
},
|
|
50
55
|
"overrides": {
|
|
51
56
|
"prettier": "^3.6.2"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
|
-
"@nuxt/devtools": "^1.
|
|
59
|
+
"@nuxt/devtools": "^3.1.0",
|
|
55
60
|
"@nuxt/eslint-config": "^0.7.6",
|
|
56
61
|
"@nuxt/kit": "^4.1.3",
|
|
57
62
|
"@nuxt/module-builder": "^1.0.2",
|
|
@@ -59,19 +64,21 @@
|
|
|
59
64
|
"@nuxt/test-utils": "^3.19.0",
|
|
60
65
|
"@nuxtjs/i18n": "9.5.4",
|
|
61
66
|
"@types/node": "latest",
|
|
67
|
+
"@typescript-eslint/parser": "^8.47.0",
|
|
62
68
|
"@vitest/coverage-v8": "^3.2.4",
|
|
63
|
-
"@vue-storefront/eslint-config": "^4.1.0",
|
|
64
69
|
"@vue/test-utils": "^2.4.6",
|
|
65
70
|
"changelogen": "^0.5.7",
|
|
66
71
|
"eslint": "^9.24.0",
|
|
67
72
|
"eslint-config-prettier": "^9.1.0",
|
|
68
73
|
"eslint-plugin-vuejs-accessibility": "^2.4.1",
|
|
69
|
-
"happy-dom": "^
|
|
74
|
+
"happy-dom": "^20.0.2",
|
|
70
75
|
"nuxi": "3.17.2",
|
|
71
76
|
"nuxt": "^4.1.3",
|
|
72
77
|
"prettier": "^3.6.2",
|
|
73
78
|
"typescript": "~5.9.2",
|
|
79
|
+
"typescript-eslint": "^8.47.0",
|
|
74
80
|
"vitest": "^3.2.4",
|
|
81
|
+
"vue-eslint-parser": "^10.2.0",
|
|
75
82
|
"vue-tsc": "^2.2.8"
|
|
76
83
|
}
|
|
77
84
|
}
|