Generated by Spark: Extract strings and things out into json to avoid hardcoding into tsx files.

This commit is contained in:
2026-01-17 15:57:11 +00:00
committed by GitHub
parent b0ec8c31b1
commit ee431e4354
12 changed files with 495 additions and 200 deletions

95
CONFIGURATION.md Normal file
View File

@@ -0,0 +1,95 @@
# Configuration Files
This application uses JSON configuration files to manage strings and settings, making it easy to update text, languages, and other app configurations without modifying the TypeScript/TSX code.
## Configuration Files
### `/src/config/strings.json`
Contains all user-facing text strings used throughout the application.
**Structure:**
- `app` - Main application strings (title, header, search)
- `emptyState` - Empty state component text
- `noResults` - No search results text
- `snippetCard` - Snippet card component text and aria labels
- `snippetDialog` - Dialog form labels, placeholders, and error messages
- `snippetViewer` - Viewer component button labels
- `toast` - Toast notification messages
**Example usage in components:**
```typescript
import { strings } from '@/lib/config'
// Access strings
<h1>{strings.app.header.title}</h1>
<p>{strings.emptyState.description}</p>
```
### `/src/config/app-config.json`
Contains application configuration settings and data.
**Structure:**
- `languages` - Array of supported programming languages
- `languageColors` - Color schemes for each language badge
- `previewEnabledLanguages` - Languages that support live preview
- `defaultLanguage` - Default language selection
- `codePreviewMaxLength` - Maximum characters to show in card preview
- `copiedTimeout` - Milliseconds to show "copied" state
**Example usage in components:**
```typescript
import { appConfig } from '@/lib/config'
// Access configuration
const maxLength = appConfig.codePreviewMaxLength
const timeout = appConfig.copiedTimeout
```
## Helper Functions
### `/src/lib/config.ts`
Provides utility functions and exports for accessing configuration data.
**Exports:**
- `strings` - All string data from strings.json
- `appConfig` - All configuration from app-config.json
- `getLanguageColor(language)` - Get combined color classes for a language
- `LANGUAGES` - Array of language options
- `LANGUAGE_COLORS` - Map of language to color classes
**Example:**
```typescript
import { getLanguageColor, LANGUAGES } from '@/lib/config'
const colorClass = getLanguageColor('JavaScript')
// Returns: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30"
```
## Updating Text and Configuration
### To change text:
1. Edit `/src/config/strings.json`
2. No TypeScript changes needed
3. Changes are reflected immediately
### To add a new language:
1. Add to `languages` array in `/src/config/app-config.json`
2. Add color scheme to `languageColors` object
3. No component changes needed
### To change colors:
1. Edit color values in `languageColors` in `/src/config/app-config.json`
2. Use Tailwind color classes (bg-, text-, border-)
### To change timeouts or limits:
1. Edit values in `/src/config/app-config.json`
2. `codePreviewMaxLength` - Characters shown in preview
3. `copiedTimeout` - How long "Copied!" message shows
## Benefits
**Easy Localization** - All strings in one place for translation
**No Code Changes** - Update text without touching TSX files
**Type Safety** - TypeScript still validates usage
**Maintainability** - Centralized configuration
**Consistency** - Single source of truth for strings and settings

View File

