1
0
mirror of synced 2025-12-23 11:54:18 -05:00

ALL translations strings shipped with every page (#44284)

Co-authored-by: Robert Sese <734194+rsese@users.noreply.github.com>
This commit is contained in:
Peter Bengtsson
2023-10-31 14:26:14 -04:00
committed by GitHub
parent 09714c271a
commit 297ddc1b2c
44 changed files with 500 additions and 382 deletions

View File

@@ -28,7 +28,7 @@ export const DefaultLayout = (props: Props) => {
fullUrl, fullUrl,
status, status,
} = useMainContext() } = useMainContext()
const { t } = useTranslation(['errors', 'meta', 'scroll_button']) const { t } = useTranslation(['meta', 'scroll_button'])
const router = useRouter() const router = useRouter()
const metaDescription = page.introPlainText ? page.introPlainText : t('default_description') const metaDescription = page.introPlainText ? page.introPlainText : t('default_description')
const { languages } = useLanguages() const { languages } = useLanguages()

View File

@@ -55,6 +55,9 @@ export type ProductTreeNode = {
childPages: Array<ProductTreeNode> childPages: Array<ProductTreeNode>
} }
type UIString = Record<string, string>
export type UIStrings = UIString | { [key: string]: UIStrings }
export type EnterpriseDeprecation = { export type EnterpriseDeprecation = {
version_was_deprecated: string version_was_deprecated: string
version_will_be_deprecated: string version_will_be_deprecated: string
@@ -67,12 +70,13 @@ type DataReusables = {
} }
type DataT = { type DataT = {
ui: Record<string, any> ui: UIStrings
reusables: DataReusables reusables: DataReusables
variables: { variables: {
release_candidate: { version: string } release_candidate: { version: string }
} }
} }
type EnterpriseServerReleases = { type EnterpriseServerReleases = {
isOldestReleaseDeprecated: boolean isOldestReleaseDeprecated: boolean
oldestSupported: string oldestSupported: string
@@ -126,6 +130,38 @@ export type MainContextT = {
fullUrl: string fullUrl: string
} }
// Write down the namespaces from `data/ui.yml` that are used on all pages,
// they will always be available and don't need to be manually added.
// Order does not matter on these.
const DEFAULT_UI_NAMESPACES = [
'header',
'search',
'survey',
'toc',
'meta',
'scroll_button',
'pages',
'picker',
'footer',
'contribution_cta',
'support',
'rest',
]
export function addUINamespaces(req: any, ui: UIStrings, namespaces: string[]) {
const pool = req.context.site.data.ui
for (const namespace of namespaces) {
if (!(namespace in pool)) {
throw new Error(
`Invalid namespace "${namespace}". It's not present in data/ui.yml as a namespace. (not one of: ${Object.keys(
pool,
)})`,
)
}
ui[namespace] = pool[namespace]
}
}
export const getMainContext = async (req: any, res: any): Promise<MainContextT> => { export const getMainContext = async (req: any, res: any): Promise<MainContextT> => {
// Our current translation process adds 'ms.*' frontmatter properties to files // Our current translation process adds 'ms.*' frontmatter properties to files
// it translates including when data/ui.yml is translated. We don't use these // it translates including when data/ui.yml is translated. We don't use these
@@ -140,6 +176,9 @@ export const getMainContext = async (req: any, res: any): Promise<MainContextT>
} }
const { documentType } = req.context.page const { documentType } = req.context.page
const ui: UIStrings = {}
addUINamespaces(req, ui, DEFAULT_UI_NAMESPACES)
// Every product landing page has a listing of all articles. // Every product landing page has a listing of all articles.
// It's used by the <ProductArticlesList> component. // It's used by the <ProductArticlesList> component.
const includeFullProductTree = documentType === 'product' const includeFullProductTree = documentType === 'product'
@@ -171,7 +210,7 @@ export const getMainContext = async (req: any, res: any): Promise<MainContextT>
isHomepageVersion: req.context.page?.documentType === 'homepage', isHomepageVersion: req.context.page?.documentType === 'homepage',
error: req.context.error ? req.context.error.toString() : '', error: req.context.error ? req.context.error.toString() : '',
data: { data: {
ui: req.context.site.data.ui, ui,
reusables, reusables,

View File

@@ -65,8 +65,8 @@ redirect_from:
- /admin/configuration/configuring-your-enterprise/configuring-data-encryption-for-your-enterprise - /admin/configuration/configuring-your-enterprise/configuring-data-encryption-for-your-enterprise
introLinks: introLinks:
overview: '{% ifversion ghes %}/admin/overview/about-github-enterprise-server{% elsif ghae %}/admin/overview/about-github-ae{% elsif ghec %}/admin/overview/about-github-enterprise-cloud{% endif %}' overview: '{% ifversion ghes %}/admin/overview/about-github-enterprise-server{% elsif ghae %}/admin/overview/about-github-ae{% elsif ghec %}/admin/overview/about-github-enterprise-cloud{% endif %}'
Releases: '{% ifversion ghes %}/admin/all-releases{% endif %}' releases: '{% ifversion ghes %}/admin/all-releases{% endif %}'
Try Enterprise Cloud for free: '{% ifversion ghec %}https://github.com/account/enterprises/new{% endif %}' try_ghec_for_free: '{% ifversion ghec %}https://github.com/account/enterprises/new{% endif %}'
changelog: changelog:
label: enterprise label: enterprise
featuredLinks: featuredLinks:
@@ -127,4 +127,3 @@ children:
- /release-notes - /release-notes
- /all-releases - /all-releases
--- ---

View File

@@ -4,7 +4,7 @@ shortTitle: Migrations
intro: "If you're moving to {% data variables.product.prodname_dotcom %} from another code hosting platform or moving between {% data variables.product.prodname_dotcom %} products, learn how to use our migration tooling to bring your work with you." intro: "If you're moving to {% data variables.product.prodname_dotcom %} from another code hosting platform or moving between {% data variables.product.prodname_dotcom %} products, learn how to use our migration tooling to bring your work with you."
introLinks: introLinks:
overview: /migrations/overview/about-githubs-migration-tooling overview: /migrations/overview/about-githubs-migration-tooling
Plan your migration: /migrations/overview/planning-your-migration-to-github plan_your_migration: /migrations/overview/planning-your-migration-to-github
featuredLinks: featuredLinks:
startHere: startHere:
- /migrations/importing-source-code/using-github-importer/about-github-importer - /migrations/importing-source-code/using-github-importer/about-github-importer

View File

@@ -23,17 +23,13 @@ picker:
release_notes: release_notes:
banner_text: GitHub began rolling these changes out to enterprises on banner_text: GitHub began rolling these changes out to enterprises on
search: search:
need_help: Need help?
placeholder: Search GitHub Docs
search_results_for: Search results for
no_content: No content
matches_found: Results found
matches_displayed: Matches displayed
search_error: An error occurred trying to perform the search.
description: Enter a search term to find it in the GitHub Docs. description: Enter a search term to find it in the GitHub Docs.
placeholder: Search GitHub Docs
label: Search GitHub Docs label: Search GitHub Docs
search_results:
search_results_for: Search results for
matches_displayed: Matches displayed
n_results: '{n} results' n_results: '{n} results'
one_result: 1 result
search_validation_error: Validation error with search query search_validation_error: Validation error with search query
homepage: homepage:
explore_by_product: Explore by product explore_by_product: Explore by product
@@ -53,11 +49,6 @@ pages:
about_versions: About versions about_versions: About versions
permissions_statement: Who can use this feature permissions_statement: Who can use this feature
video_from_transcript: See video for this transcript video_from_transcript: See video for this transcript
errors:
oops: Ooops!
something_went_wrong: It looks like something went wrong.
we_track_errors: We track these errors automatically, but if the problem persists please feel free to contact us.
page_doesnt_exist: It looks like this page doesn't exist.
support: support:
still_need_help: Still need help? still_need_help: Still need help?
contact_support: Contact support contact_support: Contact support
@@ -97,13 +88,9 @@ parameter_table:
see_preview_notices: See preview notices see_preview_notices: See preview notices
type: Type type: Type
single_enum_description: Value single_enum_description: Value
products:
audit_logs: audit_logs:
action: Action action: Action
description: Description description: Description
button_text:
copy_to_clipboard: Copy to clipboard
copied: Copied!
graphql: graphql:
reference: reference:
implements: <code>{{ GraphQLItemTitle }}</code> Implements implements: <code>{{ GraphQLItemTitle }}</code> Implements
@@ -145,7 +132,7 @@ products:
additionalPermissions: Additional permissions additionalPermissions: Additional permissions
uat: UAT uat: UAT
iat: IAT iat: IAT
reference: rest_reference:
in: In in: In
description: Description description: Description
notes: Notes notes: Notes
@@ -175,6 +162,9 @@ products:
ghcli: GitHub CLI ghcli: GitHub CLI
javascript: JavaScript javascript: JavaScript
curl: cURL curl: cURL
button_text:
copy_to_clipboard: Copy to clipboard
copied: Copied!
webhooks: webhooks:
action_type_switch_error: There was an error switching webhook action types. action_type_switch_error: There was an error switching webhook action types.
action_type: Action type action_type: Action type
@@ -202,13 +192,11 @@ product_landing:
quickstart: Quickstart quickstart: Quickstart
reference: Reference reference: Reference
overview: Overview overview: Overview
try_ghec_for_free: Try Enterprise Cloud for free
plan_your_migration: Plan your migration
releases: Releases
guides: Guides guides: Guides
explore_guides: Explore guides explore_guides: Explore guides
code_examples: Code examples
search_code_examples: Search code examples
search_results_for: Search results for
matches_displayed: Matches displayed
show_more: Show more
explore_people_and_projects: Explore people and projects explore_people_and_projects: Explore people and projects
sorry: Sorry, there is no result for sorry: Sorry, there is no result for
no_example: It looks like we don't have an example that fits your filter. no_example: It looks like we don't have an example that fits your filter.
@@ -228,9 +216,6 @@ product_landing:
view: View all view: View all
view_transcript: View video transcript view_transcript: View video transcript
all_docs: 'All {{ title }} docs' all_docs: 'All {{ title }} docs'
code_example:
search_button: Search
search_examples: 'Search code examples:'
product_guides: product_guides:
learning_paths_title: '{{ name }} learning paths' learning_paths_title: '{{ name }} learning paths'
start_path: Start learning path start_path: Start learning path
@@ -258,11 +243,6 @@ learning_track_nav:
next_guide: Next next_guide: Next
more_guides: More guides → more_guides: More guides →
current_progress: '{i} of {n} in learning path' current_progress: '{i} of {n} in learning path'
toggle_images:
off: Images are off, click to show
on: Images are on, click to hide
hide_single: Hide image
show_single: Show image
scroll_button: scroll_button:
scroll_to_top: Scroll to top scroll_to_top: Scroll to top
popovers: popovers:

View File

@@ -14,7 +14,7 @@ type Props = {
} }
export default function GroupedEvents({ auditLogEvents, category }: Props) { export default function GroupedEvents({ auditLogEvents, category }: Props) {
const { t } = useTranslation('products') const { t } = useTranslation('audit_logs')
const eventSlug = slug(category) const eventSlug = slug(category)
return ( return (
<> <>
@@ -24,8 +24,8 @@ export default function GroupedEvents({ auditLogEvents, category }: Props) {
<table> <table>
<thead> <thead>
<tr> <tr>
<th scope="col">{t('audit_logs.action')}</th> <th scope="col">{t('action')}</th>
<th scope="col">{t('audit_logs.description')}</th> <th scope="col">{t('description')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -1,6 +1,11 @@
import { GetServerSideProps } from 'next' import { GetServerSideProps } from 'next'
import { getMainContext, MainContext, MainContextT } from 'components/context/MainContext' import {
addUINamespaces,
getMainContext,
MainContext,
MainContextT,
} from 'components/context/MainContext'
import { import {
getAutomatedPageContextFromRequest, getAutomatedPageContextFromRequest,
AutomatedPageContext, AutomatedPageContext,
@@ -50,6 +55,8 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
const url = context.req.url const url = context.req.url
const mainContext = await getMainContext(req, res) const mainContext = await getMainContext(req, res)
addUINamespaces(req, mainContext.data.ui, ['audit_logs'])
const { miniTocItems } = getAutomatedPageContextFromRequest(req) const { miniTocItems } = getAutomatedPageContextFromRequest(req)
let auditLogEvents = {} as Record<string, Array<AuditLogEventT>> let auditLogEvents = {} as Record<string, Array<AuditLogEventT>>

View File

@@ -23,7 +23,7 @@ export function ChildBodyParametersRows({
childParamsGroups, childParamsGroups,
oneOfObject = false, oneOfObject = false,
}: Props) { }: Props) {
const { t } = useTranslation(['parameter_table', 'products']) const { t } = useTranslation(['parameter_table'])
return ( return (
<tr className={cx(styles.childBodyParametersRows, 'color-bg-subtle border-top-0')}> <tr className={cx(styles.childBodyParametersRows, 'color-bg-subtle border-top-0')}>
<td className="has-nested-table"> <td className="has-nested-table">

View File

@@ -43,7 +43,7 @@ export function ParameterRow({
bodyParamExpandCallback, bodyParamExpandCallback,
clickedBodyParameterName, clickedBodyParameterName,
}: Props) { }: Props) {
const { t } = useTranslation(['parameter_table', 'products']) const { t } = useTranslation(['parameter_table'])
// This will be true if `rowParams` does not have a key called `default` // This will be true if `rowParams` does not have a key called `default`
// and it will be true if it does and its actual value is `undefined`. // and it will be true if it does and its actual value is `undefined`.

View File

@@ -29,7 +29,7 @@ export function ParameterTable({
clickedBodyParameterName = '', clickedBodyParameterName = '',
variant = 'rest', variant = 'rest',
}: Props) { }: Props) {
const { t } = useTranslation(['parameter_table', 'products']) const { t } = useTranslation(['parameter_table'])
const queryParams = parameters.filter((param) => param.in === 'query') const queryParams = parameters.filter((param) => param.in === 'query')
const pathParams = parameters.filter((param) => param.in === 'path') const pathParams = parameters.filter((param) => param.in === 'path')

View File

@@ -61,7 +61,7 @@ export function PermissionsList({
currentVersion === DEFAULT_VERSION ? `/${locale}` : `/${locale}/${currentVersion}` currentVersion === DEFAULT_VERSION ? `/${locale}` : `/${locale}/${currentVersion}`
// Translated strings // Translated strings
const { t } = useTranslation('products') const { t } = useTranslation('rest')
const ENDPOINTS_TH = t('rest.overview.permissions.endpoints') const ENDPOINTS_TH = t('rest.overview.permissions.endpoints')
const ACCESS_TH = t('rest.overview.permissions.access') const ACCESS_TH = t('rest.overview.permissions.access')
const TOKENS_TH = t('rest.overview.permissions.tokens') const TOKENS_TH = t('rest.overview.permissions.tokens')

View File

@@ -9,8 +9,8 @@ type Props = {
} }
export function Enum({ item }: Props) { export function Enum({ item }: Props) {
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
const heading = t('graphql.reference.values').replace('{{ GraphQLItemTitle }}', item.name) const heading = t('reference.values').replace('{{ GraphQLItemTitle }}', item.name)
return ( return (
<GraphqlItem item={item} heading={heading}> <GraphqlItem item={item} heading={heading}>

View File

@@ -8,8 +8,8 @@ type Props = {
} }
export function InputObject({ item }: Props) { export function InputObject({ item }: Props) {
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
const heading = t('graphql.reference.input_fields').replace('{{ GraphQLItemTitle }}', item.name) const heading = t('reference.input_fields').replace('{{ GraphQLItemTitle }}', item.name)
return ( return (
<GraphqlItem item={item} heading={heading}> <GraphqlItem item={item} heading={heading}>
<Table fields={item.inputFields} /> <Table fields={item.inputFields} />

View File

@@ -13,9 +13,9 @@ type Props = {
export function Interface({ item, objects }: Props) { export function Interface({ item, objects }: Props) {
const { locale } = useRouter() const { locale } = useRouter()
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
const heading = t('graphql.reference.implemented_by').replace('{{ GraphQLItemTitle }}', item.name) const heading = t('reference.implemented_by').replace('{{ GraphQLItemTitle }}', item.name)
const heading2 = t('graphql.reference.fields').replace('{{ GraphQLItemTitle }}', item.name) const heading2 = t('reference.fields').replace('{{ GraphQLItemTitle }}', item.name)
const implementedBy = objects.filter( const implementedBy = objects.filter(
(object) => (object) =>

View File

@@ -14,9 +14,9 @@ type Props = {
export function Mutation({ item }: Props) { export function Mutation({ item }: Props) {
const { locale } = useRouter() const { locale } = useRouter()
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
const heading = t('graphql.reference.input_fields').replace('{{ GraphQLItemTitle }}', item.name) const heading = t('reference.input_fields').replace('{{ GraphQLItemTitle }}', item.name)
const heading2 = t('graphql.reference.return_fields').replace('{{ GraphQLItemTitle }}', item.name) const heading2 = t('reference.return_fields').replace('{{ GraphQLItemTitle }}', item.name)
return ( return (
<GraphqlItem item={item} heading={heading}> <GraphqlItem item={item} heading={heading}>

View File

@@ -12,11 +12,9 @@ type Props = {
export function Notice({ item, variant = 'preview' }: Props) { export function Notice({ item, variant = 'preview' }: Props) {
const { locale } = useRouter() const { locale } = useRouter()
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
const previewTitle = const previewTitle =
variant === 'preview' variant === 'preview' ? t('reference.preview_notice') : t('reference.deprecation_notice')
? t('graphql.reference.preview_notice')
: t('graphql.reference.deprecation_notice')
const noticeStyle = const noticeStyle =
variant === 'preview' variant === 'preview'
? 'ghd-spotlight-note color-border-accent-emphasis color-bg-accent' ? 'ghd-spotlight-note color-border-accent-emphasis color-bg-accent'
@@ -32,7 +30,7 @@ export function Notice({ item, variant = 'preview' }: Props) {
<Link href={item.preview.href} locale={locale}> <Link href={item.preview.href} locale={locale}>
{item.preview.title} {item.preview.title}
</Link> </Link>
. {t('graphql.reference.preview_period')} . {t('reference.preview_period')}
</p> </p>
) : item.deprecationReason ? ( ) : item.deprecationReason ? (
<div> <div>

View File

@@ -12,9 +12,9 @@ type Props = {
export function Object({ item }: Props) { export function Object({ item }: Props) {
const { locale } = useRouter() const { locale } = useRouter()
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
const heading1 = t('graphql.reference.implements').replace('{{ GraphQLItemTitle }}', item.name) const heading1 = t('reference.implements').replace('{{ GraphQLItemTitle }}', item.name)
const heading2 = t('graphql.reference.fields').replace('{{ GraphQLItemTitle }}', item.name) const heading2 = t('reference.fields').replace('{{ GraphQLItemTitle }}', item.name)
return ( return (
<GraphqlItem item={item}> <GraphqlItem item={item}>

View File

@@ -15,7 +15,7 @@ export function Previews({ schema }: Props) {
const previews = schema.map((item) => { const previews = schema.map((item) => {
const slugger = new GithubSlugger() const slugger = new GithubSlugger()
const slug = slugger.slug(item.title) const slug = slugger.slug(item.title)
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
return ( return (
<div className={cx(styles.markdownBody)} key={slug}> <div className={cx(styles.markdownBody)} key={slug}>
@@ -23,11 +23,11 @@ export function Previews({ schema }: Props) {
{item.title} {item.title}
</HeadingLink> </HeadingLink>
<p>{item.description}</p> <p>{item.description}</p>
<p>{t('graphql.overview.preview_header')}</p> <p>{t('overview.preview_header')}</p>
<pre> <pre>
<code>{item.accept_header}</code> <code>{item.accept_header}</code>
</pre> </pre>
<p>{t('graphql.overview.preview_schema_members')}:</p> <p>{t('overview.preview_schema_members')}:</p>
<ul> <ul>
{item.toggled_on.map((change) => ( {item.toggled_on.map((change) => (
<li key={change + slug}> <li key={change + slug}>

View File

@@ -12,7 +12,7 @@ type Props = {
export function Query({ item }: Props) { export function Query({ item }: Props) {
const { locale } = useRouter() const { locale } = useRouter()
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
return ( return (
<GraphqlItem item={item} headingLevel={3}> <GraphqlItem item={item} headingLevel={3}>
@@ -30,10 +30,7 @@ export function Query({ item }: Props) {
<> <>
<h4 <h4
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: t('graphql.reference.arguments').replace( __html: t('reference.arguments').replace('{{ GraphQLItemTitle }}', item.name),
'{{ GraphQLItemTitle }}',
item.name,
),
}} }}
/> />
<Table fields={item.args} /> <Table fields={item.args} />

View File

@@ -12,9 +12,9 @@ type Props = {
export function Table({ fields }: Props) { export function Table({ fields }: Props) {
const { locale } = useRouter() const { locale } = useRouter()
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
const tableName = t('graphql.reference.name') const tableName = t('reference.name')
const tableDescription = t('graphql.reference.description') const tableDescription = t('reference.description')
return ( return (
<table className="fields width-full table-fixed"> <table className="fields width-full table-fixed">
@@ -65,7 +65,7 @@ export function Table({ fields }: Props) {
<p <p
className="pt-0 mt-0 h5" className="pt-0 mt-0 h5"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: t('graphql.reference.arguments').replace( __html: t('reference.arguments').replace(
'{{ GraphQLItemTitle }}', '{{ GraphQLItemTitle }}',
field.name, field.name,
), ),

View File

@@ -11,8 +11,8 @@ type Props = {
export function Union({ item }: Props) { export function Union({ item }: Props) {
const { locale } = useRouter() const { locale } = useRouter()
const { t } = useTranslation('products') const { t } = useTranslation('graphql')
const heading = t('graphql.reference.possible_types').replace('{{ GraphQLItemTitle }}', item.name) const heading = t('reference.possible_types').replace('{{ GraphQLItemTitle }}', item.name)
return ( return (
<GraphqlItem item={item} heading={heading}> <GraphqlItem item={item} heading={heading}>

View File

@@ -2,7 +2,12 @@ import { GetServerSideProps } from 'next'
import React from 'react' import React from 'react'
import { GraphqlPage } from 'src/graphql/components/GraphqlPage' import { GraphqlPage } from 'src/graphql/components/GraphqlPage'
import { MainContextT, MainContext, getMainContext } from 'components/context/MainContext' import {
MainContextT,
MainContext,
getMainContext,
addUINamespaces,
} from 'components/context/MainContext'
import type { ObjectT, GraphqlT } from 'src/graphql/components/types' import type { ObjectT, GraphqlT } from 'src/graphql/components/types'
import { AutomatedPage } from 'src/automated-pipelines/components/AutomatedPage' import { AutomatedPage } from 'src/automated-pipelines/components/AutomatedPage'
import { import {
@@ -65,9 +70,12 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
// Update the existing context to include the miniTocItems from GraphQL // Update the existing context to include the miniTocItems from GraphQL
automatedPageContext.miniTocItems.push(...graphqlMiniTocItems) automatedPageContext.miniTocItems.push(...graphqlMiniTocItems)
const mainContext = await getMainContext(req, res)
addUINamespaces(req, mainContext.data.ui, ['graphql'])
return { return {
props: { props: {
mainContext: await getMainContext(req, res), mainContext,
automatedPageContext, automatedPageContext,
schema, schema,
language, language,

View File

@@ -1,7 +1,12 @@
import { GetServerSideProps } from 'next' import { GetServerSideProps } from 'next'
import React from 'react' import React from 'react'
import { MainContextT, MainContext, getMainContext } from 'components/context/MainContext' import {
MainContextT,
MainContext,
getMainContext,
addUINamespaces,
} from 'components/context/MainContext'
import { AutomatedPage } from 'src/automated-pipelines/components/AutomatedPage' import { AutomatedPage } from 'src/automated-pipelines/components/AutomatedPage'
import { import {
AutomatedPageContext, AutomatedPageContext,
@@ -47,9 +52,12 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
// Update the existing context to include the miniTocItems from GraphQL // Update the existing context to include the miniTocItems from GraphQL
automatedPageContext.miniTocItems.push(...changelogMiniTocItems) automatedPageContext.miniTocItems.push(...changelogMiniTocItems)
const mainContext = await getMainContext(req, res)
addUINamespaces(req, mainContext.data.ui, ['graphql'])
return { return {
props: { props: {
mainContext: await getMainContext(req, res), mainContext,
automatedPageContext, automatedPageContext,
schema, schema,
}, },

View File

@@ -6,8 +6,8 @@ import { ArticleCard } from './ArticleCard'
import { ItemInput } from '@primer/react/lib/deprecated/ActionList/List' import { ItemInput } from '@primer/react/lib/deprecated/ActionList/List'
export const ArticleCards = () => { export const ArticleCards = () => {
const { t } = useTranslation('product_guides') const { t, tObject } = useTranslation('product_guides')
const guideTypes: Record<string, string> = t('guide_types') const guideTypes = tObject('guide_types')
const { allTopics, includeGuides } = useProductGuidesContext() const { allTopics, includeGuides } = useProductGuidesContext()
const articleCardRef = useRef<HTMLUListElement>(null) const articleCardRef = useRef<HTMLUListElement>(null)
@@ -33,7 +33,7 @@ export const ArticleCards = () => {
? t('guides_found.none') ? t('guides_found.none')
: guides.length === 1 : guides.length === 1
? t('guides_found.one') ? t('guides_found.one')
: t('guides_found.multiple').replace('{n}', guides.length)} : t('guides_found.multiple').replace('{n}', `${guides.length}`)}
</div> </div>
</div> </div>
@@ -48,7 +48,7 @@ export const ArticleCards = () => {
tabIndex={-1} tabIndex={-1}
key={card.href + i} key={card.href + i}
card={card} card={card}
typeLabel={guideTypes[card.type]} typeLabel={guideTypes[card.type] as string}
/> />
) )
})} })}

View File

@@ -54,7 +54,7 @@ export const LandingHero = () => {
href={link} href={link}
className={cx('btn btn-large f4 mt-3 mr-3 ', i === 0 && 'btn-primary')} className={cx('btn btn-large f4 mt-3 mr-3 ', i === 0 && 'btn-primary')}
> >
{t(key) || key} {t(key)}
</FullLink> </FullLink>
) )
})} })}

View File

@@ -1,7 +1,12 @@
import React from 'react' import React from 'react'
import type { GetServerSideProps } from 'next' import type { GetServerSideProps } from 'next'
import { MainContextT, MainContext, getMainContext } from 'components/context/MainContext' import {
MainContextT,
MainContext,
getMainContext,
addUINamespaces,
} from 'components/context/MainContext'
import { DefaultLayout } from 'components/DefaultLayout' import { DefaultLayout } from 'components/DefaultLayout'
import { useTranslation } from 'src/languages/components/useTranslation' import { useTranslation } from 'src/languages/components/useTranslation'
@@ -59,11 +64,11 @@ function HomePage(props: HomePageProps) {
<div className="container-xl"> <div className="container-xl">
<div className="gutter gutter-xl-spacious clearfix"> <div className="gutter gutter-xl-spacious clearfix">
<div className="col-12 col-lg-6 mb-md-4 mb-lg-0 float-left"> <div className="col-12 col-lg-6 mb-md-4 mb-lg-0 float-left">
<ArticleList title={t('toc:getting_started')} articles={gettingStartedLinks} /> <ArticleList title={t('getting_started')} articles={gettingStartedLinks} />
</div> </div>
<div className="col-12 col-lg-6 float-left"> <div className="col-12 col-lg-6 float-left">
<ArticleList title={t('toc:popular')} articles={popularLinks} /> <ArticleList title={t('popular')} articles={popularLinks} />
</div> </div>
</div> </div>
</div> </div>
@@ -76,9 +81,12 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
const req = context.req as any const req = context.req as any
const res = context.res as any const res = context.res as any
const mainContext = await getMainContext(req, res)
addUINamespaces(req, mainContext.data.ui, ['homepage', 'product_landing'])
return { return {
props: { props: {
mainContext: await getMainContext(req, res), mainContext,
productGroups: req.context.productGroups, productGroups: req.context.productGroups,
gettingStartedLinks: req.context.featuredLinks.gettingStarted.map( gettingStartedLinks: req.context.featuredLinks.gettingStarted.map(
({ title, href, intro }: any) => ({ title, href, intro }), ({ title, href, intro }: any) => ({ title, href, intro }),

View File

@@ -7,7 +7,12 @@ import copyCode from 'components/lib/copy-code'
import toggleAnnotation from 'components/lib/toggle-annotations' import toggleAnnotation from 'components/lib/toggle-annotations'
import wrapCodeTerms from 'components/lib/wrap-code-terms' import wrapCodeTerms from 'components/lib/wrap-code-terms'
import { MainContextT, MainContext, getMainContext } from 'components/context/MainContext' import {
MainContextT,
MainContext,
getMainContext,
addUINamespaces,
} from 'components/context/MainContext'
import { import {
getProductLandingContextFromRequest, getProductLandingContextFromRequest,
@@ -111,16 +116,28 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
} }
const { currentLayoutName, relativePath } = props.mainContext const { currentLayoutName, relativePath } = props.mainContext
const additionalUINamespaces: string[] = []
// This looks a little funky, but it's so we only send one context's data to the client // This looks a little funky, but it's so we only send one context's data to the client
if (currentLayoutName === 'product-landing') { if (currentLayoutName === 'product-landing') {
props.productLandingContext = await getProductLandingContextFromRequest(req) props.productLandingContext = await getProductLandingContextFromRequest(req)
additionalUINamespaces.push('product_landing')
} else if (currentLayoutName === 'product-guides') { } else if (currentLayoutName === 'product-guides') {
props.productGuidesContext = getProductGuidesContextFromRequest(req) props.productGuidesContext = getProductGuidesContextFromRequest(req)
additionalUINamespaces.push('product_guides')
} else if (relativePath?.endsWith('index.md')) { } else if (relativePath?.endsWith('index.md')) {
props.tocLandingContext = getTocLandingContextFromRequest(req) props.tocLandingContext = getTocLandingContextFromRequest(req)
} else { } else {
// All articles that might have hover cards needs this
additionalUINamespaces.push('popovers')
props.articleContext = getArticleContextFromRequest(req) props.articleContext = getArticleContextFromRequest(req)
if (props.articleContext.currentLearningTrack?.trackName) {
additionalUINamespaces.push('learning_track_nav')
} }
}
addUINamespaces(req, props.mainContext.data.ui, additionalUINamespaces)
return { return {
props, props,

View File

@@ -1,41 +1,113 @@
import type { UIStrings } from 'components/context/MainContext'
import { useMainContext } from 'components/context/MainContext' import { useMainContext } from 'components/context/MainContext'
import get from 'lodash/get'
// The idea of this component is to mimic a popular i18n library (i18next) class TranslationNamespaceError extends Error {}
// so that we can set ourselves up to transition to it (or a similar library) in the future class UngettableError extends Error {}
// Used to pull translation UI strings from the page props into
// React components. When you instantiate the hook you can pass
// the name or names of the namespaces you want to use. Then, when
// refererring to specific keys you don't have to say the namespace
// (or which of the namespaces) you refer to.
// Example use:
//
// const { t, tObject } = useTranslation(['football', 'quiz'])
//
// return <div>
// <b>{t('select')}</b> {/* shorthand now for 'football.select' */}
// <select>
// {['yes', 'no', 'maybe'].map((answer) => (
// <option key={answer} value={answer}>{tObject('choices')[answer]}</option>
// )}
// </select>
// </div>
//
// You can also use the TemplateStringArray version like:
//
// <b>{t`select`)}</b>
//
// Any namespace used when you initialize `useTranslation` that isn't
// recognized (prepared in the page props) will throw an error.
// And any key not recognized will also throw an error. For example:
//
// <button>{t('sav_changes')}</button>
//
// ...will throw because of the typo 'sav_changes' instead of 'save_changes'.
export const useTranslation = (namespaces: string | Array<string>) => { export const useTranslation = (namespaces: string | Array<string>) => {
const { data } = useMainContext() const { data } = useMainContext()
// this can eventually be an object constructed from the input namespaces param above, but for now everything is already loaded const loadedData = data.ui
const loadedData: any = data.ui
const namespacesArray = Array.isArray(namespaces) ? namespaces : [namespaces]
for (const namespace of namespacesArray) {
if (!(namespace in loadedData)) {
console.warn(
'The following namespaces in data.ui have been loaded: ' +
JSON.stringify(Object.keys(loadedData).sort()),
)
throw new TranslationNamespaceError(
`Namespace "${namespace}" not found in data. ` +
'Follow the stack trace to see which useTranslation(...) call is ' +
'causing this error. If the namespace is present in data/ui.yml ' +
'but this error is happening, find the related component ' +
'getServerSideProps() it goes through and make sure it calls ' +
`addUINamespaces() with "${namespace}".`,
)
}
}
function carefulGetWrapper(path: string) {
for (const namespace of namespacesArray) {
if (!(namespace in loadedData)) {
throw new TranslationNamespaceError(`Namespace "${namespace}" not found in data. `)
}
const deeper = loadedData[namespace]
if (typeof deeper === 'string') {
continue
}
try {
return carefuleGet(deeper, path)
} catch (error) {
if (!(error instanceof UngettableError)) {
throw error
}
}
}
return carefuleGet(loadedData, path)
}
return { return {
// The compiled string supports prefixing with a namespace such as `my-namespace:path.to.value` tObject: (strings: TemplateStringsArray | string) => {
const key = typeof strings === 'string' ? strings : String.raw(strings)
return carefulGetWrapper(key) as UIStrings
},
t: (strings: TemplateStringsArray | string, ...values: Array<any>) => { t: (strings: TemplateStringsArray | string, ...values: Array<any>) => {
const key = typeof strings === 'string' ? strings : String.raw(strings, ...values) const key = typeof strings === 'string' ? strings : String.raw(strings, ...values)
return carefulGetWrapper(key) as string
const splitKey = key.split(':')
if (splitKey.length > 2) {
throw new Error('Multiple ":" not allowed in translation lookup path')
}
if (splitKey.length === 2) {
const [namespace, path] = splitKey
return get(loadedData[namespace], path)
}
const [path] = splitKey
if (Array.isArray(namespaces)) {
for (const namespace of namespaces) {
const val = get(loadedData[namespace], path)
if (val !== undefined) {
return val
}
}
return undefined
} else {
return get(loadedData[namespaces], path)
}
}, },
} }
} }
function carefuleGet(uiData: UIStrings, dottedPath: string) {
const splitPath = dottedPath.split('.')
const start = splitPath[0]
if (!(start in uiData)) {
throw new UngettableError(
`Namespace "${start}" not found in loaded data (not one of: ${Object.keys(uiData).sort()})`,
)
}
if (splitPath.length > 1) {
const deeper = uiData[start]
if (typeof deeper === 'string') {
throw new Error(`Namespace "${start}" is a string, not an object`)
}
return carefuleGet(deeper, splitPath.slice(1).join('.'))
} else {
if (!(start in uiData)) {
throw new UngettableError(`Key "${start}" not found in loaded data`)
}
return uiData[start]
}
}

View File

@@ -25,8 +25,8 @@ export function LearningTrackCard({ track }: Props) {
</h2> </h2>
<span className="f5 color-fg-muted"> <span className="f5 color-fg-muted">
{t('current_progress') {t('current_progress')
.replace('{n}', numberOfGuides) .replace('{n}', `${numberOfGuides}`)
.replace('{i}', currentGuideIndex + 1)} .replace('{i}', `${currentGuideIndex + 1}`)}
</span> </span>
<hr /> <hr />
<span className="h5 color-fg-default"> <span className="h5 color-fg-default">

View File

@@ -11,7 +11,7 @@ type Props = {
export const LearningTrack = ({ track }: Props) => { export const LearningTrack = ({ track }: Props) => {
if (!track) return <div /> if (!track) return <div />
const { t } = useTranslation('product_guides') const { t, tObject } = useTranslation('product_guides')
return ( return (
<div data-testid="learning-track" className="col-12 col-md-6 my-3 px-4"> <div data-testid="learning-track" className="col-12 col-md-6 my-3 px-4">
@@ -32,7 +32,7 @@ export const LearningTrack = ({ track }: Props) => {
{track.guides.map((guide) => ( {track.guides.map((guide) => (
<li key={guide.href + track.trackName}> <li key={guide.href + track.trackName}>
<span className="color-fg-muted mr-2"> <span className="color-fg-muted mr-2">
{t('guide_types')[guide.page?.type || '']} {tObject('guide_types')[guide.page?.type || ''] as string}
</span> </span>
<Link <Link
href={`${guide.href}?learn=${track.trackName}&learnProduct=${track.trackProduct}`} href={`${guide.href}?learn=${track.trackName}&learnProduct=${track.trackProduct}`}

View File

@@ -2,7 +2,12 @@ import { GetServerSideProps } from 'next'
import { Liquid } from 'liquidjs' import { Liquid } from 'liquidjs'
import pick from 'lodash/pick' import pick from 'lodash/pick'
import { MainContextT, MainContext, getMainContext } from 'components/context/MainContext' import {
MainContextT,
MainContext,
getMainContext,
addUINamespaces,
} from 'components/context/MainContext'
import { DefaultLayout } from 'components/DefaultLayout' import { DefaultLayout } from 'components/DefaultLayout'
import { GHAEReleaseNotes } from 'src/release-notes/components/GHAEReleaseNotes' import { GHAEReleaseNotes } from 'src/release-notes/components/GHAEReleaseNotes'
import { GHESReleaseNotes } from 'src/release-notes/components/GHESReleaseNotes' import { GHESReleaseNotes } from 'src/release-notes/components/GHESReleaseNotes'
@@ -45,9 +50,13 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
]) ])
const { latestPatch = '', latestRelease = '' } = req.context const { latestPatch = '', latestRelease = '' } = req.context
const mainContext = await getMainContext(req, res)
addUINamespaces(req, mainContext.data.ui, ['release_notes'])
return { return {
props: { props: {
mainContext: await getMainContext(req, res), mainContext,
ghesContext: ghesContext:
currentVersion.plan === 'enterprise-server' currentVersion.plan === 'enterprise-server'
? { ? {

View File

@@ -30,7 +30,7 @@ export const ApiVersionPicker = () => {
const router = useRouter() const router = useRouter()
const { currentVersion } = useVersion() const { currentVersion } = useVersion()
const { allVersions } = useMainContext() const { allVersions } = useMainContext()
const { t } = useTranslation(['products']) const { t } = useTranslation('rest')
const basePath = router.asPath.split('#')[0].split('?')[0] const basePath = router.asPath.split('#')[0].split('?')[0]
// Get current date from cookie, query path, or lastly set it to latest rest version date // Get current date from cookie, query path, or lastly set it to latest rest version date
const isValidApiVersion = const isValidApiVersion =

View File

@@ -34,7 +34,7 @@ const restRepoCategoryExceptionsTitles = {
export const RestBanner = () => { export const RestBanner = () => {
const router = useRouter() const router = useRouter()
const { t } = useTranslation('products') const { t } = useTranslation('rest')
// Having a productId === 'rest' and no router.query.category would mean a product landing page like http://docs.github.com/en/rest?apiVersion=2022-08-09 // Having a productId === 'rest' and no router.query.category would mean a product landing page like http://docs.github.com/en/rest?apiVersion=2022-08-09
const isRestPage = router.query.productId === 'rest' || router.query.category const isRestPage = router.query.productId === 'rest' || router.query.category
const restPage = router.query.category as string const restPage = router.query.category as string

View File

@@ -41,7 +41,7 @@ function getLanguageHighlight(selectedLanguage: string) {
} }
export function RestCodeSamples({ operation, slug, heading }: Props) { export function RestCodeSamples({ operation, slug, heading }: Props) {
const { t } = useTranslation('products') const { t } = useTranslation(['rest_reference'])
const { isEnterpriseServer } = useVersion() const { isEnterpriseServer } = useVersion()
// Refs to track the request example, response example // Refs to track the request example, response example
@@ -264,7 +264,7 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
}} }}
href="#" href="#"
> >
{t(`rest.reference.code_sample_options.${optionKey}`)} {t(`code_sample_options.${optionKey}`)}
</TabNav.Link> </TabNav.Link>
))} ))}
</TabNav> </TabNav>
@@ -311,7 +311,7 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
<h4 <h4
className="mt-5 mb-2 h5" className="mt-5 mb-2 h5"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: displayedExample.response.description || t('rest.reference.response'), __html: displayedExample.response.description || t('response'),
}} }}
></h4> ></h4>
<div className="border rounded-1"> <div className="border rounded-1">
@@ -335,7 +335,7 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
}} }}
href="#" href="#"
> >
{t(`rest.reference.response_options.${optionKey}`)} {t(`response_options.${optionKey}`)}
</TabNav.Link> </TabNav.Link>
))} ))}
</TabNav> </TabNav>

View File

@@ -28,7 +28,7 @@ const DEFAULT_ACCEPT_HEADER = {
export function RestOperation({ operation }: Props) { export function RestOperation({ operation }: Props) {
const titleSlug = slug(operation.title) const titleSlug = slug(operation.title)
const { t } = useTranslation('products') const { t } = useTranslation('rest_reference')
const router = useRouter() const router = useRouter()
const headers = [DEFAULT_ACCEPT_HEADER] const headers = [DEFAULT_ACCEPT_HEADER]
@@ -48,10 +48,8 @@ export function RestOperation({ operation }: Props) {
<CheckCircleFillIcon size={16} /> <CheckCircleFillIcon size={16} />
</span> </span>
<span> <span>
{t('rest.reference.works_with') + ' '} {t('works_with') + ' '}
<Link className="" href={`/${router.locale}/developers/apps`}> <Link href={`/${router.locale}/developers/apps`}>GitHub Apps</Link>
GitHub Apps
</Link>
</span> </span>
</div> </div>
)} )}
@@ -66,10 +64,7 @@ export function RestOperation({ operation }: Props) {
<ParameterTable <ParameterTable
slug={titleSlug} slug={titleSlug}
numPreviews={numPreviews} numPreviews={numPreviews}
heading={t('rest.reference.parameters').replace( heading={t('parameters').replace('{{ RESTOperationTitle }}', operation.title)}
'{{ RESTOperationTitle }}',
operation.title,
)}
headers={headers} headers={headers}
parameters={operation.parameters} parameters={operation.parameters}
bodyParameters={operation.bodyParameters} bodyParameters={operation.bodyParameters}
@@ -80,10 +75,7 @@ export function RestOperation({ operation }: Props) {
<RestStatusCodes <RestStatusCodes
statusCodes={operation.statusCodes} statusCodes={operation.statusCodes}
slug={titleSlug} slug={titleSlug}
heading={t('rest.reference.http_status_code').replace( heading={t('http_status_code').replace('{{ RESTOperationTitle }}', operation.title)}
'{{ RESTOperationTitle }}',
operation.title,
)}
/> />
)} )}
</div> </div>
@@ -95,10 +87,7 @@ export function RestOperation({ operation }: Props) {
<RestCodeSamples <RestCodeSamples
operation={operation} operation={operation}
slug={titleSlug} slug={titleSlug}
heading={t('rest.reference.code_samples').replace( heading={t('code_samples').replace('{{ RESTOperationTitle }}', operation.title)}
'{{ RESTOperationTitle }}',
operation.title,
)}
/> />
)} )}
@@ -108,14 +97,8 @@ export function RestOperation({ operation }: Props) {
previews={operation.previews} previews={operation.previews}
heading={ heading={
operation.previews.length > 1 operation.previews.length > 1
? `${t('rest.reference.preview_notices').replace( ? `${t('preview_notices').replace('{{ RESTOperationTitle }}', operation.title)}`
'{{ RESTOperationTitle }}', : `${t('preview_notice').replace('{{ RESTOperationTitle }}', operation.title)}`
operation.title,
)}`
: `${t('rest.reference.preview_notice').replace(
'{{ RESTOperationTitle }}',
operation.title,
)}`
} }
/> />
)} )}

View File

@@ -8,7 +8,7 @@ type Props = {
} }
export function RestStatusCodes({ statusCodes, slug, heading }: Props) { export function RestStatusCodes({ statusCodes, slug, heading }: Props) {
const { t } = useTranslation('products') const { t } = useTranslation('rest_reference')
return ( return (
<> <>
@@ -19,8 +19,8 @@ export function RestStatusCodes({ statusCodes, slug, heading }: Props) {
<table> <table>
<thead> <thead>
<tr className="text-left"> <tr className="text-left">
<th>{t('rest.reference.status_code')}</th> <th>{t('status_code')}</th>
<th>{t('rest.reference.description')}</th> <th>{t('description')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -1,7 +1,12 @@
import { GetServerSideProps } from 'next' import { GetServerSideProps } from 'next'
import { Operation } from 'src/rest/components/types' import { Operation } from 'src/rest/components/types'
import { RestReferencePage } from 'src/rest/components/RestReferencePage' import { RestReferencePage } from 'src/rest/components/RestReferencePage'
import { getMainContext, MainContext, MainContextT } from 'components/context/MainContext' import {
addUINamespaces,
getMainContext,
MainContext,
MainContextT,
} from 'components/context/MainContext'
import { import {
AutomatedPageContext, AutomatedPageContext,
AutomatedPageContextT, AutomatedPageContextT,
@@ -77,10 +82,13 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
restOperationsMiniTocItems && miniTocItems.push(...restOperationsMiniTocItems) restOperationsMiniTocItems && miniTocItems.push(...restOperationsMiniTocItems)
} }
const mainContext = await getMainContext(req, res)
addUINamespaces(req, mainContext.data.ui, ['parameter_table', 'rest_reference'])
return { return {
props: { props: {
restOperations, restOperations,
mainContext: await getMainContext(req, res), mainContext,
automatedPageContext: getAutomatedPageContextFromRequest(req), automatedPageContext: getAutomatedPageContextFromRequest(req),
}, },
} }

View File

@@ -45,12 +45,12 @@ function SearchResultHits({ hits, search }: { hits: SearchResultHitT[]; search:
} }
function NoSearchResults() { function NoSearchResults() {
const { t } = useTranslation('search') const { t } = useTranslation('search_results')
return ( return (
<div className="d-flex flex-items-center flex-column my-6 border rounded-2"> <div className="d-flex flex-items-center flex-column my-6 border rounded-2">
<div className="d-flex flex-items-center flex-column p-4"> <div className="d-flex flex-items-center flex-column p-4">
<SearchIcon size={24} /> <SearchIcon size={24} />
<Text className="f2 mt-3">{t('n_results').replace('{n}', 0)}</Text> <Text className="f2 mt-3">{t('n_results').replace('{n}', '0')}</Text>
</div> </div>
</div> </div>
) )

View File

@@ -8,7 +8,7 @@ interface Props {
} }
export function ValidationErrors({ errors }: Props) { export function ValidationErrors({ errors }: Props) {
const { t } = useTranslation('search') const { t } = useTranslation('search_results')
return ( return (
<div> <div>

View File

@@ -16,7 +16,7 @@ type Props = {
export function Search({ search }: Props) { export function Search({ search }: Props) {
const { formatInteger } = useNumberFormatter() const { formatInteger } = useNumberFormatter()
const { t } = useTranslation('search') const { t } = useTranslation('search_results')
const { currentVersion } = useVersion() const { currentVersion } = useVersion()
const { query } = search.search const { query } = search.search

View File

@@ -1,6 +1,11 @@
import type { GetServerSideProps } from 'next' import type { GetServerSideProps } from 'next'
import { MainContextT, MainContext, getMainContext } from 'components/context/MainContext' import {
MainContextT,
MainContext,
getMainContext,
addUINamespaces,
} from 'components/context/MainContext'
import { DefaultLayout } from 'components/DefaultLayout' import { DefaultLayout } from 'components/DefaultLayout'
import type { SearchT } from 'src/search/components/types' import type { SearchT } from 'src/search/components/types'
import { Search } from 'src/search/components/index' import { Search } from 'src/search/components/index'
@@ -37,6 +42,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
} }
const mainContext = await getMainContext(req, res) const mainContext = await getMainContext(req, res)
addUINamespaces(req, mainContext.data.ui, ['search_results'])
if (!req.context.search) { if (!req.context.search) {
// This should have been done by the middleware. // This should have been done by the middleware.

View File

@@ -5,7 +5,6 @@ import { useRouter } from 'next/router'
import { slug } from 'github-slugger' import { slug } from 'github-slugger'
import cx from 'classnames' import cx from 'classnames'
import { useMainContext } from 'components/context/MainContext'
import { useVersion } from 'src/versions/components/useVersion' import { useVersion } from 'src/versions/components/useVersion'
import { HeadingLink } from 'components/article/HeadingLink' import { HeadingLink } from 'components/article/HeadingLink'
import { useTranslation } from 'src/languages/components/useTranslation' import { useTranslation } from 'src/languages/components/useTranslation'
@@ -48,15 +47,14 @@ function isScrapedGhesVersion(version: ReturnType<typeof useVersion>) {
export function Webhook({ webhook }: Props) { export function Webhook({ webhook }: Props) {
// Get version for requests to switch webhook action type // Get version for requests to switch webhook action type
const version = useVersion() const version = useVersion()
const { t } = useTranslation('products') const { t, tObject } = useTranslation('webhooks')
const router = useRouter() const router = useRouter()
const context = useMainContext()
// Get more user friendly language for the different availability options in // Get more user friendly language for the different availability options in
// the webhook schema (we can't change it directly in the schema). Note that // the webhook schema (we can't change it directly in the schema). Note that
// we specifically don't want to translate these strings with useTranslation() // we specifically don't want to translate these strings with useTranslation()
// like we usually do with strings from data/ui.yml. // like we usually do with strings from data/ui.yml.
const rephraseAvailability = context.data.ui.products.webhooks.rephrase_availability const rephraseAvailability = tObject('rephrase_availability')
// The param that was clicked so we can expand its property <details> element // The param that was clicked so we can expand its property <details> element
const [clickedBodyParameterName, setClickedBodyParameterName] = useState<undefined | string>('') const [clickedBodyParameterName, setClickedBodyParameterName] = useState<undefined | string>('')
@@ -150,10 +148,7 @@ export function Webhook({ webhook }: Props) {
<div dangerouslySetInnerHTML={{ __html: currentWebhookAction.summaryHtml }}></div> <div dangerouslySetInnerHTML={{ __html: currentWebhookAction.summaryHtml }}></div>
<h3 <h3
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: t('webhooks.availability').replace( __html: t('availability').replace('{{ WebhookName }}', currentWebhookAction.category),
'{{ WebhookName }}',
currentWebhookAction.category,
),
}} }}
/> />
<ul> <ul>
@@ -169,7 +164,9 @@ export function Webhook({ webhook }: Props) {
} else { } else {
return ( return (
<li key={`availability-${availability}`}> <li key={`availability-${availability}`}>
{rephraseAvailability[availability] ?? availability} {availability in rephraseAvailability
? (rephraseAvailability[availability] as string)
: availability}
</li> </li>
) )
} }
@@ -177,7 +174,7 @@ export function Webhook({ webhook }: Props) {
</ul> </ul>
<h3 <h3
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: t('webhooks.webhook_payload_object').replace( __html: t('webhook_payload_object').replace(
'{{ WebhookName }}', '{{ WebhookName }}',
currentWebhookAction.category, currentWebhookAction.category,
), ),
@@ -185,7 +182,7 @@ export function Webhook({ webhook }: Props) {
/> />
{error && ( {error && (
<Flash className="mb-5" variant="danger"> <Flash className="mb-5" variant="danger">
<p>{t('webhooks.action_type_switch_error')}</p> <p>{t('action_type_switch_error')}</p>
<p> <p>
<code className="f6" style={{ background: 'none' }}> <code className="f6" style={{ background: 'none' }}>
{error.toString()} {error.toString()}
@@ -201,8 +198,7 @@ export function Webhook({ webhook }: Props) {
aria-label="Select a webhook action type" aria-label="Select a webhook action type"
className="text-normal" className="text-normal"
> >
{t('webhooks.action_type')}:{' '} {t('action_type')}: <span className="text-bold">{currentWebhookActionType}</span>
<span className="text-bold">{currentWebhookActionType}</span>
</ActionMenu.Button> </ActionMenu.Button>
<ActionMenu.Overlay> <ActionMenu.Overlay>
<ActionList selectionVariant="single"> <ActionList selectionVariant="single">
@@ -240,11 +236,8 @@ export function Webhook({ webhook }: Props) {
{webhook.data.payloadExample && ( {webhook.data.payloadExample && (
<> <>
<h3>{t('webhooks.webhook_payload_example')}</h3> <h3>{t('webhook_payload_example')}</h3>
<div <div className={cx(styles.payloadExample, 'border rounded-1 my-0')} data-highlight="json">
className={cx(styles.payloadExample, 'border rounded-1 my-0')}
data-highlight={'json'}
>
<code>{JSON.stringify(webhook.data.payloadExample, null, 2)}</code> <code>{JSON.stringify(webhook.data.payloadExample, null, 2)}</code>
</div> </div>
</> </>

View File

@@ -2,7 +2,12 @@ import { GetServerSideProps } from 'next'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useEffect } from 'react' import { useEffect } from 'react'
import { getMainContext, MainContext, MainContextT } from 'components/context/MainContext' import {
addUINamespaces,
getMainContext,
MainContext,
MainContextT,
} from 'components/context/MainContext'
import { import {
getAutomatedPageContextFromRequest, getAutomatedPageContextFromRequest,
AutomatedPageContext, AutomatedPageContext,
@@ -77,6 +82,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
const res = context.res as object const res = context.res as object
const currentVersion = context.query.versionId as string const currentVersion = context.query.versionId as string
const mainContext = await getMainContext(req, res) const mainContext = await getMainContext(req, res)
addUINamespaces(req, mainContext.data.ui, ['parameter_table', 'webhooks'])
const { miniTocItems } = getAutomatedPageContextFromRequest(req) const { miniTocItems } = getAutomatedPageContextFromRequest(req)
// Get data for initial webhooks page (i.e. only 1 action type per webhook and // Get data for initial webhooks page (i.e. only 1 action type per webhook and

View File

@@ -23,17 +23,13 @@ picker:
release_notes: release_notes:
banner_text: GitHub began rolling these changes out to enterprises on banner_text: GitHub began rolling these changes out to enterprises on
search: search:
need_help: Need help?
placeholder: Search GitHub Docs
search_results_for: Search results for
no_content: No content
matches_found: Results found
matches_displayed: Matches displayed
search_error: An error occurred trying to perform the search.
description: Enter a search term to find it in the GitHub Docs. description: Enter a search term to find it in the GitHub Docs.
placeholder: Search GitHub Docs
label: Search GitHub Docs label: Search GitHub Docs
search_results:
search_results_for: Search results for
matches_displayed: Matches displayed
n_results: '{n} results' n_results: '{n} results'
one_result: 1 result
search_validation_error: Validation error with search query search_validation_error: Validation error with search query
homepage: homepage:
explore_by_product: Explore by product explore_by_product: Explore by product
@@ -53,11 +49,6 @@ pages:
about_versions: About versions about_versions: About versions
permissions_statement: Who can use this feature permissions_statement: Who can use this feature
video_from_transcript: See video for this transcript video_from_transcript: See video for this transcript
errors:
oops: Ooops!
something_went_wrong: It looks like something went wrong.
we_track_errors: We track these errors automatically, but if the problem persists please feel free to contact us.
page_doesnt_exist: It looks like this page doesn't exist.
support: support:
still_need_help: Still need help? still_need_help: Still need help?
contact_support: Contact support contact_support: Contact support
@@ -97,13 +88,9 @@ parameter_table:
see_preview_notices: See preview notices see_preview_notices: See preview notices
type: Type type: Type
single_enum_description: Value single_enum_description: Value
products:
audit_logs: audit_logs:
action: Action action: Action
description: Description description: Description
button_text:
copy_to_clipboard: Copy to clipboard
copied: Copied!
graphql: graphql:
reference: reference:
implements: <code>{{ GraphQLItemTitle }}</code> Implements implements: <code>{{ GraphQLItemTitle }}</code> Implements
@@ -145,7 +132,7 @@ products:
additionalPermissions: Additional permissions additionalPermissions: Additional permissions
uat: UAT uat: UAT
iat: IAT iat: IAT
reference: rest_reference:
in: In in: In
description: Description description: Description
notes: Notes notes: Notes
@@ -175,6 +162,9 @@ products:
ghcli: GitHub CLI ghcli: GitHub CLI
javascript: JavaScript javascript: JavaScript
curl: cURL curl: cURL
button_text:
copy_to_clipboard: Copy to clipboard
copied: Copied!
webhooks: webhooks:
action_type_switch_error: There was an error switching webhook action types. action_type_switch_error: There was an error switching webhook action types.
action_type: Action type action_type: Action type
@@ -202,13 +192,11 @@ product_landing:
quickstart: Quickstart quickstart: Quickstart
reference: Reference reference: Reference
overview: Overview overview: Overview
try_ghec_for_free: Try Enterprise Cloud for free
plan_your_migration: Plan your migration
releases: Releases
guides: Guides guides: Guides
explore_guides: Explore guides explore_guides: Explore guides
code_examples: Code examples
search_code_examples: Search code examples
search_results_for: Search results for
matches_displayed: Matches displayed
show_more: Show more
explore_people_and_projects: Explore people and projects explore_people_and_projects: Explore people and projects
sorry: Sorry, there is no result for sorry: Sorry, there is no result for
no_example: It looks like we don't have an example that fits your filter. no_example: It looks like we don't have an example that fits your filter.
@@ -228,9 +216,6 @@ product_landing:
view: View all view: View all
view_transcript: View video transcript view_transcript: View video transcript
all_docs: 'All {{ title }} docs' all_docs: 'All {{ title }} docs'
code_example:
search_button: Search
search_examples: 'Search code examples:'
product_guides: product_guides:
learning_paths_title: '{{ name }} learning paths' learning_paths_title: '{{ name }} learning paths'
start_path: Start learning path start_path: Start learning path
@@ -258,11 +243,6 @@ learning_track_nav:
next_guide: Next next_guide: Next
more_guides: More guides → more_guides: More guides →
current_progress: '{i} of {n} in learning path' current_progress: '{i} of {n} in learning path'
toggle_images:
off: Images are off, click to show
on: Images are on, click to hide
hide_single: Hide image
show_single: Show image
scroll_button: scroll_button:
scroll_to_top: Scroll to top scroll_to_top: Scroll to top
popovers: popovers: