Skip to content

Commit

Permalink
translations
Browse files Browse the repository at this point in the history
  • Loading branch information
elitan committed Nov 5, 2023
1 parent 8fdeda9 commit f3c7fa3
Show file tree
Hide file tree
Showing 24 changed files with 665 additions and 142 deletions.
2 changes: 1 addition & 1 deletion db/apply.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
atlas schema apply \
-u "postgres://postgres:password@ai:9001/postgres?sslmode=disable" \
--to file://schema.sql \
--dev-url "docker://postgres/15/test" \
--dev-url "docker://postgres/15/test"
28 changes: 23 additions & 5 deletions db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,40 @@ CREATE TABLE articles(
id serial PRIMARY KEY,
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
title varchar(255),
slug varchar(255),
body text,
title varchar(255), -- REMOVE after translation
slug varchar(255), -- REMOVE after translation
body text, -- REMOVE after translation
sveriges_radio_title varchar(255) NOT NULL,
sveriges_radio_link varchar(255) NOT NULL,
transcribed_text text,
article_image_id integer REFERENCES article_images(id),
image_url text, -- deprecated
image_prompt text, -- deprecated
image_is_ai_generated boolean DEFAULT TRUE, -- deprecated
audio_url text,
audio_url text, -- REMOVE after translation
is_related_to_sweden boolean,
is_published boolean DEFAULT FALSE, -- REMOVE after translation
is_published_on_social_media boolean DEFAULT FALSE, -- REMOVE after translation
category text, -- REMOVE after translation
page_views integer DEFAULT 0 -- REMOVE after translation
);

CREATE TABLE article_translations(
id serial PRIMARY KEY,
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
article_id integer REFERENCES articles(id) ON DELETE CASCADE,
"language" text NOT NULL,
title varchar(255),
slug varchar(255),
body text,
audio_url text,
is_published boolean DEFAULT FALSE,
social_media_hook text,
is_published_on_social_media boolean DEFAULT FALSE,
category text,
page_views integer DEFAULT 0
page_views integer DEFAULT 0,
UNIQUE (article_id, "language")
);

