-
Notifications
You must be signed in to change notification settings - Fork 0
feat: SEO, Open Graph, and i18n document head #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,22 @@ server { | |||||||||||||||||||||||||||||||||||||||||||
| root /usr/share/nginx/html; | ||||||||||||||||||||||||||||||||||||||||||||
| index index.html; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # Static assets — long cache | ||||||||||||||||||||||||||||||||||||||||||||
| location ~* \.(js|css|png|jpg|jpeg|svg|ico|woff2?)$ { | ||||||||||||||||||||||||||||||||||||||||||||
| expires 30d; | ||||||||||||||||||||||||||||||||||||||||||||
| add_header Cache-Control "public, immutable"; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+6
to
+10
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # robots.txt and sitemap — short cache | ||||||||||||||||||||||||||||||||||||||||||||
| location = /robots.txt { | ||||||||||||||||||||||||||||||||||||||||||||
| expires 7d; | ||||||||||||||||||||||||||||||||||||||||||||
| add_header Cache-Control "public"; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| location = /sitemap.xml { | ||||||||||||||||||||||||||||||||||||||||||||
| expires 7d; | ||||||||||||||||||||||||||||||||||||||||||||
| add_header Cache-Control "public"; | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+8
to
+19
|
||||||||||||||||||||||||||||||||||||||||||||
| expires 30d; | |
| add_header Cache-Control "public, immutable"; | |
| } | |
| # robots.txt and sitemap — short cache | |
| location = /robots.txt { | |
| expires 7d; | |
| add_header Cache-Control "public"; | |
| } | |
| location = /sitemap.xml { | |
| expires 7d; | |
| add_header Cache-Control "public"; | |
| add_header Cache-Control "public, max-age=2592000, immutable"; | |
| } | |
| # robots.txt and sitemap — short cache | |
| location = /robots.txt { | |
| add_header Cache-Control "public, max-age=604800"; | |
| } | |
| location = /sitemap.xml { | |
| add_header Cache-Control "public, max-age=604800"; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,5 @@ | ||||||
| User-agent: * | ||||||
| Allow: / | ||||||
| Disallow: /s/ | ||||||
|
|
||||||
| Sitemap: https://ooshare.io/sitemap.xml | ||||||
|
||||||
| Sitemap: https://ooshare.io/sitemap.xml | |
| Sitemap: /sitemap.xml |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,8 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> | ||||||
| <url> | ||||||
| <loc>https://ooshare.io/</loc> | ||||||
|
||||||
| <loc>https://ooshare.io/</loc> | |
| <loc>__BASE_URL__/</loc> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,19 @@ | ||
| import { useEffect } from "react"; | ||
| import { useTranslation } from "react-i18next"; | ||
| import { Shield, Lock, Eye, Trash2 } from "lucide-react"; | ||
| import SecurityModal from "./SecurityModal"; | ||
| import LanguageSelector from "./LanguageSelector"; | ||
|
|
||
| export default function Layout({ children }: { children: React.ReactNode }) { | ||
| const { t } = useTranslation(); | ||
| const { t, i18n } = useTranslation(); | ||
|
|
||
| useEffect(() => { | ||
| document.documentElement.lang = i18n.language; | ||
| document.title = t("meta.title"); | ||
| document | ||
| .querySelector('meta[name="description"]') | ||
| ?.setAttribute("content", t("meta.description")); | ||
| }, [i18n.language, t]); | ||
|
Comment on lines
+10
to
+16
|
||
|
|
||
| return ( | ||
| <div className="layout"> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,63 +1,67 @@ | ||
| { | ||
| "meta": { | ||
| "title": "Only Once Share — Compartilhamento seguro de segredos", | ||
| "description": "Compartilhe senhas, chaves de API e dados sensíveis com criptografia ponta a ponta. Conhecimento zero, links de uso único que se autodestroem após a visualização." | ||
| }, | ||
| "header": { | ||
| "title": "Only Once Share" | ||
| }, | ||
| "hero": { | ||
| "title": "Compartilhe segredos com seguranca", | ||
| "subtitle": "Criptografia ponta a ponta no seu navegador. O servidor nunca ve seus dados. Links se autodestroem apos uma visualizacao." | ||
| "title": "Compartilhe segredos com segurança", | ||
| "subtitle": "Criptografia ponta a ponta no seu navegador. O servidor nunca vê seus dados. Links se autodestroem após uma visualização." | ||
| }, | ||
| "create": { | ||
| "label": "Conteudo secreto", | ||
| "label": "Conteúdo secreto", | ||
| "placeholder": "Cole sua senha, chave de API ou mensagem privada...", | ||
| "charCount": "{{count}} / 50,000", | ||
| "expiresIn": "Expira em", | ||
| "encrypting": "Criptografando...", | ||
| "submit": "Criar link secreto", | ||
| "linkCreated": "Link secreto criado", | ||
| "linkInfo": "Compartilhe este link com seu destinatario. Ele so pode ser aberto uma vez, depois e permanentemente destruido.", | ||
| "linkInfo": "Compartilhe este link com seu destinatário. Ele só pode ser aberto uma vez, depois é permanentemente destruído.", | ||
| "shareVia": "Compartilhar via", | ||
| "copy": "Copiar", | ||
| "copied": "Copiado", | ||
| "whatsapp": "WhatsApp", | ||
| "email": "E-mail", | ||
| "createAnother": "Criar outro", | ||
| "whatsappMsg": "Estou compartilhando um segredo com voce. Abra este link para ve-lo (apenas uma vez):\n\n{{link}}", | ||
| "emailSubject": "Aqui esta um segredo para voce", | ||
| "emailBody": "Estou compartilhando um segredo com voce. Abra este link para ve-lo — so pode ser aberto uma vez:\n\n{{link}}" | ||
| "whatsappMsg": "Estou compartilhando um segredo com você. Abra este link para vê-lo (apenas uma vez):\n\n{{link}}", | ||
| "emailSubject": "Aqui está um segredo para você", | ||
| "emailBody": "Estou compartilhando um segredo com você. Abra este link para vê-lo — só pode ser aberto uma vez:\n\n{{link}}" | ||
| }, | ||
| "view": { | ||
| "loading": "Recuperando e descriptografando segredo...", | ||
| "destroyed": "Este segredo foi permanentemente destruido. Nao pode ser visualizado novamente.", | ||
| "destroyed": "Este segredo foi permanentemente destruído. Não pode ser visualizado novamente.", | ||
| "copySecret": "Copiar segredo", | ||
| "copiedClipboard": "Copiado para a area de transferencia", | ||
| "notFoundTitle": "Segredo nao disponivel", | ||
| "notFoundMsg": "Este segredo ja foi visualizado ou expirou. Segredos so podem ser acessados uma vez.", | ||
| "copiedClipboard": "Copiado para a área de transferência", | ||
| "notFoundTitle": "Segredo não disponível", | ||
| "notFoundMsg": "Este segredo já foi visualizado ou expirou. Segredos só podem ser acessados uma vez.", | ||
| "errorTitle": "Algo deu errado", | ||
| "errorMsg": "Nao foi possivel descriptografar o segredo. O link pode ser invalido.", | ||
| "invalidLink": "Link invalido — chave de descriptografia ausente", | ||
| "errorMsg": "Não foi possível descriptografar o segredo. O link pode ser inválido.", | ||
| "invalidLink": "Link inválido — chave de descriptografia ausente", | ||
| "newSecret": "Compartilhar novo segredo", | ||
| "backHome": "Voltar ao inicio" | ||
| "backHome": "Voltar ao início" | ||
| }, | ||
| "footer": { | ||
| "encryption": "AES-256-GCM", | ||
| "zeroKnowledge": "Conhecimento zero", | ||
| "autoDelete": "Auto-exclusao" | ||
| "autoDelete": "Auto-exclusão" | ||
| }, | ||
| "security": { | ||
| "title": "Como funciona", | ||
| "e2eTitle": "Criptografia ponta a ponta", | ||
| "e2eDesc": "Seu segredo e criptografado no seu navegador usando AES-256-GCM com um IV aleatorio de 96 bits antes de sair do seu dispositivo. A chave de criptografia nunca e enviada ao nosso servidor.", | ||
| "hkdfTitle": "Derivacao de chave HKDF", | ||
| "hkdfDesc": "Uma chave de criptografia unica e derivada para cada segredo usando HKDF-SHA-256 com o ID do segredo como contexto. Mesmo com a chave mestra, cada segredo tem sua propria chave criptograficamente independente.", | ||
| "aadTitle": "Vinculacao de dados autenticados", | ||
| "aadDesc": "O ID do segredo e vinculado como Dados Autenticados Adicionais (AAD) durante a criptografia. Se alguem adulterar o ID ou trocar o texto cifrado entre segredos, a descriptografia falhara.", | ||
| "e2eDesc": "Seu segredo é criptografado no seu navegador usando AES-256-GCM com um IV aleatório de 96 bits antes de sair do seu dispositivo. A chave de criptografia nunca é enviada ao nosso servidor.", | ||
| "hkdfTitle": "Derivação de chave HKDF", | ||
| "hkdfDesc": "Uma chave de criptografia única é derivada para cada segredo usando HKDF-SHA-256 com o ID do segredo como contexto. Mesmo com a chave mestra, cada segredo tem sua própria chave criptograficamente independente.", | ||
| "aadTitle": "Vinculação de dados autenticados", | ||
| "aadDesc": "O ID do segredo é vinculado como Dados Autenticados Adicionais (AAD) durante a criptografia. Se alguém adulterar o ID ou trocar o texto cifrado entre segredos, a descriptografia falhará.", | ||
| "zkTitle": "Conhecimento zero", | ||
| "zkDesc": "O servidor armazena apenas dados criptografados. Nao podemos ler, descriptografar ou acessar seus segredos de nenhuma forma.", | ||
| "zkDesc": "O servidor armazena apenas dados criptografados. Não podemos ler, descriptografar ou acessar seus segredos de nenhuma forma.", | ||
| "keyTitle": "A chave nunca sai do navegador", | ||
| "keyDesc": "A chave de descriptografia e colocada apos o # na URL. Fragmentos de URL do navegador nunca sao enviados aos servidores.", | ||
| "oneTimeTitle": "Visualizacao unica", | ||
| "oneTimeDesc": "Quando um segredo e recuperado, ele e atomicamente excluido do armazenamento na mesma operacao.", | ||
| "expiryTitle": "Expiracao automatica", | ||
| "expiryDesc": "Segredos expiram automaticamente apos o TTL escolhido (1-72 horas), mesmo se nunca forem visualizados." | ||
| "keyDesc": "A chave de descriptografia é colocada após o # na URL. Fragmentos de URL do navegador nunca são enviados aos servidores.", | ||
| "oneTimeTitle": "Visualização única", | ||
| "oneTimeDesc": "Quando um segredo é recuperado, ele é atomicamente excluído do armazenamento na mesma operação.", | ||
| "expiryTitle": "Expiração automática", | ||
| "expiryDesc": "Segredos expiram automaticamente após o TTL escolhido (1-72 horas), mesmo se nunca forem visualizados." | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The canonical/OG/Twitter URLs are hard-coded to
https://ooshare.io/. If this UI is deployed in other environments (staging, self-hosted, preview domains), these tags will point to the wrong origin and produce incorrect share previews/canonicalization. Consider using a build-time env placeholder (Vite supports%VITE_*%inindex.html) or otherwise parameterizing the site base URL per deployment.