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:
@@ -911,19 +911,6 @@
|
|||||||
"other": {
|
"other": {
|
||||||
"driverLicense": "<strong>Type B</strong>"
|
"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": {
|
"meta": {
|
||||||
"version": "2025-11-09",
|
"version": "2025-11-09",
|
||||||
"lastUpdated": "2025-11-08",
|
"lastUpdated": "2025-11-08",
|
||||||
|
|||||||
@@ -916,19 +916,6 @@
|
|||||||
"other": {
|
"other": {
|
||||||
"driverLicense": "<strong>Tipo B</strong>"
|
"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": {
|
"meta": {
|
||||||
"version": "2025-11-09",
|
"version": "2025-11-09",
|
||||||
"lastUpdated": "2025-11-08",
|
"lastUpdated": "2025-11-08",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,13 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load UI translations
|
||||||
|
ui, err := models.LoadUI(lang)
|
||||||
|
if err != nil {
|
||||||
|
HandleError(w, r, DataLoadError(err, "UI"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate duration for each experience
|
// Calculate duration for each experience
|
||||||
for i := range cv.Experience {
|
for i := range cv.Experience {
|
||||||
cv.Experience[i].Duration = calculateDuration(
|
cv.Experience[i].Duration = calculateDuration(
|
||||||
@@ -78,6 +85,7 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Prepare template data
|
// Prepare template data
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"CV": cv,
|
"CV": cv,
|
||||||
|
"UI": ui,
|
||||||
"Lang": lang,
|
"Lang": lang,
|
||||||
"SkillsLeft": skillsLeft,
|
"SkillsLeft": skillsLeft,
|
||||||
"SkillsRight": skillsRight,
|
"SkillsRight": skillsRight,
|
||||||
@@ -120,6 +128,13 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load UI translations
|
||||||
|
ui, err := models.LoadUI(lang)
|
||||||
|
if err != nil {
|
||||||
|
HandleError(w, r, DataLoadError(err, "UI"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate duration for each experience
|
// Calculate duration for each experience
|
||||||
for i := range cv.Experience {
|
for i := range cv.Experience {
|
||||||
cv.Experience[i].Duration = calculateDuration(
|
cv.Experience[i].Duration = calculateDuration(
|
||||||
@@ -147,6 +162,7 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Prepare template data
|
// Prepare template data
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"CV": cv,
|
"CV": cv,
|
||||||
|
"UI": ui,
|
||||||
"Lang": lang,
|
"Lang": lang,
|
||||||
"SkillsLeft": skillsLeft,
|
"SkillsLeft": skillsLeft,
|
||||||
"SkillsRight": skillsRight,
|
"SkillsRight": skillsRight,
|
||||||
|
|||||||
+25
-1
@@ -22,7 +22,6 @@ type CV struct {
|
|||||||
Courses []Course `json:"courses"`
|
Courses []Course `json:"courses"`
|
||||||
References []Reference `json:"references"`
|
References []Reference `json:"references"`
|
||||||
Other Other `json:"other"`
|
Other Other `json:"other"`
|
||||||
UI UI `json:"ui"`
|
|
||||||
Meta Meta `json:"meta"`
|
Meta Meta `json:"meta"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,3 +211,28 @@ func LoadCV(lang string) (*CV, error) {
|
|||||||
|
|
||||||
return &cv, nil
|
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 |
@@ -297,37 +297,37 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="info-modal-header">
|
<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 class="info-modal-cv-title">CV {{.CurrentYear}} - {JAMR}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-modal-body">
|
<div class="info-modal-body">
|
||||||
<p class="info-modal-description">
|
<p class="info-modal-description">
|
||||||
{{.CV.UI.InfoModal.Description}}
|
{{.UI.InfoModal.Description}}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="info-modal-tech">
|
<div class="info-modal-tech">
|
||||||
<div class="info-tech-item">
|
<div class="info-tech-item">
|
||||||
<iconify-icon icon="mdi:language-go" width="32" height="32"></iconify-icon>
|
<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>
|
||||||
<div class="info-tech-item">
|
<div class="info-tech-item">
|
||||||
<iconify-icon icon="mdi:lightning-bolt" width="32" height="32"></iconify-icon>
|
<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>
|
||||||
<div class="info-tech-item">
|
<div class="info-tech-item">
|
||||||
<iconify-icon icon="mdi:language-html5" width="32" height="32"></iconify-icon>
|
<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>
|
||||||
<div class="info-tech-item">
|
<div class="info-tech-item">
|
||||||
<iconify-icon icon="mdi:language-css3" width="32" height="32"></iconify-icon>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="https://github.com/juanatsap/cv-site" target="_blank" rel="noopener noreferrer" class="info-modal-github">
|
<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>
|
<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>
|
<iconify-icon icon="mdi:arrow-right" width="20" height="20"></iconify-icon>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user