CREATE TABLE article_social_media_hooks(
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/ArticleSummaryLarge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function ArticleSummaryLarge({ article }: { article: any }) {
key={article.id}
className={`flex space-x-4 md:col-span-1 col-span-2 border-b-1 border-gray-200 my-0 py-0 `}
>
<Link className="w-full" href={`/nyheter/${article.slug}`}>
<Link className="w-full" href={`/${article.language}/${article.slug}`}>
<div
className={`border border-gray-200 h-80`}
style={{
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/ArticleSummarySmall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function ArticleSummarySmall({ article }: { article: any }) {
>
<Link
className="w-full rounded-lg p-1 flex justify-between space-x-4 items-start"
href={`/nyheter/${article.slug}`}
href={`/${article.language}/${article.slug}`}
>
<div className="py-1 lg:py-2">
<h1 className="w-full text-lg lg:text-xl mb-1 font-semibold font-serif group-hover:text-gray-500">
Expand Down
1 change: 1 addition & 0 deletions web/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from 'next/link';
const navigation = {
main: [
{ name: 'About', href: '/about' },
{ name: 'Articles', href: '/articles' },
// { name: 'Blog', href: '#' },
// { name: 'Jobs', href: '#' },
// { name: 'Press', href: '#' },
Expand Down
57 changes: 41 additions & 16 deletions web/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { format } from 'date-fns';
import Link from 'next/link';
import { Footer } from './Footer';
import { UserButton, useAuth } from '@clerk/nextjs';
import { useRouter } from 'next/router';
import { languages } from '@/utils/helpers';

function TopBanner() {
return (
Expand All @@ -23,20 +25,22 @@ function TopBanner() {
}

export function Layout({ children }: { children: React.ReactNode }) {
const now = new Date();
const currentDay = format(now, 'EEEE');
const currentDate = format(now, 'MMM d, yyyy');
const router = useRouter();

const lang = (router.query.lang as string) ?? 'en';

const { isSignedIn } = useAuth();

const dir = lang && languages[lang].rtl ? 'rtl' : 'ltr';

return (
<>
{/* <TopBanner /> */}
<div className="bg-slate-200">
<div className="bg-slate-200" dir={dir}>
<div className="bg-white top-0 sticky">
<MainContainer>
<div className="relative flex flex-col md:flex-row space-y-4 text-left justify-between py-4 items-center">
<Link className="space-x-4 flex items-center" href="/">
<div className="relative flex flex-col md:flex-row text-left justify-between py-2 items-center">
<Link className="space-x-4 flex items-center" href={`/${lang}`}>
<div>
<img src="/logo.png" className="h-10 w-10 " alt="Logo" />
</div>
Expand All @@ -48,20 +52,11 @@ export function Layout({ children }: { children: React.ReactNode }) {
</div>
<div>
<h1 className="text-gray-900 font-semibold text-xl">
Swedish News in English
{languages[lang].slogan}
</h1>
</div>
</div>
</Link>
<div className="flex items-center space-x-2">
<div>
<CalendarDaysIcon className="h-7 w-7" />
</div>
<div>
<div className="font-bold">{currentDay}</div>
<div>{currentDate}</div>
</div>
</div>
<div className="flex space-x-4">
<Link
href="/about"
Expand All @@ -79,6 +74,36 @@ export function Layout({ children }: { children: React.ReactNode }) {
)}
<UserButton />
</div>
<div className="flex items-center space-x-2">
<div>
<select
id="location"
name="location"
className="mt-1 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6"
defaultValue="Canada"
onChange={(e) => {
router.push(`/${e.target.value}`);
}}
>
{Object.keys(languages).map((key) => {
const language = languages[key];
return (
<option key={key} value={key}>
{language.name}
</option>
);
})}
</select>
</div>

{/* <div>
<CalendarDaysIcon className="h-7 w-7" />
</div>
<div>
<div className="font-bold">{currentDay}</div>
<div>{currentDate}</div>
</div> */}
</div>
</div>
</MainContainer>
</div>
Expand Down
3 changes: 3 additions & 0 deletions web/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { authMiddleware } from '@clerk/nextjs';
export default authMiddleware({
publicRoutes: [
'/',
'/:lang',
'/:lang/:slug',
'/nyheter',
'/nyheter/:slug',
'/nyheter/:lang/:slug',
'/about',
'/api/og-image/:slug',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,36 @@ import Link from 'next/link';
const ReactPlayer = dynamic(() => import('react-player/lazy'), { ssr: false });

interface IParams extends ParsedUrlQuery {
lang: string;
slug: string;
}

export async function getServerSideProps({ params }: { params: IParams }) {
const { slug } = params;
const { lang, slug } = params;

const article = await db
.selectFrom('articles')
.innerJoin('articleImages', 'articles.articleImageId', 'articleImages.id')
.selectFrom('articleTranslations as at')
.innerJoin('articles as a', 'a.id', 'at.articleId')
.innerJoin('articleImages as ai', 'a.articleImageId', 'ai.id')
.select([
'articles.id',
'articles.createdAt',
'articles.updatedAt',
'articles.title',
'articles.body',
'articles.slug',
'articles.sverigesRadioLink',
'articles.sverigesRadioTitle',
'articles.audioUrl',
'articleImages.imageUrl',
'articleImages.imageIsAiGenerated',
'articleImages.creditInfo',
'articleImages.imagePrompt',
'at.id',
'at.slug',
'at.title',
'at.body',
'at.category',
'at.audioUrl',
'a.createdAt',
'a.updatedAt',
'a.sverigesRadioLink',
'a.sverigesRadioTitle',
'ai.imageUrl',
'ai.imageIsAiGenerated',
'ai.creditInfo',
'ai.imagePrompt',
])
.where('slug', '=', slug)
.where('isPublished', '=', true)
.where('at.slug', '=', slug)
.where('at.language', '=', lang as string)
.where('at.isPublished', '=', true)
.executeTakeFirst();

if (!article) {
Expand All @@ -46,7 +50,7 @@ export async function getServerSideProps({ params }: { params: IParams }) {
}

await db
.updateTable('articles')
.updateTable('articleTranslations')
.set((eb) => ({
pageViews: eb.bxp('pageViews', '+', 1),
}))
Expand Down
142 changes: 142 additions & 0 deletions web/src/pages/[lang]/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { ArticleSummaryLarge } from '@/components/ArticleSummaryLarge';
import { ArticleSummarySmall } from '@/components/ArticleSummarySmall';
import { MainContainer } from '@/components/MainContainer';
import { db } from '@/utils/db';
import type { InferGetServerSidePropsType } from 'next';
import { type ParsedUrlQuery } from 'querystring';
import Link from 'next/link';

interface IParams extends ParsedUrlQuery {
lang: string;
}

export async function getServerSideProps({ params }: { params: IParams }) {
const { lang } = params;

if (lang === 'en') {
return {
redirect: {
destination: '/',
permanent: true,
},
};
}

const articles = await db
.selectFrom('articleTranslations as at')
.innerJoin('articles as a', 'a.id', 'at.articleId')
.innerJoin('articleImages as ai', 'a.articleImageId', 'ai.id')
.select([
'a.id',
'a.createdAt',
'at.title',
'at.slug',
'at.body',
'at.category',
'at.language',
'ai.imageUrl',
])
.where('at.title', 'is not', null)
.where('at.isPublished', '=', true)
.where('at.language', '=', lang)
.orderBy('a.createdAt', 'desc')
.limit(25)
.execute();

let today = new Date(); // get the current date
let sevenDaysAgo = new Date(today); // create a copy of the current date

sevenDaysAgo.setDate(today.getDate() - 7); // subtract 7 days

const popularArticles = await db
.selectFrom('articles')
.innerJoin('articleImages', 'articles.articleImageId', 'articleImages.id')
.select([
'articles.id',
'articles.createdAt',
'articles.title',
'articles.slug',
'articles.body',
'articles.category',
'articleImages.imageUrl',
])
.where('title', 'is not', null)
.where('isPublished', '=', true)
.where('articles.createdAt', '>', sevenDaysAgo)
.orderBy('pageViews', 'desc')
.limit(8)
.execute();

return {
props: {
articles,
popularArticles,
lang,
},
};
}

const Page = (
props: InferGetServerSidePropsType<typeof getServerSideProps>,
) => {
// get first three articles
// const firstThreeArticles = props.articles.slice(0, 3);

const { articles, popularArticles, lang } = props;

return (
<MainContainer>
{/* <HeaderIndex articles={firstThreeArticles} /> */}
<div className="grid grid-cols-11 gap-6">
<div className="col-span-11 lg:col-span-7 bg-gray-50 shadow-md mb-12 divide-y divide-slate-200 last:pb-0 mt-12">
{articles.map((article, i) => {
if (i % 5 === 0) {
return (
<ArticleSummaryLarge article={article} key={article.slug} />
);
} else {
return (
<ArticleSummarySmall article={article} key={article.slug} />
);
}
})}
</div>
<div className="lg:col-span-4 mt-12 bg-gray-50 shadow-md p-4 self-start">
<div className="font-bold text-xl">Most Read</div>
<div className="space-y-4 mt-4 pt-4 border-t">
{popularArticles.map((article) => {
return (
<Link
href={`/${lang}/${article.slug}`}
key={article.id}
className="space-x-4 flex"
>
<div
className={`border border-gray-200 rounded-md w-20 h-20 flex-shrink-0 `}
style={{
backgroundImage: `url(${article.imageUrl})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
/>
<div>
<div className="font-semibold font-serif group-hover:text-gray-500 text-sm">
{article.title}
</div>
<div className="flex mr-6 mt-3">
<p className="text-cyan-700 text-xs">
{article.category}
</p>
</div>
</div>
</Link>
);
})}
</div>
</div>
</div>
</MainContainer>
);
};

export default Page;
Loading

0 comments on commit f3c7fa3

Please sign in to comment.