택소노미

이 페이지

택소노미는 하나 이상의 컬렉션에 적용되는 이름 있는 분류입니다. EmDash는 게시물용 계층형 category 택소노미와 평면 tag 택소노미로 시작합니다. 사이트는 genre, topic, difficulty 같은 택소노미도 정의할 수 있습니다.

용어는 택소노미에 속합니다. “Guides” 같은 카테고리는 자식 카테고리를 가질 수 있고, 태그와 다른 평면 택소노미는 한 수준입니다.

용어 관리

EmDash 관리의 Taxonomies에서 택소노미를 엽니다.

  1. Add Category, Add Tag, 또는 현재 택소노미에 해당하는 작업을 클릭합니다.

  2. 레이블과 슬러그를 입력합니다. 계층형 택소노미에서는 용어가 다른 용어 아래에 속할 때 부모를 선택합니다.

  3. 선택적 설명을 추가한 뒤 용어를 만듭니다.

  4. 용어 목록의 이동 컨트롤을 사용해 현재 부모 그룹 내 순서를 설정합니다.

편집자는 콘텐츠 항목의 택소노미 패널에서 용어를 할당합니다. 택소노미 정의가 어떤 컬렉션이 각 패널을 표시할지 제어합니다.

용어를 삭제하면 콘텐츠에서 그 할당이 제거됩니다. 콘텐츠 항목 자체는 삭제되지 않습니다.

여러 게시물에 하나의 태그 추가

편집자는 컬렉션 목록에서 게시물을 선택하고 Add tag를 클릭하거나, Tags를 열고 Add to posts를 클릭해 공개 게시물 URL을 붙여넣을 수 있습니다(줄당 하나, 최대 50). 기존 태그를 선택하거나 대화상자에서 만들고 Review posts를 클릭합니다. 일치한 제목과 언어를 모두 확인한 뒤, 몇 개의 게시물이 태그될지 보여주는 버튼을 클릭합니다. 이 사이트의 게시된 게시물과 정확히 일치하지 않는 링크는 추측되지 않고 표시됩니다.

태그 추가는 즉시 적용되며, 게시물에 다른 미게시 초안 편집이 있어도 그 편집을 게시하지 않습니다. 기존 태그는 그대로 두고, 이미 해당 태그가 있는 게시물은 건너뜁니다. 결과 목록은 태그됨·건너뜀·태그할 수 없음을 보여 주며, 쓰기 실패에는 Retry failures를 사용합니다. URL 일치는 사이트의 구성된 공개 오리진과 컬렉션 URL 패턴(날짜·언어 경로 포함)을 사용합니다.

사용자 지정 택소노미 추가

기존 컬렉션에 별도의 분류가 필요할 때 택소노미를 만듭니다.

  1. Taxonomies를 열고 New Taxonomy를 클릭합니다.

  2. 레이블과 안정적인 이름을 입력합니다. 이름은 소문자로 시작하며 소문자, 숫자, 밑줄만 포함합니다.

  3. 용어에 부모·자식 관계가 필요하면 Hierarchical를 활성화합니다.

  4. 택소노미를 사용할 수 있는 모든 컬렉션을 선택한 뒤 Create Taxonomy를 클릭합니다.

  5. 초기 용어를 추가하고 콘텐츠에 할당합니다.

템플릿은 안정적인 이름을 쿼리합니다. 표시 레이블을 바꿔도 템플릿 변경이 필요 없습니다.

사용자 지정 택소노미는 카테고리·태그와 같은 쿼리·필터 헬퍼를 사용합니다. 다음 예는 genre 용어를 읽고 슬러그 하나로 책을 필터합니다.

import { getEmDashCollection, getTaxonomyTerms } from "emdash";

const genres = await getTaxonomyTerms("genre", { includeCounts: false });
const { entries: scienceFictionBooks } = await getEmDashCollection("books", {
  where: { genre: "science-fiction" },
});

택소노미 삭제

관리에서 택소노미를 연 뒤 페이지 헤더 작업 메뉴에서 Delete taxonomy를 선택하고 확인합니다. 이 작업에는 편집자와 관리자가 가진 taxonomies:manage 권한이 필요합니다.

