refactor: separate UI translations from CV data

- Created separate ui-en.json and ui-es.json files for UI strings
- Removed 'ui' section from cv-en.json and cv-es.json
- Added LoadUI() function to load UI translations separately
- Updated handlers to load UI data independently from CV data
- Updated template to use .UI instead of .CV.UI

This separation follows proper concerns:
- CV JSON files contain only professional CV content
- UI JSON files contain only application interface strings
- Each can be updated independently without affecting the other
This commit is contained in:
juanatsap
2025-11-09 21:03:39 +00:00
parent de0c443764
commit 13c59c3699
9 changed files with 74 additions and 34 deletions
-13
View File
@@ -911,19 +911,6 @@
"other": {
"driverLicense": "<strong>Type B</strong>"
},
"ui": {
"infoModal": {
"title": "About this CV",
"description": "This interactive CV was built by myself with <strong>Go + HTMX</strong>, showcasing modern hypermedia architecture without heavy JavaScript frameworks.",
"techStack": {
"goHono": "Go + Hono",
"htmx": "HTMX",
"html5": "Semantic HTML5",
"css3": "Pure CSS3"
},
"viewSource": "View Source Code"
}
},
"meta": {
"version": "2025-11-09",
"lastUpdated": "2025-11-08",
-13
View File
@@ -916,19 +916,6 @@
"other": {
"driverLicense": "<strong>Tipo B</strong>"
},
"ui": {
"infoModal": {
"title": "Acerca de este CV",
"description": "Este CV interactivo fue construido por mí mismo con <strong>Go + HTMX</strong>, demostrando arquitectura moderna de hipermedia sin frameworks pesados de JavaScript.",
"techStack": {
"goHono": "Go + Hono",
"htmx": "HTMX",
"html5": "HTML5 Semántico",
"css3": "CSS3 Puro"
},
"viewSource": "Ver código fuente"
}
},
"meta": {
"version": "2025-11-09",
"lastUpdated": "2025-11-08",
+13
View File
@@ -0,0 +1,13 @@
{
"infoModal": {
"title": "About this CV",
"description": "This interactive CV was built by myself with <strong>Go + HTMX</strong>, showcasing modern hypermedia architecture without heavy JavaScript frameworks.",
"techStack": {
"goHono": "Go + Hono",
"htmx": "HTMX",
"html5": "Semantic HTML5",
"css3": "Pure CSS3"
},
"viewSource": "View Source Code"
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"infoModal": {
"title": "Acerca de este CV",
"description": "Este CV interactivo fue construido por mí mismo con <strong>Go + HTMX</strong>, demostrando arquitectura moderna de hipermedia sin frameworks pesados de JavaScript.",
"techStack": {
"goHono": "Go + Hono",
"htmx": "HTMX",
"html5": "HTML5 Semántico",
"css3": "CSS3 Puro"
},
"viewSource": "Ver código fuente"
}
}
+16
View File
@@ -51,6 +51,13 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
return
}
// Load UI translations
ui, err := models.LoadUI(lang)
if err != nil {
HandleError(w, r, DataLoadError(err, "UI"))
return
}
// Calculate duration for each experience
for i := range cv.Experience {
cv.Experience[i].Duration = calculateDuration(
@@ -78,6 +85,7 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
// Prepare template data
data := map[string]interface{}{
"CV": cv,
"UI": ui,
"Lang": lang,
"SkillsLeft": skillsLeft,
"SkillsRight": skillsRight,
@@ -120,6 +128,13 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
return
}
// Load UI translations
ui, err := models.LoadUI(lang)
if err != nil {
HandleError(w, r, DataLoadError(err, "UI"))
return
}
// Calculate duration for each experience
for i := range cv.Experience {
cv.Experience[i].Duration = calculateDuration(
@@ -147,6 +162,7 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
// Prepare template data
data := map[string]interface{}{
"CV": cv,
"UI": ui,
"Lang": lang,
"SkillsLeft": skillsLeft,
"SkillsRight": skillsRight,
+25 -1
View File
@@ -22,7 +22,6 @@ type CV struct {
Courses []Course `json:"courses"`
References []Reference `json:"references"`
Other Other `json:"other"`
UI UI `json:"ui"`
Meta Meta `json:"meta"`
}
@@ -212,3 +211,28 @@ func LoadCV(lang string) (*CV, error) {
return &cv, nil
}
// LoadUI loads UI translations from a JSON file for the specified language
func LoadUI(lang string) (*UI, error) {
// Validate language
if lang != "en" && lang != "es" {
return nil, fmt.Errorf("unsupported language: %s", lang)
}
// Determine which JSON file to load
filename := fmt.Sprintf("data/ui-%s.json", lang)
// Read the JSON file
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading file %s: %w", filename, err)
}
// Parse JSON
var ui UI
if err := json.Unmarshal(data, &ui); err != nil {
return nil, fmt.Errorf("error parsing JSON: %w", err)
}
return &ui, nil
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

+7 -7
View File
@@ -297,37 +297,37 @@
</button>
<div class="info-modal-header">
<h2>{{.CV.UI.InfoModal.Title}}</h2>
<h2>{{.UI.InfoModal.Title}}</h2>
<div class="info-modal-cv-title">CV {{.CurrentYear}} - {JAMR}</div>
</div>
<div class="info-modal-body">
<p class="info-modal-description">
{{.CV.UI.InfoModal.Description}}
{{.UI.InfoModal.Description}}
</p>
<div class="info-modal-tech">
<div class="info-tech-item">
<iconify-icon icon="mdi:language-go" width="32" height="32"></iconify-icon>
<span>{{.CV.UI.InfoModal.TechStack.GoHono}}</span>
<span>{{.UI.InfoModal.TechStack.GoHono}}</span>
</div>
<div class="info-tech-item">
<iconify-icon icon="mdi:lightning-bolt" width="32" height="32"></iconify-icon>
<span>{{.CV.UI.InfoModal.TechStack.HTMX}}</span>
<span>{{.UI.InfoModal.TechStack.HTMX}}</span>
</div>
<div class="info-tech-item">
<iconify-icon icon="mdi:language-html5" width="32" height="32"></iconify-icon>
<span>{{.CV.UI.InfoModal.TechStack.HTML5}}</span>
<span>{{.UI.InfoModal.TechStack.HTML5}}</span>
</div>
<div class="info-tech-item">
<iconify-icon icon="mdi:language-css3" width="32" height="32"></iconify-icon>
<span>{{.CV.UI.InfoModal.TechStack.CSS3}}</span>
<span>{{.UI.InfoModal.TechStack.CSS3}}</span>
</div>
</div>
<a href="https://github.com/juanatsap/cv-site" target="_blank" rel="noopener noreferrer" class="info-modal-github">
<iconify-icon icon="mdi:github" width="24" height="24"></iconify-icon>
<span>{{.CV.UI.InfoModal.ViewSource}}</span>
<span>{{.UI.InfoModal.ViewSource}}</span>
<iconify-icon icon="mdi:arrow-right" width="20" height="20"></iconify-icon>
</a>
</div>