883 lines
44 KiB
TypeScript
883 lines
44 KiB
TypeScript
// UI language: English by default, Indonesian on request. A module-level store so API helpers
|
||
// and the instruction parser can translate too, not just components.
|
||
import { useSyncExternalStore } from 'react'
|
||
|
||
export type Lang = 'en' | 'id'
|
||
export const LANGS: { code: Lang; label: string }[] = [{ code: 'en', label: 'EN' }, { code: 'id', label: 'ID' }]
|
||
|
||
const en = {
|
||
// Layout
|
||
'nav.brain': 'Brain',
|
||
'nav.instruction': 'Instruction',
|
||
'nav.schedule': 'Schedule',
|
||
'nav.results': 'Results',
|
||
'nav.dashboard': 'Dashboard',
|
||
'nav.settings': 'Settings',
|
||
'nav.production': 'Production',
|
||
'layout.learningSpace': 'Learning space',
|
||
'layout.switchOrg': 'Switch organization',
|
||
'layout.breadcrumb': 'Workspace',
|
||
'layout.signOut': 'Sign out',
|
||
'layout.engine': 'learning-lab engine',
|
||
'layout.creditsTitle': 'Workspace credit balance',
|
||
'layout.toStudio': 'Switch to Learning Lab',
|
||
'layout.devStrip': 'Development phase: connected to the learning-lab engine. Review AI output before using it.',
|
||
'layout.footer': 'A workspace for knowledge that keeps growing.',
|
||
'layout.mainNav': 'Main navigation',
|
||
'layout.language': 'Language',
|
||
|
||
// Auth
|
||
'auth.loading': 'Loading…',
|
||
'auth.eyebrow': 'NEW LEARNING, WITHOUT STARTING FROM SCRATCH',
|
||
'auth.headline1': 'Team knowledge.',
|
||
'auth.headline2': 'New ideas.',
|
||
'auth.headline3': 'Learning that never stops.',
|
||
'auth.copy': 'Upload material to the Brain, give one instruction, and AI keeps creating new learning content on schedule.',
|
||
'auth.benefitUpload': 'Upload material to the Brain',
|
||
'auth.benefitSchedule': 'Schedule research and content',
|
||
'auth.benefitDownload': 'Open and download the results',
|
||
'auth.footer': 'Your knowledge. New possibilities.',
|
||
'auth.pickOrg': 'Choose an organization',
|
||
'auth.pickOrgCopy': 'Express Mode runs inside an organization so the Brain, schedules, and credits are shared with your team.',
|
||
|
||
// Shared
|
||
'common.loading': 'Loading…',
|
||
'common.save': 'Save',
|
||
'common.cancel': 'Cancel',
|
||
'common.retry': 'Try again',
|
||
'common.credits': '{n} credits',
|
||
|
||
// Production
|
||
'prod.title': 'Production',
|
||
'prod.description': 'Everything Express is producing from the Brain: material, content made, what is live, and what is being made right now.',
|
||
'prod.metricWorking': 'Content in progress',
|
||
'prod.metricWorkingSub': 'being researched or generated now',
|
||
'prod.metricMaterial': 'Material',
|
||
'prod.metricMaterialSub': '{n} in the Brain',
|
||
'prod.metricMade': 'Content made',
|
||
'prod.metricMadeSub': 'results the AI has produced',
|
||
'prod.metricLive': 'In production',
|
||
'prod.metricLiveSub': 'approved for use',
|
||
'prod.metricLms': 'On the LMS',
|
||
'prod.metricLmsSub': 'approved SCORM modules',
|
||
'prod.graphTitle': 'Knowledge graph',
|
||
'prod.clickNode': 'Click a node',
|
||
'prod.loadingGraph': 'Drawing the knowledge graph…',
|
||
'prod.graphLabel': 'Knowledge graph of {nodes} nodes',
|
||
'prod.hub.sources': 'Sources',
|
||
'prod.hub.made': 'Made',
|
||
'prod.hub.working': 'In progress',
|
||
'prod.hub.research': 'Research',
|
||
'prod.hub.schedules': 'Repeats',
|
||
'prod.nodes': 'Nodes',
|
||
'prod.nodesSub': 'in this Brain',
|
||
'prod.links': 'Links',
|
||
'prod.linksSub': 'connections between nodes',
|
||
'prod.scheduleTitle': 'Schedule',
|
||
'prod.noSchedules': 'No schedules yet.',
|
||
'prod.giveInstruction': 'Give an instruction',
|
||
'prod.nowTitle': 'Happening now',
|
||
'prod.nothingNow': 'Nothing is being made right now.',
|
||
'prod.more': '+{n} more in Results',
|
||
|
||
// Brain
|
||
'brain.title': 'Brain',
|
||
'brain.description': 'All team material is processed into one Brain that keeps growing. New content the AI creates goes in here too.',
|
||
'brain.metricMaterial': 'Material',
|
||
'brain.metricMaterialSub': 'documents in the workspace',
|
||
'brain.metricReady': 'In the Brain',
|
||
'brain.metricReadySub': 'knowledge base ready',
|
||
'brain.metricAction': 'Needs action',
|
||
'brain.metricActionSub': 'waiting for your approval',
|
||
'brain.listTitle': 'Material',
|
||
'brain.loading': 'Loading material…',
|
||
'brain.engineDown': 'Cannot reach the engine',
|
||
'brain.empty': 'No material yet',
|
||
'brain.emptyCopy': 'Upload documents to start filling the Brain.',
|
||
'brain.uploadTitle': 'Upload material to the Brain',
|
||
'brain.uploadCopy': 'Drag files anywhere on this page, or choose them from your computer. The engine reads them and adds them to the Brain.',
|
||
'brain.formats': 'Formats: {list}',
|
||
'brain.formatsLoading': 'Loading supported formats…',
|
||
'brain.chooseFiles': 'Choose files',
|
||
'brain.uploading': 'Uploading…',
|
||
'brain.uploaded': 'Uploaded',
|
||
'brain.unsupported': 'Unsupported format',
|
||
'brain.dropHere': 'Drop files to upload them to the Brain',
|
||
'brain.statusReady': 'In the Brain',
|
||
'brain.statusBuilding': 'Building Brain',
|
||
'brain.statusProcessing': 'Processing',
|
||
'brain.statusAnswer': 'Needs action',
|
||
'brain.statusFailed': 'Failed',
|
||
'brain.queued': 'Waiting in queue…',
|
||
'brain.versionWithCandidate': 'Looks like “{name}”. Is this a new version of it?',
|
||
'brain.versionGeneric': 'Is this a new version of an existing document?',
|
||
'brain.versionYes': 'Yes, new version',
|
||
'brain.versionNo': 'No, keep separate',
|
||
'brain.needsCredits': 'Not enough credits to add this to the Brain{need}. Top up, then try again.',
|
||
'brain.needsCreditsNeed': ' (needs {credits})',
|
||
'brain.failed': 'Could not add this to the Brain.',
|
||
'brain.retryWith': 'Try again ({credits})',
|
||
'brain.failedAfterBuild': 'Processing this file failed after its knowledge was built. Schedules that use the whole Brain stop until it is removed: delete it and upload it again.',
|
||
'brain.confirmCopy': 'Adding this document to the Brain uses {credits} from the workspace.',
|
||
'brain.confirmCreditsFallback': 'credits',
|
||
'brain.addToBrain': 'Add to Brain',
|
||
'brain.delete': 'Delete {name}',
|
||
'brain.open': 'Open {name}',
|
||
'brain.deleteConfirm': 'Delete “{name}” from the Brain?',
|
||
|
||
// Instruction
|
||
'ins.title': 'Instruction',
|
||
'ins.description': 'Write a plain instruction. The AI turns it into research and content schedules that keep running until you stop them.',
|
||
'ins.label': 'Instruction',
|
||
'ins.ex1': 'Make 2 summaries per day and 1 quiz per week about leadership',
|
||
'ins.ex2': 'Research the internet every day about leadership',
|
||
'ins.ex3': '3 roleplays per week about negotiating with customers',
|
||
'ins.ex4': 'Research every Monday about sales trends, then make 5 flashcards per week',
|
||
'ins.ex5': '1 video per week, 2 podcasts per week and 1 SCORM module per week about onboarding',
|
||
'ins.previewTitle': 'Schedules to create',
|
||
'ins.readyCount': '{n} ready',
|
||
'ins.approveNote': 'Once approved, schedules start right away. Content is created from the Brain without repeating existing content, using workspace credits within the limits above.',
|
||
'ins.approve': 'Approve {n} schedules',
|
||
'ins.approving': 'Creating schedules…',
|
||
'ins.active': 'Active',
|
||
'ins.failed': 'Failed',
|
||
'ins.viewSchedule': 'View in Schedule',
|
||
'ins.kind': 'Content type',
|
||
'ins.count': 'Count',
|
||
'ins.frequency': 'Frequency',
|
||
'ins.perDay': 'per day',
|
||
'ins.perWeek': 'per week',
|
||
'ins.researchFrequency': 'Research frequency',
|
||
'ins.everyDay': 'Every day',
|
||
'ins.everyWeek': 'Every week',
|
||
'ins.weekday': 'Day',
|
||
'ins.topic': 'Topic',
|
||
'ins.topicPlaceholder': 'Topic (empty = whole Brain)',
|
||
'ins.autoApprove': 'New sources go straight into the Brain (no review first)',
|
||
'ins.notCreated': 'This schedule will not be created yet.',
|
||
'ins.tooMany': 'At most 50 items per week for one schedule.',
|
||
'ins.budget': 'Credit limit: {first}, {monthly} per month.',
|
||
'ins.budgetWeekly': '{n} per week',
|
||
'ins.budgetRun': '{n} per research run',
|
||
'ins.removeRule': 'Remove from list',
|
||
|
||
// Rules (parser output)
|
||
'rule.everyDay': 'Every day',
|
||
'rule.everyWeekday': 'Every {day}',
|
||
'rule.research': '{when}: internet research{about}',
|
||
'rule.about': ' about {topic}',
|
||
'rule.wholeBrain': ' from the whole Brain',
|
||
'rule.content': '{kind}: {count} {per}{about}',
|
||
'rule.monthNote': '{count} per month is scheduled as {weekly} per week.',
|
||
'kind.summary': 'Summary',
|
||
'kind.quiz': 'Quiz',
|
||
'kind.flashcards': 'Flashcards',
|
||
'kind.roleplay': 'Roleplay',
|
||
'kind.simulator': 'Simulation',
|
||
'kind.slide_deck': 'Slides',
|
||
'kind.video': 'Video',
|
||
'kind.audio': 'Audio',
|
||
'kind.scorm': 'SCORM',
|
||
'day.0': 'Monday', 'day.1': 'Tuesday', 'day.2': 'Wednesday', 'day.3': 'Thursday', 'day.4': 'Friday', 'day.5': 'Saturday', 'day.6': 'Sunday',
|
||
|
||
// Schedule
|
||
'sch.title': 'Schedule',
|
||
'sch.description': 'Research and content schedules that are running. Change the topic any time; the next content follows the new topic.',
|
||
'sch.newInstruction': 'New instruction',
|
||
'sch.cardTitle': 'Schedules',
|
||
'sch.loading': 'Loading schedules…',
|
||
'sch.loadFailed': 'Could not load schedules',
|
||
'sch.empty': 'No schedules yet',
|
||
'sch.emptyCopy': 'Give an instruction like “2 summaries per day about leadership” to start.',
|
||
'sch.nextResearch': 'Next research: {when}',
|
||
'sch.paused': 'Paused',
|
||
'sch.active': 'Active',
|
||
'sch.newTopic': 'New topic',
|
||
'sch.changeTopic': 'Change topic',
|
||
'sch.pause': 'Pause',
|
||
'sch.resume': 'Resume',
|
||
'sch.runNow': 'Run now',
|
||
'sch.history': 'History',
|
||
'sch.hideHistory': 'Hide history',
|
||
'sch.delete': 'Delete',
|
||
'sch.deleteConfirm': 'Delete the schedule “{name}”? Content already created stays.',
|
||
'sch.loadingProgress': 'Loading progress…',
|
||
'sch.planningOff': 'Automatic production is off in the engine: an admin needs to set a price for “agent.content.plan”.',
|
||
'sch.produceNow': 'Produce now',
|
||
'prod.runningTitle': 'Producing {n} new pieces',
|
||
'prod.lastTitle': 'Last production',
|
||
'prod.count': '{ready} of {total} ready',
|
||
'prod.stepTopics': 'AI chooses new topics',
|
||
'prod.stepStart': 'Starting',
|
||
'prod.stepCreate': 'Creating content ({ready}/{total})',
|
||
'prod.stepDone': 'Done',
|
||
'prod.failed': 'Production failed ({code}).',
|
||
'prod.someFailed': '{n} failed.',
|
||
'prod.shortfall': 'The Brain had new material for fewer pieces than asked ({n} short); add documents for more topics.',
|
||
'prod.viewResults': 'View in Results',
|
||
'sch.produceHowMany': 'How many pieces now? (up to {max} left this week)',
|
||
'sch.checkPrice': 'Check price',
|
||
'sch.checkingPrice': 'Checking…',
|
||
'sch.produceQuote': 'Planning {n} pieces costs {credits} credits. Each piece is then charged as it is created, within this schedule’s weekly limit of {budget} credits. Production starts within about a minute.',
|
||
'sch.startProduction': 'Start production',
|
||
'sch.starting': 'Starting…',
|
||
'sch.alreadyProducing': 'This schedule is already producing. Wait for it to finish first.',
|
||
'sch.weekFull': 'This week’s target is already used up. The next pieces come with next week’s schedule.',
|
||
'sch.thisWeek': 'This week: {ready} of {target} ready{failed}',
|
||
'sch.thisWeekFailed': ', {n} failed',
|
||
'sch.waitingFirst': 'Waiting for the first production slot',
|
||
'sch.loadingHistory': 'Loading history…',
|
||
'sch.neverRan': 'Has not run yet.',
|
||
'sch.scheduled': 'Scheduled',
|
||
'sch.manual': 'Manual',
|
||
'run.queued': 'Queued', 'run.planning': 'Planning', 'run.running': 'Running', 'run.completed': 'Completed', 'run.failed': 'Failed', 'run.cancelled': 'Cancelled',
|
||
|
||
// Results
|
||
'res.title': 'Results',
|
||
'res.description': 'Everything Express creates: text, quizzes, roleplays, video, audio, and SCORM.',
|
||
'res.empty': 'No results yet',
|
||
'res.emptyCopy': 'Content and sources from your schedules appear here. Give an instruction to start a schedule.',
|
||
'res.tabContent': 'Content',
|
||
'res.tabResearch': 'Research',
|
||
'res.pendingCount': '{n} to review',
|
||
'res.allContent': 'All content',
|
||
'res.foundSources': 'Sources found by research',
|
||
'res.search': 'Search titles…',
|
||
'res.filterKind': 'Filter by type',
|
||
'res.filterStatus': 'Filter by status',
|
||
'res.allKinds': 'All types',
|
||
'res.status.all': 'All statuses',
|
||
'res.status.review': 'Needs review',
|
||
'res.status.approved': 'Approved',
|
||
'res.status.working': 'In progress',
|
||
'res.status.failed': 'Failed or rejected',
|
||
'res.state.preparing': 'Preparing',
|
||
'res.state.generating': 'Generating',
|
||
'res.state.ready': 'Needs review',
|
||
'res.state.accepted': 'Approved',
|
||
'res.state.failed': 'Failed',
|
||
'res.state.rejected': 'Rejected',
|
||
'res.source.pending': 'Needs review',
|
||
'res.source.accepted': 'Added to Library',
|
||
'res.source.rejected': 'Rejected',
|
||
'res.loadFailed': 'Results could not be loaded',
|
||
'res.noMatch': 'Nothing matches',
|
||
'res.noMatchCopy': 'Try another keyword, type, or status.',
|
||
'res.noteContent': 'Download as PDF, Markdown or SCORM; slide decks also as PowerPoint, videos as MP4, audio scripts as text. Roleplay and simulation run interactively in Learning Studio.',
|
||
'res.noteResearch': 'Research finds web sources. Add the useful ones to the Library, then build their knowledge on the Brain page.',
|
||
'res.sourceCount': '{n} sources',
|
||
'res.sourceOne': '1 source',
|
||
'res.working': 'Being created…',
|
||
'res.close': 'Close',
|
||
'res.reviewNotice': 'AI-generated draft. Check it, then approve it or reject it.',
|
||
'res.noContent': 'No content to show',
|
||
'res.noContentCopy': 'This item has not finished or its output is no longer available.',
|
||
'res.info': 'Content details',
|
||
'res.statusLabel': 'Status',
|
||
'res.schedule': 'Schedule',
|
||
'res.created': 'Created',
|
||
'res.sources': 'Sources',
|
||
'res.footNote': 'Created by Express from your Brain.',
|
||
'res.openStudio': 'Open in Learning Studio',
|
||
'res.download': 'Download',
|
||
'res.preparingFile': 'Preparing file…',
|
||
'res.pdfHint': 'Opens the print dialog: choose “Save as PDF”',
|
||
'res.pptxHint': 'Slides rendered by Learning Studio',
|
||
'res.mdHint': 'Plain text with headings, for editing',
|
||
'res.accept': 'Approve',
|
||
'res.reject': 'Reject',
|
||
'res.addToBrain': 'Add to Library',
|
||
'res.inBrain': 'see Brain',
|
||
'res.answer': 'Answer',
|
||
'res.slide': 'Slide {n}',
|
||
'res.field.description': 'Description',
|
||
'res.field.topic': 'Topic',
|
||
'res.field.objective': 'Objective',
|
||
'res.field.learning_objectives': 'Learning objectives',
|
||
'res.field.user_role': 'Learner role',
|
||
'res.field.ai_character': 'AI character',
|
||
'res.field.difficulty': 'Difficulty',
|
||
'res.field.context': 'Context',
|
||
|
||
// Media and SCORM (Phases 4, 5, 7)
|
||
'ins.noteVideo': 'One video per run, about {seconds} seconds in {scenes} scenes, made by Video Studio. Each video waits for your review in Results.',
|
||
'ins.noteAudio': 'Written as a narration script. Results plays it with the device voice and downloads it as text (no MP3 yet: the engine has no audio service for agents).',
|
||
'ins.noteScorm': 'Each module is a lesson with a 10-question quiz, downloaded from Results as a SCORM 1.2 package for your LMS.',
|
||
'ins.videoCadence': 'A video schedule makes one video per run: choose 1 per day or 1 per week.',
|
||
'ins.videoQuoteFailed': 'The video price could not be checked: {message}',
|
||
'ins.budgetVideo': '{n} per video',
|
||
'agent.name': 'Express Agent',
|
||
'agent.status': 'Asks when something is unclear',
|
||
'agent.statusThinking': 'Thinking…',
|
||
'agent.think1': 'Reading your message',
|
||
'agent.think2': 'Checking the plan',
|
||
'agent.think3': 'Working out what is still unclear',
|
||
'agent.thought': 'Thought for {s}s',
|
||
'agent.greeting': 'Hi! Tell me what to create from the Brain and how often, or what to research on the internet. If anything is unclear, I will ask you here.',
|
||
'agent.understood': 'Got it. Here is the plan so far:\n{list}',
|
||
'agent.added': 'Added to the plan:\n{list}',
|
||
'agent.notUnderstood': 'I could not find a content type or research request in that. Try something like “2 summaries per day about leadership”.',
|
||
'agent.unclear': 'Sorry, I did not catch that. Pick one of the options below or answer in your own words.',
|
||
'agent.askUnknown': 'I did not understand “{part}”. What did you mean? Rephrase it, or skip it.',
|
||
'agent.askVideo': '{rule}: a video schedule makes one video per run, so it can be 1 per week or 1 per day. Which one?',
|
||
'agent.askTooMany': '{rule} is more than 50 per week, the limit for one schedule. How many per week should it be?',
|
||
'agent.askWeekday': 'Weekly internet research{about}: which day should it run? It is set to Monday for now.',
|
||
'agent.askTopic': 'What topic should this be about?\n{list}\nType a topic, or use the whole Brain.',
|
||
'agent.ready': 'Everything is clear: {n} schedules are ready. You can still change them in the plan. Shall I start them?',
|
||
'agent.readyLanguage': 'Everything is clear: {n} schedules are ready, with content written in {language}. You can still change them in the plan. Shall I start them?',
|
||
'agent.askLanguage': 'Which language should the content be written in?',
|
||
'agent.languageSet': 'Content will be written in {language}.',
|
||
'ins.language': 'Content language',
|
||
'ins.languagePending': 'Not chosen yet',
|
||
'agent.skipped': 'OK, I skipped “{part}”.',
|
||
'agent.removed': 'Removed {rule}.',
|
||
'agent.updated': 'Updated: {rule}.',
|
||
'agent.topicSet': 'Topic set to “{topic}”.',
|
||
'agent.wholeBrain': 'OK, the whole Brain it is.',
|
||
'agent.reset': 'Starting over. What should I create?',
|
||
'agent.approving': 'Creating the schedules…',
|
||
'agent.done': 'Done: {n} schedules are active and start right away.',
|
||
'agent.partial': '{ok} of {n} schedules were created. The ones that failed are still in the plan: fix them, then approve again.',
|
||
'agent.optSkip': 'Skip',
|
||
'agent.optRemove': 'Remove it',
|
||
'agent.optWholeBrain': 'Whole Brain',
|
||
'agent.optApprove': 'Yes, start them',
|
||
'agent.optReset': 'Start over',
|
||
'agent.optPerWeek': '1 per week',
|
||
'agent.optPerDay': '1 per day',
|
||
'agent.opt50': '50 per week',
|
||
'agent.placeholder': 'Reply to the agent or give a new instruction…',
|
||
'agent.send': 'Send',
|
||
'agent.hint': 'Enter to send, Shift+Enter for a new line.',
|
||
'agent.planEmpty': 'The plan fills in as you chat with the agent.',
|
||
'sch.nextVideo': 'Next video: {when}',
|
||
'sch.videoNow': 'Make a video now',
|
||
'sch.videoStarted': 'Video started. It shows in Results while it is being made.',
|
||
'sch.runStarted': 'Research started.',
|
||
'sch.deleted': 'Schedule deleted.',
|
||
'res.videoProgress': 'Rendering… {n}%',
|
||
'res.videoNotReady': 'The video is not ready yet',
|
||
'res.videoFailed': 'The video could not be made',
|
||
'res.videoFailedCopy': 'Rendering stopped before the video was finished.',
|
||
'res.downloadVideo': 'Download video (MP4)',
|
||
'res.creditsUsed': 'Credits used',
|
||
'res.accepted': 'Approved.',
|
||
'res.rejected': 'Rejected.',
|
||
'res.retry': 'Retry',
|
||
'res.retrying': 'Retrying…',
|
||
'res.retried': 'Production started again.',
|
||
'res.audioNotice': 'Played with this device’s voice, which can differ between devices. Download the script to record it or use it in a TTS tool.',
|
||
'res.fullScript': 'Show the full script',
|
||
'res.scriptTxt': 'Script (.txt)',
|
||
'res.scriptHint': 'Narration text, for recording or a TTS tool',
|
||
'res.quiz': 'Quiz',
|
||
'res.quizPending': 'The quiz is not ready yet.',
|
||
'res.scormModule': 'SCORM 1.2 module',
|
||
'res.scormMeta': 'Lesson + {n} questions · pass mark {mastery}%',
|
||
'res.scormHint': 'For an LMS: the lesson, marked complete when finished',
|
||
'res.scormQuizHint': 'For an LMS: lesson and quiz, reports score and pass/fail',
|
||
'res.scormReady': 'SCORM package downloaded. Upload the .zip to your LMS.',
|
||
'scorm.submit': 'Check answers',
|
||
'scorm.retry': 'Try again',
|
||
'scorm.complete': 'Mark as complete',
|
||
'scorm.completed': 'Completed ✓',
|
||
'scorm.score': 'Score: {score}%.',
|
||
'scorm.passed': 'Passed (pass mark {n}%).',
|
||
'scorm.failed': 'Not passed yet (pass mark {n}%).',
|
||
'scorm.answerAll': 'Answer every question first.',
|
||
'scorm.noLms': 'Opened outside an LMS: progress is not saved.',
|
||
'audio.copy': 'The narration script, read aloud by this device.',
|
||
'audio.unsupported': 'This browser cannot read text aloud. Download the script instead.',
|
||
'audio.play': 'Listen',
|
||
'audio.pause': 'Pause',
|
||
'audio.resume': 'Resume',
|
||
'audio.stop': 'Stop',
|
||
'audio.speed': 'Speed',
|
||
'audio.ready': '{n} sentences · press Listen to start',
|
||
'audio.sentence': 'Sentence {n} of {total}',
|
||
'brain.deleted': '“{name}” deleted.',
|
||
|
||
// Guide
|
||
'help.boxTitle': 'How Express works',
|
||
'help.boxBody': 'Four steps from your material to finished content.',
|
||
'help.open': 'View the guide',
|
||
'help.title': 'How Learning Lab Express works',
|
||
'help.subtitle': 'Brain → Instruction → Schedule → Results',
|
||
'help.brainTitle': 'Brain',
|
||
'help.brainBody': 'Upload your training material. Once its knowledge is built, the AI creates everything from it.',
|
||
'help.instructionTitle': 'Instruction',
|
||
'help.instructionBody': 'Say what you need in one sentence, e.g. “3 quizzes per week and 1 video per week about leadership”. Check the schedules, then approve them.',
|
||
'help.scheduleTitle': 'Schedule',
|
||
'help.scheduleBody': 'Schedules run on their own. Pause them, change the topic, or produce now; each stays within its credit limit.',
|
||
'help.resultsTitle': 'Results',
|
||
'help.resultsBody': 'Review what was made, approve or reject it, and download it: PDF, Markdown, PowerPoint, MP4, audio script, or SCORM for your LMS.',
|
||
'help.note': 'Content is AI-generated: always review it before using it. Credits come from your organization’s workspace.',
|
||
'help.done': 'Got it',
|
||
|
||
// Errors from the engine client
|
||
'err.unreachable': 'Cannot reach the learning-lab engine.',
|
||
'err.generic': 'The request to the engine failed.',
|
||
'err.credits': 'Not enough credits: needs {required}, balance {balance}.',
|
||
'err.creditsShort': 'Not enough credits.',
|
||
'err.docFailed': 'The document could not be processed.',
|
||
'err.timeout': 'Document processing took too long.',
|
||
'err.researchFailed': 'Research could not run ({code}).',
|
||
} as const
|
||
|
||
export type Key = keyof typeof en
|
||
|
||
const id: Record<Key, string> = {
|
||
'prod.title': 'Produksi',
|
||
'prod.description': 'Semua yang diproduksi Express dari Otak: materi, konten yang dibuat, yang sudah tayang, dan yang sedang dibuat saat ini.',
|
||
'prod.metricWorking': 'Konten sedang dibuat',
|
||
'prod.metricWorkingSub': 'sedang diriset atau dibuat sekarang',
|
||
'prod.metricMaterial': 'Materi',
|
||
'prod.metricMaterialSub': '{n} sudah di Otak',
|
||
'prod.metricMade': 'Konten dibuat',
|
||
'prod.metricMadeSub': 'hasil yang sudah dibuat AI',
|
||
'prod.metricLive': 'Sudah produksi',
|
||
'prod.metricLiveSub': 'disetujui untuk dipakai',
|
||
'prod.metricLms': 'Sudah di LMS',
|
||
'prod.metricLmsSub': 'modul SCORM yang disetujui',
|
||
'prod.graphTitle': 'Knowledge graph',
|
||
'prod.clickNode': 'Klik sebuah node',
|
||
'prod.loadingGraph': 'Menggambar knowledge graph…',
|
||
'prod.graphLabel': 'Knowledge graph berisi {nodes} node',
|
||
'prod.hub.sources': 'Sumber',
|
||
'prod.hub.made': 'Dibuat',
|
||
'prod.hub.working': 'Sedang dibuat',
|
||
'prod.hub.research': 'Riset',
|
||
'prod.hub.schedules': 'Berulang',
|
||
'prod.nodes': 'Node',
|
||
'prod.nodesSub': 'di Otak ini',
|
||
'prod.links': 'Tautan',
|
||
'prod.linksSub': 'koneksi antar node',
|
||
'prod.scheduleTitle': 'Jadwal',
|
||
'prod.noSchedules': 'Belum ada jadwal.',
|
||
'prod.giveInstruction': 'Beri perintah',
|
||
'prod.nowTitle': 'Sedang berjalan',
|
||
'prod.nothingNow': 'Tidak ada yang sedang dibuat.',
|
||
'prod.more': '+{n} lagi di Hasil',
|
||
'nav.brain': 'Otak',
|
||
'nav.instruction': 'Perintah',
|
||
'nav.schedule': 'Jadwal',
|
||
'nav.results': 'Hasil',
|
||
'nav.dashboard': 'Dasbor',
|
||
'nav.settings': 'Pengaturan',
|
||
'nav.production': 'Produksi',
|
||
'layout.learningSpace': 'Ruang belajar',
|
||
'layout.switchOrg': 'Ganti organisasi',
|
||
'layout.breadcrumb': 'Ruang kerja',
|
||
'layout.signOut': 'Keluar',
|
||
'layout.engine': 'Engine learning-lab',
|
||
'layout.creditsTitle': 'Saldo kredit workspace',
|
||
'layout.toStudio': 'Beralih ke Learning Lab',
|
||
'layout.devStrip': 'Fase development: terhubung ke engine learning-lab. Hasil AI perlu ditinjau sebelum dipakai.',
|
||
'layout.footer': 'Ruang kerja untuk pengetahuan yang terus berkembang.',
|
||
'layout.mainNav': 'Navigasi utama',
|
||
'layout.language': 'Bahasa',
|
||
|
||
'auth.loading': 'Memuat…',
|
||
'auth.eyebrow': 'BELAJAR BARU, TANPA MULAI DARI NOL',
|
||
'auth.headline1': 'Pengetahuan tim.',
|
||
'auth.headline2': 'Ide baru.',
|
||
'auth.headline3': 'Pembelajaran tanpa henti.',
|
||
'auth.copy': 'Unggah materi ke Otak, beri satu perintah, dan AI terus membuat konten pembelajaran baru sesuai jadwal.',
|
||
'auth.benefitUpload': 'Unggah materi ke Otak',
|
||
'auth.benefitSchedule': 'Jadwalkan riset dan konten',
|
||
'auth.benefitDownload': 'Buka dan unduh hasilnya',
|
||
'auth.footer': 'Pengetahuan Anda. Kemungkinan baru.',
|
||
'auth.pickOrg': 'Pilih organisasi',
|
||
'auth.pickOrgCopy': 'Express Mode berjalan di dalam organisasi agar Otak, jadwal, dan kredit dipakai bersama tim.',
|
||
|
||
'common.loading': 'Memuat…',
|
||
'common.save': 'Simpan',
|
||
'common.cancel': 'Batal',
|
||
'common.retry': 'Coba lagi',
|
||
'common.credits': '{n} kredit',
|
||
|
||
'brain.title': 'Otak',
|
||
'brain.description': 'Semua materi tim diproses menjadi satu Otak yang terus bertambah. Konten baru yang dihasilkan juga masuk ke sini.',
|
||
'brain.metricMaterial': 'Materi',
|
||
'brain.metricMaterialSub': 'dokumen di ruang kerja',
|
||
'brain.metricReady': 'Masuk Otak',
|
||
'brain.metricReadySub': 'knowledge base siap',
|
||
'brain.metricAction': 'Perlu tindakan',
|
||
'brain.metricActionSub': 'menunggu persetujuan Anda',
|
||
'brain.listTitle': 'Materi',
|
||
'brain.loading': 'Memuat materi…',
|
||
'brain.engineDown': 'Engine tidak dapat dihubungi',
|
||
'brain.empty': 'Belum ada materi',
|
||
'brain.emptyCopy': 'Unggah dokumen untuk mulai mengisi Otak.',
|
||
'brain.uploadTitle': 'Unggah materi ke Otak',
|
||
'brain.uploadCopy': 'Tarik file ke mana saja di halaman ini, atau pilih dari komputer. Engine membaca isinya lalu menambahkannya ke Otak.',
|
||
'brain.formats': 'Format: {list}',
|
||
'brain.formatsLoading': 'Memuat format yang didukung…',
|
||
'brain.chooseFiles': 'Pilih file',
|
||
'brain.uploading': 'Mengunggah…',
|
||
'brain.uploaded': 'Terunggah',
|
||
'brain.unsupported': 'Format tidak didukung',
|
||
'brain.dropHere': 'Lepaskan file untuk diunggah ke Otak',
|
||
'brain.statusReady': 'Masuk Otak',
|
||
'brain.statusBuilding': 'Membangun Otak',
|
||
'brain.statusProcessing': 'Diproses',
|
||
'brain.statusAnswer': 'Perlu tindakan',
|
||
'brain.statusFailed': 'Gagal',
|
||
'brain.queued': 'Menunggu antrean…',
|
||
'brain.versionWithCandidate': 'Mirip dengan “{name}”. Apakah ini versi barunya?',
|
||
'brain.versionGeneric': 'Apakah ini versi baru dari dokumen yang sudah ada?',
|
||
'brain.versionYes': 'Ya, versi baru',
|
||
'brain.versionNo': 'Bukan, simpan terpisah',
|
||
'brain.needsCredits': 'Kredit tidak cukup untuk memasukkan ke Otak{need}. Isi ulang kredit, lalu coba lagi.',
|
||
'brain.needsCreditsNeed': ' (butuh {credits})',
|
||
'brain.failed': 'Gagal memasukkan ke Otak.',
|
||
'brain.retryWith': 'Coba lagi ({credits})',
|
||
'brain.failedAfterBuild': 'Pemrosesan file ini gagal setelah pengetahuannya dibangun. Jadwal yang memakai seluruh Otak berhenti sampai file ini dihapus: hapus lalu unggah ulang.',
|
||
'brain.confirmCopy': 'Memasukkan dokumen ini ke Otak memakai {credits} dari ruang kerja.',
|
||
'brain.confirmCreditsFallback': 'kredit',
|
||
'brain.addToBrain': 'Masukkan ke Otak',
|
||
'brain.delete': 'Hapus {name}',
|
||
'brain.open': 'Buka {name}',
|
||
'brain.deleteConfirm': 'Hapus “{name}” dari Otak?',
|
||
|
||
'ins.title': 'Perintah',
|
||
'ins.description': 'Tulis instruksi biasa. AI menyusunnya menjadi jadwal riset dan pembuatan konten, lalu terus berjalan sampai Anda menghentikannya.',
|
||
'ins.label': 'Instruksi',
|
||
'ins.ex1': 'Bikin 2 ringkasan per hari dan 1 kuis per minggu soal leadership',
|
||
'ins.ex2': 'Riset internet tiap hari soal leadership',
|
||
'ins.ex3': '3 roleplay per minggu tentang negosiasi dengan pelanggan',
|
||
'ins.ex4': 'Riset tiap Senin soal tren sales, lalu buat 5 flashcard per minggu',
|
||
'ins.ex5': '1 video per minggu, 2 podcast per minggu dan 1 modul SCORM per minggu soal onboarding',
|
||
'ins.previewTitle': 'Jadwal yang akan dibuat',
|
||
'ins.readyCount': '{n} siap',
|
||
'ins.approveNote': 'Setelah disetujui, jadwal langsung aktif. Konten dibuat dari materi di Otak tanpa mengulang yang sudah ada, dan memakai kredit ruang kerja sesuai batas di atas.',
|
||
'ins.approve': 'Setujui {n} jadwal',
|
||
'ins.approving': 'Membuat jadwal…',
|
||
'ins.active': 'Aktif',
|
||
'ins.failed': 'Gagal',
|
||
'ins.viewSchedule': 'Lihat di Jadwal',
|
||
'ins.kind': 'Jenis konten',
|
||
'ins.count': 'Jumlah',
|
||
'ins.frequency': 'Frekuensi',
|
||
'ins.perDay': 'per hari',
|
||
'ins.perWeek': 'per minggu',
|
||
'ins.researchFrequency': 'Frekuensi riset',
|
||
'ins.everyDay': 'Setiap hari',
|
||
'ins.everyWeek': 'Setiap minggu',
|
||
'ins.weekday': 'Hari',
|
||
'ins.topic': 'Topik',
|
||
'ins.topicPlaceholder': 'Topik (kosong = seluruh Otak)',
|
||
'ins.autoApprove': 'Sumber baru langsung masuk ke Otak (tanpa ditinjau dulu)',
|
||
'ins.notCreated': 'Jadwal ini belum dibuat.',
|
||
'ins.tooMany': 'Maksimal 50 konten per minggu untuk satu jadwal.',
|
||
'ins.budget': 'Batas kredit: {first}, {monthly} per bulan.',
|
||
'ins.budgetWeekly': '{n} per minggu',
|
||
'ins.budgetRun': '{n} per riset',
|
||
'ins.removeRule': 'Hapus dari daftar',
|
||
|
||
'rule.everyDay': 'Setiap hari',
|
||
'rule.everyWeekday': 'Setiap {day}',
|
||
'rule.research': '{when}: riset internet{about}',
|
||
'rule.about': ' tentang {topic}',
|
||
'rule.wholeBrain': ' dari seluruh Otak',
|
||
'rule.content': '{kind}: {count} {per}{about}',
|
||
'rule.monthNote': '{count} per bulan dijadwalkan sebagai {weekly} per minggu.',
|
||
'kind.summary': 'Ringkasan',
|
||
'kind.quiz': 'Kuis',
|
||
'kind.flashcards': 'Flashcard',
|
||
'kind.roleplay': 'Roleplay',
|
||
'kind.simulator': 'Simulasi',
|
||
'kind.slide_deck': 'Slide',
|
||
'kind.video': 'Video',
|
||
'kind.audio': 'Audio',
|
||
'kind.scorm': 'SCORM',
|
||
'day.0': 'Senin', 'day.1': 'Selasa', 'day.2': 'Rabu', 'day.3': 'Kamis', 'day.4': 'Jumat', 'day.5': 'Sabtu', 'day.6': 'Minggu',
|
||
|
||
'sch.title': 'Jadwal',
|
||
'sch.description': 'Jadwal riset dan pembuatan konten yang sedang berjalan. Ubah topik kapan saja; konten berikutnya mengikuti topik baru.',
|
||
'sch.newInstruction': 'Perintah baru',
|
||
'sch.cardTitle': 'Jadwal',
|
||
'sch.loading': 'Memuat jadwal…',
|
||
'sch.loadFailed': 'Jadwal tidak dapat dimuat',
|
||
'sch.empty': 'Belum ada jadwal',
|
||
'sch.emptyCopy': 'Beri perintah seperti “2 ringkasan per hari soal leadership” untuk mulai.',
|
||
'sch.nextResearch': 'Riset berikutnya: {when}',
|
||
'sch.paused': 'Dijeda',
|
||
'sch.active': 'Aktif',
|
||
'sch.newTopic': 'Topik baru',
|
||
'sch.changeTopic': 'Ganti topik',
|
||
'sch.pause': 'Jeda',
|
||
'sch.resume': 'Lanjutkan',
|
||
'sch.runNow': 'Jalankan sekarang',
|
||
'sch.history': 'Riwayat',
|
||
'sch.hideHistory': 'Sembunyikan riwayat',
|
||
'sch.delete': 'Hapus',
|
||
'sch.deleteConfirm': 'Hapus jadwal “{name}”? Konten yang sudah dibuat tetap ada.',
|
||
'sch.loadingProgress': 'Memuat progres…',
|
||
'sch.planningOff': 'Produksi otomatis belum aktif di engine: admin perlu mengatur harga “agent.content.plan”.',
|
||
'sch.produceNow': 'Produksi sekarang',
|
||
'prod.runningTitle': 'Memproduksi {n} konten baru',
|
||
'prod.lastTitle': 'Produksi terakhir',
|
||
'prod.count': '{ready} dari {total} siap',
|
||
'prod.stepTopics': 'AI memilih topik baru',
|
||
'prod.stepStart': 'Memulai',
|
||
'prod.stepCreate': 'Membuat konten ({ready}/{total})',
|
||
'prod.stepDone': 'Selesai',
|
||
'prod.failed': 'Produksi gagal ({code}).',
|
||
'prod.someFailed': '{n} gagal.',
|
||
'prod.shortfall': 'Brain hanya punya materi baru untuk lebih sedikit konten dari yang diminta (kurang {n}); tambah dokumen untuk topik lain.',
|
||
'prod.viewResults': 'Lihat di Results',
|
||
'sch.produceHowMany': 'Berapa konten sekarang? (sisa {max} untuk minggu ini)',
|
||
'sch.checkPrice': 'Cek harga',
|
||
'sch.checkingPrice': 'Mengecek…',
|
||
'sch.produceQuote': 'Merencanakan {n} konten memakai {credits} kredit. Setiap konten lalu dipotong kreditnya saat dibuat, dalam batas mingguan jadwal ini sebesar {budget} kredit. Produksi mulai dalam sekitar satu menit.',
|
||
'sch.startProduction': 'Mulai produksi',
|
||
'sch.starting': 'Memulai…',
|
||
'sch.alreadyProducing': 'Jadwal ini sedang berproduksi. Tunggu sampai selesai dulu.',
|
||
'sch.weekFull': 'Target minggu ini sudah terpakai. Konten berikutnya ikut jadwal minggu depan.',
|
||
'sch.thisWeek': 'Minggu ini: {ready} dari {target} siap{failed}',
|
||
'sch.thisWeekFailed': ', {n} gagal',
|
||
'sch.waitingFirst': 'Menunggu jadwal produksi pertama',
|
||
'sch.loadingHistory': 'Memuat riwayat…',
|
||
'sch.neverRan': 'Belum pernah berjalan.',
|
||
'sch.scheduled': 'Terjadwal',
|
||
'sch.manual': 'Manual',
|
||
'run.queued': 'Antre', 'run.planning': 'Merencanakan', 'run.running': 'Berjalan', 'run.completed': 'Selesai', 'run.failed': 'Gagal', 'run.cancelled': 'Dibatalkan',
|
||
|
||
'res.title': 'Hasil',
|
||
'res.description': 'Semua konten yang dihasilkan Express: teks, kuis, roleplay, video, audio, dan SCORM.',
|
||
'res.empty': 'Belum ada hasil',
|
||
'res.emptyCopy': 'Konten dan sumber dari jadwal Anda tampil di sini. Beri perintah untuk memulai jadwal.',
|
||
'res.tabContent': 'Konten',
|
||
'res.tabResearch': 'Riset',
|
||
'res.pendingCount': '{n} perlu ditinjau',
|
||
'res.allContent': 'Semua konten',
|
||
'res.foundSources': 'Sumber hasil riset',
|
||
'res.search': 'Cari judul…',
|
||
'res.filterKind': 'Filter jenis',
|
||
'res.filterStatus': 'Filter status',
|
||
'res.allKinds': 'Semua jenis',
|
||
'res.status.all': 'Semua status',
|
||
'res.status.review': 'Perlu ditinjau',
|
||
'res.status.approved': 'Disetujui',
|
||
'res.status.working': 'Sedang dibuat',
|
||
'res.status.failed': 'Gagal atau ditolak',
|
||
'res.state.preparing': 'Menyiapkan',
|
||
'res.state.generating': 'Membuat',
|
||
'res.state.ready': 'Perlu ditinjau',
|
||
'res.state.accepted': 'Disetujui',
|
||
'res.state.failed': 'Gagal',
|
||
'res.state.rejected': 'Ditolak',
|
||
'res.source.pending': 'Perlu ditinjau',
|
||
'res.source.accepted': 'Masuk Library',
|
||
'res.source.rejected': 'Ditolak',
|
||
'res.loadFailed': 'Hasil tidak dapat dimuat',
|
||
'res.noMatch': 'Tidak ada yang cocok',
|
||
'res.noMatchCopy': 'Coba kata kunci, jenis, atau status lain.',
|
||
'res.noteContent': 'Unduh sebagai PDF, Markdown, atau SCORM; slide deck juga sebagai PowerPoint, video sebagai MP4, naskah audio sebagai teks. Roleplay dan simulasi dijalankan secara interaktif di Learning Studio.',
|
||
'res.noteResearch': 'Riset menemukan sumber dari web. Masukkan yang berguna ke Library, lalu bangun pengetahuannya di halaman Brain.',
|
||
'res.sourceCount': '{n} sumber',
|
||
'res.sourceOne': '1 sumber',
|
||
'res.working': 'Sedang dibuat…',
|
||
'res.close': 'Tutup',
|
||
'res.reviewNotice': 'Draft buatan AI. Periksa dulu, lalu setujui atau tolak.',
|
||
'res.noContent': 'Tidak ada konten untuk ditampilkan',
|
||
'res.noContentCopy': 'Item ini belum selesai atau hasilnya sudah tidak tersedia.',
|
||
'res.info': 'Informasi konten',
|
||
'res.statusLabel': 'Status',
|
||
'res.schedule': 'Jadwal',
|
||
'res.created': 'Dibuat',
|
||
'res.sources': 'Sumber',
|
||
'res.footNote': 'Dibuat oleh Express dari Brain Anda.',
|
||
'res.openStudio': 'Buka di Learning Studio',
|
||
'res.download': 'Unduh',
|
||
'res.preparingFile': 'Menyiapkan file…',
|
||
'res.pdfHint': 'Membuka dialog cetak: pilih “Save as PDF” / “Simpan sebagai PDF”',
|
||
'res.pptxHint': 'Slide dari Learning Studio',
|
||
'res.mdHint': 'Teks dengan judul, untuk disunting',
|
||
'res.accept': 'Setujui',
|
||
'res.reject': 'Tolak',
|
||
'res.addToBrain': 'Masukkan ke Library',
|
||
'res.inBrain': 'lihat Brain',
|
||
'res.answer': 'Jawaban',
|
||
'res.slide': 'Slide {n}',
|
||
'res.field.description': 'Deskripsi',
|
||
'res.field.topic': 'Topik',
|
||
'res.field.objective': 'Tujuan',
|
||
'res.field.learning_objectives': 'Tujuan pembelajaran',
|
||
'res.field.user_role': 'Peran peserta',
|
||
'res.field.ai_character': 'Karakter AI',
|
||
'res.field.difficulty': 'Tingkat kesulitan',
|
||
'res.field.context': 'Konteks',
|
||
|
||
// Media dan SCORM (Fase 4, 5, 7)
|
||
'ins.noteVideo': 'Satu video per proses, sekitar {seconds} detik dalam {scenes} adegan, dibuat oleh Video Studio. Setiap video menunggu tinjauan Anda di Hasil.',
|
||
'ins.noteAudio': 'Dibuat sebagai naskah narasi. Hasil memutarnya dengan suara perangkat dan mengunduhnya sebagai teks (belum MP3: engine belum punya layanan audio untuk agen).',
|
||
'ins.noteScorm': 'Setiap modul berisi materi dan kuis 10 soal, diunduh dari Hasil sebagai paket SCORM 1.2 untuk LMS Anda.',
|
||
'ins.videoCadence': 'Jadwal video membuat satu video per proses: pilih 1 per hari atau 1 per minggu.',
|
||
'ins.videoQuoteFailed': 'Harga video tidak bisa dicek: {message}',
|
||
'ins.budgetVideo': '{n} per video',
|
||
'agent.name': 'Agen Express',
|
||
'agent.status': 'Bertanya bila ada yang belum jelas',
|
||
'agent.statusThinking': 'Sedang berpikir…',
|
||
'agent.think1': 'Membaca pesan Anda',
|
||
'agent.think2': 'Memeriksa rencana',
|
||
'agent.think3': 'Mencari yang masih belum jelas',
|
||
'agent.thought': 'Berpikir {s} detik',
|
||
'agent.greeting': 'Halo! Ceritakan konten apa yang perlu dibuat dari Otak dan seberapa sering, atau apa yang perlu diriset di internet. Kalau ada yang belum jelas, saya tanyakan di sini.',
|
||
'agent.understood': 'Siap. Ini rencananya sejauh ini:\n{list}',
|
||
'agent.added': 'Ditambahkan ke rencana:\n{list}',
|
||
'agent.notUnderstood': 'Saya belum menemukan jenis konten atau permintaan riset di situ. Coba misalnya “2 ringkasan per hari soal leadership”.',
|
||
'agent.unclear': 'Maaf, saya belum menangkap maksudnya. Pilih salah satu opsi di bawah atau jawab dengan kata-kata sendiri.',
|
||
'agent.askUnknown': 'Saya belum paham “{part}”. Maksudnya apa? Tulis ulang, atau lewati saja.',
|
||
'agent.askVideo': '{rule}: jadwal video membuat satu video per proses, jadi pilihannya 1 per minggu atau 1 per hari. Mau yang mana?',
|
||
'agent.askTooMany': '{rule} lebih dari 50 per minggu, batas untuk satu jadwal. Berapa per minggu?',
|
||
'agent.askWeekday': 'Riset internet mingguan{about}: dijalankan hari apa? Sementara diatur ke Senin.',
|
||
'agent.askTopic': 'Topiknya tentang apa?\n{list}\nTulis topiknya, atau pakai seluruh Otak.',
|
||
'agent.ready': 'Semua sudah jelas: {n} jadwal siap. Anda masih bisa mengubahnya di rencana. Mulai sekarang?',
|
||
'agent.readyLanguage': 'Semua sudah jelas: {n} jadwal siap, dengan konten berbahasa {language}. Anda masih bisa mengubahnya di rencana. Mulai sekarang?',
|
||
'agent.askLanguage': 'Kontennya mau ditulis dalam bahasa apa?',
|
||
'agent.languageSet': 'Konten akan ditulis dalam {language}.',
|
||
'ins.language': 'Bahasa konten',
|
||
'ins.languagePending': 'Belum dipilih',
|
||
'agent.skipped': 'Oke, “{part}” saya lewati.',
|
||
'agent.removed': '{rule} dihapus.',
|
||
'agent.updated': 'Diperbarui: {rule}.',
|
||
'agent.topicSet': 'Topik diatur ke “{topic}”.',
|
||
'agent.wholeBrain': 'Oke, pakai seluruh Otak.',
|
||
'agent.reset': 'Mulai dari awal. Apa yang perlu dibuat?',
|
||
'agent.approving': 'Membuat jadwal…',
|
||
'agent.done': 'Selesai: {n} jadwal aktif dan langsung berjalan.',
|
||
'agent.partial': '{ok} dari {n} jadwal berhasil dibuat. Yang gagal masih ada di rencana: perbaiki, lalu setujui lagi.',
|
||
'agent.optSkip': 'Lewati',
|
||
'agent.optRemove': 'Hapus saja',
|
||
'agent.optWholeBrain': 'Seluruh Otak',
|
||
'agent.optApprove': 'Ya, mulai',
|
||
'agent.optReset': 'Mulai ulang',
|
||
'agent.optPerWeek': '1 per minggu',
|
||
'agent.optPerDay': '1 per hari',
|
||
'agent.opt50': '50 per minggu',
|
||
'agent.placeholder': 'Balas agen atau beri instruksi baru…',
|
||
'agent.send': 'Kirim',
|
||
'agent.hint': 'Enter untuk kirim, Shift+Enter untuk baris baru.',
|
||
'agent.planEmpty': 'Rencana terisi seiring Anda mengobrol dengan agen.',
|
||
'sch.nextVideo': 'Video berikutnya: {when}',
|
||
'sch.videoNow': 'Buat video sekarang',
|
||
'sch.videoStarted': 'Video mulai dibuat. Video tampil di Hasil selama proses berjalan.',
|
||
'sch.runStarted': 'Riset dimulai.',
|
||
'sch.deleted': 'Jadwal dihapus.',
|
||
'res.videoProgress': 'Merender… {n}%',
|
||
'res.videoNotReady': 'Video belum siap',
|
||
'res.videoFailed': 'Video gagal dibuat',
|
||
'res.videoFailedCopy': 'Proses render berhenti sebelum video selesai.',
|
||
'res.downloadVideo': 'Unduh video (MP4)',
|
||
'res.creditsUsed': 'Kredit terpakai',
|
||
'res.accepted': 'Disetujui.',
|
||
'res.rejected': 'Ditolak.',
|
||
'res.retry': 'Coba lagi',
|
||
'res.retrying': 'Mencoba lagi…',
|
||
'res.retried': 'Produksi dimulai lagi.',
|
||
'res.audioNotice': 'Diputar dengan suara perangkat ini, yang bisa berbeda antarperangkat. Unduh naskahnya untuk direkam atau dipakai di alat TTS.',
|
||
'res.fullScript': 'Lihat naskah lengkap',
|
||
'res.scriptTxt': 'Naskah (.txt)',
|
||
'res.scriptHint': 'Teks narasi, untuk direkam atau alat TTS',
|
||
'res.quiz': 'Kuis',
|
||
'res.quizPending': 'Kuisnya belum siap.',
|
||
'res.scormModule': 'Modul SCORM 1.2',
|
||
'res.scormMeta': 'Materi + {n} soal · nilai lulus {mastery}%',
|
||
'res.scormHint': 'Untuk LMS: materi, ditandai selesai setelah dibaca',
|
||
'res.scormQuizHint': 'Untuk LMS: materi dan kuis, melaporkan nilai dan lulus/tidak',
|
||
'res.scormReady': 'Paket SCORM terunduh. Unggah file .zip ke LMS Anda.',
|
||
'scorm.submit': 'Periksa jawaban',
|
||
'scorm.retry': 'Coba lagi',
|
||
'scorm.complete': 'Tandai selesai',
|
||
'scorm.completed': 'Selesai ✓',
|
||
'scorm.score': 'Nilai: {score}%.',
|
||
'scorm.passed': 'Lulus (nilai lulus {n}%).',
|
||
'scorm.failed': 'Belum lulus (nilai lulus {n}%).',
|
||
'scorm.answerAll': 'Jawab semua soal terlebih dahulu.',
|
||
'scorm.noLms': 'Dibuka di luar LMS: progres tidak disimpan.',
|
||
'audio.copy': 'Naskah narasi, dibacakan oleh perangkat ini.',
|
||
'audio.unsupported': 'Browser ini tidak bisa membacakan teks. Unduh naskahnya saja.',
|
||
'audio.play': 'Dengarkan',
|
||
'audio.pause': 'Jeda',
|
||
'audio.resume': 'Lanjutkan',
|
||
'audio.stop': 'Hentikan',
|
||
'audio.speed': 'Kecepatan',
|
||
'audio.ready': '{n} kalimat · tekan Dengarkan untuk mulai',
|
||
'audio.sentence': 'Kalimat {n} dari {total}',
|
||
'brain.deleted': '“{name}” dihapus.',
|
||
|
||
// Panduan
|
||
'help.boxTitle': 'Cara kerja Express',
|
||
'help.boxBody': 'Empat langkah dari materi sampai konten jadi.',
|
||
'help.open': 'Lihat panduan',
|
||
'help.title': 'Cara kerja Learning Lab Express',
|
||
'help.subtitle': 'Otak → Perintah → Jadwal → Hasil',
|
||
'help.brainTitle': 'Otak',
|
||
'help.brainBody': 'Unggah materi pelatihan. Setelah pengetahuannya dibangun, AI membuat semua konten dari situ.',
|
||
'help.instructionTitle': 'Perintah',
|
||
'help.instructionBody': 'Tulis kebutuhan Anda dalam satu kalimat, misalnya “3 kuis per minggu dan 1 video per minggu soal leadership”. Periksa jadwalnya, lalu setujui.',
|
||
'help.scheduleTitle': 'Jadwal',
|
||
'help.scheduleBody': 'Jadwal berjalan sendiri. Jeda, ganti topik, atau produksi sekarang; masing-masing tetap dalam batas kreditnya.',
|
||
'help.resultsTitle': 'Hasil',
|
||
'help.resultsBody': 'Tinjau hasilnya, setujui atau tolak, lalu unduh: PDF, Markdown, PowerPoint, MP4, naskah audio, atau SCORM untuk LMS.',
|
||
'help.note': 'Konten dibuat AI: selalu tinjau sebelum dipakai. Kredit diambil dari ruang kerja organisasi Anda.',
|
||
'help.done': 'Mengerti',
|
||
|
||
'err.unreachable': 'Engine learning-lab tidak dapat dihubungi.',
|
||
'err.generic': 'Permintaan ke engine gagal.',
|
||
'err.credits': 'Kredit tidak cukup: butuh {required}, saldo {balance}.',
|
||
'err.creditsShort': 'Kredit tidak cukup.',
|
||
'err.docFailed': 'Dokumen gagal diproses.',
|
||
'err.timeout': 'Pemrosesan dokumen melebihi batas waktu.',
|
||
'err.researchFailed': 'Riset gagal dijalankan ({code}).',
|
||
}
|
||
|
||
const dictionaries: Record<Lang, Record<Key, string>> = { en, id }
|
||
const STORAGE = 'lle-lang'
|
||
|
||
function initial(): Lang {
|
||
try {
|
||
const saved = localStorage.getItem(STORAGE)
|
||
if (saved === 'en' || saved === 'id') return saved
|
||
} catch { /* storage blocked: fall back to the default */ }
|
||
return 'en'
|
||
}
|
||
|
||
let lang: Lang = initial()
|
||
// The language of the text being built right now, when it differs from the UI (see inLang).
|
||
let override: Lang | null = null
|
||
const listeners = new Set<() => void>()
|
||
if (typeof document !== 'undefined') document.documentElement.lang = lang
|
||
|
||
export const getLang = () => lang
|
||
|
||
export function setLang(next: Lang) {
|
||
if (next === lang) return
|
||
lang = next
|
||
if (typeof document !== 'undefined') document.documentElement.lang = next
|
||
try { localStorage.setItem(STORAGE, next) } catch { /* per-device preference only */ }
|
||
listeners.forEach(l => l())
|
||
}
|
||
|
||
export function t(key: Key, vars?: Record<string, string | number>): string {
|
||
const text = dictionaries[override ?? lang][key] ?? en[key]
|
||
return vars ? text.replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m)) : text
|
||
}
|
||
|
||
/** Runs fn with t() in another language, e.g. the agent answering in the language the user writes in. */
|
||
export function inLang<T>(l: Lang, fn: () => T): T {
|
||
const previous = override
|
||
override = l
|
||
try { return fn() } finally { override = previous }
|
||
}
|
||
|
||
/** Re-renders the component when the language changes; returns the active language. */
|
||
export function useLang(): Lang {
|
||
return useSyncExternalStore(cb => { listeners.add(cb); return () => listeners.delete(cb) }, getLang)
|
||
}
|
||
|
||
/** BCP 47 locale for dates and numbers. */
|
||
export const locale = () => (lang === 'id' ? 'id-ID' : 'en-US')
|