택소노미를 삭제하면 모든 언어의 용어가 삭제되고, 그 아래에 분류된 콘텐츠에서 해당 용어가 제거됩니다. 콘텐츠 항목 자체는 유지됩니다.

용어 목록 쿼리

getTaxonomyTerms()를 사용해 택소노미 인덱스, 탐색 목록, 필터 집합을 렌더링합니다. 계층형 택소노미는 각 용어의 children 배열을 통해 트리를 반환합니다.

용어 수는 기본적으로 포함되며 택소노미의 할당된 컬렉션에 대한 집계가 필요합니다. 컴포넌트가 수를 표시하지 않으면 그 작업을 건너뜁니다.

다음 컴포넌트는 수 없이 카테고리 링크를 렌더링합니다.

---
import { getTaxonomyTerms } from "emdash";
import { getRelativeLocaleUrl } from "astro:i18n";

const locale = Astro.currentLocale;
const categories = await getTaxonomyTerms("category", {
  locale,
  includeCounts: false,
});

function categoryHref(slug: string) {
  const path = `/category/${slug}`;
  return locale ? getRelativeLocaleUrl(locale, path) : path;
}
---

<nav aria-label="Categories">
  <ul>
    {categories.map((category) => (
      <li>
        <a href={categoryHref(category.slug)}>{category.label}</a>
        {category.children.length > 0 && (
          <ul>
            {category.children.map((child) => (
              <li><a href={categoryHref(child.slug)}>{child.label}</a></li>
            ))}
          </ul>
        )}
      </li>
    ))}
  </ul>
</nav>

컴포넌트가 사용량을 표시할 때는 includeCounts: false를 생략하고 term.count를 렌더링합니다. 수는 쿼리에 사용된 로케일의 공개 가시 항목을 포함합니다.

택소노미 아카이브 만들기

용어를 조회하기 전에 동적 라우트 매개변수를 디코딩합니다. 같은 로케일로 용어와 콘텐츠를 쿼리하고, 생성된 경로를 Astro의 로케일 URL 헬퍼에 통과시킵니다.

다음 라우트는 한 카테고리의 게시된 게시물을 나열합니다.

---
import { decodeSlug, getEmDashCollection, getTerm } from "emdash";
import { getRelativeLocaleUrl } from "astro:i18n";
import Base from "../../layouts/Base.astro";

const locale = Astro.currentLocale;
const slug = decodeSlug(Astro.params.slug);
const category = slug
  ? await getTerm("category", slug, { locale, includeCounts: false })
  : null;

if (!category) {
  return Astro.redirect("/404");
}

const { entries: posts } = await getEmDashCollection("posts", {
  status: "published",
  locale,
  where: { category: category.slug },
  orderBy: { published_at: "desc" },
});

function postHref(postSlug: string) {
  const path = `/posts/${postSlug}`;
  return locale ? getRelativeLocaleUrl(locale, path) : path;
}
---

<Base title={`${category.label} posts`}>
  <h1>{category.label}</h1>
  {category.description && <p>{category.description}</p>}

  {posts.length > 0 ? (
    <ul>
      {posts.map((post) => (
        post.data.slug && (
          <li>
            <a href={postHref(post.data.slug)}>{post.data.title}</a>
          </li>
        )
      ))}
    </ul>
  ) : (
    <p>No posts in this category.</p>
  )}
</Base>

where는 택소노미 이름을 키로, 용어 슬러그를 값으로 사용합니다. 쿼리 정렬 식별자는 published_at 같은 데이터베이스 필드 이름을 쓰고, 항목 데이터는 해당 값을 publishedAt으로 노출합니다.

postHref()에서는 컬렉션의 실제 공개 라우트를 사용하세요. 컬렉션이 사용자 지정 urlPattern을 쓰면 /posts/{slug}를 가정하지 말고 그 패턴으로 링크를 만드세요.

항목의 용어 표시

getEmDashEntry()와 getEmDashCollection()은 할당된 용어를 entry.data.terms에 하이드레이트합니다. 목록의 모든 항목에 getEntryTerms()를 한 번씩 실행하는 대신 그 값을 읽으세요.

