## Translation Files - Add 11 new language files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ missing translation keys across all 15 languages - New sections: notebook, pagination, ai.batchOrganization, ai.autoLabels - Update nav section with workspace, quickAccess, myLibrary keys ## Component Updates - Update 15+ components to use translation keys instead of hardcoded text - Components: notebook dialogs, sidebar, header, note-input, ghost-tags, etc. - Replace 80+ hardcoded English/French strings with t() calls - Ensure consistent UI across all supported languages ## Code Quality - Remove 77+ console.log statements from codebase - Clean up API routes, components, hooks, and services - Keep only essential error handling (no debugging logs) ## UI/UX Improvements - Update Keep logo to yellow post-it style (from-yellow-400 to-amber-500) - Change selection colors to #FEF3C6 (notebooks) and #EFB162 (nav items) - Make "+" button permanently visible in notebooks section - Fix grammar and syntax errors in multiple components ## Bug Fixes - Fix JSON syntax errors in it.json, nl.json, pl.json, zh.json - Fix syntax errors in notebook-suggestion-toast.tsx - Fix syntax errors in use-auto-tagging.ts - Fix syntax errors in paragraph-refactor.service.ts - Fix duplicate "fusion" section in nl.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Ou une version plus courte si vous préférez : feat(i18n): Add 15 languages, remove logs, update UI components - Create 11 new translation files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ translation keys: notebook, pagination, AI features - Update 15+ components to use translations (80+ strings) - Remove 77+ console.log statements from codebase - Fix JSON syntax errors in 4 translation files - Fix component syntax errors (toast, hooks, services) - Update logo to yellow post-it style - Change selection colors (#FEF3C6, #EFB162) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
246 lines
7.0 KiB
TypeScript
246 lines
7.0 KiB
TypeScript
/**
|
||
* Migration Script: Tags → Notebooks with Contextual Labels
|
||
*
|
||
* This script migrates from the flat tags system to the new notebooks system.
|
||
* It creates a default "Labels Migrés" notebook for each user and moves all
|
||
* existing labels to that notebook. Notes remain in "Notes générales" (Inbox).
|
||
*
|
||
* IMPORTANT: This is a NON-BREAKING migration. The existing system continues
|
||
* to work during and after migration.
|
||
*
|
||
* Usage:
|
||
* npx tsx scripts/migrate-to-notebooks.ts
|
||
*
|
||
* Rollback:
|
||
* npx tsx scripts/rollback-notebooks.ts
|
||
*/
|
||
|
||
import { prisma } from '../keep-notes/lib/prisma'
|
||
|
||
interface MigrationStats {
|
||
usersProcessed: number
|
||
notebooksCreated: number
|
||
labelsMigrated: number
|
||
notesAffected: number
|
||
errors: string[]
|
||
}
|
||
|
||
/**
|
||
* Main migration function
|
||
*/
|
||
async function migrateToNotebooks(): Promise<MigrationStats> {
|
||
console.log('🚀 Starting migration to notebooks...\n')
|
||
|
||
const stats: MigrationStats = {
|
||
usersProcessed: 0,
|
||
notebooksCreated: 0,
|
||
labelsMigrated: 0,
|
||
notesAffected: 0,
|
||
errors: [],
|
||
}
|
||
|
||
try {
|
||
// Step 1: Get all users
|
||
console.log('📊 Fetching users...')
|
||
const users = await prisma.user.findMany({
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
email: true,
|
||
},
|
||
})
|
||
|
||
if (users.length === 0) {
|
||
console.log('⚠️ No users found. Nothing to migrate.')
|
||
return stats
|
||
}
|
||
|
||
console.log(`✅ Found ${users.length} user(s)\n`)
|
||
|
||
// Step 2: Process each user
|
||
for (const user of users) {
|
||
console.log(`\n👤 Processing user: ${user.name || user.email} (${user.id})`)
|
||
|
||
try {
|
||
// Step 2.1: Check if migration notebook already exists
|
||
const existingNotebook = await prisma.notebook.findFirst({
|
||
where: {
|
||
name: 'Labels Migrés',
|
||
userId: user.id,
|
||
},
|
||
})
|
||
|
||
if (existingNotebook) {
|
||
console.log(' ⏭️ Migration notebook already exists, skipping...')
|
||
continue
|
||
}
|
||
|
||
// Step 2.2: Create "Labels Migrés" notebook
|
||
console.log(' 📁 Creating "Labels Migrés" notebook...')
|
||
const migrationNotebook = await prisma.notebook.create({
|
||
data: {
|
||
id: `migrate-${user.id}`, // Deterministic ID
|
||
name: 'Labels Migrés',
|
||
icon: '📦',
|
||
color: '#9CA3AF', // gray-400
|
||
order: 999, // At the bottom
|
||
userId: user.id,
|
||
},
|
||
})
|
||
|
||
stats.notebooksCreated++
|
||
console.log(` ✅ Created notebook: ${migrationNotebook.id}`)
|
||
|
||
// Step 2.3: Migrate all labels for this user
|
||
console.log(' 🏷️ Migrating labels...')
|
||
const labels = await prisma.label.findMany({
|
||
where: {
|
||
userId: user.id,
|
||
notebookId: {
|
||
equals: '', // Empty string = not migrated yet
|
||
},
|
||
},
|
||
})
|
||
|
||
// Also try with null (in case database has null values)
|
||
const labelsWithNull = await prisma.label.findMany({
|
||
where: {
|
||
userId: user.id,
|
||
notebookId: null,
|
||
},
|
||
})
|
||
|
||
// Combine both results (avoid duplicates)
|
||
const allLabels = [...labels]
|
||
for (const label of labelsWithNull) {
|
||
if (!allLabels.find(l => l.id === label.id)) {
|
||
allLabels.push(label)
|
||
}
|
||
}
|
||
|
||
if (allLabels.length === 0) {
|
||
console.log(' ℹ️ No labels to migrate')
|
||
continue
|
||
}
|
||
|
||
// Update all labels to belong to the migration notebook
|
||
const labelUpdates = allLabels.map((label) =>
|
||
prisma.label.update({
|
||
where: { id: label.id },
|
||
data: { notebookId: migrationNotebook.id },
|
||
})
|
||
)
|
||
|
||
await prisma.$transaction(labelUpdates)
|
||
stats.labelsMigrated += allLabels.length
|
||
console.log(` ✅ Migrated ${allLabels.length} label(s)`)
|
||
|
||
// Step 2.4: Count notes that will be affected (information only)
|
||
const noteCount = await prisma.note.count({
|
||
where: { userId: user.id },
|
||
})
|
||
stats.notesAffected += noteCount
|
||
console.log(` ℹ️ User has ${noteCount} note(s) (will remain in "Notes générales")`)
|
||
|
||
stats.usersProcessed++
|
||
} catch (userError) {
|
||
const errorMsg = `Failed to migrate user ${user.id}: ${userError}`
|
||
console.error(` ❌ ${errorMsg}`)
|
||
stats.errors.push(errorMsg)
|
||
}
|
||
}
|
||
|
||
// Summary
|
||
console.log('\n' + '='.repeat(60))
|
||
console.log('✅ Migration complete!\n')
|
||
console.log('📊 Summary:')
|
||
console.log(` Users processed: ${stats.usersProcessed}`)
|
||
console.log(` Notebooks created: ${stats.notebooksCreated}`)
|
||
console.log(` Labels migrated: ${stats.labelsMigrated}`)
|
||
console.log(` Notes affected: ${stats.notesAffected} (all remain in Inbox)`)
|
||
|
||
if (stats.errors.length > 0) {
|
||
console.log(`\n⚠️ Errors: ${stats.errors.length}`)
|
||
stats.errors.forEach((error) => console.log(` ❌ ${error}`))
|
||
}
|
||
|
||
console.log('\n' + '='.repeat(60))
|
||
console.log('\n✨ Migration successful!')
|
||
console.log('\n📌 Next steps:')
|
||
console.log(' 1. Test the application to ensure everything works')
|
||
console.log(' 2. Users can now organize their notes into notebooks')
|
||
console.log(' 3. Users can move labels from "Labels Migrés" to new notebooks')
|
||
console.log(' 4. Consider deleting old labels field from Note model after verification\n')
|
||
|
||
return stats
|
||
} catch (error) {
|
||
console.error('\n❌ Migration failed:', error)
|
||
throw error
|
||
} finally {
|
||
await prisma.$disconnect()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Dry-run mode: Show what would happen without making changes
|
||
*/
|
||
async function dryRunMigration() {
|
||
console.log('🔍 DRY RUN MODE - No changes will be made\n')
|
||
|
||
const users = await prisma.user.findMany({
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
email: true,
|
||
},
|
||
})
|
||
|
||
console.log(`Found ${users.length} user(s)\n`)
|
||
|
||
for (const user of users) {
|
||
console.log(`\n👤 User: ${user.name || user.email}`)
|
||
|
||
const labels = await prisma.label.findMany({
|
||
where: { userId: user.id },
|
||
select: { id: true, name: true },
|
||
})
|
||
|
||
const notes = await prisma.note.count({
|
||
where: { userId: user.id },
|
||
})
|
||
|
||
console.log(` Labels: ${labels.length}`)
|
||
console.log(` Notes: ${notes}`)
|
||
console.log(` Would create: "Labels Migrés" notebook`)
|
||
console.log(` Would migrate: ${labels.length} labels`)
|
||
}
|
||
|
||
await prisma.$disconnect()
|
||
}
|
||
|
||
// Run migration
|
||
const args = process.argv.slice(2)
|
||
const isDryRun = args.includes('--dry-run')
|
||
|
||
if (isDryRun) {
|
||
dryRunMigration()
|
||
.then(() => {
|
||
console.log('\n✅ Dry run complete')
|
||
process.exit(0)
|
||
})
|
||
.catch((error) => {
|
||
console.error('\n❌ Dry run failed:', error)
|
||
process.exit(1)
|
||
})
|
||
} else {
|
||
migrateToNotebooks()
|
||
.then(() => {
|
||
console.log('\n✨ All done!')
|
||
process.exit(0)
|
||
})
|
||
.catch((error) => {
|
||
console.error('\n💥 Fatal error:', error)
|
||
process.exit(1)
|
||
})
|
||
}
|