-
+
+
+
@@ -164,28 +285,31 @@ import { useMainStore } from '@/store';
import { useI18n } from 'vue-i18n';
import changelogData from '@/data/changelog.json';
import { trackEvent } from '@/utils/analytics';
+import { docsUrl } from '@/utils/docs-url.js';
import { formatIsoDate } from '@/utils/time-utils';
import { fallbackChain } from '@/utils/locale-registry.js';
import { Sheet, SheetContent, SheetClose } from '@/components/ui/sheet';
-import { JnTooltip } from '@/components/ui/tooltip';
+import { Dialog, DialogContent, DialogHeader } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
-import { Compass, ExternalLink, CircleFadingArrowUp, CirclePlus, BugOff, Smile } from '@lucide/vue';
+import { Compass, ExternalLink, CircleFadingArrowUp, CirclePlus, BugOff, Smile, Terminal, Feather } from '@lucide/vue';
+import brandIcon from './svgicons/Brand.vue';
import { Icon } from '@iconify/vue';
-import { useRoute } from 'vue-router';
import { useRouteActive } from '@/composables/use-route-active.js';
const { t, tm, rt, locale } = useI18n();
const store = useMainStore();
-const isMobile = computed(() => store.isMobile);
const configs = computed(() => store.configs);
-const route = useRoute();
-const isInHomePage = computed(() => route.path === '/');
+const curl = computed(() => store.curl);
+const curlDomainsHadSet = computed(() => store.curlDomainsHadSet);
const tabs = ['about', 'changelog', 'acknowledgement'];
const content = ref('about');
+
+const headingClass = 'mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground';
+const linkClass = 'inline-flex items-center gap-1 text-sm text-muted-foreground no-underline transition-colors hover:text-foreground cursor-pointer';
// Static data from JSON — reverse once via computed so the template stays tidy.
const changelogReversed = computed(() => changelogData.slice().reverse());
@@ -266,6 +390,12 @@ const openAboutTab = (tab, trackLabel) => {
trackEvent('Footer', 'FooterClick', trackLabel);
};
+const isCurlOpen = ref(false);
+const openCurl = () => {
+ isCurlOpen.value = true;
+ trackEvent('Footer', 'FooterClick', 'Curl');
+};
+
watch(content, () => {
nextTick(() => {
if (sheetBody.value) sheetBody.value.scrollTop = 0;
@@ -273,3 +403,20 @@ watch(content, () => {
});
+
+
diff --git a/frontend/components/Home.vue b/frontend/components/Home.vue
index 05240831..e608e91e 100644
--- a/frontend/components/Home.vue
+++ b/frontend/components/Home.vue
@@ -18,7 +18,6 @@
-
@@ -28,7 +27,7 @@
//
// Components — the test sections and the always-visible chrome load
// synchronously; everything the first paint can't show (dialogs, the
-// below-fold Additional/Footer) is an async component so its code stays
+// below-fold Footer) is an async component so its code stays
// out of the route chunk and out of the mount's critical path. Their
// template refs are null until the chunk lands — consumers (use-shortcuts)
// must optional-chain.
@@ -45,7 +44,6 @@ import FloatingDock from './widgets/FloatingDock.vue';
import { ref, computed, onMounted, defineAsyncComponent } from 'vue';
// Async (off-critical-path) components
-const Additional = defineAsyncComponent(() => import('./Additional.vue'));
const Footer = defineAsyncComponent(() => import('./Footer.vue'));
const Achievements = defineAsyncComponent(() => import('./Achievements.vue'));
const QueryIP = defineAsyncComponent(() => import('./widgets/QueryIP.vue'));
diff --git a/frontend/locales/en.json b/frontend/locales/en.json
index f41342c9..a4322200 100644
--- a/frontend/locales/en.json
+++ b/frontend/locales/en.json
@@ -384,11 +384,9 @@
"Note2_1": "You can use",
"Note2_2": "command, get the IP address of your machine in terminal",
"Note3": "is an optional path, adding it will obtain the geographical information of the IP",
- "Note4": "is your IPCheck.ing API key",
"getIPv4": "Get local machine's IPv4 address",
"getIPv6": "Get local machine's IPv6 address",
- "get6and4": "Get the preferred network exit IP address of the local machine",
- "notAvailable": "This feature is not available yet"
+ "get6and4": "Get the preferred network exit IP address of the local machine"
},
"browserinfo": {
"Title": "Browser Information",
@@ -2077,13 +2075,25 @@
"helpModal": {
"Title": "Keyboard Shortcuts"
},
+ "footer": {
+ "Tagline": "A free, open-source, all-in-one privacy toolbox.",
+ "Project": "Project",
+ "Support": "Docs & Support",
+ "DeveloperDocs": "Developer Docs",
+ "SelfHosting": "Self-hosting Guide",
+ "ReportIssue": "Report an Issue",
+ "License": "MIT License",
+ "Clients": "Clients",
+ "Raycast": "Raycast Extension",
+ "Lite": "Lite Edition"
+ },
"about": {
"Title": "About",
"project": [
"IPCheck.ing is a free all-in-one IP toolbox that helps you check your own IP information, test website availability, examine DNS exit information, and run speed tests, global latency tests, MTR tests and more.",
"This is an open-source project — click the GitHub icon at the bottom of the page to browse the source code.",
"If you are unsure how a feature works, open the \"Help Center\" link at the bottom of the page. The documentation is thorough: beyond describing each feature, it also explains networking concepts and how to troubleshoot network problems.",
- "If you would like to take part in the open-source project, click the \"Contribute Code\" button at the bottom of the page to see how."
+ "If you would like to take part in the open-source project, open the \"Developer Docs\" link at the bottom of the page to see how."
],
"founderTitle": "About the Founder",
"founder": [
@@ -2104,7 +2114,6 @@
"Sponsor": "Sponsor Us",
"Privacy": "Privacy",
"HelpCenter": "Help Center",
- "Contribute": "Contribute Code",
"Close": "Close"
},
"acknowledgement": {
diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json
index 69efbdd3..b7cd4255 100644
--- a/frontend/locales/fr.json
+++ b/frontend/locales/fr.json
@@ -384,11 +384,9 @@
"Note2_1": "Tu peux, dans le terminal, via",
"Note2_2": "commande, obtenir l'adresse IP de cette machine",
"Note3": "est un chemin optionnel, son ajout permettra d'obtenir les informations géographiques de l'IP",
- "Note4": "est votre clé API IPCheck.ing",
"getIPv4": "Obtenir l'adresse IPv4 de la machine locale",
"getIPv6": "Obtenir l'adresse IPv6 de la machine locale",
- "get6and4": "Obtenir l'adresse IP de sortie de réseau préférée de la machine locale",
- "notAvailable": "Cette fonctionnalité n'est pas encore disponible"
+ "get6and4": "Obtenir l'adresse IP de sortie de réseau préférée de la machine locale"
},
"browserinfo": {
"Title": "Informations du Navigateur",
@@ -2077,13 +2075,25 @@
"helpModal": {
"Title": "Raccourcis"
},
+ "footer": {
+ "Tagline": "Une boîte à outils de confidentialité tout-en-un, gratuite et open source.",
+ "Project": "Projet",
+ "Support": "Documentation et support",
+ "DeveloperDocs": "Documentation développeur",
+ "SelfHosting": "Guide d'auto-hébergement",
+ "ReportIssue": "Signaler un problème",
+ "License": "Licence MIT",
+ "Clients": "Clients",
+ "Raycast": "Extension Raycast",
+ "Lite": "Édition Lite"
+ },
"about": {
"Title": "À propos",
"project": [
"IPCheck.ing est une boîte à outils IP tout-en-un et gratuite : elle vous aide à consulter vos informations IP, à tester la disponibilité des sites web, à examiner les informations de sortie DNS, à mesurer votre débit, à tester la latence mondiale, à lancer des tests MTR, et bien plus encore.",
"Il s'agit d'un projet open source : cliquez sur l'icône GitHub en bas de la page pour consulter le code source.",
"Si vous ne comprenez pas comment utiliser une fonctionnalité, ouvrez le « Centre d'aide » en bas de la page. Notre documentation est très détaillée : en plus de présenter chaque fonctionnalité, elle vous aide à comprendre les différents concepts réseau et à diagnostiquer vos problèmes de connexion.",
- "Si vous souhaitez participer au projet open source, cliquez sur le bouton « Contribuer au code » en bas de la page pour découvrir comment faire."
+ "Si vous souhaitez participer au projet open source, ouvrez le lien « Documentation développeur » en bas de la page pour découvrir comment faire."
],
"founderTitle": "À propos du fondateur",
"founder": [
@@ -2104,7 +2114,6 @@
"Sponsor": "Nous soutenir",
"Privacy": "Confidentialité",
"HelpCenter": "Centre d'aide",
- "Contribute": "Contribuer au code",
"Close": "Fermer"
},
"acknowledgement": {
diff --git a/frontend/locales/pt-BR.json b/frontend/locales/pt-BR.json
index f974e088..fdc39ff1 100644
--- a/frontend/locales/pt-BR.json
+++ b/frontend/locales/pt-BR.json
@@ -384,11 +384,9 @@
"Note2_1": "Você pode usar o comando",
"Note2_2": "para obter o IP da sua máquina no terminal",
"Note3": "é um caminho opcional; adicioná-lo obtém as informações geográficas do IP",
- "Note4": "é sua chave de API do IPCheck.ing",
"getIPv4": "Obter o endereço IPv4 da máquina local",
"getIPv6": "Obter o endereço IPv6 da máquina local",
- "get6and4": "Obter o IP de saída de rede preferencial da máquina local",
- "notAvailable": "Este recurso ainda não está disponível"
+ "get6and4": "Obter o IP de saída de rede preferencial da máquina local"
},
"browserinfo": {
"Title": "Informações do navegador",
@@ -2077,13 +2075,25 @@
"helpModal": {
"Title": "Atalhos de teclado"
},
+ "footer": {
+ "Tagline": "Uma caixa de ferramentas de privacidade tudo em um, gratuita e de código aberto.",
+ "Project": "Projeto",
+ "Support": "Documentação e suporte",
+ "DeveloperDocs": "Documentação para desenvolvedores",
+ "SelfHosting": "Guia de auto-hospedagem",
+ "ReportIssue": "Relatar um problema",
+ "License": "Licença MIT",
+ "Clients": "Clientes",
+ "Raycast": "Extensão para Raycast",
+ "Lite": "Edição Lite"
+ },
"about": {
"Title": "Sobre",
"project": [
"IPCheck.ing é uma caixa de ferramentas de IP gratuita e tudo em um que ajuda você a verificar suas próprias informações de IP, testar disponibilidade de sites, examinar informações de saída DNS e executar testes de velocidade, latência global, MTR e muito mais.",
"Este é um projeto de código aberto — clique no ícone do GitHub no rodapé para navegar pelo código-fonte.",
"Se você não tiver certeza de como um recurso funciona, abra o link \"Central de ajuda\" no rodapé. A documentação é completa: além de descrever cada recurso, ela também explica conceitos de rede e como solucionar problemas de rede.",
- "Se quiser participar do projeto de código aberto, clique no botão \"Contribuir com código\" no rodapé para ver como."
+ "Se quiser participar do projeto de código aberto, abra o link \"Documentação para desenvolvedores\" no rodapé para ver como."
],
"founderTitle": "Sobre o fundador",
"founder": [
@@ -2104,7 +2114,6 @@
"Sponsor": "Patrocine-nos",
"Privacy": "Privacidade",
"HelpCenter": "Central de ajuda",
- "Contribute": "Contribuir com código",
"Close": "Fechar"
},
"acknowledgement": {
diff --git a/frontend/locales/ru.json b/frontend/locales/ru.json
index b2c05627..6103318e 100644
--- a/frontend/locales/ru.json
+++ b/frontend/locales/ru.json
@@ -384,11 +384,9 @@
"Note2_1": "Можно использовать",
"Note2_2": "команду, чтобы получить IP-адрес этого устройства в терминале",
"Note3": "— необязательный путь; добавьте его, чтобы получить географические сведения об IP",
- "Note4": "— ваш ключ API IPCheck.ing",
"getIPv4": "Получить IPv4-адрес этого устройства",
"getIPv6": "Получить IPv6-адрес этого устройства",
- "get6and4": "Получить предпочтительный внешний IP-адрес этого устройства",
- "notAvailable": "Эта функция пока недоступна"
+ "get6and4": "Получить предпочтительный внешний IP-адрес этого устройства"
},
"browserinfo": {
"Title": "Сведения о браузере",
@@ -2077,13 +2075,25 @@
"helpModal": {
"Title": "Сочетания клавиш"
},
+ "footer": {
+ "Tagline": "Бесплатный универсальный набор инструментов для приватности с открытым исходным кодом.",
+ "Project": "Проект",
+ "Support": "Документация и поддержка",
+ "DeveloperDocs": "Документация для разработчиков",
+ "SelfHosting": "Руководство по самостоятельному размещению",
+ "ReportIssue": "Сообщить о проблеме",
+ "License": "Лицензия MIT",
+ "Clients": "Клиенты",
+ "Raycast": "Расширение для Raycast",
+ "Lite": "Версия Lite"
+ },
"about": {
"Title": "О проекте",
"project": [
"IPCheck.ing — бесплатный универсальный набор IP-инструментов: он помогает посмотреть сведения о своём IP, проверить доступность сайтов и выход DNS, измерить скорость соединения, задержку до серверов по всему миру, выполнить MTR-тест и многое другое.",
"Это проект с открытым исходным кодом — нажмите значок GitHub внизу страницы, чтобы посмотреть исходный код.",
"Если вам непонятно, как пользоваться той или иной функцией, откройте «Справочный центр» внизу страницы. Документация очень подробная: помимо описания функций, она помогает разобраться в сетевых понятиях и в том, как диагностировать проблемы с сетью.",
- "Если вы хотите поучаствовать в проекте с открытым исходным кодом, нажмите внизу страницы кнопку «Стать разработчиком» и посмотрите, как это сделать."
+ "Если вы хотите поучаствовать в проекте с открытым исходным кодом, откройте внизу страницы ссылку «Документация для разработчиков» и посмотрите, как это сделать."
],
"founderTitle": "Об основателе проекта",
"founder": [
@@ -2104,7 +2114,6 @@
"Sponsor": "Поддержать",
"Privacy": "Приватность",
"HelpCenter": "Справочный центр",
- "Contribute": "Стать разработчиком",
"Close": "Закрыть"
},
"acknowledgement": {
diff --git a/frontend/locales/zh-TW.json b/frontend/locales/zh-TW.json
index 593b2223..5825b033 100644
--- a/frontend/locales/zh-TW.json
+++ b/frontend/locales/zh-TW.json
@@ -384,11 +384,9 @@
"Note2_1": "你可以在終端裡,透過",
"Note2_2": "命令取得本機的 IP 位址",
"Note3": "是可選路徑,新增後可取得到 IP 的地理資訊",
- "Note4": "請使用你申請的 IPCheck.ing API 金鑰",
"getIPv4": "取得本機 IPv4 位址",
"getIPv6": "取得本機 IPv6 位址",
- "get6and4": "取得本機優先網路出口的 IP 位址",
- "notAvailable": "此功能暫未開放,敬請期待"
+ "get6and4": "取得本機優先網路出口的 IP 位址"
},
"browserinfo": {
"Title": "瀏覽器資訊",
@@ -2077,13 +2075,25 @@
"helpModal": {
"Title": "鍵盤快速鍵"
},
+ "footer": {
+ "Tagline": "免費、開源、一站式的隱私工具箱",
+ "Project": "專案",
+ "Support": "文件與支援",
+ "DeveloperDocs": "開發者文件",
+ "SelfHosting": "自行架設指南",
+ "ReportIssue": "回報問題",
+ "License": "MIT 授權條款",
+ "Clients": "用戶端",
+ "Raycast": "Raycast 擴充功能",
+ "Lite": "Lite 版"
+ },
"about": {
"Title": "關於",
"project": [
"IPCheck.ing 是一個免費的多合一 IP 工具箱,可以協助你檢視自己的 IP 資訊,檢測網站的可用性,檢視 DNS 出口資訊,進行網速測試,全球延遲測試,MTR 測試等。",
"這是一個開源專案,你可以點選頁面底部的 GitHub 圖示檢視原始碼。",
"如果你對於產品中功能使用不理解,可以透過底部的「說明中心」取得協助,我們已經寫了非常詳細的協助文件,除了功能介紹,也能協助你理解各種網路概念和如何排查網路問題。",
- "如果你有興趣參與到開源專案中,可以點選頁面底部的「貢獻程式碼」按鈕,檢視如何貢獻程式碼。"
+ "如果你有興趣參與到開源專案中,可以點選頁面底部的「開發者文件」,檢視如何貢獻程式碼。"
],
"founderTitle": "關於專案發起人",
"founder": [
@@ -2104,7 +2114,6 @@
"Sponsor": "贊助我們",
"Privacy": "隱私政策",
"HelpCenter": "說明中心",
- "Contribute": "貢獻程式碼",
"Close": "關閉"
},
"acknowledgement": {
diff --git a/frontend/locales/zh.json b/frontend/locales/zh.json
index 105d5771..0b3d450e 100644
--- a/frontend/locales/zh.json
+++ b/frontend/locales/zh.json
@@ -384,11 +384,9 @@
"Note2_1": "你可以在终端里,通过",
"Note2_2": "命令获取本机的 IP 地址",
"Note3": "是可选路径,添加后可获取到 IP 的地理信息",
- "Note4": "请使用你申请的 IPCheck.ing API 密钥",
"getIPv4": "获取本机 IPv4 地址",
"getIPv6": "获取本机 IPv6 地址",
- "get6and4": "获取本机优先网络出口的 IP 地址",
- "notAvailable": "此功能暂未开放,敬请期待"
+ "get6and4": "获取本机优先网络出口的 IP 地址"
},
"browserinfo": {
"Title": "浏览器信息",
@@ -2077,13 +2075,25 @@
"helpModal": {
"Title": "快捷键"
},
+ "footer": {
+ "Tagline": "免费、开源、一站式的隐私工具箱",
+ "Project": "项目",
+ "Support": "文档与支持",
+ "DeveloperDocs": "开发者文档",
+ "SelfHosting": "自托管指南",
+ "ReportIssue": "报告问题",
+ "License": "MIT 许可证",
+ "Clients": "客户端",
+ "Raycast": "Raycast 扩展",
+ "Lite": "Lite 版"
+ },
"about": {
"Title": "关于",
"project": [
"IPCheck.ing 是一个免费的多合一 IP 工具箱,可以帮助你查看自己的 IP 信息,检测网站的可用性,查看 DNS 出口信息,进行网速测试,全球延迟测试,MTR 测试等。",
"这是一个开源项目,你可以点击页面底部的 GitHub 图标查看源代码。",
"如果你对于产品中功能使用不理解,可以通过底部的「帮助中心」获取帮助,我们已经写了非常详细的帮助文档,除了功能介绍,也能帮助你理解各种网络概念和如何排查网络问题。",
- "如果你有兴趣参与到开源项目中,可以点击页面底部的「贡献代码」按钮,查看如何贡献代码。"
+ "如果你有兴趣参与到开源项目中,可以点击页面底部的「开发者文档」,查看如何贡献代码。"
],
"founderTitle": "关于项目发起人",
"founder": [
@@ -2104,7 +2114,6 @@
"Sponsor": "赞助我们",
"Privacy": "隐私政策",
"HelpCenter": "帮助中心",
- "Contribute": "贡献代码",
"Close": "关闭"
},
"acknowledgement": {
diff --git a/frontend/style/style.css b/frontend/style/style.css
index 117076d2..ecdd8d3b 100644
--- a/frontend/style/style.css
+++ b/frontend/style/style.css
@@ -218,17 +218,6 @@
max-width: 1400px;
}
-footer {
- height: 100pt;
- padding-top: 20pt;
-}
-
-@media only screen and (max-width: 768px) {
- footer {
- height: 140pt;
- }
-}
-
.body-dark-mode {
background-color: rgb(19, 21, 21) !important;
color: #e3e3e3 !important;
diff --git a/frontend/utils/docs-url.js b/frontend/utils/docs-url.js
new file mode 100644
index 00000000..9711bbe7
--- /dev/null
+++ b/frontend/utils/docs-url.js
@@ -0,0 +1,12 @@
+// Builds a docs.ipcheck.ing link: English lives at the section root, every
+// other locale under `/
//`, with `path` appended after that.
+
+const DOCS_ORIGIN = 'https://docs.ipcheck.ing';
+
+const trimSlashes = (value) => String(value ?? '').replace(/^\/+|\/+$/g, '');
+
+export const docsUrl = (section, locale, path = '') => {
+ const base = `${DOCS_ORIGIN}/${trimSlashes(section)}/`;
+ const lang = !locale || locale === 'en' ? '' : `${trimSlashes(locale)}/`;
+ return base + lang + String(path ?? '').replace(/^\/+/, '');
+};
diff --git a/public/additional/curl.webp b/public/additional/curl.webp
deleted file mode 100644
index 165277da..00000000
Binary files a/public/additional/curl.webp and /dev/null differ
diff --git a/public/additional/lite.webp b/public/additional/lite.webp
deleted file mode 100644
index ef9ee365..00000000
Binary files a/public/additional/lite.webp and /dev/null differ
diff --git a/public/additional/raycast.webp b/public/additional/raycast.webp
deleted file mode 100644
index bc671891..00000000
Binary files a/public/additional/raycast.webp and /dev/null differ
diff --git a/tests/docs-url.test.js b/tests/docs-url.test.js
new file mode 100644
index 00000000..1707284f
--- /dev/null
+++ b/tests/docs-url.test.js
@@ -0,0 +1,36 @@
+// docsUrl() — locale-aware links into docs.ipcheck.ing.
+
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { docsUrl } from '../frontend/utils/docs-url.js';
+
+describe('docsUrl()', () => {
+ it('puts English at the section root', () => {
+ assert.equal(docsUrl('knowledge-base', 'en'), 'https://docs.ipcheck.ing/knowledge-base/');
+ assert.equal(docsUrl('developer', 'en'), 'https://docs.ipcheck.ing/developer/');
+ });
+
+ it('puts any other locale under the section', () => {
+ assert.equal(docsUrl('knowledge-base', 'zh'), 'https://docs.ipcheck.ing/knowledge-base/zh/');
+ assert.equal(docsUrl('developer', 'pt-BR'), 'https://docs.ipcheck.ing/developer/pt-BR/');
+ });
+
+ it('appends the path after the locale segment', () => {
+ assert.equal(docsUrl('developer', 'en', 'getting-started/'), 'https://docs.ipcheck.ing/developer/getting-started/');
+ assert.equal(docsUrl('developer', 'zh', 'getting-started/'), 'https://docs.ipcheck.ing/developer/zh/getting-started/');
+ });
+
+ it('never produces a double slash', () => {
+ const urls = [
+ docsUrl('/developer/', 'zh', '/getting-started/'),
+ docsUrl('developer', 'en', '/getting-started/'),
+ docsUrl('developer/', 'fr'),
+ ];
+ for (const url of urls) assert.doesNotMatch(url.replace('https://', ''), /\/\//);
+ assert.equal(urls[0], 'https://docs.ipcheck.ing/developer/zh/getting-started/');
+ });
+
+ it('treats a missing locale as English', () => {
+ assert.equal(docsUrl('developer', undefined), 'https://docs.ipcheck.ing/developer/');
+ });
+});