Skip to content

Commit f1e1209

Browse files
committed
fix: social share
1 parent f20b707 commit f1e1209

12 files changed

Lines changed: 220 additions & 38 deletions

File tree

TODO.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
- [x] Improve sharing visuals
33
- [ ] Check SEO
44
- [x] Highlighting tokens when selecting (hovering) and when selected (with current color, which will be the link color)
5-
- [ ] Make sure instructions are clear and concise, up to date, and complete.
5+
- [x] Make sure instructions are clear and concise, up to date, and complete.
66
- [ ] Add better examples - with complex links and advanced tokenization.
77
- [x] Improve export card comment
8-
- [ ] Twitter doesn't show visual preview
8+
- [x] Twitter doesn't show visual preview
99
- [x] Dependency CVE check (`npm audit` / CI)
1010
- [x] Add QR code export method
1111
- [-] Add QR code to visualization exports (small in the corner - only site link; `siteLandingQrDataUrl` + `siteQrPngDataUri` in svg — wiring disabled in ExportMenu) - out of scope for now

bitext/src/lib/components/editor/Editor.svelte

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
<script lang="ts">
2-
import { Button, Card } from 'flowbite-svelte';
2+
import { Card } from 'flowbite-svelte';
33
import SentenceField from './SentenceField.svelte';
44
import GlossInputRow from './GlossInputRow.svelte';
55
import { projectStore } from '$lib/state/project.svelte.js';
66
import { settingsStore } from '$lib/state/settings.svelte.js';
77
</script>
88

99
<Card class="w-full max-w-none p-4 sm:p-6" aria-labelledby="editor-heading">
10-
<h2
11-
id="editor-heading"
12-
class="font-heading mb-4 text-lg font-semibold text-gray-900 dark:text-white"
13-
>
14-
Editor
15-
</h2>
16-
<div class="mb-4 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
17-
<p class="max-w-prose text-base text-gray-600 dark:text-gray-400">
18-
Edit the sentences here. To add word links, click words in the preview below — you can select
19-
several words on one side for many-to-one or one-to-many alignments.
20-
</p>
21-
<Button color="light" size="sm" class="shrink-0" onclick={() => projectStore.loadExample()}>
10+
<div class="mb-3 flex flex-wrap items-center justify-between gap-x-4 gap-y-2">
11+
<h2
12+
id="editor-heading"
13+
class="font-heading text-lg font-semibold text-gray-900 dark:text-white"
14+
>
15+
Editor
16+
</h2>
17+
<button
18+
type="button"
19+
class="shrink-0 rounded-none border-0 bg-transparent px-2 py-1 text-sm font-medium text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-800/80 dark:hover:text-gray-100 dark:focus-visible:outline-primary-500"
20+
onclick={() => projectStore.loadExample()}
21+
>
2222
Load example
23-
</Button>
23+
</button>
2424
</div>
25+
<p class="mb-4 w-full text-base text-gray-600 dark:text-gray-400">
26+
Edit the sentences here. To link words, click a word in the preview below, then click the
27+
matching word on the other line — the connector will appear. You can link a word to multiple
28+
words on the other side. Click a connector to remove it. Click a selected word again to deselect
29+
it.
30+
</p>
2531
<div class="grid grid-cols-12 gap-4">
2632
<div class="col-span-12 md:col-span-6">
2733
<SentenceField

bitext/src/lib/components/seo/SeoSections.svelte

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818
</h3>
1919
<p>
2020
Type your <strong class="font-medium text-gray-900 dark:text-white">source sentence</strong> and
21-
<strong class="font-medium text-gray-900 dark:text-white">target sentence</strong>, tap words on
22-
each side to select them, then create a link. The tool draws connector lines in the preview. You
23-
can use a
21+
<strong class="font-medium text-gray-900 dark:text-white">target sentence</strong>, click a word
22+
in the preview, then click the matching word on the other line — a connector is drawn right
23+
away. You can link a word to multiple words on the other side. Click any connector to remove it.
24+
A
2425
<strong class="font-medium text-gray-900 dark:text-white"
2526
>bilingual sentence alignment tool</strong
26-
> like this when textbook examples need to be legible at a glance.
27+
> like this keeps textbook examples legible at a glance.
2728
</p>
2829
<h3 class="font-heading mt-6 text-xl font-semibold text-gray-900 dark:text-white">
2930
Interlinear gloss explanation

