Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m24s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 7m26s
Build and Release / Lint (push) Successful in 8m44s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 0s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 3m39s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m53s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h4m28s
Build and Release / Build Binary (linux/arm64) (push) Failing after 12m10s
Show loading spinner and message when AI content generation or translation is in progress. Disables submit button and hides form to prevent duplicate submissions. Adds Copilot icon to AI buttons. Marks file inputs with data-ays-ignore to prevent "unsaved changes" warnings. Improves UX by providing visual feedback during long-running AI operations.
376 lines
18 KiB
Handlebars
376 lines
18 KiB
Handlebars
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings pages")}}
|
|
<div class="user-main-content twelve wide column">
|
|
<h4 class="ui top attached header">
|
|
{{ctx.Locale.Tr "repo.settings.pages.languages"}}
|
|
</h4>
|
|
<div class="ui attached segment">
|
|
<form class="ui form" method="post">
|
|
{{.CsrfTokenHtml}}
|
|
<input type="hidden" name="action" value="update_i18n">
|
|
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.default_lang"}}</label>
|
|
<select name="default_lang" class="ui dropdown">
|
|
{{range $code, $name := .LanguageNames}}
|
|
<option value="{{$code}}" {{if eq $code $.Config.I18n.DefaultLang}}selected{{end}}>{{$name}}</option>
|
|
{{end}}
|
|
</select>
|
|
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.default_lang_help"}}</p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.enabled_languages"}}</label>
|
|
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.enabled_languages_help"}}</p>
|
|
<div class="grouped fields">
|
|
{{range $code, $name := .LanguageNames}}
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" name="languages" value="{{$code}}" {{if index $.EnabledLangs $code}}checked{{end}}>
|
|
<label>{{$name}} ({{$code}})</label>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.pages.save_languages"}}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
{{if .Config.I18n.Languages}}
|
|
<h4 class="ui top attached header tw-mt-4">
|
|
{{ctx.Locale.Tr "repo.settings.pages.translations"}}
|
|
</h4>
|
|
<div class="ui attached segment">
|
|
{{/* ── Action bar: language selector + buttons ── */}}
|
|
<div class="tw-flex tw-flex-wrap tw-gap-2 tw-items-center tw-mb-4">
|
|
<select id="lang-selector" class="ui dropdown" style="min-width:200px;">
|
|
{{range .Config.I18n.Languages}}
|
|
{{if ne . $.Config.I18n.DefaultLang}}
|
|
<option value="{{.}}">{{index $.LanguageNames .}} ({{.}}) {{if index $.TranslationMap .}}✓{{end}}</option>
|
|
{{end}}
|
|
{{end}}
|
|
</select>
|
|
{{if .AIEnabled}}
|
|
<form method="post" class="tw-inline-block" id="ai-translate-form">
|
|
{{.CsrfTokenHtml}}
|
|
<input type="hidden" name="action" value="ai_translate">
|
|
<input type="hidden" name="target_lang" id="ai-translate-lang" value="">
|
|
<button class="ui purple tiny button" type="submit">{{svg "octicon-copilot" 14}} {{ctx.Locale.Tr "repo.settings.pages.ai_translate"}}</button>
|
|
</form>
|
|
<form method="post" class="tw-inline-block" id="ai-translate-all-form">
|
|
{{.CsrfTokenHtml}}
|
|
<input type="hidden" name="action" value="ai_translate_all">
|
|
<button class="ui purple tiny button" type="submit">{{svg "octicon-copilot" 14}} {{ctx.Locale.Tr "repo.settings.pages.ai_translate_all"}}</button>
|
|
</form>
|
|
{{end}}
|
|
<form method="post" class="tw-inline-block" id="delete-translation-form" style="display:none;">
|
|
{{.CsrfTokenHtml}}
|
|
<input type="hidden" name="action" value="delete_translation">
|
|
<input type="hidden" name="target_lang" id="delete-translate-lang" value="">
|
|
<button class="ui red tiny button" type="submit">{{svg "octicon-trash" 14}} {{ctx.Locale.Tr "repo.settings.pages.delete_translation"}}</button>
|
|
</form>
|
|
</div>
|
|
<div id="ai-translate-loading" class="tw-hidden tw-py-4">
|
|
<div class="ui active centered inline loader"></div>
|
|
<p class="tw-text-center tw-mt-2" style="color:var(--color-text-light);">{{ctx.Locale.Tr "repo.settings.pages.ai_translating"}}</p>
|
|
</div>
|
|
|
|
{{/* ── Language panels (one per non-default language, only one visible at a time) ── */}}
|
|
{{range .Config.I18n.Languages}}
|
|
{{if ne . $.Config.I18n.DefaultLang}}
|
|
<div class="lang-panel" id="lang-panel-{{.}}" style="display:none;">
|
|
<form class="ui form" method="post">
|
|
{{$.CsrfTokenHtml}}
|
|
<input type="hidden" name="action" value="save_translation">
|
|
<input type="hidden" name="target_lang" value="{{.}}">
|
|
|
|
{{$trans := index $.TranslationMap .}}
|
|
|
|
{{/* ── Brand ── */}}
|
|
{{if or $.Config.Brand.Name $.Config.Brand.Tagline}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_brand"}}</h5>
|
|
{{if $.Config.Brand.Name}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_brand_name"}}</label>
|
|
<input name="trans_brand_name" value="{{if $trans}}{{$trans.BrandName}}{{end}}" placeholder="{{$.Config.Brand.Name}}">
|
|
</div>
|
|
{{end}}
|
|
{{if $.Config.Brand.Tagline}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_brand_tagline"}}</label>
|
|
<input name="trans_brand_tagline" value="{{if $trans}}{{$trans.BrandTagline}}{{end}}" placeholder="{{$.Config.Brand.Tagline}}">
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* ── Hero ── */}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_hero"}}</h5>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_headline"}}</label>
|
|
<input name="trans_headline" value="{{if $trans}}{{$trans.Headline}}{{end}}" placeholder="{{$.Config.Hero.Headline}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_subheadline"}}</label>
|
|
<input name="trans_subheadline" value="{{if $trans}}{{$trans.Subheadline}}{{end}}" placeholder="{{$.Config.Hero.Subheadline}}">
|
|
</div>
|
|
<div class="two fields">
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_primary_cta"}}</label>
|
|
<input name="trans_primary_cta" value="{{if $trans}}{{$trans.PrimaryCTA}}{{end}}" placeholder="{{$.Config.Hero.PrimaryCTA.Label}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_secondary_cta"}}</label>
|
|
<input name="trans_secondary_cta" value="{{if $trans}}{{$trans.SecondaryCTA}}{{end}}" placeholder="{{$.Config.Hero.SecondaryCTA.Label}}">
|
|
</div>
|
|
</div>
|
|
|
|
{{/* ── Stats ── */}}
|
|
{{if $.Config.Stats}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_stats"}}</h5>
|
|
{{range $i, $s := $.Config.Stats}}
|
|
<div class="two fields">
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_stat_value"}} — <em>{{$s.Value}}</em></label>
|
|
<input name="trans_stat_{{$i}}_value" value="{{if $trans}}{{index $trans.StatsValues $i}}{{end}}" placeholder="{{$s.Value}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_stat_label"}} — <em>{{$s.Label}}</em></label>
|
|
<input name="trans_stat_{{$i}}_label" value="{{if $trans}}{{index $trans.StatsLabels $i}}{{end}}" placeholder="{{$s.Label}}">
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* ── Value Props ── */}}
|
|
{{if $.Config.ValueProps}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_value_props"}}</h5>
|
|
{{range $i, $vp := $.Config.ValueProps}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_title"}} — <em>{{$vp.Title}}</em></label>
|
|
<input name="trans_vp_{{$i}}_title" value="{{if $trans}}{{index $trans.ValuePropTitles $i}}{{end}}" placeholder="{{$vp.Title}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_description"}}</label>
|
|
<input name="trans_vp_{{$i}}_desc" value="{{if $trans}}{{index $trans.ValuePropDescs $i}}{{end}}" placeholder="{{$vp.Description}}">
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* ── Features ── */}}
|
|
{{if $.Config.Features}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_features"}}</h5>
|
|
{{range $i, $f := $.Config.Features}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_title"}} — <em>{{$f.Title}}</em></label>
|
|
<input name="trans_feat_{{$i}}_title" value="{{if $trans}}{{index $trans.FeatureTitles $i}}{{end}}" placeholder="{{$f.Title}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_description"}}</label>
|
|
<input name="trans_feat_{{$i}}_desc" value="{{if $trans}}{{index $trans.FeatureDescs $i}}{{end}}" placeholder="{{$f.Description}}">
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* ── Testimonials ── */}}
|
|
{{if $.Config.SocialProof.Testimonials}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_testimonials"}}</h5>
|
|
{{range $i, $t := $.Config.SocialProof.Testimonials}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_quote"}} — <em>{{$t.Author}}</em></label>
|
|
<textarea name="trans_test_{{$i}}_quote" rows="2" placeholder="{{$t.Quote}}">{{if $trans}}{{index $trans.TestimonialQuotes $i}}{{end}}</textarea>
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_role"}}</label>
|
|
<input name="trans_test_{{$i}}_role" value="{{if $trans}}{{index $trans.TestimonialRoles $i}}{{end}}" placeholder="{{$t.Role}}">
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* ── Pricing ── */}}
|
|
{{if or $.Config.Pricing.Headline $.Config.Pricing.Plans}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_pricing"}}</h5>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_pricing_headline"}}</label>
|
|
<input name="trans_pricing_headline" value="{{if $trans}}{{$trans.PricingHeadline}}{{end}}" placeholder="{{$.Config.Pricing.Headline}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_pricing_subheadline"}}</label>
|
|
<input name="trans_pricing_subheadline" value="{{if $trans}}{{$trans.PricingSubheadline}}{{end}}" placeholder="{{$.Config.Pricing.Subheadline}}">
|
|
</div>
|
|
{{range $i, $p := $.Config.Pricing.Plans}}
|
|
<div class="three fields">
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_plan_name"}} — <em>{{$p.Name}}</em></label>
|
|
<input name="trans_plan_{{$i}}_name" value="{{if $trans}}{{index $trans.PlanNames $i}}{{end}}" placeholder="{{$p.Name}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_plan_period"}}</label>
|
|
<input name="trans_plan_{{$i}}_period" value="{{if $trans}}{{index $trans.PlanPeriods $i}}{{end}}" placeholder="{{$p.Period}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_plan_cta"}}</label>
|
|
<input name="trans_plan_{{$i}}_cta" value="{{if $trans}}{{index $trans.PlanCTAs $i}}{{end}}" placeholder="{{$p.CTA}}">
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* ── CTA Section ── */}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_cta"}}</h5>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_cta_headline"}}</label>
|
|
<input name="trans_cta_headline" value="{{if $trans}}{{$trans.CTAHeadline}}{{end}}" placeholder="{{$.Config.CTASection.Headline}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_cta_subheadline"}}</label>
|
|
<input name="trans_cta_subheadline" value="{{if $trans}}{{$trans.CTASubheadline}}{{end}}" placeholder="{{$.Config.CTASection.Subheadline}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_cta_button"}}</label>
|
|
<input name="trans_cta_button" value="{{if $trans}}{{$trans.CTAButton}}{{end}}" placeholder="{{$.Config.CTASection.Button.Label}}">
|
|
</div>
|
|
|
|
{{/* ── Blog ── */}}
|
|
{{if $.Config.Blog.Enabled}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_blog"}}</h5>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_blog_headline"}}</label>
|
|
<input name="trans_blog_headline" value="{{if $trans}}{{$trans.BlogHeadline}}{{end}}" placeholder="{{$.Config.Blog.Headline}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_blog_subheadline"}}</label>
|
|
<input name="trans_blog_subheadline" value="{{if $trans}}{{$trans.BlogSubheadline}}{{end}}" placeholder="{{$.Config.Blog.Subheadline}}">
|
|
</div>
|
|
{{if $.Config.Blog.CTAButton.Label}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_blog_cta"}}</label>
|
|
<input name="trans_blog_cta" value="{{if $trans}}{{$trans.BlogCTAButton}}{{end}}" placeholder="{{$.Config.Blog.CTAButton.Label}}">
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* ── Gallery ── */}}
|
|
{{if $.Config.Gallery.Enabled}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_gallery"}}</h5>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_gallery_headline"}}</label>
|
|
<input name="trans_gallery_headline" value="{{if $trans}}{{$trans.GalleryHeadline}}{{end}}" placeholder="{{$.Config.Gallery.Headline}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_gallery_subheadline"}}</label>
|
|
<input name="trans_gallery_subheadline" value="{{if $trans}}{{$trans.GallerySubheadline}}{{end}}" placeholder="{{$.Config.Gallery.Subheadline}}">
|
|
</div>
|
|
{{end}}
|
|
|
|
{{/* ── Comparison ── */}}
|
|
{{if $.Config.Comparison.Enabled}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_comparison"}}</h5>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_comparison_headline"}}</label>
|
|
<input name="trans_comparison_headline" value="{{if $trans}}{{$trans.ComparisonHeadline}}{{end}}" placeholder="{{$.Config.Comparison.Headline}}">
|
|
</div>
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_comparison_subheadline"}}</label>
|
|
<input name="trans_comparison_subheadline" value="{{if $trans}}{{$trans.ComparisonSubheadline}}{{end}}" placeholder="{{$.Config.Comparison.Subheadline}}">
|
|
</div>
|
|
{{end}}
|
|
|
|
{{/* ── Footer ── */}}
|
|
{{if or $.Config.Footer.Copyright $.Config.Footer.Links}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_footer"}}</h5>
|
|
{{if $.Config.Footer.Copyright}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_footer_copyright"}}</label>
|
|
<input name="trans_footer_copyright" value="{{if $trans}}{{$trans.FooterCopyright}}{{end}}" placeholder="{{$.Config.Footer.Copyright}}">
|
|
</div>
|
|
{{end}}
|
|
{{range $i, $link := $.Config.Footer.Links}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_footer_link"}} — <em>{{$link.Label}}</em></label>
|
|
<input name="trans_footer_link_{{$i}}" value="{{if $trans}}{{index $trans.FooterLinkLabels $i}}{{end}}" placeholder="{{$link.Label}}">
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* ── SEO ── */}}
|
|
{{if or $.Config.SEO.Title $.Config.SEO.Description}}
|
|
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_seo"}}</h5>
|
|
{{if $.Config.SEO.Title}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_seo_title"}}</label>
|
|
<input name="trans_seo_title" value="{{if $trans}}{{$trans.SEOTitle}}{{end}}" placeholder="{{$.Config.SEO.Title}}">
|
|
</div>
|
|
{{end}}
|
|
{{if $.Config.SEO.Description}}
|
|
<div class="field">
|
|
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_seo_description"}}</label>
|
|
<textarea name="trans_seo_description" rows="2" placeholder="{{$.Config.SEO.Description}}">{{if $trans}}{{$trans.SEODescription}}{{end}}</textarea>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
<div class="field">
|
|
<button class="ui primary tiny button">{{ctx.Locale.Tr "repo.settings.pages.save_translation"}}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
var selector = document.getElementById('lang-selector');
|
|
if (!selector) return;
|
|
|
|
var aiForm = document.getElementById('ai-translate-form');
|
|
var aiLangInput = document.getElementById('ai-translate-lang');
|
|
var deleteForm = document.getElementById('delete-translation-form');
|
|
var deleteLangInput = document.getElementById('delete-translate-lang');
|
|
|
|
function showPanel() {
|
|
var lang = selector.value;
|
|
document.querySelectorAll('.lang-panel').forEach(function(p) { p.style.display = 'none'; });
|
|
var panel = document.getElementById('lang-panel-' + lang);
|
|
if (panel) panel.style.display = '';
|
|
|
|
// Update AI translate target
|
|
if (aiLangInput) aiLangInput.value = lang;
|
|
|
|
// Update delete button target + visibility
|
|
if (deleteLangInput) deleteLangInput.value = lang;
|
|
if (deleteForm) {
|
|
// Show delete button only if this language has a translation
|
|
var hasTranslation = selector.options[selector.selectedIndex].textContent.indexOf('\u2713') !== -1;
|
|
deleteForm.style.display = hasTranslation ? '' : 'none';
|
|
}
|
|
}
|
|
|
|
selector.addEventListener('change', showPanel);
|
|
// Show first language on load
|
|
showPanel();
|
|
|
|
// Loading spinners for AI translate buttons
|
|
function showTranslateLoading(form) {
|
|
if (!form) return;
|
|
form.addEventListener('submit', function() {
|
|
var btn = form.querySelector('button[type="submit"]');
|
|
btn.classList.add('loading', 'disabled');
|
|
btn.disabled = true;
|
|
// Hide all lang panels and action bar buttons, show loader
|
|
document.querySelectorAll('.lang-panel').forEach(function(p) { p.style.display = 'none'; });
|
|
var loading = document.getElementById('ai-translate-loading');
|
|
if (loading) loading.classList.remove('tw-hidden');
|
|
});
|
|
}
|
|
showTranslateLoading(document.getElementById('ai-translate-form'));
|
|
showTranslateLoading(document.getElementById('ai-translate-all-form'));
|
|
})();
|
|
</script>
|
|
{{template "repo/settings/layout_footer" .}}
|