From 9ffbc7a2cade60157b9ff47226cab5e8409af318 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Fri, 28 Aug 2026 20:52:49 +0200 Subject: [PATCH] wiki multilang support --- README.md | 28 +++++++++++ .../api/app/controllers/api/rss_controller.rb | 9 ++-- .../app/controllers/api/wiki_controller.rb | 7 ++- apps/api/app/services/rss/blog_feed.rb | 6 +-- apps/api/app/services/rss/feed.rb | 12 ++++- apps/api/app/services/rss/howtos_feed.rb | 6 +-- apps/api/app/services/rss/releases_feed.rb | 6 +-- apps/api/app/services/wiki/pages.rb | 22 +++++---- apps/api/config/locales/en.yml | 11 +++++ apps/api/config/locales/hu.yml | 18 ++++++++ apps/api/lib/locales.rb | 9 ++++ apps/api/spec/lib/locales_spec.rb | 20 ++++++++ apps/api/spec/requests/api_wiki_spec.rb | 31 +++++++++++++ apps/api/spec/services/wiki/pages_spec.rb | 46 +++++++++++++++++++ .../src/api/__tests__/wiki.api.test.ts | 35 ++++++++++++++ apps/frontend/src/api/wiki.api.ts | 40 +++++++++++----- .../src/composables/useLocaleReload.ts | 13 ++++++ apps/frontend/src/layout/AppLayout.vue | 4 +- .../src/page/engines/EnginesIndexPage.vue | 6 +-- .../src/page/howtos/HowtosIndexPage.vue | 8 ++-- .../src/page/stores/StoresIndexPage.vue | 12 ++--- .../stores/__tests__/engines.store.test.ts | 28 +++++++++++ apps/frontend/src/stores/blog.store.ts | 10 ++++ apps/frontend/src/stores/engines.store.ts | 10 +++- apps/frontend/src/stores/howtos.store.ts | 6 +++ 25 files changed, 351 insertions(+), 52 deletions(-) create mode 100644 apps/api/config/locales/hu.yml create mode 100644 apps/api/lib/locales.rb create mode 100644 apps/api/spec/lib/locales_spec.rb create mode 100644 apps/api/spec/requests/api_wiki_spec.rb create mode 100644 apps/api/spec/services/wiki/pages_spec.rb create mode 100644 apps/frontend/src/api/__tests__/wiki.api.test.ts create mode 100644 apps/frontend/src/composables/useLocaleReload.ts diff --git a/README.md b/README.md index a71d988..3ef53d9 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,34 @@ The API is split in two layers: Devise/ActiveAdmin authentication, theming and assets. It consumes WarpEngine as a path gem and mounts it at `/`. +## Languages + +The site is bilingual (English and Hungarian) and so is the wiki behind it. +`Locales` (`lib/locales.rb`) is the one list of supported codes; anything else +falls back to `en`. + +The wiki serves English unprefixed and Hungarian under `/hu`, so +`Wiki::Pages` builds the language into the request path rather than into a query +parameter: + +``` +GET /api/wiki/pages?tag=howto&lang=hu -> https://wiki.teletypegames.org/hu/custom/pages.json?tag=howto +GET /api/wiki/pages?tag=howto -> https://wiki.teletypegames.org/custom/pages.json?tag=howto +``` + +Each page in the response carries a `locale` saying which language its body is +**actually** written in. That differs from the requested `lang` for the wiki +pages that exist only in Hungarian: they are served as-is rather than 404ing, and +a client can label them. + +The RSS feeds take the same `lang` parameter (`/api/rss/blog?lang=hu`), which +sets the channel language and picks the wiki language and the feed's own strings +from `config/locales/`. + +The frontend passes the interface language on every wiki call and rebuilds its +outbound wiki links with `wikiUrl()`, so switching EN/HU re-reads the catalog and +points the links at the matching wiki pages. + ## The store registry `GET /api/stores` lists the stores a client can install from. The desktop diff --git a/apps/api/app/controllers/api/rss_controller.rb b/apps/api/app/controllers/api/rss_controller.rb index ddd2dfa..1c57597 100644 --- a/apps/api/app/controllers/api/rss_controller.rb +++ b/apps/api/app/controllers/api/rss_controller.rb @@ -5,23 +5,26 @@ class Api::RssController < ApiController end api :GET, "/api/rss/blog", "Blog RSS feed" + param :lang, String, required: false, desc: "Feed language (en, hu). Unknown values fall back to en" returns code: 200, desc: "RSS XML feed of blog posts" def blog - xml = Rss::BlogFeed.new.xml + xml = Rss::BlogFeed.new(lang: params[:lang]).xml render xml: xml, content_type: "application/rss+xml" end api :GET, "/api/rss/releases", "Software releases RSS feed" + param :lang, String, required: false, desc: "Feed language (en, hu). Unknown values fall back to en" returns code: 200, desc: "RSS XML feed of software releases" def releases - xml = Rss::ReleasesFeed.new.xml + xml = Rss::ReleasesFeed.new(lang: params[:lang]).xml render xml: xml, content_type: "application/rss+xml" end api :GET, "/api/rss/howtos", "HowTos RSS feed" + param :lang, String, required: false, desc: "Feed language (en, hu). Unknown values fall back to en" returns code: 200, desc: "RSS XML feed of tech howtos" def howtos - xml = Rss::HowtosFeed.new.xml + xml = Rss::HowtosFeed.new(lang: params[:lang]).xml render xml: xml, content_type: "application/rss+xml" end end diff --git a/apps/api/app/controllers/api/wiki_controller.rb b/apps/api/app/controllers/api/wiki_controller.rb index fe362ef..b6d92cb 100644 --- a/apps/api/app/controllers/api/wiki_controller.rb +++ b/apps/api/app/controllers/api/wiki_controller.rb @@ -7,8 +7,10 @@ class Api::WikiController < ApiController param :tag, String, required: false, desc: "Filter by tag (blog, howto, engine)" param :limit, :number, required: false, desc: "Limit number of results" param :body, String, required: false, desc: "Include body content (1 = yes)" + param :lang, String, required: false, desc: "Content language (en, hu). Unknown values fall back to en" returns code: 200, desc: "Wiki pages response" do property :tag, String, desc: "Applied tag filter" + property :lang, String, desc: "Requested content language" property :count, Integer, desc: "Number of pages returned" property :pages, Array, desc: "Array of wiki pages" do property :id, String, desc: "Page ID" @@ -17,7 +19,7 @@ class Api::WikiController < ApiController property :description, String, desc: "Short description" property :createdAt, String, desc: "Created date (ISO 8601)" property :updatedAt, String, desc: "Updated date (ISO 8601)" - property :locale, String, desc: "Locale code" + property :locale, String, desc: "Language the page body is actually written in (differs from lang when the page has no translation)" property :route, String, desc: "URL slug" property :tags, Array, of: String, desc: "Tags" property :repo, String, desc: "Git repository URL (from page metadata, engines)" @@ -31,7 +33,8 @@ class Api::WikiController < ApiController render json: Wiki::Pages.new.fetch( tag: params[:tag], limit: params[:limit], - body: params[:body] + body: params[:body], + lang: params[:lang] ) end end diff --git a/apps/api/app/services/rss/blog_feed.rb b/apps/api/app/services/rss/blog_feed.rb index e283db5..b558504 100644 --- a/apps/api/app/services/rss/blog_feed.rb +++ b/apps/api/app/services/rss/blog_feed.rb @@ -2,12 +2,12 @@ module Rss class BlogFeed < Feed private - def title = "Teletype Games Blog" + def title = translate("rss.blog.title") def link = "#{site_url}/blog" - def description = "Latest blog posts from Teletype Games" + def description = translate("rss.blog.description") def items - Wiki::Pages.new.all(tag: "blog", limit: 30).map do |page| + Wiki::Pages.new.all(tag: "blog", limit: 30, lang: lang).map do |page| { title: page["title"], link: "#{site_url}/blog/#{page['route']}", diff --git a/apps/api/app/services/rss/feed.rb b/apps/api/app/services/rss/feed.rb index c8c2b11..9b6af43 100644 --- a/apps/api/app/services/rss/feed.rb +++ b/apps/api/app/services/rss/feed.rb @@ -2,12 +2,16 @@ require "rss" module Rss class Feed + def initialize(lang: nil) + @lang = Locales.resolve(lang) + end + def xml RSS::Maker.make("2.0") do |maker| maker.channel.title = title maker.channel.link = link maker.channel.description = description - maker.channel.language = "hu" + maker.channel.language = lang items.each do |item| maker.items.new_item do |rss_item| @@ -22,9 +26,13 @@ module Rss private + attr_reader :lang + def site_url = Rails.configuration.x.site_url - def wiki_url = Rails.configuration.x.wiki_url + def wiki_url = "#{Rails.configuration.x.wiki_url}#{Wiki::Pages.path_prefix(lang)}" + + def translate(key, **options) = I18n.t(key, locale: lang, **options) def parse_time(value) Time.parse(value.to_s) diff --git a/apps/api/app/services/rss/howtos_feed.rb b/apps/api/app/services/rss/howtos_feed.rb index d6a9b93..cd8c8b8 100644 --- a/apps/api/app/services/rss/howtos_feed.rb +++ b/apps/api/app/services/rss/howtos_feed.rb @@ -2,12 +2,12 @@ module Rss class HowtosFeed < Feed private - def title = "Teletype Games HowTos" + def title = translate("rss.howtos.title") def link = "#{site_url}/howtos" - def description = "Latest tech howtos from Teletype Games" + def description = translate("rss.howtos.description") def items - Wiki::Pages.new.all(tag: "howto", limit: 30).map do |page| + Wiki::Pages.new.all(tag: "howto", limit: 30, lang: lang).map do |page| { title: page["title"], link: "#{wiki_url}/#{page['path']}", diff --git a/apps/api/app/services/rss/releases_feed.rb b/apps/api/app/services/rss/releases_feed.rb index e845268..36d871f 100644 --- a/apps/api/app/services/rss/releases_feed.rb +++ b/apps/api/app/services/rss/releases_feed.rb @@ -2,9 +2,9 @@ module Rss class ReleasesFeed < Feed private - def title = "Teletype Games Releases" + def title = translate("rss.releases.title") def link = "#{site_url}/catalog" - def description = "Latest game releases from Teletype Games" + def description = translate("rss.releases.description") def items WarpEngine::Release.includes(:software).order(created_at: :desc).limit(50).filter_map do |release| @@ -14,7 +14,7 @@ module Rss { title: "#{software.title} v#{release.version}", link: "#{site_url}/catalog/#{software.name}", - description: "#{software.title} #{release.version} released – #{software.desc}", + description: translate("rss.releases.item_description", title: software.title, version: release.version, desc: software.desc), published_at: release.created_at.to_time } end diff --git a/apps/api/app/services/wiki/pages.rb b/apps/api/app/services/wiki/pages.rb index 53f04d1..bd6fd08 100644 --- a/apps/api/app/services/wiki/pages.rb +++ b/apps/api/app/services/wiki/pages.rb @@ -3,12 +3,18 @@ require "json" module Wiki class Pages - def fetch(tag:, limit: nil, body: nil) + def self.path_prefix(language) + language == Locales::DEFAULT ? "" : "/#{language}" + end + + def fetch(tag:, limit: nil, body: nil, lang: nil) + language = Locales.resolve(lang) + query = { tag: tag } query[:limit] = limit if limit.present? query[:body] = body if body.present? - uri = URI.parse("#{Rails.configuration.x.wiki_url}/custom/pages.json") + uri = URI.parse("#{Rails.configuration.x.wiki_url}#{self.class.path_prefix(language)}/custom/pages.json") uri.query = URI.encode_www_form(query) response = Net::HTTP.start( @@ -17,21 +23,21 @@ module Wiki open_timeout: 5, read_timeout: 10 ) { |http| http.get(uri.request_uri) } - return empty(tag, "grav responded #{response.code}") unless response.is_a?(Net::HTTPSuccess) + return empty(tag, language, "grav responded #{response.code}") unless response.is_a?(Net::HTTPSuccess) JSON.parse(response.body) rescue StandardError => e - empty(tag, e.message) + empty(tag, language, e.message) end - def all(tag:, limit: nil) - fetch(tag: tag, limit: limit).fetch("pages", []) + def all(tag:, limit: nil, lang: nil) + fetch(tag: tag, limit: limit, lang: lang).fetch("pages", []) end private - def empty(tag, error) - { "tag" => tag, "count" => 0, "pages" => [], "error" => error } + def empty(tag, language, error) + { "tag" => tag, "lang" => language, "count" => 0, "pages" => [], "error" => error } end end end diff --git a/apps/api/config/locales/en.yml b/apps/api/config/locales/en.yml index 93ab3a0..74178b3 100644 --- a/apps/api/config/locales/en.yml +++ b/apps/api/config/locales/en.yml @@ -5,3 +5,14 @@ en: date: formats: long: "%Y-%m-%d" + rss: + blog: + title: "Teletype Games Blog" + description: "Latest blog posts from Teletype Games" + howtos: + title: "Teletype Games HowTos" + description: "Latest tech howtos from Teletype Games" + releases: + title: "Teletype Games Releases" + description: "Latest game releases from Teletype Games" + item_description: "%{title} %{version} released – %{desc}" diff --git a/apps/api/config/locales/hu.yml b/apps/api/config/locales/hu.yml new file mode 100644 index 0000000..d113bb6 --- /dev/null +++ b/apps/api/config/locales/hu.yml @@ -0,0 +1,18 @@ +hu: + time: + formats: + long: "%Y-%m-%d %H:%M" + date: + formats: + long: "%Y-%m-%d" + rss: + blog: + title: "Teletype Games blog" + description: "A Teletype Games legfrissebb blogbejegyzései" + howtos: + title: "Teletype Games útmutatók" + description: "A Teletype Games legfrissebb technikai útmutatói" + releases: + title: "Teletype Games kiadások" + description: "A Teletype Games legfrissebb játékkiadásai" + item_description: "Megjelent a %{title} %{version} – %{desc}" diff --git a/apps/api/lib/locales.rb b/apps/api/lib/locales.rb new file mode 100644 index 0000000..75d8204 --- /dev/null +++ b/apps/api/lib/locales.rb @@ -0,0 +1,9 @@ +module Locales + SUPPORTED = %w[en hu].freeze + DEFAULT = "en" + + def self.resolve(value) + normalized = value.to_s.strip.downcase + SUPPORTED.include?(normalized) ? normalized : DEFAULT + end +end diff --git a/apps/api/spec/lib/locales_spec.rb b/apps/api/spec/lib/locales_spec.rb new file mode 100644 index 0000000..705c36b --- /dev/null +++ b/apps/api/spec/lib/locales_spec.rb @@ -0,0 +1,20 @@ +require "rails_helper" + +RSpec.describe Locales do + describe ".resolve" do + it "accepts the supported languages" do + expect(Locales.resolve("en")).to eq("en") + expect(Locales.resolve("hu")).to eq("hu") + end + + it "normalises case and whitespace" do + expect(Locales.resolve(" HU ")).to eq("hu") + end + + it "falls back to the default for anything else" do + expect(Locales.resolve("de")).to eq(Locales::DEFAULT) + expect(Locales.resolve(nil)).to eq(Locales::DEFAULT) + expect(Locales.resolve("")).to eq(Locales::DEFAULT) + end + end +end diff --git a/apps/api/spec/requests/api_wiki_spec.rb b/apps/api/spec/requests/api_wiki_spec.rb new file mode 100644 index 0000000..6c8194d --- /dev/null +++ b/apps/api/spec/requests/api_wiki_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe "Api::Wiki", type: :request do + let(:service) { instance_double(Wiki::Pages) } + + before do + host! "teletypegames.org" + allow(Wiki::Pages).to receive(:new).and_return(service) + end + + it "passes the requested language through to the wiki service" do + expect(service).to receive(:fetch) + .with(tag: "howto", limit: nil, body: nil, lang: "hu") + .and_return({ "tag" => "howto", "lang" => "hu", "count" => 0, "pages" => [] }) + + get "/api/wiki/pages", params: { tag: "howto", lang: "hu" } + + expect(response).to have_http_status(:ok) + expect(response.parsed_body["lang"]).to eq("hu") + end + + it "leaves the fallback to the service when no language is asked for" do + expect(service).to receive(:fetch) + .with(tag: "howto", limit: nil, body: nil, lang: nil) + .and_return({ "tag" => "howto", "lang" => "en", "count" => 0, "pages" => [] }) + + get "/api/wiki/pages", params: { tag: "howto" } + + expect(response.parsed_body["lang"]).to eq("en") + end +end diff --git a/apps/api/spec/services/wiki/pages_spec.rb b/apps/api/spec/services/wiki/pages_spec.rb new file mode 100644 index 0000000..014725e --- /dev/null +++ b/apps/api/spec/services/wiki/pages_spec.rb @@ -0,0 +1,46 @@ +require "rails_helper" + +RSpec.describe Wiki::Pages do + let(:requested_paths) { [] } + + before do + http = instance_double(Net::HTTP) + response = Net::HTTPOK.new("1.1", "200", "OK") + allow(response).to receive(:body).and_return({ tag: "howto", lang: "en", count: 0, pages: [] }.to_json) + allow(http).to receive(:get) do |path| + requested_paths << path + response + end + allow(Net::HTTP).to receive(:start) { |*_args, **_opts, &block| block.call(http) } + end + + describe "#fetch" do + it "requests the default language without a path prefix" do + described_class.new.fetch(tag: "howto") + + expect(requested_paths.first).to start_with("/custom/pages.json") + end + + it "prefixes the path with the requested language" do + described_class.new.fetch(tag: "howto", lang: "hu") + + expect(requested_paths.first).to start_with("/hu/custom/pages.json") + end + + it "falls back to the default language for an unsupported one" do + described_class.new.fetch(tag: "howto", lang: "de") + + expect(requested_paths.first).to start_with("/custom/pages.json") + end + + it "reports the resolved language when grav is unreachable" do + allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED) + + result = described_class.new.fetch(tag: "howto", lang: "hu") + + expect(result["lang"]).to eq("hu") + expect(result["pages"]).to eq([]) + expect(result["error"]).to be_present + end + end +end diff --git a/apps/frontend/src/api/__tests__/wiki.api.test.ts b/apps/frontend/src/api/__tests__/wiki.api.test.ts new file mode 100644 index 0000000..a9b636e --- /dev/null +++ b/apps/frontend/src/api/__tests__/wiki.api.test.ts @@ -0,0 +1,35 @@ +import { describe, it, expect, afterEach } from 'vitest' +import { i18n } from '../../i18n' +import { WIKI_BASE, wikiUrl } from '../wiki.api' + +afterEach(() => { + i18n.global.locale.value = 'en' +}) + +describe('wikiUrl', () => { + it('leaves English unprefixed', () => { + expect(wikiUrl('development/godot', 'en')).toBe(`${WIKI_BASE}/development/godot`) + }) + + it('prefixes Hungarian with /hu', () => { + expect(wikiUrl('development/godot', 'hu')).toBe(`${WIKI_BASE}/hu/development/godot`) + }) + + it('accepts a route that already starts with a slash', () => { + expect(wikiUrl('/development/godot', 'hu')).toBe(`${WIKI_BASE}/hu/development/godot`) + }) + + it('returns the wiki root when no page is given', () => { + expect(wikiUrl('', 'hu')).toBe(`${WIKI_BASE}/hu`) + expect(wikiUrl()).toBe(WIKI_BASE) + }) + + it('falls back to English for an unsupported language', () => { + expect(wikiUrl('development/godot', 'de')).toBe(`${WIKI_BASE}/development/godot`) + }) + + it('follows the active interface language when none is passed', () => { + i18n.global.locale.value = 'hu' + expect(wikiUrl('development/godot')).toBe(`${WIKI_BASE}/hu/development/godot`) + }) +}) diff --git a/apps/frontend/src/api/wiki.api.ts b/apps/frontend/src/api/wiki.api.ts index 4ecb813..6d593e2 100644 --- a/apps/frontend/src/api/wiki.api.ts +++ b/apps/frontend/src/api/wiki.api.ts @@ -1,9 +1,27 @@ import type { WikiPage, WikiPageWithContent, WikiPageContent } from '../lib/interfaces/wiki.interface' import { CONFIG } from '../lib/config' +import { i18n } from '../i18n' const WIKI_BASE = CONFIG.wikiBase +const WIKI_LOCALES = ['en', 'hu'] as const +const DEFAULT_WIKI_LOCALE = 'en' + +function resolveLocale(locale?: string): string { + const value = (locale ?? String(i18n.global.locale.value)).toLowerCase() + return (WIKI_LOCALES as readonly string[]).includes(value) ? value : DEFAULT_WIKI_LOCALE +} + +// Grav serves English unprefixed and Hungarian under /hu, so a page link is the +// base plus the prefix plus the language-neutral route. +function wikiUrl(path = '', locale?: string): string { + const lang = resolveLocale(locale) + const prefix = lang === DEFAULT_WIKI_LOCALE ? '' : `/${lang}` + const route = path ? `/${path.replace(/^\/+/, '')}` : '' + return `${WIKI_BASE}${prefix}${route}` +} + interface RawWikiPage { id: number path: string @@ -22,9 +40,9 @@ const HIGHLIGHTED_TAG = 'highlighted' async function fetchPages( tag: string, - opts: { body?: boolean; limit?: number } = {}, + opts: { body?: boolean; limit?: number; locale?: string } = {}, ): Promise { - const params = new URLSearchParams({ tag }) + const params = new URLSearchParams({ tag, lang: resolveLocale(opts.locale) }) if (opts.body) params.set('body', '1') if (opts.limit) params.set('limit', String(opts.limit)) @@ -34,8 +52,8 @@ async function fetchPages( return json?.pages ?? [] } -const listBlogPages = async (): Promise => { - const pages = await fetchPages('blog', { body: true }) +const listBlogPages = async (locale?: string): Promise => { + const pages = await fetchPages('blog', { body: true, locale }) return pages.map((p): WikiPageWithContent => ({ id: p.id, path: p.path, @@ -48,8 +66,8 @@ const listBlogPages = async (): Promise => { })) } -const getBlogPage = async (slug: string): Promise => { - const pages = await fetchPages('blog', { body: true }) +const getBlogPage = async (slug: string, locale?: string): Promise => { + const pages = await fetchPages('blog', { body: true, locale }) const matched = pages.find((p) => { const pageSlug = p.path.startsWith('blog/') ? p.path.replace('blog/', '') : p.path @@ -70,8 +88,8 @@ const getBlogPage = async (slug: string): Promise => { } } -const listEnginePages = async (): Promise => { - const pages = await fetchPages('engine', { body: true }) +const listEnginePages = async (locale?: string): Promise => { + const pages = await fetchPages('engine', { body: true, locale }) return pages .filter((p) => (p.tags ?? []).includes(HIGHLIGHTED_TAG)) .map((p): WikiPageWithContent => ({ @@ -87,8 +105,8 @@ const listEnginePages = async (): Promise => { })) } -const listHowtoPages = async (): Promise => { - const pages = await fetchPages('howto', { limit: 30 }) +const listHowtoPages = async (locale?: string): Promise => { + const pages = await fetchPages('howto', { limit: 30, locale }) return pages.map((p): WikiPage => ({ id: p.id, path: p.path, @@ -100,5 +118,5 @@ const listHowtoPages = async (): Promise => { })) } -export { WIKI_BASE } +export { WIKI_BASE, WIKI_LOCALES, DEFAULT_WIKI_LOCALE, wikiUrl } export default { listBlogPages, getBlogPage, listHowtoPages, listEnginePages } diff --git a/apps/frontend/src/composables/useLocaleReload.ts b/apps/frontend/src/composables/useLocaleReload.ts new file mode 100644 index 0000000..921d325 --- /dev/null +++ b/apps/frontend/src/composables/useLocaleReload.ts @@ -0,0 +1,13 @@ +import { watch } from 'vue' +import { i18n } from '../i18n' + +// Wiki content is fetched per language, so anything cached from the wiki has to +// be dropped and re-read when the visitor switches EN/HU. +export function useLocaleReload(reload: () => void | Promise) { + watch( + () => i18n.global.locale.value, + () => { + void reload() + }, + ) +} diff --git a/apps/frontend/src/layout/AppLayout.vue b/apps/frontend/src/layout/AppLayout.vue index ea91a98..cac50d9 100644 --- a/apps/frontend/src/layout/AppLayout.vue +++ b/apps/frontend/src/layout/AppLayout.vue @@ -53,7 +53,7 @@ @@ -63,7 +63,7 @@ import { computed, onMounted } from 'vue' import { storeToRefs } from 'pinia' import { useI18n } from 'vue-i18n' -import { WIKI_BASE } from '../../api/wiki.api' +import { wikiUrl } from '../../api/wiki.api' import { useEnginesStore, getEngineDigest } from '../../stores/engines.store' import type { WikiPageWithContent } from '../../lib/interfaces/wiki.interface' import SkeletonCard from '../../components/SkeletonCard.vue' @@ -79,7 +79,7 @@ const cards = computed(() => ) const exploreUrl = (page: WikiPageWithContent): string => - page.repo || `${WIKI_BASE}/${page.path}` + page.repo || wikiUrl(page.path) onMounted(() => store.fetch()) diff --git a/apps/frontend/src/page/howtos/HowtosIndexPage.vue b/apps/frontend/src/page/howtos/HowtosIndexPage.vue index ff19d2b..9a1666e 100644 --- a/apps/frontend/src/page/howtos/HowtosIndexPage.vue +++ b/apps/frontend/src/page/howtos/HowtosIndexPage.vue @@ -6,7 +6,7 @@

{{ t('howtos.title') }}

{{ t('howtos.subtitle') }}

@@ -30,7 +30,7 @@ @@ -63,7 +63,7 @@ import { onMounted } from 'vue' import { storeToRefs } from 'pinia' import { useI18n } from 'vue-i18n' import { formatDateTime } from '../../lib/dateFormat' -import { WIKI_BASE } from '../../api/wiki.api' +import { wikiUrl } from '../../api/wiki.api' import { useHowtosStore } from '../../stores/howtos.store' import SkeletonCard from '../../components/SkeletonCard.vue' diff --git a/apps/frontend/src/page/stores/StoresIndexPage.vue b/apps/frontend/src/page/stores/StoresIndexPage.vue index f97d2ee..f568bf6 100644 --- a/apps/frontend/src/page/stores/StoresIndexPage.vue +++ b/apps/frontend/src/page/stores/StoresIndexPage.vue @@ -30,7 +30,7 @@ {{ t('stores.clientDownload') }} - + {{ t('stores.docs') }} @@ -82,7 +82,7 @@