bitext/src/lib/components/settings/ColorsTab.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@
5353
</Label>
5454
</div>
5555
<p class="mb-4 text-base text-gray-600 dark:text-gray-400">
56-
New links pick the next color from the palette. Remove a link in the preview by clicking its
57-
line.
56+
New links pick the next unused color from the palette. Changing the palette recolors every
57+
existing link.
5858
</p>
5959
<div class="mb-4 flex flex-wrap gap-2">
6060
{#each names as name (name)}
411 KB
Binary file not shown.
402 KB
Binary file not shown.
410 KB
Binary file not shown.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
2+
3+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
4+
This license is copied below, and is also available with a FAQ at:
5+
http://scripts.sil.org/OFL
6+
7+
-----------------------------------------------------------
8+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9+
-----------------------------------------------------------
10+
11+
PREAMBLE
12+
The goals of the Open Font License (OFL) are to stimulate worldwide
13+
development of collaborative font projects, to support the font creation
14+
efforts of academic and linguistic communities, and to provide a free and
15+
open framework in which fonts may be shared and improved in partnership
16+
with others.
17+
18+
The OFL allows the licensed fonts to be used, studied, modified and
19+
redistributed freely as long as they are not sold by themselves. The
20+
fonts, including any derivative works, can be bundled, embedded,
21+
redistributed and/or sold with any software provided that any reserved
22+
names are not used by derivative works. The fonts and derivatives,
23+
however, cannot be released under any other type of license. The
24+
requirement for fonts to remain under this license does not apply
25+
to any document created using the fonts or their derivatives.
26+
27+
DEFINITIONS
28+
"Font Software" refers to the set of files released by the Copyright
29+
Holder(s) under this license and clearly marked as such. This may
30+
include source files, build scripts and documentation.
31+
32+
"Reserved Font Name" refers to any names specified as such after the
33+
copyright statement(s).
34+
35+
"Original Version" refers to the collection of Font Software components as
36+
distributed by the Copyright Holder(s).
37+
38+
"Modified Version" refers to any derivative made by adding to, deleting,
39+
or substituting -- in part or in whole -- any of the components of the
40+
Original Version, by changing formats or by porting the Font Software to a
41+
new environment.
42+
43+
"Author" refers to any designer, engineer, programmer, technical
44+
writer or other person who contributed to the Font Software.
45+
46+
PERMISSION AND CONDITIONS
47+
Permission is hereby granted, free of charge, to any person obtaining
48+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
49+
redistribute, and sell modified and unmodified copies of the Font
50+
Software, subject to the following conditions:
51+
52+
1) Neither the Font Software nor any of its individual components,
53+
in Original or Modified Versions, may be sold by itself.
54+
55+
2) Original or Modified Versions of the Font Software may be bundled,
56+
redistributed and/or sold with any software, provided that each copy
57+
contains the above copyright notice and this license. These can be
58+
included either as stand-alone text files, human-readable headers or
59+
in the appropriate machine-readable metadata fields within text or
60+
binary files as long as those fields can be easily viewed by the user.
61+
62+
3) No Modified Version of the Font Software may use the Reserved Font
63+
Name(s) unless explicit written permission is granted by the corresponding
64+
Copyright Holder. This restriction only applies to the primary font name as
65+
presented to the users.
66+
67+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68+
Software shall not be used to promote, endorse or advertise any
69+
Modified Version, except to acknowledge the contribution(s) of the
70+
Copyright Holder(s) and the Author(s) or with their explicit written
71+
permission.
72+
73+
5) The Font Software, modified or unmodified, in part or in whole,
74+
must be distributed entirely under this license, and must not be
75+
distributed under any other license. The requirement for fonts to
76+
remain under this license does not apply to any document created
77+
using the Font Software.
78+
79+
TERMINATION
80+
This license becomes null and void if any of the above conditions are
81+
not met.
82+
83+
DISCLAIMER
84+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92+
OTHER DEALINGS IN THE FONT SOFTWARE.

bitext/src/lib/seo/og-fonts.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { mkdtempSync, writeFileSync } from 'node:fs';
2+
import { tmpdir } from 'node:os';
3+
import { join } from 'node:path';
4+
import { read } from '$app/server';
5+
import InterRegular from './fonts/Inter-Regular.ttf';
6+
import InterSemiBold from './fonts/Inter-SemiBold.ttf';
7+
import InterBold from './fonts/Inter-Bold.ttf';
8+
9+
interface FontAsset {
10+
url: string;
11+
filename: string;
12+
}
13+
14+
const FONT_ASSETS: FontAsset[] = [
15+
{ url: InterRegular, filename: 'Inter-Regular.ttf' },
16+
{ url: InterSemiBold, filename: 'Inter-SemiBold.ttf' },
17+
{ url: InterBold, filename: 'Inter-Bold.ttf' }
18+
];
19+
20+
let cached: string[] | null = null;
21+
22+
/**
23+
* Materialize the bundled Inter TTF assets to filesystem paths for `@resvg/resvg-js`.
24+
*
25+
* Resvg 2.6.x has no built-in fonts and its `fontFiles` option only accepts filesystem paths
26+
* (not buffers, not WOFF2). Without these paths every `<text>` in the SVG silently renders as
27+
* empty on slim server images (Railway, Docker alpine) → a blank OG PNG.
28+
*
29+
* We import the TTFs through SvelteKit's `$app/server:read` so the files are copied into the
30+
* build by Vite, then spill their bytes into a temp directory and hand those paths to resvg.
31+
* Cached in-memory for the lifetime of the process.
32+
*/
33+
export async function loadOgFontFiles(): Promise<string[]> {
34+
if (cached) return cached;
35+
const dir = mkdtempSync(join(tmpdir(), 'bitext-og-fonts-'));
36+
const paths: string[] = [];
37+
for (const asset of FONT_ASSETS) {
38+
try {
39+
const res = read(asset.url);
40+
const buf = Buffer.from(await res.arrayBuffer());
41+
const target = join(dir, asset.filename);
42+
writeFileSync(target, buf);
43+
paths.push(target);
44+
} catch {
45+
// best-effort: missing weight only affects weight matching, not rendering
46+
}
47+
}
48+
cached = paths;
49+
return cached;
50+
}