다음 컴포넌트는 게시물과 함께 이미 로드된 카테고리와 태그를 렌더링합니다.

---
import type { ContentEntry, InferCollectionData } from "emdash";
import { getRelativeLocaleUrl } from "astro:i18n";

interface Props {
  post: ContentEntry<InferCollectionData<"posts">>;
}

const { post } = Astro.props;
const locale = Astro.currentLocale;
const categories = post.data.terms?.category ?? [];
const tags = post.data.terms?.tag ?? [];

function termHref(taxonomy: string, slug: string) {
  const path = `/${taxonomy}/${slug}`;
  return locale ? getRelativeLocaleUrl(locale, path) : path;
}
---

{categories.length > 0 && (
  <ul aria-label="Categories">
    {categories.map((category) => (
      <li>
        <a href={termHref("category", category.slug)}>{category.label}</a>
      </li>
    ))}
  </ul>
)}

{tags.length > 0 && (
  <ul aria-label="Tags">
    {tags.map((tag) => (
      <li>
        <a href={termHref("tag", tag.slug)}>{tag.label}</a>
      </li>
    ))}
  </ul>
)}

컬렉션 이름과 항목 ID만 있을 때는 getEntryTerms()를 사용하세요. 콘텐츠 쿼리로 하이드레이트되지 않은 여러 항목의 용어를 묶을 때는 getTermsForEntries()를 사용하세요.

택소노미와 용어 번역

택소노미 정의와 용어는 로케일당 한 행이 있습니다. EmDash는 어떤 행이 같은 택소노미 또는 용어의 번역인지 기록합니다. 콘텐츠 할당은 그 공유 정체성을 사용하므로, 한 로케일에서 만든 할당은 다른 로케일에 번역 용어가 있으면 그쪽으로 해석됩니다.

택소노미 정의는 필드를 택소노미와 각 로케일에 나눕니다.

필드소속효과
name택소노미생성 후 고정. 모든 로케일 정의가 같은 이름을 사용합니다.
hierarchical, collections택소노미모든 로케일에서 동일. 어느 로케일을 통해 바꾸든 모든 로케일에 적용됩니다.
label, labelSingular로케일각 로케일 정의가 자체 값을 가집니다.

다른 로케일에 이미 있는 이름으로 만든 정의는 translationOf 여부와 관계없이 그 택소노미에 합류하고 hierarchical과 collections를 가져옵니다. 다른 값으로 만들면 실패하므로 업데이트로 변경하세요. 자체 정의가 없는 로케일도 택소노미 용어를 나열하며, 폴백 체인에서 레이블이 있는 첫 로케일의 레이블, 없으면 기본 로케일 레이블, 없으면 로케일 코드가 가장 낮은 로케일의 레이블을 표시합니다.

택소노미 페이지의 로케일 전환기를 사용해 구성된 각 로케일에서 용어를 관리하세요. 용어 편집 대화상자를 열고 Translations 패널로 다른 로케일을 추가하거나 엽니다. 번역된 용어는 다른 슬러그와 레이블을 사용할 수 있습니다.

용어의 부모와 위치는 모든 로케일이 공유합니다. 부모 없이 만든 번역은 해당 용어의 부모와 위치를 가져옵니다. 다른 부모 아래에 번역을 만들거나 어느 로케일을 통해 부모를 바꾸면 모든 로케일에서 용어가 이동합니다.

쿼리 헬퍼는 제공되면 명시적 로케일을 사용합니다. 그렇지 않으면 현재 요청 로케일, 그다음 구성된 기본값을 사용합니다. 단일 용어 조회는 요청한 번역이 없을 때 구성된 폴백 체인을 따릅니다.

로케일 라우팅과 폴백 구성은 Internationalization, 항목 편집은 Working with Content를 참고하세요. 런타임 API 참조는 택소노미 쿼리 헬퍼를 문서화합니다. 프로그래밍 방식 변경에는 Bearer 토큰으로 인증하고, 상태를 바꾸는 모든 요청에 X-EmDash-Request: 1을 추가하세요. 요청 본문과 응답은 택소노미 엔드포인트를 참고하세요.