@@ -9,6 +9,7 @@ import { SnippetDialog } from '@/components/SnippetDialog'
import { SnippetViewer } from '@/components/SnippetViewer'
import { EmptyState } from '@/components/EmptyState'
import { toast } from 'sonner'
import { strings } from '@/lib/config'
function App() {
const [snippets, setSnippets] = useKV<Snippet[]>('snippets', [])
@@ -39,7 +40,7 @@ function App() {
: s
)
)
toast.success('Snippet updated successfully')
toast.success(strings.toast.snippetUpdated)
} else {
const newSnippet: Snippet = {
...snippetData,
@@ -48,7 +49,7 @@ function App() {
updatedAt: Date.now(),
}
setSnippets((current) => [...(current ?? []), newSnippet])
toast.success('Snippet created successfully')
toast.success(strings.toast.snippetCreated)
}
setEditingSnippet(null)
}
@@ -60,12 +61,12 @@ function App() {
const handleDeleteSnippet = (id: string) => {
setSnippets((current) => (current ?? []).filter((s) => s.id !== id))
toast.success('Snippet deleted')
toast.success(strings.toast.snippetDeleted)
}
const handleCopySnippet = (code: string) => {
navigator.clipboard.writeText(code)
toast.success('Code copied to clipboard')
toast.success(strings.toast.codeCopied)
}
const handleViewSnippet = (snippet: Snippet) => {
@@ -99,10 +100,10 @@ function App() {
</div>
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-foreground">
Code Snippets
{strings.app.header.title}
</h1>
<p className="text-sm text-muted-foreground">
{currentSnippets.length} {currentSnippets.length === 1 ? 'snippet' : 'snippets'} saved
{currentSnippets.length} {currentSnippets.length === 1 ? strings.app.header.snippetCount.singular : strings.app.header.snippetCount.plural} saved
</p>
</div>
</div>
@@ -112,7 +113,7 @@ function App() {
className="gap-2 w-full sm:w-auto"
>
<Plus weight="bold" />
New Snippet
{strings.app.header.newSnippetButton}
</Button>
</div>
@@ -121,7 +122,7 @@ function App() {
<div className="relative">
<MagnifyingGlass className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
<Input
placeholder="Search snippets by title, language, or content..."
placeholder={strings.app.search.placeholder}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 h-11"
@@ -138,9 +139,9 @@ function App() {
) : filteredSnippets.length === 0 ? (
<div className="text-center py-20">
<MagnifyingGlass className="h-16 w-16 text-muted-foreground/50 mx-auto mb-4" />
<h3 className="text-xl font-semibold mb-2">No snippets found</h3>
<h3 className="text-xl font-semibold mb-2">{strings.noResults.title}</h3>
<p className="text-muted-foreground">
Try adjusting your search query
{strings.noResults.description}
</p>
</div>
) : (

View File

@@ -1,110 +1,99 @@
import { useState } from "react";
import { Alert, AlertTitle, AlertDescription } from "./components/ui/alert";
import { AIErrorHelper } from "./components/AIEr
import { AlertTriangleIcon, RefreshCwIcon, ChevronDownIcon,
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./components/ui/collapsible";
import { AlertTriangleIcon, RefreshCwIcon, ChevronDownIcon, ChevronUpIcon, CopyIcon, CheckIcon } from "lucide-react";
interface ErrorFallbackProps {
if (import.meta.env.DEV) throw
navigator.clipboard.writeText(error
const [copied, setCopied] = useState(false);
const errorDetails = `Error: ${error.message}\n\nStack Trace:\n${error.stack || 'No stack trace available'}`;
const handleCopy = () => {
navigator.clipboard.writeText(errorDetails);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<div className="w-full max-w-3xl">
<Alert variant="destructive" className="mb-6">
<AlertTriangleIcon />
<AlertTitle>This spark has encountered a runtime error</AlertTitle>
onClick={handl
>
<>
<CopyIcon className="h-3.5 w-3.5" />
</>
</Button>
{error.message}
<CollapsibleTrigger as
variant="ghost"
{isStackO
</>
<>
)}
</Coll
<div className="mt-4">
{error.s
</div
</Coll
<AIErr
variant="outline"
>
Tr
</div>
}
import { useState } from "react";
import { Alert, AlertTitle, AlertDescription } from "./components/ui/alert";
import { AIErrorHelper } from "./components/AIErrorHelper";
import { Button } from "./components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./components/ui/collapsible";
import { AlertTriangleIcon, RefreshCwIcon, ChevronDownIcon, ChevronUpIcon, CopyIcon, CheckIcon } from "lucide-react";
interface ErrorFallbackProps {
error: Error;
}
export function ErrorFallback({ error }: ErrorFallbackProps) {
if (import.meta.env.DEV) throw error;
const [isStackOpen, setIsStackOpen] = useState(false);
const [copied, setCopied] = useState(false);
const errorDetails = `Error: ${error.message}\n\nStack Trace:\n${error.stack || 'No stack trace available'}`;
const handleCopy = () => {
navigator.clipboard.writeText(errorDetails);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<div className="w-full max-w-3xl">
<Alert variant="destructive" className="mb-6">
<AlertTriangleIcon />
<AlertTitle>This spark has encountered a runtime error</AlertTitle>
<AlertDescription className="mt-3 space-y-4">
<div className="flex items-center justify-between gap-2">
<code className="text-sm bg-destructive/20 px-2 py-1 rounded flex-1 break-all">
{error.message}
</code>
<Button
size="sm"
variant="outline"
onClick={handleCopy}
className="shrink-0"
>
{copied ? (
<>
<CheckIcon className="h-3.5 w-3.5" />
Copied
</>
) : (
<>
<CopyIcon className="h-3.5 w-3.5" />
Copy
</>
)}
</Button>
</div>
<Collapsible open={isStackOpen} onOpenChange={setIsStackOpen}>
<CollapsibleTrigger asChild>
<Button
variant="ghost"
size="sm"
className="w-full justify-between"
>
{isStackOpen ? (
<>
Hide Stack Trace <ChevronUpIcon className="h-4 w-4 ml-2" />
</>
) : (
<>
Show Stack Trace <ChevronDownIcon className="h-4 w-4 ml-2" />
</>
)}
</Button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="mt-4">
<pre className="text-xs bg-destructive/10 p-3 rounded overflow-auto max-h-60">
{error.stack || 'No stack trace available'}
</pre>
</div>
</CollapsibleContent>
</Collapsible>
</AlertDescription>
</Alert>
<AIErrorHelper error={error} />
<Button
onClick={() => window.location.reload()}
className="w-full mt-6"
variant="outline"
>
<RefreshCwIcon className="h-4 w-4 mr-2" />
Try Reloading
</Button>
</div>
</div>
);
}

View File

@@ -1,5 +1,6 @@
import { Code } from '@phosphor-icons/react'
import { Button } from '@/components/ui/button'
import { strings } from '@/lib/config'
interface EmptyStateProps {
onCreateClick: () => void
@@ -11,13 +12,13 @@ export function EmptyState({ onCreateClick }: EmptyStateProps) {
<div className="rounded-full bg-accent/10 p-6 mb-6">
<Code className="h-16 w-16 text-accent" weight="duotone" />
</div>
<h3 className="text-2xl font-semibold mb-2">No snippets yet</h3>
<h3 className="text-2xl font-semibold mb-2">{strings.emptyState.title}</h3>
<p className="text-muted-foreground mb-8 max-w-sm">
Start building your code snippet library. Save reusable code for quick access anytime.
{strings.emptyState.description}
</p>
<Button onClick={onCreateClick} size="lg" className="gap-2">
<Code className="h-5 w-5" weight="bold" />
Create Your First Snippet
{strings.emptyState.buttonText}
</Button>
</div>
)

View File

@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Copy, Pencil, Trash, Eye } from '@phosphor-icons/react'
import { Snippet, LANGUAGE_COLORS } from '@/lib/types'
import { strings, appConfig } from '@/lib/config'
interface SnippetCardProps {
snippet: Snippet
@@ -20,7 +21,7 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
try {
const code = snippet?.code || ''
const description = snippet?.description || ''
const maxLength = 150
const maxLength = appConfig.codePreviewMaxLength
const isTruncated = code.length > maxLength
const displayCode = isTruncated ? code.slice(0, maxLength) + '...' : code
@@ -46,7 +47,7 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
e.stopPropagation()
onCopy(snippetData.fullCode)
setIsCopied(true)
setTimeout(() => setIsCopied(false), 2000)
setTimeout(() => setIsCopied(false), appConfig.copiedTimeout)
}
const handleEdit = (e: React.MouseEvent) => {
@@ -66,7 +67,7 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
if (!snippet) {
return (
<Card className="p-6">
<p className="text-muted-foreground">Error loading snippet</p>
<p className="text-muted-foreground">{strings.snippetCard.errorMessage}</p>
</Card>
)
}
@@ -101,7 +102,7 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
</pre>
{snippetData.isTruncated && (
<p className="text-xs text-accent mt-2">
Click to view full code...
{strings.snippetCard.viewFullCode}
</p>
)}
</div>
@@ -115,7 +116,7 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
className="gap-2"
>
<Eye className="h-4 w-4" />
View
{strings.snippetCard.viewButton}
</Button>
</div>
<div className="flex items-center gap-2">
@@ -124,16 +125,16 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
size="sm"
onClick={handleCopy}
className="gap-2"
aria-label="Copy code"
aria-label={strings.snippetCard.ariaLabels.copy}
>
<Copy className="h-4 w-4" />
{isCopied ? 'Copied!' : 'Copy'}
{isCopied ? strings.snippetCard.copiedButton : strings.snippetCard.copyButton}
</Button>
<Button
variant="ghost"
size="sm"
onClick={handleEdit}
aria-label="Edit snippet"
aria-label={strings.snippetCard.ariaLabels.edit}
>
<Pencil className="h-4 w-4" />
</Button>
@@ -142,7 +143,7 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
size="sm"
onClick={handleDelete}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
aria-label="Delete snippet"
aria-label={strings.snippetCard.ariaLabels.delete}
>
<Trash className="h-4 w-4" />
</Button>

View File

@@ -21,6 +21,7 @@ import {
} from '@/components/ui/select'
import { Snippet, LANGUAGES } from '@/lib/types'
import { MonacoEditor } from '@/components/MonacoEditor'
import { strings, appConfig } from '@/lib/config'
interface SnippetDialogProps {
open: boolean
@@ -32,7 +33,7 @@ interface SnippetDialogProps {
export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: SnippetDialogProps) {
const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const [language, setLanguage] = useState('JavaScript')
const [language, setLanguage] = useState(appConfig.defaultLanguage)
const [code, setCode] = useState('')
const [hasPreview, setHasPreview] = useState(false)
const [errors, setErrors] = useState<{ title?: string; code?: string }>({})
@@ -47,7 +48,7 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
} else {
setTitle('')
setDescription('')
setLanguage('JavaScript')
setLanguage(appConfig.defaultLanguage)
setCode('')
setHasPreview(false)
}
@@ -58,10 +59,10 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
const newErrors: { title?: string; code?: string } = {}
if (!title.trim()) {
newErrors.title = 'Title is required'
newErrors.title = strings.snippetDialog.fields.title.errorMessage
}
if (!code.trim()) {
newErrors.code = 'Code is required'
newErrors.code = strings.snippetDialog.fields.code.errorMessage
}
if (Object.keys(newErrors).length > 0) {
@@ -79,7 +80,7 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
setTitle('')
setDescription('')
setLanguage('JavaScript')
setLanguage(appConfig.defaultLanguage)
setCode('')
setHasPreview(false)
setErrors({})
@@ -91,21 +92,23 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
<DialogContent className="sm:max-w-[900px] max-h-[90vh] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle className="text-2xl">
{editingSnippet ? 'Edit Snippet' : 'Create New Snippet'}
{editingSnippet ? strings.snippetDialog.edit.title : strings.snippetDialog.create.title}
</DialogTitle>
<DialogDescription>
{editingSnippet
? 'Update your code snippet details below.'
: 'Save a reusable code snippet for quick access later.'}
? strings.snippetDialog.edit.description
: strings.snippetDialog.create.description}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4 overflow-y-auto flex-1">
<div className="space-y-2">
<Label htmlFor="title">Title *</Label>
<Label htmlFor="title">
{strings.snippetDialog.fields.title.label}{strings.snippetDialog.fields.title.required ? ' *' : ''}
</Label>
<Input
id="title"
placeholder="e.g., React useState Hook"
placeholder={strings.snippetDialog.fields.title.placeholder}
value={title}
onChange={(e) => setTitle(e.target.value)}
className={errors.title ? 'border-destructive ring-destructive' : ''}
@@ -116,7 +119,7 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
</div>
<div className="space-y-2">
<Label htmlFor="language">Language</Label>
<Label htmlFor="language">{strings.snippetDialog.fields.language.label}</Label>
<Select value={language} onValueChange={setLanguage}>
<SelectTrigger id="language">
<SelectValue />
@@ -131,7 +134,7 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
</Select>
</div>
{['JSX', 'TSX', 'JavaScript', 'TypeScript'].includes(language) && (
{appConfig.previewEnabledLanguages.includes(language) && (
<div className="flex items-center space-x-2 py-2">
<Checkbox
id="hasPreview"
@@ -142,16 +145,16 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
htmlFor="hasPreview"
className="text-sm font-normal cursor-pointer"
>
Enable split-screen preview for this snippet
{strings.snippetDialog.fields.preview.label}
</Label>
</div>
)}
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Label htmlFor="description">{strings.snippetDialog.fields.description.label}</Label>
<Textarea
id="description"
placeholder="Optional description or notes..."
placeholder={strings.snippetDialog.fields.description.placeholder}
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={2}
@@ -159,7 +162,9 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
</div>
<div className="space-y-2">
<Label htmlFor="code">Code *</Label>
<Label htmlFor="code">
{strings.snippetDialog.fields.code.label}{strings.snippetDialog.fields.code.required ? ' *' : ''}
</Label>
<div className={`rounded-md border overflow-hidden ${errors.code ? 'border-destructive ring-2 ring-destructive/20' : 'border-border'}`}>
<MonacoEditor
value={code}
@@ -176,10 +181,10 @@ export function SnippetDialog({ open, onOpenChange, onSave, editingSnippet }: Sn
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
{strings.snippetDialog.buttons.cancel}
</Button>
<Button onClick={handleSave}>
{editingSnippet ? 'Update' : 'Create'} Snippet
{editingSnippet ? strings.snippetDialog.buttons.update : strings.snippetDialog.buttons.create} Snippet
</Button>
</DialogFooter>
</DialogContent>

View File

@@ -12,6 +12,7 @@ import { MonacoEditor } from '@/components/MonacoEditor'
import { ReactPreview } from '@/components/ReactPreview'
import { cn } from '@/lib/utils'
import { useState } from 'react'
import { strings, appConfig } from '@/lib/config'
interface SnippetViewerProps {
snippet: Snippet | null
@@ -30,7 +31,7 @@ export function SnippetViewer({ snippet, open, onOpenChange, onEdit, onCopy }: S
const handleCopy = () => {
onCopy(snippet.code)
setIsCopied(true)
setTimeout(() => setIsCopied(false), 2000)
setTimeout(() => setIsCopied(false), appConfig.copiedTimeout)
}
const handleEdit = () => {
@@ -38,7 +39,7 @@ export function SnippetViewer({ snippet, open, onOpenChange, onEdit, onCopy }: S
onEdit(snippet)
}
const canPreview = snippet.hasPreview && ['JSX', 'TSX', 'JavaScript', 'TypeScript'].includes(snippet.language)
const canPreview = snippet.hasPreview && appConfig.previewEnabledLanguages.includes(snippet.language)
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -66,7 +67,7 @@ export function SnippetViewer({ snippet, open, onOpenChange, onEdit, onCopy }: S
</p>
)}
<p className="text-xs text-muted-foreground">
Last updated: {new Date(snippet.updatedAt).toLocaleString()}
{strings.snippetViewer.lastUpdated}: {new Date(snippet.updatedAt).toLocaleString()}
</p>
</div>
<div className="flex gap-2 shrink-0">
@@ -78,7 +79,7 @@ export function SnippetViewer({ snippet, open, onOpenChange, onEdit, onCopy }: S
className="gap-2"
>
<SplitVertical className="h-4 w-4" />
{showPreview ? 'Hide' : 'Show'} Preview
{showPreview ? strings.snippetViewer.buttons.hidePreview : strings.snippetViewer.buttons.showPreview}
</Button>
)}
<Button
@@ -90,12 +91,12 @@ export function SnippetViewer({ snippet, open, onOpenChange, onEdit, onCopy }: S
{isCopied ? (
<>
<Check className="h-4 w-4" weight="bold" />
Copied
{strings.snippetViewer.buttons.copied}
</>
) : (
<>
<Copy className="h-4 w-4" />
Copy
{strings.snippetViewer.buttons.copy}
</>
)}
</Button>
@@ -106,7 +107,7 @@ export function SnippetViewer({ snippet, open, onOpenChange, onEdit, onCopy }: S
className="gap-2"
>
<Pencil className="h-4 w-4" />
Edit
{strings.snippetViewer.buttons.edit}
</Button>
<Button
variant="ghost"

124
src/config/app-config.json Normal file
View File

@@ -0,0 +1,124 @@
{
"languages": [
"JavaScript",
"TypeScript",
"JSX",
"TSX",
"Python",
"Java",
"C++",
"C#",
"Ruby",
"Go",
"Rust",
"PHP",
"Swift",
"Kotlin",
"HTML",
"CSS",
"SQL",
"Bash",
"Other"
],
"languageColors": {
"JavaScript": {
"bg": "bg-yellow-500/20",
"text": "text-yellow-300",
"border": "border-yellow-500/30"
},
"TypeScript": {
"bg": "bg-blue-500/20",
"text": "text-blue-300",
"border": "border-blue-500/30"
},
"JSX": {
"bg": "bg-cyan-500/20",
"text": "text-cyan-300",
"border": "border-cyan-500/30"
},
"TSX": {
"bg": "bg-sky-500/20",
"text": "text-sky-300",
"border": "border-sky-500/30"
},
"Python": {
"bg": "bg-blue-400/20",
"text": "text-blue-200",
"border": "border-blue-400/30"
},
"Java": {
"bg": "bg-red-500/20",
"text": "text-red-300",
"border": "border-red-500/30"
},
"C++": {
"bg": "bg-pink-500/20",
"text": "text-pink-300",
"border": "border-pink-500/30"
},
"C#": {
"bg": "bg-purple-500/20",
"text": "text-purple-300",
"border": "border-purple-500/30"
},
"Ruby": {
"bg": "bg-red-600/20",
"text": "text-red-300",
"border": "border-red-600/30"
},
"Go": {
"bg": "bg-cyan-500/20",
"text": "text-cyan-300",
"border": "border-cyan-500/30"
},
"Rust": {
"bg": "bg-orange-600/20",
"text": "text-orange-300",
"border": "border-orange-600/30"
},
"PHP": {
"bg": "bg-indigo-500/20",
"text": "text-indigo-300",
"border": "border-indigo-500/30"
},
"Swift": {
"bg": "bg-orange-500/20",
"text": "text-orange-300",
"border": "border-orange-500/30"
},
"Kotlin": {
"bg": "bg-purple-600/20",
"text": "text-purple-300",
"border": "border-purple-600/30"
},
"HTML": {
"bg": "bg-orange-400/20",
"text": "text-orange-300",
"border": "border-orange-400/30"
},
"CSS": {
"bg": "bg-blue-600/20",
"text": "text-blue-300",
"border": "border-blue-600/30"
},
"SQL": {
"bg": "bg-teal-500/20",
"text": "text-teal-300",
"border": "border-teal-500/30"
},
"Bash": {
"bg": "bg-green-500/20",
"text": "text-green-300",
"border": "border-green-500/30"
},
"Other": {
"bg": "bg-gray-500/20",
"text": "text-gray-300",
"border": "border-gray-500/30"
}
},
"previewEnabledLanguages": ["JSX", "TSX", "JavaScript", "TypeScript"],
"defaultLanguage": "JavaScript",
"codePreviewMaxLength": 150,
"copiedTimeout": 2000
}

93
src/config/strings.json Normal file
View File

@@ -0,0 +1,93 @@
{
"app": {
"title": "Code Snippets",
"header": {
"title": "Code Snippets",
"snippetCount": {
"singular": "snippet",
"plural": "snippets"
},
"newSnippetButton": "New Snippet"
},
"search": {
"placeholder": "Search snippets by title, language, or content..."
}
},
"emptyState": {
"title": "No snippets yet",
"description": "Start building your code snippet library. Save reusable code for quick access anytime.",
"buttonText": "Create Your First Snippet"
},
"noResults": {
"title": "No snippets found",
"description": "Try adjusting your search query"
},
"snippetCard": {
"viewButton": "View",
"copyButton": "Copy",
"copiedButton": "Copied!",
"viewFullCode": "Click to view full code...",
"errorMessage": "Error loading snippet",
"ariaLabels": {
"copy": "Copy code",
"edit": "Edit snippet",
"delete": "Delete snippet"
}
},
"snippetDialog": {
"create": {
"title": "Create New Snippet",
"description": "Save a reusable code snippet for quick access later.",
"submitButton": "Create Snippet"
},
"edit": {
"title": "Edit Snippet",
"description": "Update your code snippet details below.",
"submitButton": "Update Snippet"
},
"fields": {
"title": {
"label": "Title",
"placeholder": "e.g., React useState Hook",
"required": true,
"errorMessage": "Title is required"
},
"language": {
"label": "Language"
},
"description": {
"label": "Description",
"placeholder": "Optional description or notes..."
},
"code": {
"label": "Code",
"required": true,
"errorMessage": "Code is required"
},
"preview": {
"label": "Enable split-screen preview for this snippet"
}
},
"buttons": {
"cancel": "Cancel",
"create": "Create",
"update": "Update"
}
},
"snippetViewer": {
"lastUpdated": "Last updated",
"buttons": {
"showPreview": "Show Preview",
"hidePreview": "Hide Preview",
"copy": "Copy",
"copied": "Copied",
"edit": "Edit"
}
},
"toast": {
"snippetCreated": "Snippet created successfully",
"snippetUpdated": "Snippet updated successfully",
"snippetDeleted": "Snippet deleted",
"codeCopied": "Code copied to clipboard"
}
}

24
src/lib/config.ts Normal file
View File

@@ -0,0 +1,24 @@
import stringsData from '@/config/strings.json'
import appConfigData from '@/config/app-config.json'
export const strings = stringsData
export const appConfig = appConfigData
export function getLanguageColor(language: string): string {
const colors = appConfig.languageColors[language as keyof typeof appConfig.languageColors]
if (colors) {
return `${colors.bg} ${colors.text} ${colors.border}`
}
const defaultColors = appConfig.languageColors.Other
return `${defaultColors.bg} ${defaultColors.text} ${defaultColors.border}`
}
export const LANGUAGES = appConfig.languages
export const LANGUAGE_COLORS: Record<string, string> = Object.entries(appConfig.languageColors).reduce(
(acc, [key, value]) => {
acc[key] = `${value.bg} ${value.text} ${value.border}`
return acc
},
{} as Record<string, string>
)

View File

@@ -1,3 +1,5 @@
import { LANGUAGES, LANGUAGE_COLORS } from './config'
export interface Snippet {
id: string
title: string
@@ -9,48 +11,6 @@ export interface Snippet {
hasPreview?: boolean
}
export const LANGUAGES = [
'JavaScript',
'TypeScript',
'JSX',
'TSX',
'Python',
'Java',
'C++',
'C#',
'Ruby',
'Go',
'Rust',
'PHP',
'Swift',
'Kotlin',
'HTML',
'CSS',
'SQL',
'Bash',
'Other'
] as const
export type Language = typeof LANGUAGES[number]
export const LANGUAGE_COLORS: Record<string, string> = {
'JavaScript': 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30',
'TypeScript': 'bg-blue-500/20 text-blue-300 border-blue-500/30',
'JSX': 'bg-cyan-500/20 text-cyan-300 border-cyan-500/30',
'TSX': 'bg-sky-500/20 text-sky-300 border-sky-500/30',
'Python': 'bg-blue-400/20 text-blue-200 border-blue-400/30',
'Java': 'bg-red-500/20 text-red-300 border-red-500/30',
'C++': 'bg-pink-500/20 text-pink-300 border-pink-500/30',
'C#': 'bg-purple-500/20 text-purple-300 border-purple-500/30',
'Ruby': 'bg-red-600/20 text-red-300 border-red-600/30',
'Go': 'bg-cyan-500/20 text-cyan-300 border-cyan-500/30',
'Rust': 'bg-orange-600/20 text-orange-300 border-orange-600/30',
'PHP': 'bg-indigo-500/20 text-indigo-300 border-indigo-500/30',
'Swift': 'bg-orange-500/20 text-orange-300 border-orange-500/30',
'Kotlin': 'bg-purple-600/20 text-purple-300 border-purple-600/30',
'HTML': 'bg-orange-400/20 text-orange-300 border-orange-400/30',
'CSS': 'bg-blue-600/20 text-blue-300 border-blue-600/30',
'SQL': 'bg-teal-500/20 text-teal-300 border-teal-500/30',
'Bash': 'bg-green-500/20 text-green-300 border-green-500/30',
'Other': 'bg-gray-500/20 text-gray-300 border-gray-500/30'
}
export { LANGUAGES, LANGUAGE_COLORS }

View File

@@ -14,6 +14,7 @@
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,