@jjlmoya/utils-language 1.8.0 → 1.10.0

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.
Files changed (70) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +3 -1
  4. package/src/index.ts +5 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/tool_validation.test.ts +1 -1
  7. package/src/tool/cefr-language-skill-profile-planner/bibliography.astro +6 -0
  8. package/src/tool/cefr-language-skill-profile-planner/bibliography.ts +6 -0
  9. package/src/tool/cefr-language-skill-profile-planner/cefr-language-skill-profile-planner.css +565 -0
  10. package/src/tool/cefr-language-skill-profile-planner/component.astro +100 -0
  11. package/src/tool/cefr-language-skill-profile-planner/controller.ts +158 -0
  12. package/src/tool/cefr-language-skill-profile-planner/dom-views.ts +154 -0
  13. package/src/tool/cefr-language-skill-profile-planner/entry.ts +34 -0
  14. package/src/tool/cefr-language-skill-profile-planner/evaluator.ts +12 -0
  15. package/src/tool/cefr-language-skill-profile-planner/i18n/de.ts +26 -0
  16. package/src/tool/cefr-language-skill-profile-planner/i18n/en.ts +59 -0
  17. package/src/tool/cefr-language-skill-profile-planner/i18n/es.ts +15 -0
  18. package/src/tool/cefr-language-skill-profile-planner/i18n/fr.ts +11 -0
  19. package/src/tool/cefr-language-skill-profile-planner/i18n/id.ts +11 -0
  20. package/src/tool/cefr-language-skill-profile-planner/i18n/it.ts +11 -0
  21. package/src/tool/cefr-language-skill-profile-planner/i18n/ja.ts +11 -0
  22. package/src/tool/cefr-language-skill-profile-planner/i18n/ko.ts +11 -0
  23. package/src/tool/cefr-language-skill-profile-planner/i18n/nl.ts +11 -0
  24. package/src/tool/cefr-language-skill-profile-planner/i18n/pl.ts +11 -0
  25. package/src/tool/cefr-language-skill-profile-planner/i18n/pt.ts +11 -0
  26. package/src/tool/cefr-language-skill-profile-planner/i18n/ru.ts +11 -0
  27. package/src/tool/cefr-language-skill-profile-planner/i18n/sv.ts +11 -0
  28. package/src/tool/cefr-language-skill-profile-planner/i18n/tr.ts +11 -0
  29. package/src/tool/cefr-language-skill-profile-planner/i18n/zh.ts +11 -0
  30. package/src/tool/cefr-language-skill-profile-planner/index.ts +14 -0
  31. package/src/tool/cefr-language-skill-profile-planner/logic.test.ts +38 -0
  32. package/src/tool/cefr-language-skill-profile-planner/logic.ts +131 -0
  33. package/src/tool/cefr-language-skill-profile-planner/seo.astro +9 -0
  34. package/src/tool/cefr-language-skill-profile-planner/storage.ts +24 -0
  35. package/src/tool/cefr-language-skill-profile-planner/types.ts +45 -0
  36. package/src/tool/cefr-language-skill-profile-planner/ui.ts +43 -0
  37. package/src/tool/language-shadowing-session-planner/bibliography.astro +6 -0
  38. package/src/tool/language-shadowing-session-planner/bibliography.ts +6 -0
  39. package/src/tool/language-shadowing-session-planner/component.astro +142 -0
  40. package/src/tool/language-shadowing-session-planner/controller.ts +152 -0
  41. package/src/tool/language-shadowing-session-planner/dom-views.ts +82 -0
  42. package/src/tool/language-shadowing-session-planner/entry.ts +34 -0
  43. package/src/tool/language-shadowing-session-planner/evaluator.ts +17 -0
  44. package/src/tool/language-shadowing-session-planner/i18n/de.ts +54 -0
  45. package/src/tool/language-shadowing-session-planner/i18n/en.ts +128 -0
  46. package/src/tool/language-shadowing-session-planner/i18n/es.ts +45 -0
  47. package/src/tool/language-shadowing-session-planner/i18n/fr.ts +45 -0
  48. package/src/tool/language-shadowing-session-planner/i18n/id.ts +40 -0
  49. package/src/tool/language-shadowing-session-planner/i18n/it.ts +40 -0
  50. package/src/tool/language-shadowing-session-planner/i18n/ja.ts +40 -0
  51. package/src/tool/language-shadowing-session-planner/i18n/ko.ts +40 -0
  52. package/src/tool/language-shadowing-session-planner/i18n/nl.ts +40 -0
  53. package/src/tool/language-shadowing-session-planner/i18n/pl.ts +40 -0
  54. package/src/tool/language-shadowing-session-planner/i18n/pt.ts +40 -0
  55. package/src/tool/language-shadowing-session-planner/i18n/ru.ts +40 -0
  56. package/src/tool/language-shadowing-session-planner/i18n/sv.ts +40 -0
  57. package/src/tool/language-shadowing-session-planner/i18n/tr.ts +40 -0
  58. package/src/tool/language-shadowing-session-planner/i18n/zh.ts +40 -0
  59. package/src/tool/language-shadowing-session-planner/index.ts +14 -0
  60. package/src/tool/language-shadowing-session-planner/language-shadowing-session-planner.css +561 -0
  61. package/src/tool/language-shadowing-session-planner/logic.test.ts +53 -0
  62. package/src/tool/language-shadowing-session-planner/logic.ts +94 -0
  63. package/src/tool/language-shadowing-session-planner/seo.astro +9 -0
  64. package/src/tool/language-shadowing-session-planner/storage.ts +18 -0
  65. package/src/tool/language-shadowing-session-planner/timer-view.ts +55 -0
  66. package/src/tool/language-shadowing-session-planner/timer.test.ts +50 -0
  67. package/src/tool/language-shadowing-session-planner/timer.ts +158 -0
  68. package/src/tool/language-shadowing-session-planner/types.ts +32 -0
  69. package/src/tool/language-shadowing-session-planner/ui.ts +55 -0
  70. package/src/tools.ts +3 -1
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Date cible', weeklyHours: 'Temps d étude hebdomadaire', listening: 'Compréhension orale', reading: 'Compréhension écrite', spokenInteraction: 'Interaction orale', spokenProduction: 'Production orale', writing: 'Expression écrite', currentLevel: 'Niveau actuel', targetLevel: 'Niveau cible', planButton: 'Cartographier mon profil', resetButton: 'Réinitialiser', presetLabel: 'Commencer avec un rythme', gentlePreset: '3 heures', steadyPreset: '5 heures', focusedPreset: '10 heures', resultTitle: 'Ton profil de compétences', emptyResult: 'Indique tes cinq niveaux actuels, tes objectifs, ta date et ton temps hebdomadaire pour voir la carte du profil.', statusOnTrack: 'Ton calendrier laisse une marge pour l estimation.', statusTight: 'Ton calendrier atteint l estimation basse.', statusInsufficient: 'Ton calendrier est plus court que l estimation basse.', totalHours: 'Heures guidées estimées', availableHours: 'Heures disponibles', weeksAvailable: 'Semaines disponibles', skillProfile: 'Cinq parcours de compétences', milestoneMap: 'Carte des étapes', hours: 'heures', week: 'semaine', noGap: 'Niveau atteint', checkInputs: 'Vérifie tes données', futureDateError: 'Choisis une date cible future.', dateFormatError: 'Choisis une date cible valide.', hoursError: 'Le temps d étude hebdomadaire doit être compris entre 0,5 et 40 heures.', targetBelowCurrentError: 'Le niveau cible ne peut pas être inférieur au niveau actuel.', noProgressError: 'Augmente au moins un niveau cible pour créer un plan.', plannerNote: 'Utilise les niveaux du CECRL comme point de départ pour une autoévaluation, pas comme résultat de certification.', hoursEstimateNote: 'La fourchette d heures sert à planifier et varie selon la distance linguistique, l exposition et la qualité des études.', skillListening: 'Compréhension orale', skillReading: 'Compréhension écrite', skillSpokenInteraction: 'Interaction orale', skillSpokenProduction: 'Production orale', skillWriting: 'Expression écrite' };
6
+ const faq = [{ question: 'Que calcule le planificateur de profil de compétences du CECRL ?', answer: 'Il compare le niveau actuel et le niveau cible en compréhension orale, compréhension écrite, interaction orale, production orale et expression écrite. Il estime une fourchette d heures guidées, la compare à tes semaines disponibles et répartit l effort entre les compétences qui ont un écart.' }, { question: 'Puis-je l utiliser comme test de langue ?', answer: 'Non. Le planificateur organise une autoévaluation et une charge d étude. Il ne teste pas la performance, ne vérifie pas un certificat et ne garantit pas l atteinte d un niveau à une date donnée.' }, { question: 'Pourquoi les heures sont-elles présentées en fourchette ?', answer: 'Les progrès varient selon la distance entre les langues, l expérience, l exposition, la qualité de l enseignement, les occasions de pratiquer et les preuves utilisées pour juger le niveau. La fourchette est donc un repère de planification, pas une promesse.' }, { question: 'Comment le planificateur répartit-il le temps hebdomadaire ?', answer: 'Les compétences dont l écart de niveaux est le plus grand reçoivent une part plus importante du temps. Celles qui ont déjà atteint leur objectif sont indiquées comme acquises et ne reçoivent aucune part planifiée.' }, { question: 'Que faire si le statut est insuffisant ?', answer: 'Ajoute un temps hebdomadaire réaliste, repousse la date cible ou réduis un ou plusieurs niveaux cibles. Réévalue le profil après un bloc d étude au lieu d essayer de rattraper toutes les heures d un coup.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Planificateur de profil de compétences linguistiques du CECRL', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/fr/planificateur-profil-competences-langue-cecrl' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'Créer un plan de profil de compétences du CECRL', step: [{ '@type': 'HowToStep', name: 'Fixer la date', text: 'Choisis la date à laquelle tu veux revoir le profil cible.' }, { '@type': 'HowToStep', name: 'Évaluer chaque compétence', text: 'Choisis un niveau actuel et un niveau cible du CECRL pour les cinq compétences.' }, { '@type': 'HowToStep', name: 'Protéger le temps hebdomadaire', text: 'Indique le temps d étude que tu peux tenir chaque semaine.' }, { '@type': 'HowToStep', name: 'Lire la carte', text: 'Utilise le statut, les parcours et les étapes pour ajuster le plan.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'planificateur-profil-competences-langue-cecrl', title: 'Planificateur de profil de compétences linguistiques du CECRL', description: 'Cartographie les niveaux actuels et cibles du CECRL pour cinq compétences, estime les heures guidées et fixe des étapes réalistes.', ui, seo: [{ type: 'title', text: 'Cartographier un objectif de langue sur cinq compétences', level: 2 }, { type: 'paragraph', html: 'Un seul niveau de langue peut cacher un profil déséquilibré. Tu peux lire au niveau B1, écouter au niveau A2 et avoir encore besoin de pratiquer l interaction orale. Ce planificateur rend ces écarts visibles et les transforme en charge de travail et en étapes.' }, { type: 'title', text: 'Fonctionnement de l estimation du profil', level: 2 }, { type: 'paragraph', html: 'Le calcul attribue une fourchette d heures guidées à chaque étape du CECRL et additionne les fourchettes de chaque compétence présentant un écart. La répartition hebdomadaire suit le milieu de chaque fourchette, afin qu un écart de deux niveaux reçoive plus de temps qu un écart d un niveau. Il s agit d un modèle de planification, pas d une mesure de capacité.' }, { type: 'title', text: 'Lire le statut du calendrier', level: 2 }, { type: 'table', headers: ['Statut', 'Signification', 'Prochaine action utile'], rows: [['Marge pour l estimation', 'Les heures disponibles atteignent le haut de la fourchette combinée.', 'Garde la routine et utilise les étapes comme points de révision.'], ['Estimation basse atteinte', 'Les heures disponibles atteignent le bas mais pas le haut de la fourchette.', 'Garde l objectif flexible et protège du temps pour le retour et la révision.'], ['En dessous de l estimation', 'Les heures disponibles n atteignent pas le bas de la fourchette combinée.', 'Repousse la date, réduis un objectif ou ajoute du temps durable.']] }, { type: 'title', text: 'Transformer les étapes en preuves', level: 2 }, { type: 'paragraph', html: 'Utilise chaque étape pour recueillir des preuves, pas comme une promotion automatique. Garde un extrait oral, une courte conversation, une tâche de lecture et un texte correspondant au niveau de descripteur visé. Si une compétence stagne, change sa pratique au lieu de cacher l écart dans une moyenne générale.' }, { type: 'list', items: ['Choisis des niveaux à partir de tâches récentes que tu peux décrire.', 'Garde un temps hebdomadaire durable pendant tout le calendrier.', 'Révise le profil après un bloc fixe et ne change qu une variable.', 'Demande un retour enseignant ou une évaluation officielle si le niveau compte pour les études, le travail ou l immigration.'] }, { type: 'tip', title: 'Limites de l estimation', html: 'Le CECRL décrit la capacité de communication avec des descripteurs et ne prescrit pas un nombre universel d heures. La distance linguistique, l expérience, l exposition, le retour et les conditions d étude peuvent modifier fortement l effort réel. Ne considère pas ce plan comme un certificat ou une garantie.' }], faq, bibliography: [{ name: 'Conseil de l Europe: Cadre européen commun de référence pour les langues', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Fixer la date', text: 'Choisis la date à laquelle tu veux revoir le profil cible.' }, { name: 'Évaluer les compétences', text: 'Choisis les niveaux actuels et cibles du CECRL pour les cinq compétences.' }, { name: 'Protéger le temps', text: 'Indique un temps d étude que tu peux tenir chaque semaine, puis cartographie le profil.' }, { name: 'Lire et réviser', text: 'Utilise le statut, les parcours et les dates des étapes pour ajuster le plan après une pratique réelle.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Tanggal target', weeklyHours: 'Waktu belajar per minggu', listening: 'Menyimak', reading: 'Membaca', spokenInteraction: 'Interaksi lisan', spokenProduction: 'Produksi lisan', writing: 'Menulis', currentLevel: 'Level saat ini', targetLevel: 'Level target', planButton: 'Petakan profil saya', resetButton: 'Atur ulang', presetLabel: 'Mulai dengan ritme', gentlePreset: '3 jam', steadyPreset: '5 jam', focusedPreset: '10 jam', resultTitle: 'Profil keterampilan Anda', emptyResult: 'Tetapkan lima level saat ini, target, tanggal, dan waktu mingguan untuk melihat peta profil.', statusOnTrack: 'Kalender Anda memiliki ruang untuk estimasi.', statusTight: 'Kalender Anda mencapai estimasi bawah.', statusInsufficient: 'Kalender Anda lebih singkat daripada estimasi bawah.', totalHours: 'Perkiraan jam belajar terpandu', availableHours: 'Jam tersedia', weeksAvailable: 'Minggu tersedia', skillProfile: 'Lima jalur keterampilan', milestoneMap: 'Peta tonggak', hours: 'jam', week: 'minggu', noGap: 'Sudah pada target', checkInputs: 'Periksa masukan Anda', futureDateError: 'Pilih tanggal target di masa depan.', dateFormatError: 'Pilih tanggal target yang valid.', hoursError: 'Waktu belajar mingguan harus antara 0,5 dan 40 jam.', targetBelowCurrentError: 'Level target tidak boleh di bawah level saat ini.', noProgressError: 'Naikkan setidaknya satu level target untuk membuat rencana.', plannerNote: 'Gunakan level CEFR sebagai titik awal penilaian diri, bukan hasil sertifikasi.', hoursEstimateNote: 'Rentang jam adalah kisaran perencanaan dan berubah menurut jarak bahasa, paparan, dan kualitas belajar.', skillListening: 'Menyimak', skillReading: 'Membaca', skillSpokenInteraction: 'Interaksi lisan', skillSpokenProduction: 'Produksi lisan', skillWriting: 'Menulis' };
6
+ const faq = [{ question: 'Apa yang dihitung oleh perencana profil keterampilan CEFR?', answer: 'Perencana ini membandingkan level saat ini dan target untuk menyimak, membaca, interaksi lisan, produksi lisan, dan menulis. Perencana memperkirakan rentang jam terpandu, membandingkannya dengan minggu yang tersedia, lalu membagi usaha di antara keterampilan yang memiliki kesenjangan.' }, { question: 'Bisakah saya menggunakannya sebagai tes bahasa?', answer: 'Tidak. Perencana ini mengatur penilaian diri dan beban belajar. Perencana tidak menguji kemampuan, memverifikasi sertifikat, atau menjamin pencapaian level tertentu pada tanggal tertentu.' }, { question: 'Mengapa jam ditampilkan sebagai rentang?', answer: 'Kemajuan berbeda menurut jarak bahasa, pengalaman sebelumnya, paparan, mutu pengajaran, kesempatan berlatih, dan bukti yang digunakan untuk menilai level. Karena itu rentang ini adalah panduan perencanaan, bukan janji.' }, { question: 'Bagaimana waktu mingguan dibagi?', answer: 'Keterampilan dengan kesenjangan level lebih besar mendapat porsi waktu mingguan yang lebih besar. Keterampilan yang sudah mencapai target ditampilkan sebagai sudah pada target dan tidak mendapat porsi terencana.' }, { question: 'Apa yang harus dilakukan jika statusnya tidak cukup?', answer: 'Tambahkan waktu mingguan yang realistis, geser tanggal target, atau turunkan satu atau beberapa level target. Tinjau ulang profil setelah satu blok belajar, bukan mencoba mengejar semua jam sekaligus.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Perencana profil keterampilan bahasa CEFR', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/id/perencana-profil-keterampilan-bahasa-cefr' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'Membuat rencana profil keterampilan CEFR', step: [{ '@type': 'HowToStep', name: 'Tetapkan tanggal', text: 'Pilih tanggal untuk meninjau profil target.' }, { '@type': 'HowToStep', name: 'Nilai setiap keterampilan', text: 'Pilih level CEFR saat ini dan target untuk kelima keterampilan.' }, { '@type': 'HowToStep', name: 'Lindungi waktu mingguan', text: 'Masukkan waktu belajar yang dapat dipertahankan setiap minggu.' }, { '@type': 'HowToStep', name: 'Baca peta', text: 'Gunakan status, jalur keterampilan, dan tonggak untuk menyesuaikan rencana.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'perencana-profil-keterampilan-bahasa-cefr', title: 'Perencana profil keterampilan bahasa CEFR', description: 'Petakan level CEFR saat ini dan target pada lima keterampilan, perkirakan jam terpandu, dan tetapkan tonggak untuk rencana belajar yang realistis.', ui, seo: [{ type: 'title', text: 'Petakan tujuan bahasa pada lima keterampilan', level: 2 }, { type: 'paragraph', html: 'Satu level bahasa dapat menyembunyikan profil yang tidak seimbang. Anda mungkin membaca di B1, menyimak di A2, dan masih membutuhkan latihan interaksi lisan. Perencana ini membuat perbedaan itu terlihat lalu mengubahnya menjadi beban kerja dan peta tonggak.' }, { type: 'title', text: 'Cara kerja perkiraan profil', level: 2 }, { type: 'paragraph', html: 'Perhitungan memberi kisaran jam terpandu yang luas untuk setiap langkah CEFR dan menjumlahkan kisaran bagi setiap keterampilan yang memiliki kesenjangan. Pembagian mingguan mengikuti titik tengah setiap kisaran, sehingga kesenjangan dua level mendapat lebih banyak waktu daripada kesenjangan satu level. Hasilnya adalah model perencanaan, bukan pengukuran kemampuan.' }, { type: 'title', text: 'Baca status kalender', level: 2 }, { type: 'table', headers: ['Status', 'Makna', 'Langkah berikutnya'], rows: [['Ada ruang untuk estimasi', 'Jam yang tersedia mencapai batas atas kisaran gabungan.', 'Pertahankan rutinitas dan gunakan tonggak sebagai waktu peninjauan.'], ['Estimasi bawah tercapai', 'Jam yang tersedia mencapai batas bawah, tetapi tidak batas atas.', 'Jaga target tetap fleksibel dan sisihkan waktu untuk umpan balik serta tinjauan.'], ['Lebih singkat dari estimasi', 'Jam yang tersedia tidak mencapai batas bawah kisaran gabungan.', 'Geser tanggal, kurangi target, atau tambahkan waktu yang berkelanjutan.']] }, { type: 'title', text: 'Ubah tonggak menjadi bukti', level: 2 }, { type: 'paragraph', html: 'Gunakan setiap tonggak sebagai alasan mengumpulkan bukti, bukan sebagai kenaikan otomatis. Simpan contoh menyimak, percakapan singkat, tugas membaca, dan tulisan yang sesuai dengan deskriptor level target. Jika satu keterampilan berhenti berkembang, ubah latihannya daripada menyembunyikan kesenjangan dalam rata-rata umum.' }, { type: 'list', items: ['Pilih level dari tugas terbaru yang dapat Anda jelaskan.', 'Jaga waktu mingguan tetap berkelanjutan sepanjang kalender.', 'Tinjau profil setelah blok belajar tetap dan ubah satu variabel.', 'Gunakan umpan balik pengajar atau penilaian resmi jika level penting untuk studi, pekerjaan, atau imigrasi.'] }, { type: 'tip', title: 'Batas perkiraan', html: 'CEFR menjelaskan kemampuan komunikatif melalui deskriptor dan tidak menetapkan satu jumlah jam universal. Jarak bahasa, pengalaman, paparan, umpan balik, dan kondisi belajar dapat mengubah beban nyata secara besar. Jangan anggap rencana ini sebagai sertifikat atau jaminan.' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Tetapkan tanggal', text: 'Pilih tanggal untuk meninjau profil target.' }, { name: 'Nilai keterampilan', text: 'Pilih level CEFR saat ini dan target untuk kelima keterampilan.' }, { name: 'Lindungi waktu mingguan', text: 'Masukkan waktu belajar yang dapat dipertahankan setiap minggu, lalu petakan profil.' }, { name: 'Baca dan revisi', text: 'Gunakan status, jalur, dan tanggal tonggak untuk menyesuaikan rencana setelah latihan nyata.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Data obiettivo', weeklyHours: 'Tempo di studio settimanale', listening: 'Comprensione orale', reading: 'Comprensione scritta', spokenInteraction: 'Interazione orale', spokenProduction: 'Produzione orale', writing: 'Scrittura', currentLevel: 'Livello attuale', targetLevel: 'Livello obiettivo', planButton: 'Mappa il mio profilo', resetButton: 'Reimposta', presetLabel: 'Inizia con un ritmo', gentlePreset: '3 ore', steadyPreset: '5 ore', focusedPreset: '10 ore', resultTitle: 'Il tuo profilo di competenze', emptyResult: 'Imposta i cinque livelli attuali, gli obiettivi, la data e il tempo settimanale per vedere la mappa del profilo.', statusOnTrack: 'Il tuo calendario lascia spazio alla stima.', statusTight: 'Il tuo calendario raggiunge la stima inferiore.', statusInsufficient: 'Il tuo calendario è più breve della stima inferiore.', totalHours: 'Ore guidate stimate', availableHours: 'Ore disponibili', weeksAvailable: 'Settimane disponibili', skillProfile: 'Cinque percorsi di competenza', milestoneMap: 'Mappa delle tappe', hours: 'ore', week: 'settimana', noGap: 'Livello raggiunto', checkInputs: 'Controlla i dati', futureDateError: 'Scegli una data obiettivo futura.', dateFormatError: 'Scegli una data obiettivo valida.', hoursError: 'Il tempo di studio settimanale deve essere compreso tra 0,5 e 40 ore.', targetBelowCurrentError: 'Il livello obiettivo non può essere inferiore a quello attuale.', noProgressError: 'Aumenta almeno un livello obiettivo per creare un piano.', plannerNote: 'Usa i livelli del QCER come punto di partenza per un autovalutazione, non come risultato di certificazione.', hoursEstimateNote: 'La fascia di ore serve per pianificare e varia in base alla distanza linguistica, all esposizione e alla qualità dello studio.', skillListening: 'Comprensione orale', skillReading: 'Comprensione scritta', skillSpokenInteraction: 'Interazione orale', skillSpokenProduction: 'Produzione orale', skillWriting: 'Scrittura' };
6
+ const faq = [{ question: 'Che cosa calcola il pianificatore del profilo di competenze QCER?', answer: 'Confronta il livello attuale e quello obiettivo per comprensione orale, comprensione scritta, interazione orale, produzione orale e scrittura. Stima una fascia di ore guidate, la confronta con le settimane disponibili e distribuisce l impegno tra le competenze con un divario.' }, { question: 'Posso usarlo come test linguistico?', answer: 'No. Il pianificatore organizza un autovalutazione e un carico di studio. Non verifica la prestazione, non convalida certificati e non garantisce il raggiungimento di un livello entro una data precisa.' }, { question: 'Perché le ore sono mostrate come intervallo?', answer: 'I progressi cambiano in base alla distanza tra le lingue, all esperienza precedente, all esposizione, alla qualità dell insegnamento, alle occasioni di pratica e alle prove usate per valutare il livello. L intervallo è quindi una guida alla pianificazione, non una promessa.' }, { question: 'Come distribuisce il tempo settimanale?', answer: 'Le competenze con divari di livello maggiori ricevono una quota più grande del tempo settimanale. Quelle già al livello obiettivo risultano raggiunte e non ricevono una quota pianificata.' }, { question: 'Che cosa faccio se lo stato è insufficiente?', answer: 'Aggiungi tempo settimanale realistico, sposta la data obiettivo oppure riduci uno o più livelli obiettivo. Ricontrolla il profilo dopo un blocco di studio invece di cercare di recuperare tutte le ore insieme.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Pianificatore del profilo di competenze linguistiche QCER', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/it/pianificatore-profilo-competenze-linguistiche-cefr' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'Creare un piano del profilo di competenze QCER', step: [{ '@type': 'HowToStep', name: 'Imposta la data', text: 'Scegli la data in cui vuoi controllare il profilo obiettivo.' }, { '@type': 'HowToStep', name: 'Valuta ogni competenza', text: 'Scegli un livello attuale e uno obiettivo del QCER per tutte le cinque competenze.' }, { '@type': 'HowToStep', name: 'Proteggi il tempo settimanale', text: 'Inserisci il tempo di studio che puoi mantenere ogni settimana.' }, { '@type': 'HowToStep', name: 'Leggi la mappa', text: 'Usa stato, percorsi e date delle tappe per adattare il piano.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'pianificatore-profilo-competenze-linguistiche-cefr', title: 'Pianificatore del profilo di competenze linguistiche QCER', description: 'Mappa i livelli attuali e obiettivo del QCER per cinque competenze, stima le ore guidate e fissa tappe per un piano di studio realistico.', ui, seo: [{ type: 'title', text: 'Mappa un obiettivo linguistico su cinque competenze', level: 2 }, { type: 'paragraph', html: 'Un solo livello linguistico può nascondere un profilo irregolare. Potresti leggere a B1, ascoltare ad A2 e avere ancora bisogno di pratica nell interazione orale. Questo pianificatore rende visibili le differenze e trasforma i divari in carico di lavoro e tappe.' }, { type: 'title', text: 'Come funziona la stima del profilo', level: 2 }, { type: 'paragraph', html: 'Il calcolo assegna una fascia ampia di ore guidate a ogni passaggio QCER e somma le fasce di ogni competenza con un divario. La ripartizione settimanale segue il punto medio di ogni intervallo, quindi un divario di due livelli riceve più tempo di uno di un livello. È un modello di pianificazione, non una misura della capacità.' }, { type: 'title', text: 'Leggi lo stato del calendario', level: 2 }, { type: 'table', headers: ['Stato', 'Significato', 'Prossimo passo utile'], rows: [['Margine per la stima', 'Le ore disponibili raggiungono il limite superiore dell intervallo combinato.', 'Mantieni la routine e usa le tappe come momenti di revisione.'], ['Stima inferiore raggiunta', 'Le ore disponibili raggiungono il limite inferiore ma non quello superiore.', 'Mantieni flessibile l obiettivo e proteggi tempo per feedback e ripasso.'], ['Sotto la stima', 'Le ore disponibili non raggiungono il limite inferiore dell intervallo combinato.', 'Sposta la data, riduci un obiettivo o aggiungi tempo sostenibile.']] }, { type: 'title', text: 'Trasforma le tappe in prove', level: 2 }, { type: 'paragraph', html: 'Usa ogni tappa per raccogliere prove, non come promozione automatica. Conserva un campione d ascolto, una breve conversazione, un compito di lettura e un testo coerenti con il descrittore obiettivo. Se una competenza si blocca, cambia la pratica invece di nascondere il divario in una media generale.' }, { type: 'list', items: ['Scegli livelli basati su compiti recenti che sai descrivere.', 'Mantieni sostenibile il tempo settimanale per tutto il calendario.', 'Rivedi il profilo dopo un blocco fisso e cambia una sola variabile.', 'Usa il feedback di un insegnante o una valutazione ufficiale quando il livello conta per studio, lavoro o immigrazione.'] }, { type: 'tip', title: 'Limiti della stima', html: 'Il QCER descrive la capacità comunicativa attraverso descrittori e non stabilisce un numero universale di ore. Distanza linguistica, esperienza, esposizione, feedback e condizioni di studio possono cambiare molto l impegno reale. Non considerare questo piano un certificato o una garanzia.' }], faq, bibliography: [{ name: 'Consiglio d Europa: Quadro comune europeo di riferimento per le lingue', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Imposta la data', text: 'Scegli la data in cui vuoi controllare il profilo obiettivo.' }, { name: 'Valuta le competenze', text: 'Scegli i livelli attuali e obiettivo del QCER per tutte le cinque competenze.' }, { name: 'Proteggi il tempo', text: 'Inserisci un tempo di studio sostenibile ogni settimana e mappa il profilo.' }, { name: 'Leggi e rivedi', text: 'Usa stato, percorsi e date delle tappe per adattare il piano dopo la pratica reale.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: '目標日', weeklyHours: '週あたりの学習時間', listening: '聞く力', reading: '読む力', spokenInteraction: '会話のやり取り', spokenProduction: '話す力', writing: '書く力', currentLevel: '現在のレベル', targetLevel: '目標レベル', planButton: 'プロフィールを作成', resetButton: 'リセット', presetLabel: 'ペースを選ぶ', gentlePreset: '3時間', steadyPreset: '5時間', focusedPreset: '10時間', resultTitle: 'スキルプロフィール', emptyResult: '5つの現在レベルと目標レベル、目標日、週の学習時間を設定するとプロフィールマップが表示されます。', statusOnTrack: '予定には見積もり分の余裕があります。', statusTight: '予定は下限の見積もりに届きます。', statusInsufficient: '予定は下限の見積もりより短いです。', totalHours: '推定ガイド学習時間', availableHours: '利用できる時間', weeksAvailable: '利用できる週数', skillProfile: '5つのスキル経路', milestoneMap: 'マイルストーンマップ', hours: '時間', week: '週', noGap: '目標達成', checkInputs: '入力を確認してください', futureDateError: '未来の日付を目標日に選んでください。', dateFormatError: '有効な目標日を選んでください。', hoursError: '週の学習時間は0.5時間から40時間の間で入力してください。', targetBelowCurrentError: '目標レベルは現在のレベルより低くできません。', noProgressError: '計画を作るには少なくとも1つの目標レベルを上げてください。', plannerNote: 'CEFRレベルは自己評価の出発点として使い、認定試験の結果とは考えないでください。', hoursEstimateNote: '時間の範囲は計画用の目安で、言語間の距離、接触量、学習の質によって変わります。', skillListening: '聞く力', skillReading: '読む力', skillSpokenInteraction: '会話のやり取り', skillSpokenProduction: '話す力', skillWriting: '書く力' };
6
+ const faq = [{ question: 'CEFRスキルプロフィールプランナーは何を計算しますか?', answer: '聞く力、読む力、会話のやり取り、話す力、書く力について、現在のCEFRレベルと目標レベルを比較します。ガイド学習時間の範囲を見積もり、利用できる週数と比べ、差があるスキルに学習量を配分します。' }, { question: '語学テストとして使えますか?', answer: 'いいえ。これは自己評価と学習量を整理する計画ツールです。実力を試験したり、証明書を確認したり、特定の日までの到達を保証したりするものではありません。' }, { question: 'なぜ時間が範囲で表示されるのですか?', answer: '進歩は言語間の距離、これまでの経験、接触量、指導の質、練習機会、レベルを判断する証拠によって変わります。そのため、範囲は約束ではなく計画の目安です。' }, { question: '週の時間はどのように配分されますか?', answer: 'レベル差が大きいスキルほど週の時間を多く受け取ります。目標に達しているスキルは目標達成と表示され、計画上の時間は配分されません。' }, { question: '不足という状態になったらどうしますか?', answer: '現実的な週の時間を増やす、目標日を延ばす、または目標レベルを下げます。一度に遅れを取り戻そうとせず、一定の学習期間の後にプロフィールを見直してください。' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'CEFR語学スキルプロフィールプランナー', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/ja/cefr-language-skill-profile-planner' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'CEFRスキルプロフィール計画を作る', step: [{ '@type': 'HowToStep', name: '日付を決める', text: '目標プロフィールを確認したい日を選びます。' }, { '@type': 'HowToStep', name: '各スキルを評価する', text: '5つのスキルすべてに現在と目標のCEFRレベルを選びます。' }, { '@type': 'HowToStep', name: '週の時間を確保する', text: '毎週続けられる学習時間を入力します。' }, { '@type': 'HowToStep', name: 'マップを読む', text: '状態、スキル経路、マイルストーンを使って計画を調整します。' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'cefr-language-skill-profile-planner', title: 'CEFR語学スキルプロフィールプランナー', description: '5つの語学スキルについてCEFRの現在レベルと目標レベルを地図化し、ガイド学習時間と現実的なマイルストーンを見積もります。', ui, seo: [{ type: 'title', text: '5つのスキルで語学目標を地図化する', level: 2 }, { type: 'paragraph', html: '1つの語学レベルだけでは、スキルごとの偏りが隠れてしまいます。読む力はB1でも、聞く力はA2で、会話のやり取りにはさらに練習が必要かもしれません。このプランナーは差を見える化し、学習量とマイルストーンに変換します。' }, { type: 'title', text: 'プロフィールの見積もり方', level: 2 }, { type: 'paragraph', html: '各CEFR段階に広いガイド学習時間の範囲を割り当て、差があるスキルごとに合計します。週の配分は各範囲の中間値に従うため、2段階の差には1段階の差より多くの時間が割り当てられます。これは能力測定ではなく計画モデルです。' }, { type: 'title', text: '予定の状態を読む', level: 2 }, { type: 'table', headers: ['状態', '意味', '次にできること'], rows: [['見積もりに余裕あり', '利用できる時間が合計範囲の上限に届きます。', '習慣を続け、マイルストーンを確認時点として使います。'], ['下限の見積もりに到達', '利用できる時間が下限には届きますが、上限には届きません。', '目標を柔軟に保ち、フィードバックと復習の時間を守ります。'], ['見積もりより短い', '利用できる時間が合計範囲の下限に届きません。', '日付を延ばす、目標を下げる、継続できる時間を増やすのいずれかを選びます。']] }, { type: 'title', text: 'マイルストーンを証拠につなげる', level: 2 }, { type: 'paragraph', html: '各マイルストーンは自動的な昇格ではなく、証拠を集める機会です。目標レベルの記述に合う聞き取りサンプル、短い会話、読解課題、作文を記録します。1つのスキルが停滞したら、全体平均に隠すのではなく練習方法を変えます。' }, { type: 'list', items: ['具体的に説明できる最近の課題からレベルを選ぶ。', '予定全体を通じて毎週続けられる時間を保つ。', '一定の学習期間の後にプロフィールを見直し、変えるのは1項目にする。', '進学、仕事、移住でレベルが重要なら、教師のフィードバックや公式評価を使う。'] }, { type: 'tip', title: '見積もりの限界', html: 'CEFRは記述文でコミュニケーション能力を説明するもので、普遍的な学習時間を定めていません。言語間の距離、経験、接触、フィードバック、学習条件によって実際の負荷は大きく変わります。この計画を証明書や保証として扱わないでください。' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: '日付を決める', text: '目標プロフィールを確認したい日を選びます。' }, { name: 'スキルを評価する', text: '5つすべてに現在と目標のCEFRレベルを選びます。' }, { name: '時間を確保する', text: '毎週続けられる時間を入力してプロフィールを作成します。' }, { name: '読んで調整する', text: '状態、経路、日付を使い、実際の練習後に計画を調整します。' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: '목표 날짜', weeklyHours: '주간 학습 시간', listening: '듣기', reading: '읽기', spokenInteraction: '말하기 상호작용', spokenProduction: '말하기 표현', writing: '쓰기', currentLevel: '현재 수준', targetLevel: '목표 수준', planButton: '내 프로필 만들기', resetButton: '초기화', presetLabel: '학습 속도 선택', gentlePreset: '3시간', steadyPreset: '5시간', focusedPreset: '10시간', resultTitle: '언어 능력 프로필', emptyResult: '다섯 가지 현재 수준과 목표 수준, 날짜, 주간 시간을 정하면 프로필 지도가 표시됩니다.', statusOnTrack: '일정에 예상 학습량을 위한 여유가 있습니다.', statusTight: '일정이 낮은 예상치에 도달합니다.', statusInsufficient: '일정이 낮은 예상치보다 짧습니다.', totalHours: '예상 안내 학습 시간', availableHours: '사용 가능한 시간', weeksAvailable: '사용 가능한 주', skillProfile: '다섯 가지 능력 경로', milestoneMap: '마일스톤 지도', hours: '시간', week: '주', noGap: '목표 달성', checkInputs: '입력을 확인하세요', futureDateError: '미래의 목표 날짜를 선택하세요.', dateFormatError: '유효한 목표 날짜를 선택하세요.', hoursError: '주간 학습 시간은 0.5시간에서 40시간 사이여야 합니다.', targetBelowCurrentError: '목표 수준은 현재 수준보다 낮을 수 없습니다.', noProgressError: '계획을 만들려면 목표 수준을 하나 이상 높이세요.', plannerNote: 'CEFR 수준은 자기 평가의 출발점으로 사용하며 인증 결과로 사용하지 마세요.', hoursEstimateNote: '시간 범위는 계획을 위한 기준이며 언어 간 거리, 노출, 학습의 질에 따라 달라집니다.', skillListening: '듣기', skillReading: '읽기', skillSpokenInteraction: '말하기 상호작용', skillSpokenProduction: '말하기 표현', skillWriting: '쓰기' };
6
+ const faq = [{ question: 'CEFR 언어 능력 프로필 플래너는 무엇을 계산하나요?', answer: '듣기, 읽기, 말하기 상호작용, 말하기 표현, 쓰기의 현재 CEFR 수준과 목표 수준을 비교합니다. 안내 학습 시간의 범위를 추정하고 사용 가능한 주와 비교한 뒤, 차이가 있는 능력에 학습량을 나눕니다.' }, { question: '언어 시험으로 사용할 수 있나요?', answer: '아니요. 이 플래너는 자기 평가와 학습량을 정리합니다. 실력을 시험하거나 인증서를 확인하거나 특정 날짜까지 목표 달성을 보장하지 않습니다.' }, { question: '시간을 범위로 표시하는 이유는 무엇인가요?', answer: '진도는 언어 간 거리, 이전 경험, 노출, 수업의 질, 연습 기회와 수준을 판단하는 근거에 따라 달라집니다. 따라서 범위는 약속이 아니라 계획을 위한 기준입니다.' }, { question: '주간 시간은 어떻게 나누나요?', answer: '수준 차이가 큰 능력에 더 많은 주간 시간이 배정됩니다. 이미 목표에 도달한 능력은 목표 달성으로 표시되고 계획 시간은 배정되지 않습니다.' }, { question: '부족 상태가 나오면 어떻게 하나요?', answer: '현실적인 주간 시간을 늘리거나 목표 날짜를 늦추거나 목표 수준을 낮추세요. 놓친 시간을 한꺼번에 만회하기보다 일정한 학습 기간 뒤 프로필을 다시 확인하세요.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'CEFR 언어 능력 프로필 플래너', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/ko/cefr-eoneo-neungnyeok-peuropail-gyehoek' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'CEFR 능력 프로필 계획 만들기', step: [{ '@type': 'HowToStep', name: '날짜 설정', text: '목표 프로필을 확인할 날짜를 선택합니다.' }, { '@type': 'HowToStep', name: '각 능력 평가', text: '다섯 가지 능력 모두에 현재와 목표 CEFR 수준을 선택합니다.' }, { '@type': 'HowToStep', name: '주간 시간 확보', text: '매주 지속할 수 있는 학습 시간을 입력합니다.' }, { '@type': 'HowToStep', name: '지도 읽기', text: '상태, 능력 경로, 마일스톤으로 계획을 조정합니다.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'cefr-language-skill-profile-planner', title: 'CEFR 언어 능력 프로필 플래너', description: '다섯 가지 언어 능력의 현재와 목표 CEFR 수준을 지도화하고 안내 학습 시간과 현실적인 마일스톤을 추정합니다.', ui, seo: [{ type: 'title', text: '다섯 가지 능력으로 언어 목표 지도화하기', level: 2 }, { type: 'paragraph', html: '하나의 언어 수준만으로는 능력별 편차를 알기 어렵습니다. 읽기는 B1이어도 듣기는 A2이고 말하기 상호작용에는 더 연습이 필요할 수 있습니다. 이 플래너는 차이를 보여 주고 학습량과 마일스톤 지도로 바꿉니다.' }, { type: 'title', text: '프로필 추정 방식', level: 2 }, { type: 'paragraph', html: '각 CEFR 단계에 넓은 안내 학습 시간 범위를 배정하고 차이가 있는 능력의 범위를 합산합니다. 주간 배분은 각 범위의 중간값을 따르므로 두 단계 차이가 한 단계 차이보다 더 많은 시간을 받습니다. 결과는 능력 측정이 아니라 계획 모델입니다.' }, { type: 'title', text: '일정 상태 읽기', level: 2 }, { type: 'table', headers: ['상태', '의미', '다음 행동'], rows: [['예상치에 여유 있음', '사용 가능한 시간이 합산 범위의 상한에 도달합니다.', '습관을 유지하고 마일스톤을 점검 시점으로 사용하세요.'], ['낮은 예상치 도달', '사용 가능한 시간이 하한에는 도달하지만 상한에는 도달하지 않습니다.', '목표를 유연하게 유지하고 피드백과 복습 시간을 확보하세요.'], ['예상치보다 짧음', '사용 가능한 시간이 합산 범위의 하한에 도달하지 않습니다.', '날짜를 늦추거나 목표를 낮추거나 지속 가능한 시간을 늘리세요.']] }, { type: 'title', text: '마일스톤을 증거로 바꾸기', level: 2 }, { type: 'paragraph', html: '각 마일스톤을 자동 승급이 아니라 증거를 모으는 기회로 사용하세요. 목표 수준의 기술문에 맞는 듣기 샘플, 짧은 대화, 읽기 과제와 글을 기록합니다. 한 능력이 정체되면 전체 평균 속에 숨기지 말고 연습 방법을 바꾸세요.' }, { type: 'list', items: ['설명할 수 있는 최근 과제에서 수준을 선택하세요.', '전체 일정 동안 지속 가능한 주간 시간을 유지하세요.', '일정한 학습 블록 뒤 프로필을 검토하고 한 번에 한 변수만 바꾸세요.', '학업, 취업, 이민에 수준이 중요하다면 교사 피드백이나 공식 평가를 사용하세요.'] }, { type: 'tip', title: '추정의 한계', html: 'CEFR은 기술문으로 의사소통 능력을 설명하며 보편적인 학습 시간 하나를 정하지 않습니다. 언어 간 거리, 경험, 노출, 피드백과 학습 조건에 따라 실제 학습량은 크게 달라질 수 있습니다. 이 계획을 인증서나 보장으로 받아들이지 마세요.' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: '날짜 설정', text: '목표 프로필을 확인할 날짜를 선택합니다.' }, { name: '능력 평가', text: '다섯 가지 능력 모두에 현재와 목표 CEFR 수준을 선택합니다.' }, { name: '시간 확보', text: '매주 지속 가능한 학습 시간을 입력하고 프로필을 만듭니다.' }, { name: '읽고 수정', text: '상태, 경로, 날짜를 사용해 실제 연습 후 계획을 조정합니다.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Streefdatum', weeklyHours: 'Studietijd per week', listening: 'Luisteren', reading: 'Lezen', spokenInteraction: 'Gesproken interactie', spokenProduction: 'Gesproken productie', writing: 'Schrijven', currentLevel: 'Huidig niveau', targetLevel: 'Doelniveau', planButton: 'Mijn profiel in kaart brengen', resetButton: 'Resetten', presetLabel: 'Start met een tempo', gentlePreset: '3 uur', steadyPreset: '5 uur', focusedPreset: '10 uur', resultTitle: 'Je vaardighedenprofiel', emptyResult: 'Stel je vijf huidige niveaus, doelen, datum en wekelijkse tijd in om de profielkaart te zien.', statusOnTrack: 'Je planning heeft ruimte voor de schatting.', statusTight: 'Je planning bereikt de lage schatting.', statusInsufficient: 'Je planning is korter dan de lage schatting.', totalHours: 'Geschatte begeleide uren', availableHours: 'Beschikbare uren', weeksAvailable: 'Beschikbare weken', skillProfile: 'Vijf vaardigheidspaden', milestoneMap: 'Mijlpalenkaart', hours: 'uur', week: 'week', noGap: 'Doel bereikt', checkInputs: 'Controleer je invoer', futureDateError: 'Kies een streefdatum in de toekomst.', dateFormatError: 'Kies een geldige streefdatum.', hoursError: 'De wekelijkse studietijd moet tussen 0,5 en 40 uur liggen.', targetBelowCurrentError: 'Het doelniveau mag niet lager zijn dan het huidige niveau.', noProgressError: 'Verhoog minstens één doelniveau om een plan te maken.', plannerNote: 'Gebruik CEFR-niveaus als startpunt voor zelfevaluatie, niet als certificeringsresultaat.', hoursEstimateNote: 'De urenreeks is een planningsbandbreedte en verandert door taalafstand, blootstelling en studiekwaliteit.', skillListening: 'Luisteren', skillReading: 'Lezen', skillSpokenInteraction: 'Gesproken interactie', skillSpokenProduction: 'Gesproken productie', skillWriting: 'Schrijven' };
6
+ const faq = [{ question: 'Wat berekent de CEFR vaardighedenprofielplanner?', answer: 'De planner vergelijkt het huidige en doelniveau voor luisteren, lezen, gesproken interactie, gesproken productie en schrijven. Hij schat een reeks begeleide uren, vergelijkt die met je beschikbare weken en verdeelt de inspanning over vaardigheden met een verschil.' }, { question: 'Kan ik dit als taaltest gebruiken?', answer: 'Nee. De planner ordent een zelfevaluatie en een studielast. Hij test geen prestaties, controleert geen certificaat en voorspelt niet dat je op een bepaalde datum een niveau bereikt.' }, { question: 'Waarom worden de uren als een reeks getoond?', answer: 'Vooruitgang hangt af van taalafstand, eerdere ervaring, blootstelling, onderwijskwaliteit, oefenkansen en het bewijs waarmee een niveau wordt beoordeeld. De reeks is daarom een planningshulp, geen belofte.' }, { question: 'Hoe verdeelt de planner de wekelijkse tijd?', answer: 'Vaardigheden met grotere niveauverschillen krijgen een groter deel van de wekelijkse tijd. Vaardigheden die hun doel al hebben bereikt, worden als doel bereikt getoond en krijgen geen gepland aandeel.' }, { question: 'Wat moet ik doen bij een onvoldoende status?', answer: 'Voeg realistische wekelijkse tijd toe, verschuif de streefdatum of verlaag een of meer doelen. Bekijk het profiel na een vast studieblok opnieuw in plaats van alle gemiste uren ineens in te halen.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'CEFR vaardighedenprofielplanner', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/nl/cefr-taalvaardigheidsprofiel-planner' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'Een CEFR vaardighedenprofielplan maken', step: [{ '@type': 'HowToStep', name: 'Datum instellen', text: 'Kies de datum waarop je het doelprofiel wilt bekijken.' }, { '@type': 'HowToStep', name: 'Elke vaardigheid beoordelen', text: 'Kies een huidig en doel-CEFR-niveau voor alle vijf vaardigheden.' }, { '@type': 'HowToStep', name: 'Wekelijkse tijd beschermen', text: 'Voer de studietijd in die je elke week kunt volhouden.' }, { '@type': 'HowToStep', name: 'De kaart lezen', text: 'Gebruik status, paden en mijlpalen om het plan aan te passen.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'cefr-taalvaardigheidsprofiel-planner', title: 'CEFR vaardighedenprofielplanner', description: 'Breng huidige en gewenste CEFR-niveaus voor vijf taalvaardigheden in kaart, schat begeleide uren en stel realistische mijlpalen vast.', ui, seo: [{ type: 'title', text: 'Een taaldoel over vijf vaardigheden verdelen', level: 2 }, { type: 'paragraph', html: 'Een enkel taalniveau kan een ongelijk profiel verbergen. Misschien lees je op B1, luister je op A2 en heb je meer oefening nodig in gesproken interactie. Deze planner maakt de verschillen zichtbaar en zet ze om in studielast en mijlpalen.' }, { type: 'title', text: 'Zo werkt de profiels schatting', level: 2 }, { type: 'paragraph', html: 'De berekening koppelt aan elke CEFR-stap een brede reeks begeleide uren en telt de reeksen op voor elke vaardigheid met een verschil. De wekelijkse verdeling volgt het midden van elke reeks, zodat een verschil van twee niveaus meer tijd krijgt dan een verschil van één niveau. Het resultaat is een planningsmodel, geen meting van bekwaamheid.' }, { type: 'title', text: 'De planningstatus lezen', level: 2 }, { type: 'table', headers: ['Status', 'Betekenis', 'Nuttige volgende stap'], rows: [['Ruimte voor de schatting', 'De beschikbare uren bereiken de bovenkant van de gecombineerde reeks.', 'Houd de routine vast en gebruik mijlpalen als controlemomenten.'], ['Lage schatting bereikt', 'De beschikbare uren bereiken de onderkant maar niet de bovenkant.', 'Houd het doel flexibel en reserveer tijd voor feedback en herhaling.'], ['Korter dan de schatting', 'De beschikbare uren bereiken de onderkant van de gecombineerde reeks niet.', 'Verschuif de datum, verlaag een doel of voeg duurzame tijd toe.']] }, { type: 'title', text: 'Mijlpalen in bewijs veranderen', level: 2 }, { type: 'paragraph', html: 'Gebruik elke mijlpaal om bewijs te verzamelen, niet als automatische promotie. Bewaar een luisterfragment, een kort gesprek, een leestaken en een tekst die passen bij de beschrijving van je doelniveau. Als een vaardigheid stagneert, verander dan de oefening in plaats van het verschil te verbergen in een algemeen gemiddelde.' }, { type: 'list', items: ['Kies niveaus uit recente taken die je concreet kunt beschrijven.', 'Houd de wekelijkse tijd gedurende de hele planning vol.', 'Bekijk het profiel na een vast studieblok en verander één variabele.', 'Gebruik feedback van een docent of een officiële beoordeling als het niveau belangrijk is voor studie, werk of migratie.'] }, { type: 'tip', title: 'Grenzen van de schatting', html: 'CEFR beschrijft communicatieve vaardigheid met descriptoren en schrijft geen universeel aantal uren voor. Taalafstand, ervaring, blootstelling, feedback en studieomstandigheden kunnen de werkelijke inspanning sterk veranderen. Beschouw dit plan niet als certificaat of garantie.' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Datum instellen', text: 'Kies de datum waarop je het doelprofiel wilt bekijken.' }, { name: 'Vaardigheden beoordelen', text: 'Kies huidige en doel-CEFR-niveaus voor alle vijf vaardigheden.' }, { name: 'Tijd beschermen', text: 'Voer een studietijd in die je elke week kunt volhouden en breng het profiel in kaart.' }, { name: 'Lezen en bijstellen', text: 'Gebruik status, paden en mijlpalen om het plan na echte oefening aan te passen.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Data docelowa', weeklyHours: 'Czas nauki w tygodniu', listening: 'Słuchanie', reading: 'Czytanie', spokenInteraction: 'Interakcja ustna', spokenProduction: 'Produkcja ustna', writing: 'Pisanie', currentLevel: 'Obecny poziom', targetLevel: 'Poziom docelowy', planButton: 'Mapuj mój profil', resetButton: 'Resetuj', presetLabel: 'Zacznij od tempa', gentlePreset: '3 godziny', steadyPreset: '5 godzin', focusedPreset: '10 godzin', resultTitle: 'Profil umiejętności', emptyResult: 'Ustaw pięć obecnych i docelowych poziomów, datę oraz tygodniowy czas, aby zobaczyć mapę profilu.', statusOnTrack: 'W kalendarzu jest miejsce na szacunek.', statusTight: 'Kalendarz osiąga dolny szacunek.', statusInsufficient: 'Kalendarz jest krótszy niż dolny szacunek.', totalHours: 'Szacowane godziny nauki z przewodnikiem', availableHours: 'Dostępne godziny', weeksAvailable: 'Dostępne tygodnie', skillProfile: 'Pięć ścieżek umiejętności', milestoneMap: 'Mapa kamieni milowych', hours: 'godzin', week: 'tydzień', noGap: 'Poziom osiągnięty', checkInputs: 'Sprawdź dane', futureDateError: 'Wybierz przyszłą datę docelową.', dateFormatError: 'Wybierz prawidłową datę docelową.', hoursError: 'Tygodniowy czas nauki musi wynosić od 0,5 do 40 godzin.', targetBelowCurrentError: 'Poziom docelowy nie może być niższy od obecnego.', noProgressError: 'Podnieś co najmniej jeden poziom docelowy, aby utworzyć plan.', plannerNote: 'Użyj poziomów CEFR jako punktu wyjścia do samooceny, a nie jako wyniku certyfikacji.', hoursEstimateNote: 'Zakres godzin służy do planowania i zmienia się wraz z dystansem językowym, kontaktem z językiem i jakością nauki.', skillListening: 'Słuchanie', skillReading: 'Czytanie', skillSpokenInteraction: 'Interakcja ustna', skillSpokenProduction: 'Produkcja ustna', skillWriting: 'Pisanie' };
6
+ const faq = [{ question: 'Co oblicza planer profilu umiejętności CEFR?', answer: 'Porównuje obecny i docelowy poziom słuchania, czytania, interakcji ustnej, produkcji ustnej i pisania. Szacuje zakres godzin nauki z przewodnikiem, porównuje go z dostępnymi tygodniami i rozdziela wysiłek między umiejętności z luką.' }, { question: 'Czy mogę używać go jako testu językowego?', answer: 'Nie. Planer porządkuje samoocenę i obciążenie nauką. Nie sprawdza wyników, nie potwierdza certyfikatu i nie gwarantuje osiągnięcia poziomu w określonym terminie.' }, { question: 'Dlaczego godziny są podane jako zakres?', answer: 'Postęp zależy od dystansu językowego, wcześniejszego doświadczenia, kontaktu z językiem, jakości nauczania, okazji do ćwiczeń oraz dowodów użytych do oceny poziomu. Zakres jest więc pomocą w planowaniu, a nie obietnicą.' }, { question: 'Jak planer dzieli czas tygodniowy?', answer: 'Umiejętności z większą różnicą poziomów otrzymują większą część czasu. Umiejętności, które osiągnęły cel, są oznaczone jako osiągnięte i nie otrzymują zaplanowanego udziału.' }, { question: 'Co zrobić, gdy status jest niewystarczający?', answer: 'Dodaj realistyczny czas tygodniowy, przesuń datę docelową albo obniż jeden lub więcej poziomów. Sprawdź profil po stałym bloku nauki, zamiast próbować nadrobić wszystkie godziny naraz.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Planer profilu umiejętności językowych CEFR', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/pl/planer-profilu-umiejetnosci-jezykowych-cefr' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'Utworzyć plan profilu umiejętności CEFR', step: [{ '@type': 'HowToStep', name: 'Ustaw datę', text: 'Wybierz datę, w której chcesz sprawdzić profil docelowy.' }, { '@type': 'HowToStep', name: 'Oceń każdą umiejętność', text: 'Wybierz obecny i docelowy poziom CEFR dla wszystkich pięciu umiejętności.' }, { '@type': 'HowToStep', name: 'Zarezerwuj czas tygodniowy', text: 'Wpisz czas nauki, który możesz utrzymać co tydzień.' }, { '@type': 'HowToStep', name: 'Odczytaj mapę', text: 'Użyj statusu, ścieżek i kamieni milowych, aby dostosować plan.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'planer-profilu-umiejetnosci-jezykowych-cefr', title: 'Planer profilu umiejętności językowych CEFR', description: 'Mapuj obecne i docelowe poziomy CEFR dla pięciu umiejętności, szacuj godziny nauki z przewodnikiem i wyznaczaj realistyczne kamienie milowe.', ui, seo: [{ type: 'title', text: 'Rozplanuj cel językowy na pięć umiejętności', level: 2 }, { type: 'paragraph', html: 'Jeden poziom języka może ukrywać nierówny profil. Możesz czytać na poziomie B1, słuchać na A2 i nadal potrzebować ćwiczeń interakcji ustnej. Ten planer pokazuje różnice i zamienia je w obciążenie nauką oraz mapę kamieni milowych.' }, { type: 'title', text: 'Jak działa szacunek profilu', level: 2 }, { type: 'paragraph', html: 'Obliczenie przypisuje szeroki zakres godzin nauki z przewodnikiem do każdego kroku CEFR i sumuje zakresy dla umiejętności z luką. Tygodniowy podział opiera się na środku każdego zakresu, więc luka dwóch poziomów otrzymuje więcej czasu niż luka jednego poziomu. Wynik jest modelem planowania, a nie pomiarem zdolności.' }, { type: 'title', text: 'Odczytaj status kalendarza', level: 2 }, { type: 'table', headers: ['Status', 'Znaczenie', 'Następny krok'], rows: [['Miejsce na szacunek', 'Dostępne godziny sięgają górnej granicy połączonego zakresu.', 'Utrzymaj rutynę i używaj kamieni milowych jako punktów przeglądu.'], ['Osiągnięto dolny szacunek', 'Dostępne godziny sięgają dolnej, ale nie górnej granicy.', 'Zachowaj elastyczność celu i chroń czas na informację zwrotną oraz powtórkę.'], ['Poniżej szacunku', 'Dostępne godziny nie sięgają dolnej granicy połączonego zakresu.', 'Przesuń datę, zmniejsz cel albo dodaj możliwy do utrzymania czas.']] }, { type: 'title', text: 'Zamień kamienie milowe w dowody', level: 2 }, { type: 'paragraph', html: 'Traktuj każdy kamień milowy jako okazję do zebrania dowodów, a nie automatyczny awans. Zapisuj próbkę słuchania, krótką rozmowę, zadanie z czytania i tekst zgodne z opisem docelowego poziomu. Gdy jedna umiejętność stoi w miejscu, zmień ćwiczenie zamiast ukrywać lukę w ogólnej średniej.' }, { type: 'list', items: ['Wybieraj poziomy na podstawie zadań, które potrafisz konkretnie opisać.', 'Utrzymuj możliwy do utrzymania czas tygodniowy przez cały kalendarz.', 'Przeglądaj profil po stałym bloku nauki i zmieniaj jedną zmienną.', 'Korzystaj z opinii nauczyciela lub oficjalnej oceny, gdy poziom ma znaczenie dla studiów, pracy albo migracji.'] }, { type: 'tip', title: 'Granice szacunku', html: 'CEFR opisuje umiejętności komunikacyjne za pomocą deskryptorów i nie wyznacza jednej uniwersalnej liczby godzin. Dystans językowy, doświadczenie, kontakt, informacja zwrotna i warunki nauki mogą znacznie zmienić rzeczywisty wysiłek. Nie traktuj tego planu jako certyfikatu ani gwarancji.' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Ustaw datę', text: 'Wybierz datę, w której chcesz sprawdzić profil docelowy.' }, { name: 'Oceń umiejętności', text: 'Wybierz obecne i docelowe poziomy CEFR dla wszystkich pięciu umiejętności.' }, { name: 'Zarezerwuj czas', text: 'Wpisz czas nauki, który możesz utrzymać co tydzień, a następnie zmapuj profil.' }, { name: 'Czytaj i poprawiaj', text: 'Użyj statusu, ścieżek i dat, aby dostosować plan po prawdziwej praktyce.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Data alvo', weeklyHours: 'Tempo de estudo por semana', listening: 'Compreensão oral', reading: 'Compreensão escrita', spokenInteraction: 'Interação oral', spokenProduction: 'Produção oral', writing: 'Escrita', currentLevel: 'Nível atual', targetLevel: 'Nível alvo', planButton: 'Mapear meu perfil', resetButton: 'Redefinir', presetLabel: 'Comece com um ritmo', gentlePreset: '3 horas', steadyPreset: '5 horas', focusedPreset: '10 horas', resultTitle: 'Seu perfil de habilidades', emptyResult: 'Defina seus cinco níveis atuais e alvos, a data e o tempo semanal para ver o mapa do perfil.', statusOnTrack: 'Seu calendário tem margem para a estimativa.', statusTight: 'Seu calendário alcança a estimativa inferior.', statusInsufficient: 'Seu calendário é mais curto que a estimativa inferior.', totalHours: 'Horas guiadas estimadas', availableHours: 'Horas disponíveis', weeksAvailable: 'Semanas disponíveis', skillProfile: 'Cinco caminhos de habilidade', milestoneMap: 'Mapa de marcos', hours: 'horas', week: 'semana', noGap: 'No objetivo', checkInputs: 'Confira seus dados', futureDateError: 'Escolha uma data alvo futura.', dateFormatError: 'Escolha uma data alvo válida.', hoursError: 'O tempo de estudo semanal deve ficar entre 0,5 e 40 horas.', targetBelowCurrentError: 'O nível alvo não pode ser inferior ao nível atual.', noProgressError: 'Aumente pelo menos um nível alvo para criar um plano.', plannerNote: 'Use os níveis do QECR como ponto de partida para uma autoavaliação, não como resultado de certificação.', hoursEstimateNote: 'O intervalo de horas é uma faixa de planejamento e muda conforme a distância linguística, a exposição e a qualidade do estudo.', skillListening: 'Compreensão oral', skillReading: 'Compreensão escrita', skillSpokenInteraction: 'Interação oral', skillSpokenProduction: 'Produção oral', skillWriting: 'Escrita' };
6
+ const faq = [{ question: 'O que o planejador de perfil de habilidades do QECR calcula?', answer: 'Ele compara o nível atual e o nível alvo de compreensão oral, compreensão escrita, interação oral, produção oral e escrita. Estima um intervalo de horas guiadas, compara-o com as semanas disponíveis e divide o esforço entre as habilidades com uma lacuna.' }, { question: 'Posso usá-lo como teste de idioma?', answer: 'Não. O planejador organiza uma autoavaliação e uma carga de estudo. Ele não testa desempenho, verifica certificados nem garante que você atingirá um nível em uma data específica.' }, { question: 'Por que as horas aparecem como um intervalo?', answer: 'O progresso varia conforme a distância linguística, a experiência anterior, a exposição, a qualidade do ensino, as oportunidades de prática e as evidências usadas para avaliar o nível. Por isso, o intervalo é uma faixa de planejamento, não uma promessa.' }, { question: 'Como o planejador divide o tempo semanal?', answer: 'As habilidades com lacunas maiores recebem uma parcela maior do tempo semanal. As habilidades que já alcançaram seu objetivo aparecem como no objetivo e não recebem uma parcela planejada.' }, { question: 'O que fazer quando o status é insuficiente?', answer: 'Adicione tempo semanal realista, mova a data alvo ou reduza um ou mais níveis alvos. Revise o perfil depois de um bloco de estudo fixo, em vez de tentar compensar todas as horas de uma vez.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Planejador de perfil de habilidades linguísticas do QECR', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/pt/planejador-perfil-habilidades-idiomas-cefr' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'Criar um plano de perfil de habilidades do QECR', step: [{ '@type': 'HowToStep', name: 'Defina a data', text: 'Escolha a data em que deseja revisar o perfil alvo.' }, { '@type': 'HowToStep', name: 'Avalie cada habilidade', text: 'Escolha um nível atual e um nível alvo do QECR para as cinco habilidades.' }, { '@type': 'HowToStep', name: 'Proteja o tempo semanal', text: 'Informe o tempo de estudo que você consegue manter toda semana.' }, { '@type': 'HowToStep', name: 'Leia o mapa', text: 'Use o status, os caminhos e os marcos para ajustar o plano.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'planejador-perfil-habilidades-idiomas-cefr', title: 'Planejador de perfil de habilidades linguísticas do QECR', description: 'Mapeie níveis atuais e alvos do QECR em cinco habilidades, estime horas guiadas e defina marcos para um plano de estudo realista.', ui, seo: [{ type: 'title', text: 'Mapeie uma meta de idioma em cinco habilidades', level: 2 }, { type: 'paragraph', html: 'Um único nível de idioma pode esconder um perfil desigual. Você pode ler em B1, ouvir em A2 e ainda precisar de mais prática na interação oral. Este planejador torna as diferenças visíveis e transforma as lacunas em carga de trabalho e marcos.' }, { type: 'title', text: 'Como funciona a estimativa do perfil', level: 2 }, { type: 'paragraph', html: 'O cálculo atribui uma faixa ampla de horas guiadas a cada passo do QECR e soma as faixas de cada habilidade com uma lacuna. A distribuição semanal segue o ponto médio de cada intervalo, então uma lacuna de dois níveis recebe mais tempo que uma de um nível. O resultado é um modelo de planejamento, não uma medição de capacidade.' }, { type: 'title', text: 'Leia o status do calendário', level: 2 }, { type: 'table', headers: ['Status', 'Significado', 'Próximo passo útil'], rows: [['Margem para a estimativa', 'As horas disponíveis chegam ao limite superior do intervalo combinado.', 'Mantenha a rotina e use os marcos como pontos de revisão.'], ['Estimativa inferior alcançada', 'As horas disponíveis chegam ao limite inferior, mas não ao superior.', 'Mantenha a meta flexível e proteja tempo para feedback e revisão.'], ['Abaixo da estimativa', 'As horas disponíveis não chegam ao limite inferior do intervalo combinado.', 'Mova a data, reduza uma meta ou acrescente tempo sustentável.']] }, { type: 'title', text: 'Transforme marcos em evidências', level: 2 }, { type: 'paragraph', html: 'Use cada marco como motivo para reunir evidências, não como promoção automática. Guarde uma amostra de compreensão oral, uma conversa curta, uma tarefa de leitura e um texto que correspondam ao descritor do nível desejado. Se uma habilidade estagnar, mude sua prática em vez de esconder a lacuna em uma média geral.' }, { type: 'list', items: ['Escolha níveis a partir de tarefas recentes que você consiga descrever.', 'Mantenha um tempo semanal sustentável durante todo o calendário.', 'Revise o perfil depois de um bloco fixo e mude uma variável por vez.', 'Use o feedback de um professor ou uma avaliação oficial quando o nível importar para estudos, trabalho ou imigração.'] }, { type: 'tip', title: 'Limites da estimativa', html: 'O QECR descreve a capacidade comunicativa por meio de descritores e não prescreve um número universal de horas. Distância linguística, experiência, exposição, feedback e condições de estudo podem alterar bastante o esforço real. Não trate este plano como certificado ou garantia.' }], faq, bibliography: [{ name: 'Conselho da Europa: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Defina a data', text: 'Escolha a data em que deseja revisar o perfil alvo.' }, { name: 'Avalie as habilidades', text: 'Escolha níveis atuais e alvos do QECR para as cinco habilidades.' }, { name: 'Proteja o tempo', text: 'Informe um tempo de estudo que você consegue manter toda semana e mapeie o perfil.' }, { name: 'Leia e revise', text: 'Use status, caminhos e datas dos marcos para ajustar o plano depois da prática real.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Целевая дата', weeklyHours: 'Время занятий в неделю', listening: 'Аудирование', reading: 'Чтение', spokenInteraction: 'Устное взаимодействие', spokenProduction: 'Устная речь', writing: 'Письмо', currentLevel: 'Текущий уровень', targetLevel: 'Целевой уровень', planButton: 'Построить мой профиль', resetButton: 'Сбросить', presetLabel: 'Выберите темп', gentlePreset: '3 часа', steadyPreset: '5 часов', focusedPreset: '10 часов', resultTitle: 'Профиль навыков', emptyResult: 'Укажите пять текущих и целевых уровней, дату и время занятий в неделю, чтобы увидеть карту профиля.', statusOnTrack: 'В календаре есть запас относительно оценки.', statusTight: 'Календарь достигает нижней оценки.', statusInsufficient: 'Календарь короче нижней оценки.', totalHours: 'Расчётные часы занятий с преподавателем', availableHours: 'Доступные часы', weeksAvailable: 'Доступные недели', skillProfile: 'Пять траекторий навыков', milestoneMap: 'Карта этапов', hours: 'часов', week: 'неделя', noGap: 'Цель достигнута', checkInputs: 'Проверьте данные', futureDateError: 'Выберите будущую целевую дату.', dateFormatError: 'Выберите корректную целевую дату.', hoursError: 'Время занятий в неделю должно быть от 0,5 до 40 часов.', targetBelowCurrentError: 'Целевой уровень не может быть ниже текущего.', noProgressError: 'Повышайте хотя бы один целевой уровень, чтобы создать план.', plannerNote: 'Используйте уровни CEFR как отправную точку самооценки, а не как результат сертификации.', hoursEstimateNote: 'Диапазон часов служит для планирования и меняется в зависимости от языковой дистанции, практики и качества обучения.', skillListening: 'Аудирование', skillReading: 'Чтение', skillSpokenInteraction: 'Устное взаимодействие', skillSpokenProduction: 'Устная речь', skillWriting: 'Письмо' };
6
+ const faq = [{ question: 'Что рассчитывает планировщик профиля языковых навыков CEFR?', answer: 'Он сравнивает текущий и целевой уровни для аудирования, чтения, устного взаимодействия, устной речи и письма. Затем оценивает диапазон часов занятий с преподавателем, сопоставляет его с доступными неделями и распределяет усилия между навыками с разрывом.' }, { question: 'Можно ли использовать его как языковой тест?', answer: 'Нет. Планировщик организует самооценку и учебную нагрузку. Он не проверяет результат, не подтверждает сертификат и не гарантирует достижение уровня к определённой дате.' }, { question: 'Почему часы показаны диапазоном?', answer: 'Прогресс зависит от языковой дистанции, прошлого опыта, контакта с языком, качества преподавания, возможностей практики и доказательств, по которым оценивается уровень. Поэтому диапазон является ориентиром для планирования, а не обещанием.' }, { question: 'Как планировщик распределяет время на неделю?', answer: 'Навыки с большим разрывом уровней получают большую долю времени. Навыки, уже достигшие цели, отмечаются как достигнутые и не получают запланированной доли.' }, { question: 'Что делать при недостаточном статусе?', answer: 'Добавьте реалистичное время на неделю, перенесите целевую дату или снизьте один или несколько целевых уровней. Пересмотрите профиль после фиксированного учебного блока, а не пытайтесь наверстать все часы сразу.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Планировщик профиля языковых навыков CEFR', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/ru/planer-profily-yazykovykh-navykov-cefr' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'Создать план профиля навыков CEFR', step: [{ '@type': 'HowToStep', name: 'Задать дату', text: 'Выберите дату, к которой хотите проверить целевой профиль.' }, { '@type': 'HowToStep', name: 'Оценить навыки', text: 'Выберите текущий и целевой уровень CEFR для всех пяти навыков.' }, { '@type': 'HowToStep', name: 'Выделить время', text: 'Введите время занятий, которое сможете поддерживать каждую неделю.' }, { '@type': 'HowToStep', name: 'Прочитать карту', text: 'Используйте статус, траектории и этапы, чтобы изменить план.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'planer-profily-yazykovykh-navykov-cefr', title: 'Планировщик профиля языковых навыков CEFR', description: 'Сопоставьте текущие и целевые уровни CEFR для пяти языковых навыков, оцените часы занятий и задайте реалистичные этапы.', ui, seo: [{ type: 'title', text: 'Разложите языковую цель на пять навыков', level: 2 }, { type: 'paragraph', html: 'Один общий уровень языка может скрывать неравномерный профиль. Вы можете читать на B1, воспринимать речь на A2 и всё ещё нуждаться в практике устного взаимодействия. Этот планировщик показывает различия и превращает их в нагрузку и карту этапов.' }, { type: 'title', text: 'Как работает оценка профиля', level: 2 }, { type: 'paragraph', html: 'Расчёт назначает широкий диапазон часов занятий для каждого шага CEFR и складывает диапазоны всех навыков с разрывом. Недельное распределение использует середину каждого диапазона, поэтому разрыв в два уровня получает больше времени, чем разрыв в один уровень. Это модель планирования, а не измерение способностей.' }, { type: 'title', text: 'Как читать статус календаря', level: 2 }, { type: 'table', headers: ['Статус', 'Значение', 'Следующий шаг'], rows: [['Есть запас', 'Доступные часы достигают верхней границы общего диапазона.', 'Сохраняйте режим и используйте этапы для проверки.'], ['Достигнута нижняя оценка', 'Доступные часы достигают нижней, но не верхней границы.', 'Сохраняйте гибкость цели и оставьте время на обратную связь и повторение.'], ['Меньше оценки', 'Доступные часы не достигают нижней границы общего диапазона.', 'Перенесите дату, снизьте цель или добавьте устойчивое время.']] }, { type: 'title', text: 'Превратите этапы в доказательства', level: 2 }, { type: 'paragraph', html: 'Используйте каждый этап как повод собрать доказательства, а не как автоматическое повышение. Сохраняйте образец аудирования, короткий разговор, задание на чтение и текст, соответствующие описанию целевого уровня. Если навык остановился, измените практику, а не прячьте разрыв в общем среднем.' }, { type: 'list', items: ['Выбирайте уровни по недавним заданиям, которые можете конкретно описать.', 'Сохраняйте устойчивое недельное время на всём протяжении календаря.', 'Проверяйте профиль после фиксированного блока и меняйте только одну переменную.', 'Используйте отзыв преподавателя или официальную оценку, если уровень важен для учёбы, работы или переезда.'] }, { type: 'tip', title: 'Ограничения оценки', html: 'CEFR описывает коммуникативные способности с помощью дескрипторов и не устанавливает универсальное число часов. Языковая дистанция, опыт, контакт, обратная связь и условия обучения могут сильно изменить реальную нагрузку. Не считайте этот план сертификатом или гарантией.' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Задать дату', text: 'Выберите дату, к которой хотите проверить целевой профиль.' }, { name: 'Оценить навыки', text: 'Выберите текущие и целевые уровни CEFR для пяти навыков.' }, { name: 'Выделить время', text: 'Введите устойчивое еженедельное время занятий и постройте профиль.' }, { name: 'Прочитать и изменить', text: 'Используйте статус, траектории и даты этапов, чтобы изменить план после практики.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Måldatum', weeklyHours: 'Studietid per vecka', listening: 'Hörförståelse', reading: 'Läsförståelse', spokenInteraction: 'Muntlig interaktion', spokenProduction: 'Muntlig produktion', writing: 'Skriftlig produktion', currentLevel: 'Nuvarande nivå', targetLevel: 'Målnivå', planButton: 'Kartlägg min profil', resetButton: 'Återställ', presetLabel: 'Börja med ett tempo', gentlePreset: '3 timmar', steadyPreset: '5 timmar', focusedPreset: '10 timmar', resultTitle: 'Din färdighetsprofil', emptyResult: 'Ange dina fem nuvarande nivåer och målnivåer, datum och tid per vecka för att se profilkartan.', statusOnTrack: 'Din kalender har utrymme för uppskattningen.', statusTight: 'Din kalender når den lägre uppskattningen.', statusInsufficient: 'Din kalender är kortare än den lägre uppskattningen.', totalHours: 'Beräknade handledda timmar', availableHours: 'Tillgängliga timmar', weeksAvailable: 'Tillgängliga veckor', skillProfile: 'Fem färdighetsvägar', milestoneMap: 'Milstolpekarta', hours: 'timmar', week: 'vecka', noGap: 'Målnivå nådd', checkInputs: 'Kontrollera uppgifterna', futureDateError: 'Välj ett framtida måldatum.', dateFormatError: 'Välj ett giltigt måldatum.', hoursError: 'Studietiden per vecka måste vara mellan 0,5 och 40 timmar.', targetBelowCurrentError: 'Målnivån får inte vara lägre än den nuvarande nivån.', noProgressError: 'Höj minst en målnivå för att skapa en plan.', plannerNote: 'Använd CEFR-nivåerna som startpunkt för självskattning, inte som ett certifieringsresultat.', hoursEstimateNote: 'Timintervallet är ett planeringsunderlag och ändras med språkligt avstånd, exponering och studiekvalitet.', skillListening: 'Hörförståelse', skillReading: 'Läsförståelse', skillSpokenInteraction: 'Muntlig interaktion', skillSpokenProduction: 'Muntlig produktion', skillWriting: 'Skriftlig produktion' };
6
+ const faq = [{ question: 'Vad beräknar planeringen av CEFR-färdighetsprofilen?', answer: 'Den jämför nuvarande nivå och målnivå för hörförståelse, läsförståelse, muntlig interaktion, muntlig produktion och skriftlig produktion. Den uppskattar ett intervall av handledda timmar, jämför det med tillgängliga veckor och fördelar arbetet mellan färdigheter med ett gap.' }, { question: 'Kan jag använda den som ett språktest?', answer: 'Nej. Planeraren organiserar en självskattning och en studiebelastning. Den testar inte prestation, verifierar inte ett certifikat och garanterar inte att du når en nivå på ett visst datum.' }, { question: 'Varför visas timmarna som ett intervall?', answer: 'Framsteg varierar med språkligt avstånd, tidigare erfarenhet, exponering, undervisningens kvalitet, möjligheter att öva och de bevis som används för att bedöma en nivå. Intervallet är därför ett planeringsunderlag, inte ett löfte.' }, { question: 'Hur fördelar planeraren tiden per vecka?', answer: 'Färdigheter med större nivåskillnader får en större del av den veckovisa tiden. Färdigheter som redan nått målet visas som uppnådda och får ingen planerad andel.' }, { question: 'Vad gör jag när statusen är otillräcklig?', answer: 'Lägg till realistisk tid per vecka, flytta måldatumet eller sänk en eller flera målnivåer. Kontrollera profilen efter ett fast studieblock i stället för att försöka ta igen alla timmar på en gång.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'Planerare för CEFR språkfärdighetsprofil', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/sv/cefr-planer-for-sprakfardighetsprofil' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'Skapa en CEFR plan för färdighetsprofil', step: [{ '@type': 'HowToStep', name: 'Ange datumet', text: 'Välj datumet då du vill granska målprofilen.' }, { '@type': 'HowToStep', name: 'Bedöm varje färdighet', text: 'Välj nuvarande nivå och målnivå enligt CEFR för alla fem färdigheter.' }, { '@type': 'HowToStep', name: 'Säkra veckotiden', text: 'Ange den studietid du kan hålla varje vecka.' }, { '@type': 'HowToStep', name: 'Läs kartan', text: 'Använd status, färdighetsvägar och milstolpar för att justera planen.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'cefr-planer-for-sprakfardighetsprofil', title: 'Planerare för CEFR språkfärdighetsprofil', description: 'Kartlägg nuvarande och önskade CEFR-nivåer för fem språkfärdigheter, uppskatta handledda timmar och sätt realistiska milstolpar.', ui, seo: [{ type: 'title', text: 'Kartlägg ett språk mål över fem färdigheter', level: 2 }, { type: 'paragraph', html: 'En enda språknivå kan dölja en ojämn profil. Du kanske läser på B1, lyssnar på A2 och fortfarande behöver öva mer på muntlig interaktion. Planeraren gör skillnaderna synliga och omvandlar dem till arbetsmängd och milstolpar.' }, { type: 'title', text: 'Så fungerar profilens uppskattning', level: 2 }, { type: 'paragraph', html: 'Beräkningen tilldelar ett brett intervall av handledda timmar till varje CEFR-steg och summerar intervallen för varje färdighet med ett gap. Veckofördelningen följer mitten av varje intervall, så ett gap på två nivåer får mer tid än ett gap på en nivå. Resultatet är en planeringsmodell, inte ett mått på förmåga.' }, { type: 'title', text: 'Läs kalenderstatusen', level: 2 }, { type: 'table', headers: ['Status', 'Betydelse', 'Nästa steg'], rows: [['Utrymme för uppskattningen', 'Tillgängliga timmar når den övre gränsen för det sammanlagda intervallet.', 'Behåll rutinen och använd milstolparna som kontrollpunkter.'], ['Den lägre uppskattningen nådd', 'Tillgängliga timmar når den lägre men inte den övre gränsen.', 'Håll målet flexibelt och skydda tid för återkoppling och repetition.'], ['Kortare än uppskattningen', 'Tillgängliga timmar når inte den lägre gränsen.', 'Flytta datumet, minska ett mål eller lägg till hållbar tid.']] }, { type: 'title', text: 'Gör milstolpar till bevis', level: 2 }, { type: 'paragraph', html: 'Använd varje milstolpe som en anledning att samla bevis, inte som en automatisk befordran. Spara ett lyssningsprov, ett kort samtal, en läsuppgift och en text som motsvarar beskrivningen av målnivån. Om en färdighet stannar, ändra övningen i stället för att dölja gapet i ett genomsnitt.' }, { type: 'list', items: ['Välj nivåer från aktuella uppgifter som du faktiskt kan beskriva.', 'Håll veckotiden hållbar under hela kalendern.', 'Granska profilen efter ett fast studieblock och ändra en variabel.', 'Använd lärarfeedback eller en officiell bedömning när nivån är viktig för studier, arbete eller migration.'] }, { type: 'tip', title: 'Begränsningar i uppskattningen', html: 'CEFR beskriver kommunikativ förmåga med deskriptorer och föreskriver inte ett universellt antal timmar. Språkligt avstånd, erfarenhet, exponering, återkoppling och studieomständigheter kan ändra den faktiska arbetsmängden betydligt. Se inte planen som ett certifikat eller en garanti.' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Ange datumet', text: 'Välj datumet då du vill granska målprofilen.' }, { name: 'Bedöm färdigheterna', text: 'Välj nuvarande och önskade CEFR-nivåer för alla fem färdigheter.' }, { name: 'Säkra tiden', text: 'Ange en studietid du kan hålla varje vecka och kartlägg profilen.' }, { name: 'Läs och justera', text: 'Använd status, vägar och datum för att ändra planen efter verklig övning.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: 'Hedef tarih', weeklyHours: 'Haftalık çalışma süresi', listening: 'Dinleme', reading: 'Okuma', spokenInteraction: 'Sözlü etkileşim', spokenProduction: 'Sözlü anlatım', writing: 'Yazma', currentLevel: 'Mevcut seviye', targetLevel: 'Hedef seviye', planButton: 'Profilimi haritala', resetButton: 'Sıfırla', presetLabel: 'Bir tempoyla başla', gentlePreset: '3 saat', steadyPreset: '5 saat', focusedPreset: '10 saat', resultTitle: 'Dil becerisi profilin', emptyResult: 'Profil haritasını görmek için beş mevcut ve hedef seviyeni, tarihi ve haftalık süreyi ayarla.', statusOnTrack: 'Takviminde tahmin için yer var.', statusTight: 'Takvimin alt tahmine ulaşıyor.', statusInsufficient: 'Takvimin alt tahminden daha kısa.', totalHours: 'Tahmini yönlendirmeli saat', availableHours: 'Kullanılabilir saat', weeksAvailable: 'Kullanılabilir hafta', skillProfile: 'Beş beceri yolu', milestoneMap: 'Kilometre taşı haritası', hours: 'saat', week: 'hafta', noGap: 'Hedefte', checkInputs: 'Girdilerini kontrol et', futureDateError: 'Gelecekte bir hedef tarih seç.', dateFormatError: 'Geçerli bir hedef tarih seç.', hoursError: 'Haftalık çalışma süresi 0,5 ile 40 saat arasında olmalı.', targetBelowCurrentError: 'Hedef seviye mevcut seviyenin altında olamaz.', noProgressError: 'Plan oluşturmak için en az bir hedef seviyeyi yükselt.', plannerNote: 'CEFR seviyelerini sertifika sonucu olarak değil, öz değerlendirme başlangıç noktası olarak kullan.', hoursEstimateNote: 'Saat aralığı bir planlama bandıdır ve dil uzaklığına, maruz kalmaya ve çalışma kalitesine göre değişir.', skillListening: 'Dinleme', skillReading: 'Okuma', skillSpokenInteraction: 'Sözlü etkileşim', skillSpokenProduction: 'Sözlü anlatım', skillWriting: 'Yazma' };
6
+ const faq = [{ question: 'CEFR dil becerisi profil planlayıcısı neyi hesaplar?', answer: 'Dinleme, okuma, sözlü etkileşim, sözlü anlatım ve yazma için mevcut ve hedef CEFR seviyelerini karşılaştırır. Yönlendirmeli çalışma saati aralığı tahmin eder, bunu kullanılabilir haftalarla karşılaştırır ve çabayı aralığı olan becerilere dağıtır.' }, { question: 'Bunu dil testi olarak kullanabilir miyim?', answer: 'Hayır. Planlayıcı öz değerlendirmeyi ve çalışma yükünü düzenler. Performansı test etmez, sertifika doğrulamaz ve belirli bir tarihte bir seviyeye ulaşacağını garanti etmez.' }, { question: 'Saatler neden aralık olarak gösteriliyor?', answer: 'İlerleme dil uzaklığına, önceki deneyime, maruz kalmaya, öğretim kalitesine, pratik fırsatlarına ve seviyeyi değerlendirmek için kullanılan kanıtlara göre değişir. Bu yüzden aralık bir planlama ölçüsüdür, söz değildir.' }, { question: 'Haftalık süre nasıl dağıtılıyor?', answer: 'Seviye farkı daha büyük olan beceriler haftalık süreden daha büyük pay alır. Hedefine ulaşmış beceriler hedefte olarak gösterilir ve planlı pay almaz.' }, { question: 'Durum yetersizse ne yapmalıyım?', answer: 'Gerçekçi haftalık süre ekle, hedef tarihi ileri al veya bir ya da daha fazla hedef seviyeyi düşür. Tüm eksik saatleri bir anda telafi etmeye çalışmak yerine sabit bir çalışma döneminden sonra profili yeniden kontrol et.' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'CEFR dil becerisi profil planlayıcısı', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/tr/cefr-dil-beceri-profili-planlayici' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: 'CEFR beceri profili planı oluşturma', step: [{ '@type': 'HowToStep', name: 'Tarihi belirle', text: 'Hedef profili gözden geçirmek istediğin tarihi seç.' }, { '@type': 'HowToStep', name: 'Her beceriyi değerlendir', text: 'Beş becerinin her biri için mevcut ve hedef CEFR seviyesini seç.' }, { '@type': 'HowToStep', name: 'Haftalık süreyi koru', text: 'Her hafta sürdürebileceğin çalışma süresini gir.' }, { '@type': 'HowToStep', name: 'Haritayı oku', text: 'Planı ayarlamak için durumu, beceri yollarını ve kilometre taşlarını kullan.' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'cefr-dil-beceri-profili-planlayici', title: 'CEFR dil becerisi profil planlayıcısı', description: 'Beş dil becerisinde mevcut ve hedef CEFR seviyelerini haritala, yönlendirmeli saatleri tahmin et ve gerçekçi bir çalışma planı için kilometre taşları belirle.', ui, seo: [{ type: 'title', text: 'Dil hedefini beş beceriye ayır', level: 2 }, { type: 'paragraph', html: 'Tek bir dil seviyesi dengesiz bir profili gizleyebilir. Okuman B1, dinlemen A2 olabilir ve sözlü etkileşim için daha fazla pratiğe ihtiyaç duyabilirsin. Bu planlayıcı farkları görünür kılar ve onları çalışma yükü ile kilometre taşı haritasına dönüştürür.' }, { type: 'title', text: 'Profil tahmini nasıl çalışır', level: 2 }, { type: 'paragraph', html: 'Hesaplama her CEFR adımına geniş bir yönlendirmeli saat aralığı verir ve aralığı bulunan her becerinin değerlerini toplar. Haftalık dağılım her aralığın orta noktasını izler; bu nedenle iki seviyelik fark, tek seviyelik farktan daha fazla süre alır. Sonuç bir planlama modelidir, beceri ölçümü değildir.' }, { type: 'title', text: 'Takvim durumunu oku', level: 2 }, { type: 'table', headers: ['Durum', 'Anlamı', 'Sonraki yararlı adım'], rows: [['Tahmin için alan var', 'Kullanılabilir saatler birleşik aralığın üst sınırına ulaşır.', 'Rutini koru ve kilometre taşlarını gözden geçirme noktaları olarak kullan.'], ['Alt tahmine ulaşıldı', 'Kullanılabilir saatler alt sınıra ulaşır, ancak üst sınıra ulaşmaz.', 'Hedefi esnek tut ve geri bildirim ile tekrar için zaman ayır.'], ['Tahminden kısa', 'Kullanılabilir saatler birleşik aralığın alt sınırına ulaşmaz.', 'Tarihi ileri al, bir hedefi azalt veya sürdürülebilir süre ekle.']] }, { type: 'title', text: 'Kilometre taşlarını kanıta dönüştür', level: 2 }, { type: 'paragraph', html: 'Her kilometre taşını otomatik bir yükselme değil, kanıt toplama fırsatı olarak kullan. Hedef seviyenin tanımına uyan bir dinleme örneği, kısa konuşma, okuma görevi ve yazı kaydet. Bir beceri duraklarsa farkı genel ortalamada gizlemek yerine pratiğini değiştir.' }, { type: 'list', items: ['Açıklayabileceğin güncel görevlerden seviyeler seç.', 'Tüm takvim boyunca sürdürülebilir haftalık süreyi koru.', 'Sabit bir çalışma bloğundan sonra profili gözden geçir ve tek değişkeni değiştir.', 'Seviye eğitim, iş veya göç için önemliyse öğretmen geri bildirimi ya da resmi değerlendirme kullan.'] }, { type: 'tip', title: 'Tahminin sınırları', html: 'CEFR iletişim becerisini tanımlayıcılarla açıklar ve evrensel bir saat sayısı öngörmez. Dil uzaklığı, deneyim, maruz kalma, geri bildirim ve çalışma koşulları gerçek yükü önemli ölçüde değiştirebilir. Bu planı sertifika veya garanti olarak görme.' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: 'Tarihi belirle', text: 'Hedef profili gözden geçirmek istediğin tarihi seç.' }, { name: 'Becerileri değerlendir', text: 'Beş becerinin her biri için mevcut ve hedef CEFR seviyesini seç.' }, { name: 'Süreyi koru', text: 'Her hafta sürdürebileceğin çalışma süresini gir ve profili haritala.' }, { name: 'Oku ve düzenle', text: 'Gerçek pratikten sonra durumu, yolları ve tarihleri kullanarak planı ayarla.' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,11 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CefrSkillProfileUI } from '../ui';
4
+
5
+ const ui: CefrSkillProfileUI = { targetDate: '目标日期', weeklyHours: '每周学习时间', listening: '听力', reading: '阅读', spokenInteraction: '口语互动', spokenProduction: '口语表达', writing: '写作', currentLevel: '当前等级', targetLevel: '目标等级', planButton: '绘制我的档案', resetButton: '重置', presetLabel: '选择学习节奏', gentlePreset: '3小时', steadyPreset: '5小时', focusedPreset: '10小时', resultTitle: '你的技能档案', emptyResult: '设置五项当前等级和目标等级、日期及每周时间,即可查看档案地图。', statusOnTrack: '日程为预计学习量留有余地。', statusTight: '日程达到较低的预计值。', statusInsufficient: '日程短于较低的预计值。', totalHours: '预计指导学习小时数', availableHours: '可用小时数', weeksAvailable: '可用周数', skillProfile: '五条技能路径', milestoneMap: '里程碑地图', hours: '小时', week: '周', noGap: '已达到目标', checkInputs: '请检查输入', futureDateError: '请选择未来的目标日期。', dateFormatError: '请选择有效的目标日期。', hoursError: '每周学习时间必须在0.5到40小时之间。', targetBelowCurrentError: '目标等级不能低于当前等级。', noProgressError: '至少提高一项目标等级才能创建计划。', plannerNote: '请将CEFR等级作为自我评估的起点,而不是认证结果。', hoursEstimateNote: '小时范围用于规划,会随语言距离、接触量和学习质量变化。', skillListening: '听力', skillReading: '阅读', skillSpokenInteraction: '口语互动', skillSpokenProduction: '口语表达', skillWriting: '写作' };
6
+ const faq = [{ question: 'CEFR语言技能档案规划器计算什么?', answer: '它比较听力、阅读、口语互动、口语表达和写作的当前CEFR等级与目标等级,估算指导学习小时范围,与可用周数比较,并将学习量分配给存在差距的技能。' }, { question: '可以把它当作语言测试吗?', answer: '不可以。规划器用于整理自我评估和学习量,不测试表现,不验证证书,也不保证你会在某个日期达到某个等级。' }, { question: '为什么小时数显示为范围?', answer: '进步会受到语言距离、既往经验、接触量、教学质量、练习机会以及评估等级所用证据的影响。因此范围是规划参考,而不是承诺。' }, { question: '每周时间如何分配?', answer: '等级差距更大的技能会获得更大的每周时间份额。已经达到目标的技能会显示为已达到目标,不会获得计划份额。' }, { question: '如果状态不足该怎么办?', answer: '增加现实可行的每周时间、推迟目标日期,或降低一个或多个目标等级。在固定学习阶段后重新检查档案,不要一次性补回所有缺少的小时。' }];
7
+ const appSchema: SoftwareApplication = { '@type': 'SoftwareApplication', name: 'CEFR语言技能档案规划器', applicationCategory: 'EducationalApplication', operatingSystem: 'Any', isAccessibleForFree: true, url: 'https://gamebob.dev/zh/cefr-yuyan-jineng-peixun-guihua' };
8
+ const howToSchema: HowTo = { '@type': 'HowTo', name: '创建CEFR技能档案计划', step: [{ '@type': 'HowToStep', name: '设置日期', text: '选择想要检查目标档案的日期。' }, { '@type': 'HowToStep', name: '评估每项技能', text: '为五项技能选择当前和目标CEFR等级。' }, { '@type': 'HowToStep', name: '保证每周时间', text: '输入每周能够持续投入的学习时间。' }, { '@type': 'HowToStep', name: '阅读地图', text: '使用状态、技能路径和里程碑调整计划。' }] };
9
+ const faqSchema: FAQPage = { '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) };
10
+ const withContext = (schema: SoftwareApplication | HowTo | FAQPage): Record<string, unknown> => ({ '@context': 'https://schema.org', ...schema });
11
+ export const content: ToolLocaleContent<CefrSkillProfileUI> = { slug: 'cefr-language-skill-profile-planner', title: 'CEFR语言技能档案规划器', description: '绘制五项语言技能的当前和目标CEFR等级,估算指导学习小时数,并为现实的学习计划设置里程碑。', ui, seo: [{ type: 'title', text: '用五项技能绘制语言目标', level: 2 }, { type: 'paragraph', html: '一个整体语言等级可能隐藏技能之间的不平衡。你可能阅读达到B1,听力只有A2,口语互动还需要更多练习。这个规划器会显示这些差距,并把它们转化为学习量和里程碑地图。' }, { type: 'title', text: '档案估算如何工作', level: 2 }, { type: 'paragraph', html: '计算会为每个CEFR阶段分配一个宽泛的指导学习小时范围,并累加每项有差距的技能。每周分配依据各范围的中点,因此两级差距会比一级差距获得更多时间。这是规划模型,不是能力测量。' }, { type: 'title', text: '阅读日程状态', level: 2 }, { type: 'table', headers: ['状态', '含义', '下一步'], rows: [['有预计余量', '可用小时数达到组合范围的上限。', '保持习惯,把里程碑作为复查节点。'], ['达到较低预计值', '可用小时数达到下限,但没有达到上限。', '保持目标灵活,为反馈和复习留出时间。'], ['短于预计值', '可用小时数没有达到组合范围的下限。', '推迟日期、降低目标,或增加可持续的学习时间。']] }, { type: 'title', text: '把里程碑变成证据', level: 2 }, { type: 'paragraph', html: '把每个里程碑当作收集证据的机会,而不是自动升级。记录符合目标等级描述的听力样本、简短对话、阅读任务和写作。若某项技能停滞,应改变练习方式,而不是让总体平均数掩盖差距。' }, { type: 'list', items: ['根据能够具体描述的近期任务选择等级。', '在整个日程中保持可持续的每周时间。', '在固定学习阶段后检查档案,每次只调整一个变量。', '如果等级关系到升学、工作或移民,请使用教师反馈或官方评估。'] }, { type: 'tip', title: '估算的局限', html: 'CEFR通过描述语说明交际能力,并不规定一个普遍适用的小时数。语言距离、经验、接触量、反馈和学习条件都会显著改变实际学习量。请不要把这个计划当成证书或保证。' }], faq, bibliography: [{ name: 'Council of Europe: Marco común europeo de referencia para las lenguas', url: 'https://rm.coe.int/marco-comun-europeo-de-referencia-para-las-lenguas-aprendizaje-ensenan/1680a52d53' }, { name: 'Cambridge English: Guided learning hours', url: 'https://support.cambridgeenglish.org/hc/en-gb/articles/202838506-Guided-learning-hours' }], howTo: [{ name: '设置日期', text: '选择想要检查目标档案的日期。' }, { name: '评估技能', text: '为五项技能选择当前和目标CEFR等级。' }, { name: '保证时间', text: '输入每周可以持续投入的学习时间,然后绘制档案。' }, { name: '阅读并调整', text: '利用状态、路径和里程碑日期,在真实练习后调整计划。' }], schemas: [withContext(appSchema), withContext(howToSchema), withContext(faqSchema)] };
@@ -0,0 +1,14 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { cefrLanguageSkillProfilePlanner } from './entry';
3
+
4
+ export type { CefrSkillProfileUI, CefrLanguageSkillProfilePlannerLocaleContent } from './entry';
5
+ export { cefrLanguageSkillProfilePlanner } from './entry';
6
+ export { calculateCefrProfile, CEFR_LEVELS, HOURS_PER_STEP, SKILL_KEYS } from './logic';
7
+ export type { CefrLevel, CefrPlannerInputs, CefrPlannerResult, CefrProfilePlan, ProfileStatus, SkillInput, SkillKey, SkillMilestone, SkillPlan } from './types';
8
+
9
+ export const CEFR_LANGUAGE_SKILL_PROFILE_PLANNER_TOOL: ToolDefinition = {
10
+ entry: cefrLanguageSkillProfilePlanner,
11
+ Component: () => import('./component.astro'),
12
+ SEOComponent: () => import('./seo.astro'),
13
+ BibliographyComponent: () => import('./bibliography.astro'),
14
+ };
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { calculateCefrProfile, getSkillHours } from './logic';
3
+ import type { CefrPlannerInputs } from './types';
4
+
5
+ const inputs: CefrPlannerInputs = {
6
+ targetDate: '2027-08-30', weeklyHours: 30,
7
+ skills: {
8
+ listening: { current: 'A2', target: 'B2' }, reading: { current: 'B1', target: 'B2' },
9
+ spokenInteraction: { current: 'A2', target: 'B1' }, spokenProduction: { current: 'A1', target: 'B1' }, writing: { current: 'B1', target: 'B1' },
10
+ },
11
+ };
12
+
13
+ describe('calculateCefrProfile', () => {
14
+ it('calculates gaps, allocation, schedule, status, and milestones', () => {
15
+ const result = calculateCefrProfile(inputs, new Date('2026-08-30T00:00:00Z'));
16
+ expect(result.ok).toBe(true);
17
+ if (!result.ok) return;
18
+ expect(result.plan.totalHoursLow).toBe(1090);
19
+ expect(result.plan.totalHoursHigh).toBe(1340);
20
+ expect(result.plan.availableHours).toBe(1590);
21
+ expect(result.plan.status).toBe('on-track');
22
+ expect(result.plan.skills.find((skill) => skill.key === 'writing')?.allocation).toBe(0);
23
+ expect(result.plan.milestones).toHaveLength(4);
24
+ });
25
+
26
+ it('rejects invalid dates, reversed levels, and empty progress', () => {
27
+ expect(calculateCefrProfile({ ...inputs, targetDate: '2026-02-31' }, new Date('2026-08-30T00:00:00Z')).ok).toBe(false);
28
+ expect(calculateCefrProfile({ ...inputs, skills: { ...inputs.skills, writing: { current: 'B2', target: 'B1' } } }).ok).toBe(false);
29
+ const flat = Object.fromEntries(Object.keys(inputs.skills).map((key) => [key, { current: 'B1', target: 'B1' }])) as CefrPlannerInputs['skills'];
30
+ expect(calculateCefrProfile({ ...inputs, skills: flat }).ok).toBe(false);
31
+ });
32
+
33
+ it('uses a documented range for each CEFR step', () => {
34
+ expect(getSkillHours('A1', 'A2')).toEqual({ low: 150, high: 200 });
35
+ expect(getSkillHours('A2', 'B2')).toEqual({ low: 380, high: 460 });
36
+ expect(getSkillHours('C1', 'C1')).toEqual({ low: 0, high: 0 });
37
+ });
38
+ });
@@ -0,0 +1,131 @@
1
+ import type { CefrLevel, CefrPlannerInputs, CefrPlannerResult, ProfileStatus, SkillKey, SkillMilestone, SkillPlan } from './types';
2
+
3
+ export const CEFR_LEVELS: CefrLevel[] = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'];
4
+ export const SKILL_KEYS: SkillKey[] = ['listening', 'reading', 'spokenInteraction', 'spokenProduction', 'writing'];
5
+ export const LEVEL_VALUES: Record<CefrLevel, number> = { A1: 1, A2: 2, B1: 3, B2: 4, C1: 5, C2: 6 };
6
+ export const HOURS_PER_STEP: Record<CefrLevel, readonly [number, number]> = {
7
+ A1: [150, 200], A2: [180, 220], B1: [200, 240], B2: [220, 260], C1: [240, 280], C2: [0, 0],
8
+ };
9
+
10
+ const DAY_MS = 86_400_000;
11
+
12
+ export function calculateCefrProfile(inputs: CefrPlannerInputs, today = new Date()): CefrPlannerResult {
13
+ const validationError = validateInputs(inputs, today);
14
+ if (validationError) return { ok: false, error: validationError };
15
+ const todayDate = startOfDay(today);
16
+ const weeksAvailable = getWeeksAvailable(inputs.targetDate, todayDate);
17
+ const skills = buildSkillPlans(inputs);
18
+ const totalHoursLow = sum(skills.map((skill) => skill.hoursLow));
19
+ const totalHoursHigh = sum(skills.map((skill) => skill.hoursHigh));
20
+ const availableHours = round(inputs.weeklyHours * weeksAvailable, 1);
21
+ const milestones = buildMilestones(skills, inputs.targetDate, weeksAvailable, todayDate);
22
+ return { ok: true, plan: { inputs, weeksAvailable, totalHoursLow, totalHoursHigh, availableHours, status: getStatus(availableHours, totalHoursLow, totalHoursHigh), skills, milestones } };
23
+ }
24
+
25
+ export function validateInputs(inputs: CefrPlannerInputs, today = new Date()): string | null {
26
+ return validateDateInput(inputs.targetDate, today) ?? validateHoursInput(inputs.weeklyHours) ?? validateSkillInputs(inputs.skills);
27
+ }
28
+
29
+ function validateDateInput(value: string, today: Date): string | null {
30
+ if (!isValidDate(value)) return 'Choose a valid target date.';
31
+ if (parseDate(value)!.getTime() <= startOfDay(today).getTime()) return 'Choose a target date in the future.';
32
+ return null;
33
+ }
34
+
35
+ function validateHoursInput(hours: number): string | null {
36
+ return !Number.isFinite(hours) || hours < 0.5 || hours > 40 ? 'Weekly study time must be between 0.5 and 40 hours.' : null;
37
+ }
38
+
39
+ function validateSkillInputs(skills: CefrPlannerInputs['skills']): string | null {
40
+ for (const key of SKILL_KEYS) {
41
+ const skill = skills[key];
42
+ if (!skill || !CEFR_LEVELS.includes(skill.current) || !CEFR_LEVELS.includes(skill.target)) return 'Choose a CEFR level for every skill.';
43
+ if (LEVEL_VALUES[skill.target] < LEVEL_VALUES[skill.current]) return 'A target level cannot be below the current level.';
44
+ }
45
+ return SKILL_KEYS.every((key) => skills[key].current === skills[key].target) ? 'Raise at least one target level to make a profile plan.' : null;
46
+ }
47
+
48
+ export function getSkillHours(current: CefrLevel, target: CefrLevel): { low: number; high: number } {
49
+ const start = LEVEL_VALUES[current];
50
+ const end = LEVEL_VALUES[target];
51
+ let low = 0;
52
+ let high = 0;
53
+ for (let value = start; value < end; value += 1) {
54
+ const level = CEFR_LEVELS[value - 1]!;
55
+ low += HOURS_PER_STEP[level][0];
56
+ high += HOURS_PER_STEP[level][1];
57
+ }
58
+ return { low, high };
59
+ }
60
+
61
+ function buildSkillPlans(inputs: CefrPlannerInputs): SkillPlan[] {
62
+ const raw = SKILL_KEYS.map((key) => {
63
+ const skill = inputs.skills[key];
64
+ const gapLevels = LEVEL_VALUES[skill.target] - LEVEL_VALUES[skill.current];
65
+ const hours = getSkillHours(skill.current, skill.target);
66
+ return { key, current: skill.current, target: skill.target, gapLevels, hoursLow: hours.low, hoursHigh: hours.high, weeklyHours: 0, allocation: 0 };
67
+ });
68
+ const active = raw.filter((skill) => skill.gapLevels > 0);
69
+ const totalMidpoint = sum(active.map((skill) => (skill.hoursLow + skill.hoursHigh) / 2));
70
+ const planned = raw.map((skill) => {
71
+ const allocation = skill.gapLevels === 0 ? 0 : Math.round(((skill.hoursLow + skill.hoursHigh) / 2 / totalMidpoint) * 100);
72
+ return { ...skill, allocation, weeklyHours: round(inputs.weeklyHours * allocation / 100, 1) };
73
+ });
74
+ const lastActive = planned.findLastIndex((skill) => skill.gapLevels > 0);
75
+ if (lastActive < 0) return planned;
76
+ const allocation = 100 - sum(planned.filter((_, index) => index !== lastActive).map((item) => item.allocation));
77
+ planned[lastActive] = { ...planned[lastActive]!, allocation: Math.max(0, allocation), weeklyHours: round(inputs.weeklyHours * Math.max(0, allocation) / 100, 1) };
78
+ return planned;
79
+ }
80
+
81
+ function buildMilestones(skills: SkillPlan[], targetDate: string, weeksAvailable: number, today: Date): SkillMilestone[] {
82
+ return skills.filter((skill) => skill.gapLevels > 0).map((skill) => {
83
+ const midpoint = (skill.hoursLow + skill.hoursHigh) / 2;
84
+ const week = Math.min(weeksAvailable, Math.max(1, Math.ceil(midpoint / Math.max(skill.weeklyHours, 0.1))));
85
+ const projectedDate = formatDate(addDays(today, week * 7));
86
+ return { key: skill.key, level: skill.target, week, date: projectedDate > targetDate ? targetDate : projectedDate };
87
+ });
88
+ }
89
+
90
+ function getWeeksAvailable(targetDate: string, today: Date): number {
91
+ const days = Math.floor((parseDate(targetDate)!.getTime() - today.getTime()) / DAY_MS);
92
+ return Math.max(1, Math.ceil(days / 7));
93
+ }
94
+
95
+ function getStatus(available: number, low: number, high: number): ProfileStatus {
96
+ if (available < low) return 'insufficient';
97
+ if (available < high) return 'tight';
98
+ return 'on-track';
99
+ }
100
+
101
+ function isValidDate(value: string): boolean {
102
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) && parseDate(value) !== null;
103
+ }
104
+
105
+ function parseDate(value: string): Date | null {
106
+ const parsed = new Date(`${value}T00:00:00Z`);
107
+ return Number.isNaN(parsed.getTime()) || formatDate(parsed) !== value ? null : parsed;
108
+ }
109
+
110
+ function startOfDay(date: Date): Date {
111
+ return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
112
+ }
113
+
114
+ function addDays(date: Date, days: number): Date {
115
+ const result = new Date(date);
116
+ result.setUTCDate(result.getUTCDate() + days);
117
+ return result;
118
+ }
119
+
120
+ function formatDate(date: Date): string {
121
+ return date.toISOString().slice(0, 10);
122
+ }
123
+
124
+ function sum(values: number[]): number {
125
+ return values.reduce((total, value) => total + value, 0);
126
+ }
127
+
128
+ function round(value: number, decimals: number): number {
129
+ const factor = 10 ** decimals;
130
+ return Math.round(value * factor) / factor;
131
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { cefrLanguageSkillProfilePlanner } from './entry';
4
+
5
+ const locale = Astro.props.locale ?? 'en';
6
+ const content = await cefrLanguageSkillProfilePlanner.i18n.en?.();
7
+ ---
8
+
9
+ {content && <SEORenderer content={{ locale, sections: content.seo }} />}