@jhits/plugin-blog 0.0.17 → 0.0.18
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/api/categories.d.ts.map +1 -1
- package/dist/api/categories.js +43 -12
- package/dist/api/handler.d.ts +1 -0
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +259 -32
- package/dist/hooks/useBlogs.d.ts +2 -0
- package/dist/hooks/useBlogs.d.ts.map +1 -1
- package/dist/hooks/useBlogs.js +10 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/lib/i18n.d.ts +14 -0
- package/dist/lib/i18n.d.ts.map +1 -0
- package/dist/lib/i18n.js +58 -0
- package/dist/lib/mappers/apiMapper.d.ts +18 -0
- package/dist/lib/mappers/apiMapper.d.ts.map +1 -1
- package/dist/lib/mappers/apiMapper.js +1 -0
- package/dist/state/reducer.d.ts.map +1 -1
- package/dist/state/reducer.js +11 -6
- package/dist/state/types.d.ts +5 -0
- package/dist/state/types.d.ts.map +1 -1
- package/dist/state/types.js +1 -0
- package/dist/types/post.d.ts +25 -0
- package/dist/types/post.d.ts.map +1 -1
- package/dist/utils/client.d.ts +2 -0
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +3 -1
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +130 -4
- package/dist/views/CanvasEditor/EditorHeader.d.ts +5 -1
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorHeader.js +23 -5
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/usePostLoader.js +14 -4
- package/dist/views/PostManager/LanguageFlags.d.ts +11 -0
- package/dist/views/PostManager/LanguageFlags.d.ts.map +1 -0
- package/dist/views/PostManager/LanguageFlags.js +60 -0
- package/dist/views/PostManager/PostCards.d.ts.map +1 -1
- package/dist/views/PostManager/PostCards.js +4 -1
- package/dist/views/PostManager/PostFilters.d.ts +4 -1
- package/dist/views/PostManager/PostFilters.d.ts.map +1 -1
- package/dist/views/PostManager/PostFilters.js +13 -3
- package/dist/views/PostManager/PostManagerView.d.ts.map +1 -1
- package/dist/views/PostManager/PostManagerView.js +24 -3
- package/dist/views/PostManager/PostTable.d.ts.map +1 -1
- package/dist/views/PostManager/PostTable.js +4 -1
- package/package.json +3 -3
- package/src/api/categories.ts +58 -11
- package/src/api/handler.ts +286 -31
- package/src/hooks/useBlogs.ts +12 -1
- package/src/index.tsx +7 -3
- package/src/lib/i18n.ts +78 -0
- package/src/lib/mappers/apiMapper.ts +20 -0
- package/src/state/reducer.ts +12 -6
- package/src/state/types.ts +5 -0
- package/src/types/post.ts +28 -0
- package/src/utils/client.ts +4 -0
- package/src/views/CanvasEditor/CanvasEditorView.tsx +164 -20
- package/src/views/CanvasEditor/EditorHeader.tsx +93 -18
- package/src/views/CanvasEditor/hooks/usePostLoader.ts +15 -4
- package/src/views/PostManager/LanguageFlags.tsx +136 -0
- package/src/views/PostManager/PostCards.tsx +22 -12
- package/src/views/PostManager/PostFilters.tsx +38 -1
- package/src/views/PostManager/PostManagerView.tsx +25 -2
- package/src/views/PostManager/PostTable.tsx +12 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"categories.d.ts","sourceRoot":"","sources":["../../src/api/categories.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,wBAAsB,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"categories.d.ts","sourceRoot":"","sources":["../../src/api/categories.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,wBAAsB,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAgFxF"}
|
package/dist/api/categories.js
CHANGED
|
@@ -5,22 +5,53 @@
|
|
|
5
5
|
import { NextResponse } from 'next/server';
|
|
6
6
|
export async function GET(req, config) {
|
|
7
7
|
try {
|
|
8
|
+
const url = new URL(req.url);
|
|
9
|
+
const language = url.searchParams.get('language');
|
|
10
|
+
const publishedOnly = url.searchParams.get('published') === 'true';
|
|
8
11
|
const dbConnection = await config.getDb();
|
|
9
12
|
const db = dbConnection.db();
|
|
10
13
|
const blogs = db.collection(config.collectionName || 'blogs');
|
|
11
|
-
//
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
// Build query
|
|
15
|
+
const query = {};
|
|
16
|
+
if (publishedOnly) {
|
|
17
|
+
// If language is specified, check language status, otherwise root status
|
|
18
|
+
if (language) {
|
|
19
|
+
query[`languages.${language}.status`] = 'published';
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
query['publicationData.status'] = 'published';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (language) {
|
|
26
|
+
query[`languages.${language}`] = { $exists: true };
|
|
27
|
+
}
|
|
28
|
+
// 1. Get unique categories from language-specific metadata
|
|
29
|
+
let categories = [];
|
|
30
|
+
if (language) {
|
|
31
|
+
const result = await blogs.distinct(`languages.${language}.metadata.categories`, query);
|
|
32
|
+
categories = result.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const result = await blogs.distinct('categoryTags.category', query);
|
|
36
|
+
categories = result.filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
// 2. Get categories from Hero blocks in contentBlocks
|
|
39
|
+
let heroCategories = [];
|
|
40
|
+
const pipeline = [];
|
|
41
|
+
if (Object.keys(query).length > 0) {
|
|
42
|
+
pipeline.push({ $match: query });
|
|
43
|
+
}
|
|
44
|
+
if (language) {
|
|
45
|
+
pipeline.push({ $project: { blocks: `$languages.${language}.blocks` } }, { $unwind: '$blocks' }, { $match: { 'blocks.type': 'hero' } }, { $project: { category: '$blocks.data.category' } });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
pipeline.push({ $unwind: '$contentBlocks' }, { $match: { 'contentBlocks.type': 'hero' } }, { $project: { category: '$contentBlocks.data.category' } });
|
|
49
|
+
}
|
|
50
|
+
pipeline.push({ $match: { category: { $exists: true, $ne: null, $nin: [''] } } }, { $group: { _id: '$category' } });
|
|
51
|
+
const heroBlocks = await blogs.aggregate(pipeline).toArray();
|
|
52
|
+
heroCategories = heroBlocks.map((block) => block._id);
|
|
22
53
|
// Combine and deduplicate
|
|
23
|
-
const allCategories = Array.from(new Set([...categories
|
|
54
|
+
const allCategories = Array.from(new Set([...categories, ...heroCategories])).sort();
|
|
24
55
|
return NextResponse.json({ categories: allCategories });
|
|
25
56
|
}
|
|
26
57
|
catch (err) {
|
package/dist/api/handler.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface BlogApiConfig {
|
|
|
21
21
|
* GET /api/blogs - List all blog posts
|
|
22
22
|
* GET /api/blogs?admin=true - List all posts for admin (includes drafts)
|
|
23
23
|
* GET /api/blogs?status=published - Filter by status
|
|
24
|
+
* GET /api/blogs?language=en - Filter by language (falls back to nl if not found)
|
|
24
25
|
*/
|
|
25
26
|
export declare function GET(req: NextRequest, config: BlogApiConfig): Promise<NextResponse>;
|
|
26
27
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/api/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD,MAAM,WAAW,aAAa;IAC1B,yFAAyF;IACzF,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,GAAG,CAAA;KAAE,CAAC,CAAC;IACxC,yDAAyD;IACzD,SAAS,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/api/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD,MAAM,WAAW,aAAa;IAC1B,yFAAyF;IACzF,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,GAAG,CAAA;KAAE,CAAC,CAAC;IACxC,yDAAyD;IACzD,SAAS,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAqKxF;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA+HzF;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC7B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC,CAiIvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC7B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC,CAiKvB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAChC,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC,CA8BvB"}
|
package/dist/api/handler.js
CHANGED
|
@@ -12,6 +12,7 @@ import { slugify } from '../lib/utils/slugify';
|
|
|
12
12
|
* GET /api/blogs - List all blog posts
|
|
13
13
|
* GET /api/blogs?admin=true - List all posts for admin (includes drafts)
|
|
14
14
|
* GET /api/blogs?status=published - Filter by status
|
|
15
|
+
* GET /api/blogs?language=en - Filter by language (falls back to nl if not found)
|
|
15
16
|
*/
|
|
16
17
|
export async function GET(req, config) {
|
|
17
18
|
try {
|
|
@@ -20,6 +21,7 @@ export async function GET(req, config) {
|
|
|
20
21
|
const skip = Number(url.searchParams.get('skip') ?? 0);
|
|
21
22
|
const statusFilter = url.searchParams.get('status');
|
|
22
23
|
const isAdminView = url.searchParams.get('admin') === 'true';
|
|
24
|
+
const requestedLanguage = url.searchParams.get('language') || 'nl';
|
|
23
25
|
const userId = await config.getUserId(req);
|
|
24
26
|
const dbConnection = await config.getDb();
|
|
25
27
|
const db = dbConnection.db();
|
|
@@ -39,9 +41,10 @@ export async function GET(req, config) {
|
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
else {
|
|
42
|
-
// Public view: only published posts
|
|
44
|
+
// Public view: only published posts in the SPECIFIC language
|
|
45
|
+
// This ensures we only fetch blogs that actually have content for this language
|
|
43
46
|
query = {
|
|
44
|
-
|
|
47
|
+
[`languages.${requestedLanguage}.status`]: 'published',
|
|
45
48
|
'publicationData.date': { $lte: new Date() },
|
|
46
49
|
};
|
|
47
50
|
if (statusFilter && statusFilter !== 'published') {
|
|
@@ -58,13 +61,99 @@ export async function GET(req, config) {
|
|
|
58
61
|
.toArray(),
|
|
59
62
|
blogs.countDocuments(query),
|
|
60
63
|
]);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
// Supported languages for fallback (in order of preference)
|
|
65
|
+
const fallbackLanguages = [requestedLanguage, 'nl', 'en'];
|
|
66
|
+
const formatted = data.map((doc) => {
|
|
67
|
+
const languages = doc.languages || {};
|
|
68
|
+
// Only use exact language match public views - no fallback for
|
|
69
|
+
// This ensures visitors see content in their language only
|
|
70
|
+
const hasExactLanguage = !!languages[requestedLanguage];
|
|
71
|
+
// Skip this post if no exact language match (for public non-admin views)
|
|
72
|
+
if (!isAdminView && !hasExactLanguage) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const postPrimaryLang = doc.metadata?.lang || 'nl';
|
|
76
|
+
// Find the best available language for this post
|
|
77
|
+
let bestLanguage = requestedLanguage;
|
|
78
|
+
let isMissingTranslation = false;
|
|
79
|
+
if (!languages[requestedLanguage]) {
|
|
80
|
+
// For admin view, if specific language is requested, show it as missing instead of falling back
|
|
81
|
+
// This ensures the UI is "synced" with the selected language
|
|
82
|
+
if (isAdminView) {
|
|
83
|
+
isMissingTranslation = true;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Public view still uses fallback or filtering
|
|
87
|
+
const fallbackLanguages = [postPrimaryLang, 'nl', 'en'];
|
|
88
|
+
for (const lang of fallbackLanguages) {
|
|
89
|
+
if (languages[lang]) {
|
|
90
|
+
bestLanguage = lang;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const langContent = languages[bestLanguage] || {};
|
|
97
|
+
const meta = langContent.metadata || {};
|
|
98
|
+
// Ensure all languages in doc have a status and updatedAt for the dashboard
|
|
99
|
+
const enrichedLanguages = { ...languages };
|
|
100
|
+
Object.keys(enrichedLanguages).forEach(lang => {
|
|
101
|
+
if (!enrichedLanguages[lang].status) {
|
|
102
|
+
enrichedLanguages[lang].status = doc.publicationData?.status === 'concept' ? 'draft' : (doc.publicationData?.status || 'draft');
|
|
103
|
+
}
|
|
104
|
+
if (!enrichedLanguages[lang].updatedAt) {
|
|
105
|
+
enrichedLanguages[lang].updatedAt = doc.updatedAt;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// Language display names for placeholders
|
|
109
|
+
const langNames = {
|
|
110
|
+
nl: 'Dutch',
|
|
111
|
+
en: 'English',
|
|
112
|
+
sv: 'Swedish',
|
|
113
|
+
de: 'German',
|
|
114
|
+
fr: 'French',
|
|
115
|
+
es: 'Spanish'
|
|
116
|
+
};
|
|
117
|
+
const displayTitle = isMissingTranslation
|
|
118
|
+
? `(No ${langNames[requestedLanguage] || requestedLanguage.toUpperCase()} translation)`
|
|
119
|
+
: (meta.title || doc.title || '');
|
|
120
|
+
const displayStatus = isMissingTranslation
|
|
121
|
+
? 'not-translated'
|
|
122
|
+
: (langContent.status || doc.publicationData?.status || 'concept');
|
|
123
|
+
return {
|
|
124
|
+
...doc,
|
|
125
|
+
_id: doc._id.toString(),
|
|
126
|
+
title: displayTitle,
|
|
127
|
+
summary: isMissingTranslation ? '' : (meta.excerpt || doc.summary || ''),
|
|
128
|
+
contentBlocks: isMissingTranslation ? [] : (langContent.blocks || doc.contentBlocks || doc.blocks || []),
|
|
129
|
+
image: isMissingTranslation ? null : (meta.featuredImage || doc.image),
|
|
130
|
+
categoryTags: isMissingTranslation ? { category: '', tags: [] } : (meta.categories ? {
|
|
131
|
+
category: meta.categories[0] || '',
|
|
132
|
+
tags: meta.tags || doc.categoryTags?.tags || []
|
|
133
|
+
} : (doc.categoryTags || { category: '', tags: [] })),
|
|
134
|
+
publicationData: {
|
|
135
|
+
...doc.publicationData,
|
|
136
|
+
status: displayStatus,
|
|
137
|
+
},
|
|
138
|
+
seo: isMissingTranslation ? {} : (meta.seo || doc.seo || {}),
|
|
139
|
+
lang: bestLanguage,
|
|
140
|
+
isMissingTranslation,
|
|
141
|
+
requestedLanguage,
|
|
142
|
+
availableLanguages: Object.keys(languages),
|
|
143
|
+
languages: enrichedLanguages,
|
|
144
|
+
updatedAt: isMissingTranslation ? doc.updatedAt : (langContent.updatedAt || doc.updatedAt),
|
|
145
|
+
status: displayStatus,
|
|
146
|
+
};
|
|
147
|
+
}).filter(Boolean); // Remove null entries
|
|
148
|
+
// Sort by updatedAt descending so the UI order makes sense
|
|
149
|
+
formatted.sort((a, b) => {
|
|
150
|
+
const dateA = new Date(a.updatedAt).getTime();
|
|
151
|
+
const dateB = new Date(b.updatedAt).getTime();
|
|
152
|
+
return dateB - dateA;
|
|
153
|
+
});
|
|
65
154
|
return NextResponse.json({
|
|
66
155
|
blogs: formatted,
|
|
67
|
-
total:
|
|
156
|
+
total: formatted.length,
|
|
68
157
|
});
|
|
69
158
|
}
|
|
70
159
|
catch (err) {
|
|
@@ -77,6 +166,8 @@ export async function GET(req, config) {
|
|
|
77
166
|
*/
|
|
78
167
|
export async function POST(req, config) {
|
|
79
168
|
try {
|
|
169
|
+
const url = new URL(req.url);
|
|
170
|
+
const language = url.searchParams.get('language') || 'nl';
|
|
80
171
|
const userId = await config.getUserId(req);
|
|
81
172
|
if (!userId) {
|
|
82
173
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
@@ -149,6 +240,24 @@ export async function POST(req, config) {
|
|
|
149
240
|
seo: seo || { title: '', description: '' },
|
|
150
241
|
slug,
|
|
151
242
|
authorId: userId,
|
|
243
|
+
languages: {
|
|
244
|
+
[language]: {
|
|
245
|
+
blocks: contentBlocks || [],
|
|
246
|
+
metadata: {
|
|
247
|
+
title: title.trim(),
|
|
248
|
+
excerpt: (summary || '').trim(),
|
|
249
|
+
featuredImage: image,
|
|
250
|
+
categories: categoryTags?.category ? [categoryTags.category] : [],
|
|
251
|
+
tags: categoryTags?.tags || [],
|
|
252
|
+
seo: seo || {},
|
|
253
|
+
},
|
|
254
|
+
updatedAt: new Date().toISOString(),
|
|
255
|
+
status: finalStatus,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
metadata: {
|
|
259
|
+
lang: language,
|
|
260
|
+
},
|
|
152
261
|
createdAt: new Date(),
|
|
153
262
|
updatedAt: new Date(),
|
|
154
263
|
};
|
|
@@ -169,6 +278,8 @@ export async function POST(req, config) {
|
|
|
169
278
|
*/
|
|
170
279
|
export async function GET_BY_SLUG(req, slug, config) {
|
|
171
280
|
try {
|
|
281
|
+
const url = new URL(req.url);
|
|
282
|
+
const requestedLanguage = url.searchParams.get('language') || 'nl';
|
|
172
283
|
const userId = await config.getUserId(req);
|
|
173
284
|
const dbConnection = await config.getDb();
|
|
174
285
|
const db = dbConnection.db();
|
|
@@ -178,14 +289,100 @@ export async function GET_BY_SLUG(req, slug, config) {
|
|
|
178
289
|
return NextResponse.json({ error: 'Blog not found' }, { status: 404 });
|
|
179
290
|
}
|
|
180
291
|
// Security check
|
|
181
|
-
const isPublished = blog.publicationData?.status === 'published';
|
|
182
292
|
const isAuthor = userId && blog.authorId === userId;
|
|
183
|
-
|
|
184
|
-
|
|
293
|
+
const isAdminView = url.searchParams.get('admin') === 'true';
|
|
294
|
+
// For public view, we must have a published version in the requested language
|
|
295
|
+
if (!isAdminView && !isAuthor) {
|
|
296
|
+
const langData = blog.languages?.[requestedLanguage];
|
|
297
|
+
if (!langData || langData.status !== 'published') {
|
|
298
|
+
return NextResponse.json({ error: 'Blog post not available in this language' }, { status: 404 });
|
|
299
|
+
}
|
|
300
|
+
// Also check publication date
|
|
301
|
+
if (blog.publicationData?.date && new Date(blog.publicationData.date) > new Date()) {
|
|
302
|
+
return NextResponse.json({ error: 'Blog post not yet published' }, { status: 404 });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const languages = blog.languages || {};
|
|
306
|
+
const postPrimaryLang = blog.metadata?.lang || 'nl';
|
|
307
|
+
// Find the best available language for this post
|
|
308
|
+
let bestLanguage = requestedLanguage;
|
|
309
|
+
let isMissingTranslation = false;
|
|
310
|
+
if (!languages[requestedLanguage]) {
|
|
311
|
+
if (isAdminView) {
|
|
312
|
+
isMissingTranslation = true;
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
// Try fallback languages in order
|
|
316
|
+
const fallbackLanguages = [requestedLanguage, 'nl', 'en'];
|
|
317
|
+
for (const lang of fallbackLanguages) {
|
|
318
|
+
if (languages[lang]) {
|
|
319
|
+
bestLanguage = lang;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// If still no match, use primary language from metadata
|
|
324
|
+
if (!languages[bestLanguage] && languages[postPrimaryLang]) {
|
|
325
|
+
bestLanguage = postPrimaryLang;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
185
328
|
}
|
|
329
|
+
const langContent = languages[bestLanguage] || {};
|
|
330
|
+
const meta = langContent.metadata || {};
|
|
331
|
+
// Language display names for placeholders
|
|
332
|
+
const langNames = {
|
|
333
|
+
nl: 'Dutch',
|
|
334
|
+
en: 'English',
|
|
335
|
+
sv: 'Swedish',
|
|
336
|
+
de: 'German',
|
|
337
|
+
fr: 'French',
|
|
338
|
+
es: 'Spanish'
|
|
339
|
+
};
|
|
340
|
+
let title = isMissingTranslation
|
|
341
|
+
? `(No ${langNames[requestedLanguage] || requestedLanguage.toUpperCase()} translation)`
|
|
342
|
+
: (meta.title || blog.title || '');
|
|
343
|
+
let summary = isMissingTranslation ? '' : (meta.excerpt || blog.summary || '');
|
|
344
|
+
let contentBlocks = isMissingTranslation ? [] : (langContent.blocks || blog.contentBlocks || blog.blocks || []);
|
|
345
|
+
let image = isMissingTranslation ? null : (meta.featuredImage || blog.image);
|
|
346
|
+
let categoryTags = isMissingTranslation ? { category: '', tags: [] } : (meta.categories ? {
|
|
347
|
+
category: meta.categories[0] || '',
|
|
348
|
+
tags: meta.tags || blog.categoryTags?.tags || []
|
|
349
|
+
} : (blog.categoryTags || { category: '', tags: [] }));
|
|
350
|
+
let seo = isMissingTranslation ? {} : (meta.seo || blog.seo || {});
|
|
351
|
+
let metadata = { ...blog.metadata, ...meta, lang: bestLanguage };
|
|
352
|
+
// Ensure all languages in doc have a status and updatedAt for the dashboard
|
|
353
|
+
const enrichedLanguages = { ...languages };
|
|
354
|
+
Object.keys(enrichedLanguages).forEach(lang => {
|
|
355
|
+
if (!enrichedLanguages[lang].status) {
|
|
356
|
+
enrichedLanguages[lang].status = blog.publicationData?.status === 'concept' ? 'draft' : (blog.publicationData?.status || 'draft');
|
|
357
|
+
}
|
|
358
|
+
if (!enrichedLanguages[lang].updatedAt) {
|
|
359
|
+
enrichedLanguages[lang].updatedAt = blog.updatedAt;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
const displayStatus = isMissingTranslation
|
|
363
|
+
? 'not-translated'
|
|
364
|
+
: (langContent.status || blog.publicationData?.status || 'concept');
|
|
186
365
|
return NextResponse.json({
|
|
187
366
|
...blog,
|
|
188
367
|
_id: blog._id.toString(),
|
|
368
|
+
title,
|
|
369
|
+
summary,
|
|
370
|
+
contentBlocks,
|
|
371
|
+
image,
|
|
372
|
+
categoryTags,
|
|
373
|
+
publicationData: {
|
|
374
|
+
...blog.publicationData,
|
|
375
|
+
status: displayStatus,
|
|
376
|
+
},
|
|
377
|
+
seo,
|
|
378
|
+
metadata,
|
|
379
|
+
languages: enrichedLanguages,
|
|
380
|
+
availableLanguages: Object.keys(languages),
|
|
381
|
+
lang: bestLanguage,
|
|
382
|
+
isMissingTranslation,
|
|
383
|
+
requestedLanguage,
|
|
384
|
+
updatedAt: isMissingTranslation ? blog.updatedAt : (langContent.updatedAt || blog.updatedAt),
|
|
385
|
+
status: displayStatus,
|
|
189
386
|
});
|
|
190
387
|
}
|
|
191
388
|
catch (err) {
|
|
@@ -198,6 +395,8 @@ export async function GET_BY_SLUG(req, slug, config) {
|
|
|
198
395
|
*/
|
|
199
396
|
export async function PUT_BY_SLUG(req, slug, config) {
|
|
200
397
|
try {
|
|
398
|
+
const url = new URL(req.url);
|
|
399
|
+
const language = url.searchParams.get('language') || 'nl';
|
|
201
400
|
const userId = await config.getUserId(req);
|
|
202
401
|
if (!userId) {
|
|
203
402
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
@@ -253,15 +452,9 @@ export async function PUT_BY_SLUG(req, slug, config) {
|
|
|
253
452
|
}, { status: 400 });
|
|
254
453
|
}
|
|
255
454
|
}
|
|
256
|
-
// Slug logic
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
finalSlug = slugify(title);
|
|
260
|
-
}
|
|
261
|
-
else if (publicationData?.status === 'concept' && !slug.includes('-draft-')) {
|
|
262
|
-
finalSlug = `${slugify(title)}-draft-${Date.now().toString().slice(-4)}`;
|
|
263
|
-
}
|
|
264
|
-
// Update data
|
|
455
|
+
// Slug logic - DON'T change slug for existing posts, keep original
|
|
456
|
+
// The slug should remain constant across all language versions
|
|
457
|
+
const finalSlug = slug;
|
|
265
458
|
// Determine the final status: if publishing, set to 'published', otherwise preserve or convert draft to concept
|
|
266
459
|
let finalStatus = publicationData?.status;
|
|
267
460
|
if (isPublishing) {
|
|
@@ -270,30 +463,64 @@ export async function PUT_BY_SLUG(req, slug, config) {
|
|
|
270
463
|
else if (publicationData?.status === 'draft') {
|
|
271
464
|
finalStatus = 'concept';
|
|
272
465
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
466
|
+
// Preserve existing languages or initialize
|
|
467
|
+
const existingLanguages = existingBlog.languages || {};
|
|
468
|
+
const primaryLanguage = existingBlog.metadata?.lang || 'nl';
|
|
469
|
+
// Update the specific language content
|
|
470
|
+
// Only the language being saved gets a new updatedAt timestamp
|
|
471
|
+
const now = new Date();
|
|
472
|
+
const updatedLanguages = {
|
|
473
|
+
...existingLanguages,
|
|
474
|
+
[language]: {
|
|
475
|
+
blocks: contentBlocks || [],
|
|
476
|
+
metadata: {
|
|
477
|
+
title: title.trim(),
|
|
478
|
+
excerpt: (summary || '').trim(),
|
|
479
|
+
featuredImage: image,
|
|
480
|
+
categories: categoryTags?.category ? [categoryTags.category] : [],
|
|
481
|
+
tags: categoryTags?.tags || [],
|
|
482
|
+
seo: seo || {},
|
|
483
|
+
},
|
|
484
|
+
updatedAt: now.toISOString(),
|
|
485
|
+
status: finalStatus,
|
|
282
486
|
},
|
|
487
|
+
};
|
|
488
|
+
// For root-level fields, only update if this is the primary language
|
|
489
|
+
// Otherwise preserve existing root values to maintain backward compatibility
|
|
490
|
+
const isPrimaryLanguage = language === primaryLanguage || !existingLanguages[primaryLanguage];
|
|
491
|
+
const updateData = {
|
|
492
|
+
// Only update root-level title/summary if saving in primary language
|
|
493
|
+
// This maintains backward compatibility
|
|
494
|
+
...(isPrimaryLanguage ? {
|
|
495
|
+
title: title.trim(),
|
|
496
|
+
summary: (summary || '').trim(),
|
|
497
|
+
contentBlocks: contentBlocks || [],
|
|
498
|
+
content: content || [],
|
|
499
|
+
image: image || {},
|
|
500
|
+
categoryTags: {
|
|
501
|
+
category: categoryTags?.category?.trim() || '',
|
|
502
|
+
tags: categoryTags?.tags || [],
|
|
503
|
+
},
|
|
504
|
+
seo: seo || {},
|
|
505
|
+
} : {}),
|
|
283
506
|
publicationData: {
|
|
507
|
+
...existingBlog.publicationData,
|
|
284
508
|
...publicationData,
|
|
285
509
|
status: finalStatus,
|
|
286
|
-
date: publicationData?.date ? new Date(publicationData.date) : new Date(),
|
|
510
|
+
date: publicationData?.date ? new Date(publicationData.date) : existingBlog.publicationData?.date || new Date(),
|
|
287
511
|
},
|
|
288
|
-
seo: seo || {},
|
|
289
|
-
slug: finalSlug,
|
|
290
512
|
authorId: userId,
|
|
513
|
+
languages: updatedLanguages,
|
|
514
|
+
metadata: {
|
|
515
|
+
...existingBlog.metadata,
|
|
516
|
+
lang: primaryLanguage,
|
|
517
|
+
},
|
|
291
518
|
updatedAt: new Date(),
|
|
292
519
|
};
|
|
293
520
|
await blogs.updateOne({ slug }, { $set: updateData });
|
|
294
521
|
return NextResponse.json({
|
|
295
522
|
message: 'Blog updated successfully',
|
|
296
|
-
slug: finalSlug
|
|
523
|
+
slug: slug, // Return original slug, not finalSlug
|
|
297
524
|
});
|
|
298
525
|
}
|
|
299
526
|
catch (err) {
|
package/dist/hooks/useBlogs.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface UseBlogsOptions {
|
|
|
10
10
|
skip?: number;
|
|
11
11
|
/** Filter by status (published, draft, concept) */
|
|
12
12
|
status?: string;
|
|
13
|
+
/** Filter by language */
|
|
14
|
+
language?: string;
|
|
13
15
|
/** Whether to fetch all posts for admin (includes drafts) */
|
|
14
16
|
admin?: boolean;
|
|
15
17
|
/** API base URL (default: '/api/blogs') */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBlogs.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlogs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC5B,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC3B,0BAA0B;IAC1B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"useBlogs.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlogs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC5B,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC3B,0BAA0B;IAC1B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CAuFtE"}
|
package/dist/hooks/useBlogs.js
CHANGED
|
@@ -13,12 +13,18 @@ import { apiToBlogPost } from '../lib/mappers/apiMapper';
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
export function useBlogs(options = {}) {
|
|
16
|
-
const { limit = 10, skip = 0, status, admin = false, apiBaseUrl = '/api/plugin-blog', } = options;
|
|
16
|
+
const { limit = 10, skip = 0, status, language, admin = false, apiBaseUrl = '/api/plugin-blog', } = options;
|
|
17
17
|
const [blogs, setBlogs] = useState([]);
|
|
18
18
|
const [loading, setLoading] = useState(true);
|
|
19
19
|
const [error, setError] = useState(null);
|
|
20
20
|
const [total, setTotal] = useState(0);
|
|
21
21
|
const fetchBlogs = async () => {
|
|
22
|
+
// If language is expected but not provided, wait for it to be ready
|
|
23
|
+
// (Prevents fetching all languages if the hook is called with a language but it's not yet loaded)
|
|
24
|
+
if (!language && !admin) {
|
|
25
|
+
// In public view, we always want a specific language
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
22
28
|
try {
|
|
23
29
|
setLoading(true);
|
|
24
30
|
setError(null);
|
|
@@ -29,6 +35,8 @@ export function useBlogs(options = {}) {
|
|
|
29
35
|
params.set('skip', skip.toString());
|
|
30
36
|
if (status)
|
|
31
37
|
params.set('status', status);
|
|
38
|
+
if (language)
|
|
39
|
+
params.set('language', language);
|
|
32
40
|
if (admin)
|
|
33
41
|
params.set('admin', 'true');
|
|
34
42
|
const url = `${apiBaseUrl}?${params.toString()}`;
|
|
@@ -71,7 +79,7 @@ export function useBlogs(options = {}) {
|
|
|
71
79
|
};
|
|
72
80
|
useEffect(() => {
|
|
73
81
|
fetchBlogs();
|
|
74
|
-
}, [limit, skip, status, admin, apiBaseUrl]);
|
|
82
|
+
}, [limit, skip, status, language, admin, apiBaseUrl]);
|
|
75
83
|
return {
|
|
76
84
|
blogs,
|
|
77
85
|
loading,
|
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface PluginProps {
|
|
|
36
36
|
export default function BlogPlugin(props: PluginProps): import("react/jsx-runtime").JSX.Element;
|
|
37
37
|
export { BlogPlugin as Index };
|
|
38
38
|
export type { Block, BlockTypeDefinition, ClientBlockDefinition, RichTextFormattingConfig, BlockEditProps, BlockPreviewProps, IBlockComponent, } from './types';
|
|
39
|
-
export type { SEOMetadata, PublicationData, PostStatus, PostMetadata, BlogPost, PostListItem, PostFilterOptions, } from './types/post';
|
|
39
|
+
export type { SEOMetadata, PublicationData, PostStatus, PostMetadata, BlogPost, PostListItem, PostFilterOptions, BlogPostLanguageContent, BlogPostLanguages, } from './types/post';
|
|
40
40
|
export { initBlogPlugin } from './init';
|
|
41
41
|
export type { BlogPluginConfig } from './init';
|
|
42
42
|
export { RichTextEditor, RichTextPreview } from './lib/rich-text';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAStD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAStD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,KAAK,EAAE,WAAW,2CA8NpD;AAID,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,CAAC;AAG/B,YAAY,EACR,KAAK,EACL,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,eAAe,GAClB,MAAM,SAAS,CAAC;AAGjB,YAAY,EACR,WAAW,EACX,eAAe,EACf,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,GACpB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AACxC,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAG/C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAGjF,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG9F,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAG5F,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG3E,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAG1E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAGzE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGpF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -140,9 +140,10 @@ export default function BlogPlugin(props) {
|
|
|
140
140
|
if (!originalSlug) {
|
|
141
141
|
throw new Error('Cannot save: no post identifier available. Please reload the page.');
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
const language = state.currentLanguage || locale;
|
|
144
|
+
console.log('[BlogPlugin] Saving post with slug:', originalSlug, 'language:', language);
|
|
144
145
|
const apiData = editorStateToAPI(state, undefined, heroBlock);
|
|
145
|
-
const response = await fetch(`/api/plugin-blog/${originalSlug}`, {
|
|
146
|
+
const response = await fetch(`/api/plugin-blog/${originalSlug}?language=${language}`, {
|
|
146
147
|
method: 'PUT',
|
|
147
148
|
headers: { 'Content-Type': 'application/json' },
|
|
148
149
|
credentials: 'include', // Include cookies for authentication
|
|
@@ -173,8 +174,9 @@ export default function BlogPlugin(props) {
|
|
|
173
174
|
case 'new':
|
|
174
175
|
return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state) => {
|
|
175
176
|
// Save to API - create new post
|
|
177
|
+
const language = state.currentLanguage || locale;
|
|
176
178
|
const apiData = editorStateToAPI(state);
|
|
177
|
-
const response = await fetch(
|
|
179
|
+
const response = await fetch(`/api/plugin-blog/new?language=${language}`, {
|
|
178
180
|
method: 'POST',
|
|
179
181
|
headers: { 'Content-Type': 'application/json' },
|
|
180
182
|
credentials: 'include', // Include cookies for authentication
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* i18n Utility
|
|
3
|
+
* Simple translation loader for the blog plugin
|
|
4
|
+
*/
|
|
5
|
+
export declare function getTranslations(locale: string): any;
|
|
6
|
+
export declare function getEditorTranslations(language: string): {
|
|
7
|
+
language: string;
|
|
8
|
+
selectLanguage: string;
|
|
9
|
+
addLanguage: string;
|
|
10
|
+
switchLanguage: string;
|
|
11
|
+
availableLanguages: string;
|
|
12
|
+
primaryLanguage: string;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/lib/i18n.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA4CH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CASnD;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CAC3B,CAYA"}
|
package/dist/lib/i18n.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* i18n Utility
|
|
3
|
+
* Simple translation loader for the blog plugin
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
function getLocalesDir() {
|
|
8
|
+
const possiblePaths = [
|
|
9
|
+
path.join(process.cwd(), 'node_modules', '@jhits', 'plugin-blog', 'data', 'locales'),
|
|
10
|
+
path.join(__dirname, '..', 'data', 'locales'),
|
|
11
|
+
path.join(__dirname, '..', '..', 'data', 'locales'),
|
|
12
|
+
];
|
|
13
|
+
for (const localesPath of possiblePaths) {
|
|
14
|
+
if (fs.existsSync(localesPath)) {
|
|
15
|
+
return localesPath;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return possiblePaths[0];
|
|
19
|
+
}
|
|
20
|
+
const localesDir = getLocalesDir();
|
|
21
|
+
const cache = {};
|
|
22
|
+
function loadLocale(locale) {
|
|
23
|
+
if (cache[locale]) {
|
|
24
|
+
return cache[locale];
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const localePath = path.join(localesDir, locale, 'common.json');
|
|
28
|
+
if (fs.existsSync(localePath)) {
|
|
29
|
+
const content = fs.readFileSync(localePath, 'utf-8');
|
|
30
|
+
cache[locale] = JSON.parse(content);
|
|
31
|
+
return cache[locale];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error(`[i18n] Failed to load locale ${locale}:`, error);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
export function getTranslations(locale) {
|
|
40
|
+
const translations = loadLocale(locale);
|
|
41
|
+
if (translations) {
|
|
42
|
+
return translations;
|
|
43
|
+
}
|
|
44
|
+
const fallback = loadLocale('en');
|
|
45
|
+
return fallback || {};
|
|
46
|
+
}
|
|
47
|
+
export function getEditorTranslations(language) {
|
|
48
|
+
const translations = getTranslations(language);
|
|
49
|
+
const enTranslations = getTranslations('en');
|
|
50
|
+
return translations.editor || enTranslations.editor || {
|
|
51
|
+
language: 'Language',
|
|
52
|
+
selectLanguage: 'Select Language',
|
|
53
|
+
addLanguage: 'Add Language',
|
|
54
|
+
switchLanguage: 'Switch Language',
|
|
55
|
+
availableLanguages: 'Available Languages',
|
|
56
|
+
primaryLanguage: 'Primary Language',
|
|
57
|
+
};
|
|
58
|
+
}
|