UNPKG

strapi-to-lokalise-plugin

Version:

Preview and sync Lokalise translations from Strapi admin

337 lines (302 loc) â€ĸ 18.1 kB
'use strict'; /** * Universal version switcher for Strapi v4 + v5 plugins * Official approach following Strapi documentation: * - v4: Export routes as plain array [{ method, path, handler, config }] * - v5: Export routes as FUNCTION: ({ strapi }) => ({ admin: { routes: [...] } }) * * v5 routes MUST be a function returning { admin: { routes: [...] } } (Official Strapi v5 format). * v4 routes file exports plain array (required by Strapi v4). */ // Load shared components (same for v4 and v5) const controllers = require('./server/controllers'); const services = require('./server/services'); const contentTypes = require('./server/content-types'); const middlewares = require('./server/middlewares'); // Load route versions // v4: exports plain array (loaded from v4-compat folder - Strapi v5 won't scan this folder) // v5: exports plain array from server/routes/index.js (CRITICAL: v5 reads backend API routes from index.js, NOT admin.js) // CRITICAL: v4 folder renamed to v4-compat to prevent Strapi v5 from auto-scanning it // Strapi v5 reads backend API routes from server/routes/index.js (plain array) // admin.js is ONLY for admin panel routing (React pages), NOT backend API endpoints const routesV4 = require('./server/v4-compat/routes/lokalise-sync'); // Verify v4 is an array if (!Array.isArray(routesV4)) { throw new Error(`v4 routes must export an array, got: ${typeof routesV4}`); } // Detect Strapi version at module load time const isV5 = (() => { try { const strapiVersion = require('@strapi/strapi/package.json')?.version || null; return strapiVersion && strapiVersion.startsWith('5'); } catch (_) { return false; } })(); // Return routes based on version // v4: Plain array (no wrapper) // v5: Function that returns { type: 'admin', routes: [...] } (REQUIRED by Strapi v5) module.exports = { register({ strapi }) { try { console.log('\n========================================'); console.log('[lokalise-sync] Plugin register() called'); console.log('========================================\n'); // CRITICAL: Add global request logging for ALL requests to admin API if (strapi.server && strapi.server.app) { const app = strapi.server.app; // Add middleware to log ALL requests before they're routed app.use(async (ctx, next) => { // Only log admin API requests if (ctx.path && ctx.path.startsWith('/admin/api/')) { console.log('\n========================================'); console.log('[lokalise-sync] 🌐 GLOBAL REQUEST LOGGER'); console.log('========================================'); console.log('[lokalise-sync] Method:', ctx.method); console.log('[lokalise-sync] Path:', ctx.path); console.log('[lokalise-sync] URL:', ctx.url); console.log('[lokalise-sync] Is lokalise-sync route:', ctx.path.includes('lokalise-sync')); console.log('========================================\n'); } await next(); }); console.log('[lokalise-sync] ✅ Global request logger middleware added'); } console.log('[lokalise-sync] Detected Strapi version:', isV5 ? 'v5' : 'v4'); console.log('[lokalise-sync] Using plugin routes from:', isV5 ? 'server/routes/index.js (v5 - backend API routes)' : 'server/v4-compat/routes/lokalise-sync.js (v4)'); console.log('[lokalise-sync] Routes format:', 'Plain array (both v4 and v5 use same format - per compatibility guide)'); console.log('[lokalise-sync] Routes will be accessible at:', isV5 ? '/admin/api/lokalise-sync/*' : '/lokalise-sync/* (v4 - per compatibility guide)'); // Verify routes export for v5 if (isV5) { const routesV5 = require('./server/routes/index'); console.log('[lokalise-sync] 🔍 v5 Routes export type:', typeof routesV5); console.log('[lokalise-sync] 🔍 v5 Routes is array:', Array.isArray(routesV5)); if (Array.isArray(routesV5)) { console.log('[lokalise-sync] ✅ v5 Routes is plain array from index.js (CORRECT FORMAT)'); console.log('[lokalise-sync] 🔍 v5 Routes count:', routesV5.length); const postRoutes = routesV5.filter(r => r.method === 'POST'); console.log('[lokalise-sync] 🔍 v5 POST routes count:', postRoutes.length); const postSettings = routesV5.find(r => r.method === 'POST' && r.path === '/settings'); console.log('[lokalise-sync] 🔍 v5 POST /settings route:', postSettings ? 'FOUND' : 'NOT FOUND'); } else { console.error('[lokalise-sync] ❌ v5 Routes is NOT an array! Type:', typeof routesV5); } } else { console.log('[lokalise-sync] Routes count:', routesV4.length); } console.log('[lokalise-sync] Using official Strapi admin authentication (admin::isAuthenticatedAdmin)'); console.log('[lokalise-sync] Controllers loaded:', typeof controllers === 'object' ? 'YES' : 'NO'); // CRITICAL: For v5, try to manually verify routes are being exported correctly if (isV5 && strapi.server && strapi.server.router) { try { console.log('[lokalise-sync] 🔍 Attempting to manually check route mounting...'); const exportedRoutes = require('./server/routes/index'); console.log('[lokalise-sync] 🔍 Exported routes type:', typeof exportedRoutes); console.log('[lokalise-sync] 🔍 Exported routes is array:', Array.isArray(exportedRoutes)); if (Array.isArray(exportedRoutes)) { console.log('[lokalise-sync] 🔍 First route sample:', JSON.stringify(exportedRoutes[0], null, 2)); } } catch (e) { console.error('[lokalise-sync] ❌ Error checking exported routes:', e.message); } } console.log('[lokalise-sync] Plugin registration complete\n'); } catch (error) { console.error('\n========================================'); console.error('[lokalise-sync] ERROR in register():', error.message); console.error('[lokalise-sync] Stack:', error.stack); console.error('========================================\n'); throw error; } }, bootstrap({ strapi }) { console.log('[lokalise-sync] Plugin bootstrap() called'); // Enhanced route verification for debugging setTimeout(() => { try { console.log('\n========================================'); console.log('[lokalise-sync] 🔍 ROUTE VERIFICATION (bootstrap)'); console.log('========================================'); // Check main server router const router = strapi.server?.router; if (router) { const stack = router.stack || router.layers || []; console.log('[lokalise-sync] Total router stack size:', stack.length); const lokaliseRoutes = []; stack.forEach((layer, idx) => { try { let path = ''; if (layer.path) path = layer.path; else if (layer.regexp) path = layer.regexp.source; else if (layer.route?.path) path = layer.route.path; if (path && (path.includes('lokalise-sync') || path.includes('lokalise'))) { const methods = layer.methods || layer.method || Object.keys(layer.route?.methods || {}).filter(m => m !== '_all'); lokaliseRoutes.push({ path, methods: Array.isArray(methods) ? methods : [methods], layerIndex: idx, route: layer.route ? 'exists' : 'none' }); } } catch (e) { // Ignore } }); console.log('[lokalise-sync] Found lokalise-sync routes in main router:', lokaliseRoutes.length); if (lokaliseRoutes.length > 0) { console.log('[lokalise-sync] Registered routes:'); lokaliseRoutes.forEach((route, idx) => { console.log(`[lokalise-sync] ${idx + 1}. ${route.methods.join(',')} ${route.path} (layer: ${route.layerIndex})`); }); // Check specifically for POST routes const postSettings = lokaliseRoutes.find(r => r.path.includes('settings') && r.methods.includes('POST')); const postTest = lokaliseRoutes.find(r => r.path.includes('settings/test') && r.methods.includes('POST')); console.log('[lokalise-sync] POST /settings route registered:', !!postSettings); console.log('[lokalise-sync] POST /settings/test route registered:', !!postTest); } else { if (isV5) { console.log('[lokalise-sync] âš ī¸ No lokalise-sync routes found in main router stack'); console.log('[lokalise-sync] â„šī¸ This may be expected - v5 admin routes register differently'); console.log('[lokalise-sync] â„šī¸ Checking admin router...'); } else { console.log('[lokalise-sync] âš ī¸ No routes found - check route registration'); } } } else { console.log('[lokalise-sync] âš ī¸ Router not available in strapi.server'); } // CRITICAL: Check admin router for v5 (admin routes are in separate router) if (isV5) { try { console.log('[lokalise-sync] 🔍 CHECKING ADMIN ROUTER (v5 specific)...'); // Try to access admin router const adminRouter = strapi.server?.router?.routes?.get?.('admin') || strapi.server?.app?.router?.routes?.get?.('admin') || strapi.admin?.router || null; if (adminRouter) { console.log('[lokalise-sync] ✅ Admin router found'); console.log('[lokalise-sync] Admin router type:', typeof adminRouter); console.log('[lokalise-sync] Admin router keys:', Object.keys(adminRouter)); // Try to find routes const adminStack = adminRouter.stack || adminRouter.layers || adminRouter.routes || []; console.log('[lokalise-sync] Admin router stack/layers/routes:', Array.isArray(adminStack) ? adminStack.length : 'not array'); if (Array.isArray(adminStack)) { const adminLokaliseRoutes = []; adminStack.forEach((layer, idx) => { try { let path = ''; if (layer.path) path = layer.path; else if (layer.regexp) path = layer.regexp.source; else if (layer.route?.path) path = layer.route.path; if (path && (path.includes('lokalise-sync') || path.includes('lokalise'))) { const methods = layer.methods || layer.method || Object.keys(layer.route?.methods || {}).filter(m => m !== '_all'); adminLokaliseRoutes.push({ path, methods: Array.isArray(methods) ? methods : [methods], layerIndex: idx }); } } catch (e) { // Ignore } }); console.log('[lokalise-sync] Found lokalise-sync routes in admin router:', adminLokaliseRoutes.length); if (adminLokaliseRoutes.length > 0) { adminLokaliseRoutes.forEach((route, idx) => { console.log(`[lokalise-sync] Admin Route ${idx + 1}. ${route.methods.join(',')} ${route.path}`); }); const adminPostSettings = adminLokaliseRoutes.find(r => r.path.includes('settings') && r.methods.includes('POST')); console.log('[lokalise-sync] ✅ POST /settings in admin router:', !!adminPostSettings); } else { console.log('[lokalise-sync] ❌ No lokalise-sync routes found in admin router'); } } } else { console.log('[lokalise-sync] âš ī¸ Admin router not found or not accessible'); console.log('[lokalise-sync] Available paths:'); console.log('[lokalise-sync] - strapi.server.router:', !!strapi.server?.router); console.log('[lokalise-sync] - strapi.server.router.routes:', !!strapi.server?.router?.routes); console.log('[lokalise-sync] - strapi.server.app:', !!strapi.server?.app); console.log('[lokalise-sync] - strapi.admin:', !!strapi.admin); console.log('[lokalise-sync] - strapi.admin.router:', !!strapi.admin?.router); } } catch (e) { console.error('[lokalise-sync] ❌ Error checking admin router:', e.message); console.error('[lokalise-sync] Stack:', e.stack); } } // Also check if routes are accessible via plugin system try { const plugin = strapi.plugin('lokalise-sync'); if (plugin) { console.log('[lokalise-sync] Plugin instance found:', !!plugin); console.log('[lokalise-sync] Plugin routes type:', typeof plugin.routes); // CRITICAL: Check what Strapi actually stored if (typeof plugin.routes === 'function') { try { const pluginRoutes = plugin.routes(); console.log('[lokalise-sync] ✅ Plugin routes() is function - calling it...'); console.log('[lokalise-sync] Plugin routes() result type:', typeof pluginRoutes); console.log('[lokalise-sync] Plugin routes() result keys:', pluginRoutes ? Object.keys(pluginRoutes) : []); console.log('[lokalise-sync] Plugin routes() has admin:', pluginRoutes ? 'admin' in pluginRoutes : false); if (pluginRoutes?.admin) { console.log('[lokalise-sync] Plugin routes() admin keys:', Object.keys(pluginRoutes.admin)); console.log('[lokalise-sync] Plugin routes() admin.routes exists:', !!pluginRoutes.admin.routes); console.log('[lokalise-sync] Plugin routes() admin.routes is array:', Array.isArray(pluginRoutes.admin.routes)); if (pluginRoutes?.admin?.routes) { console.log('[lokalise-sync] Plugin routes() admin.routes count:', pluginRoutes.admin.routes.length); const postRoutes = pluginRoutes.admin.routes.filter(r => r.method === 'POST'); console.log('[lokalise-sync] Plugin routes() POST routes count:', postRoutes.length); // Check for POST /settings specifically const postSettingsRoute = pluginRoutes.admin.routes.find(r => r.method === 'POST' && r.path === '/settings' ); console.log('[lokalise-sync] Plugin routes() POST /settings route:', postSettingsRoute ? 'FOUND' : 'NOT FOUND'); if (postSettingsRoute) { console.log('[lokalise-sync] POST /settings handler:', postSettingsRoute.handler); console.log('[lokalise-sync] POST /settings config:', JSON.stringify(postSettingsRoute.config, null, 2)); } } } else { console.log('[lokalise-sync] ❌ Plugin routes() result does NOT have admin key!'); console.log('[lokalise-sync] Plugin routes() full result:', JSON.stringify(pluginRoutes, null, 2)); } } catch (e) { console.error('[lokalise-sync] ❌ Error calling plugin.routes():', e.message); console.error('[lokalise-sync] Stack:', e.stack); } } else { console.log('[lokalise-sync] âš ī¸ Plugin routes is NOT a function, type:', typeof plugin.routes); console.log('[lokalise-sync] Plugin routes value:', plugin.routes); } } } catch (e) { console.log('[lokalise-sync] ❌ Could not access plugin instance:', e.message); console.log('[lokalise-sync] Error stack:', e.stack); } console.log('========================================\n'); } catch (err) { console.error('[lokalise-sync] ERROR checking routes:', err.message); console.error('[lokalise-sync] Stack:', err.stack); } }, 3000); // Increased timeout to 3 seconds }, // Routes: Export exactly as Strapi expects // v4: Export routes structure { admin: require('./admin-v4-wrapper.js') } which wraps { type: 'admin', routes: [...] } // This matches official Strapi v4 plugin structure where server/routes/index.js exports { admin: require('./admin') } // And admin.js exports { type: 'admin', routes: [...] } // v5: Plain array from server/routes/index.js (CRITICAL: v5 reads backend API routes from index.js, NOT admin.js) // Routes: Export exactly as Strapi expects // v4: Plain array (per compatibility guide - v4 routes MUST be plain array, NO wrapper) // Per user template: admin routes should be in server/routes/admin.js as plain array // Strapi v4 will auto-load and make accessible at /admin/api/lokalise-sync/* // v5: Plain array from server/routes/index.js routes: isV5 ? require('./server/routes/index') // v5: Backend API routes (plain array) : routesV4, // v4: Plain array (NO wrapper - per compatibility guide) controllers, services, contentTypes, middlewares, };