bitext/src/lib/seo/og-svg.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
11
import { escapeXml } from '$lib/export/xml.js';
2+
import { ALIGNER_SITE_HOST } from '$lib/brand.js';
23
import type { AppStateV1 } from '$lib/serialization/schema.js';
34

4-
/** Simple OG preview (no DOM layout) — two lines of text + branding. */
5+
/** Social preview dimensions recommended by both Facebook and Twitter/X (1.91:1). */
6+
export const OG_IMAGE_WIDTH = 1200;
7+
export const OG_IMAGE_HEIGHT = 630;
8+
9+
const FONT_FAMILY = 'Inter, system-ui, sans-serif';
10+
11+
function truncate(text: string, max: number): string {
12+
const trimmed = text.trim();
13+
if (trimmed.length <= max) return trimmed;
14+
return trimmed.slice(0, Math.max(0, max - 1)).trimEnd() + '…';
15+
}
16+
17+
/** Minimal OG preview: the two sentences as a visual sample of the alignment, plus branding. */
518
export function buildOgSvg(state: AppStateV1): string {
6-
const src = state.project.sourceText.slice(0, 120);
7-
const tgt = state.project.targetText.slice(0, 120);
8-
const w = 1200;
9-
const h = 630;
19+
const src = truncate(state.project.sourceText || 'Type a sentence…', 80);
20+
const tgt = truncate(state.project.targetText || 'Add its translation…', 80);
21+
const w = OG_IMAGE_WIDTH;
22+
const h = OG_IMAGE_HEIGHT;
23+
1024
return `<?xml version="1.0" encoding="UTF-8"?>
1125
<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
12-
<defs><linearGradient id="g" x1="0" x2="1" y1="0" y2="1">
13-
<stop offset="0%" stop-color="#0f172a"/><stop offset="100%" stop-color="#1e293b"/>
14-
</linearGradient></defs>
15-
<rect width="100%" height="100%" fill="url(#g)"/>
16-
<text x="60" y="120" fill="#f8fafc" font-family="system-ui,sans-serif" font-size="42" font-weight="600">Word-by-word translation</text>
17-
<text x="60" y="220" fill="#94a3b8" font-family="system-ui,sans-serif" font-size="28">${escapeXml(src)}</text>
18-
<text x="60" y="300" fill="#e2e8f0" font-family="system-ui,sans-serif" font-size="28">${escapeXml(tgt)}</text>
19-
<text x="60" y="560" fill="#64748b" font-family="system-ui,sans-serif" font-size="22">bitext align · word alignment visualizer</text>
26+
<defs>
27+
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
28+
<stop offset="0%" stop-color="#0f172a"/>
29+
<stop offset="100%" stop-color="#1e293b"/>
30+
</linearGradient>
31+
</defs>
32+
<rect width="100%" height="100%" fill="url(#bg)"/>
33+
<rect x="60" y="80" width="${w - 120}" height="4" fill="#6366f1" opacity="0.9"/>
34+
<text x="60" y="180" fill="#f8fafc" font-family="${FONT_FAMILY}" font-size="64" font-weight="700">Word-by-word translation</text>
35+
<text x="60" y="228" fill="#c7d2fe" font-family="${FONT_FAMILY}" font-size="26" font-weight="500">Bilingual sentence alignment visualizer</text>
36+
<text x="60" y="340" fill="#e2e8f0" font-family="${FONT_FAMILY}" font-size="40" font-weight="600">${escapeXml(src)}</text>
37+
<line x1="60" y1="372" x2="${w - 60}" y2="372" stroke="#334155" stroke-width="2" stroke-dasharray="6 8"/>
38+
<text x="60" y="430" fill="#e2e8f0" font-family="${FONT_FAMILY}" font-size="40" font-weight="600">${escapeXml(tgt)}</text>
39+
<text x="60" y="560" fill="#94a3b8" font-family="${FONT_FAMILY}" font-size="24" font-weight="500">${escapeXml(ALIGNER_SITE_HOST)}</text>
40+
<text x="${w - 60}" y="560" text-anchor="end" fill="#64748b" font-family="${FONT_FAMILY}" font-size="22" font-weight="400">Free · PNG / SVG / PDF / HTML</text>
2041
</svg>`;
2142
}

0 commit comments

Comments
 (0)