Skip to content

Commit

Permalink
feat: add support for locales=* Contentful responses (#24)
Browse files Browse the repository at this point in the history
Fixes #20

## Overview
Add a new `--localization` flag. When enabled, generated fields will wrapped in `LocalizedField` helper , for example:

```ts
      export interface IMyContentTypeFields {
        /** Array field */
        arrayField: LocalizedField<(\"one\" | \"of\" | \"the\" | \"above\")[]>
      }
```

This flag is very useful when entries  are being fetched with `locale="*"`.

## Notes
* `field.required` is ignored, because contentful api ignores it as well.
* we have to use a custom localized version of `Asset`
  • Loading branch information
zernie authored Apr 16, 2020
1 parent 2359ab8 commit 0a13f48
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 47 deletions.
10 changes: 9 additions & 1 deletion src/contentful-typescript-codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const cli = meow(
and present, and does not provide types for Sys,
Assets, or Rich Text. This is useful for ensuring raw
Contentful responses will be compatible with your code.
--localization -l Output fields with localized values
Examples
$ contentful-typescript-codegen -o src/@types/generated/contentful.d.ts
Expand All @@ -43,6 +44,11 @@ const cli = meow(
alias: "i",
required: false,
},
localization: {
type: "boolean",
alias: "l",
required: false,
},
},
},
)
Expand All @@ -59,7 +65,9 @@ async function runCodegen(outputFile: string) {
if (cli.flags.fieldsOnly) {
output = await renderFieldsOnly(contentTypes.items)
} else {
output = await render(contentTypes.items, locales.items)
output = await render(contentTypes.items, locales.items, {
localization: cli.flags.localization,
})
}

outputFileSync(outputPath, output)
Expand Down
8 changes: 4 additions & 4 deletions src/renderers/contentful/renderContentType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import renderObject from "./fields/renderObject"
import renderRichText from "./fields/renderRichText"
import renderSymbol from "./fields/renderSymbol"

export default function renderContentType(contentType: ContentType): string {
export default function renderContentType(contentType: ContentType, localization: boolean): string {
const name = renderContentTypeId(contentType.sys.id)
const fields = renderContentTypeFields(contentType.fields)
const fields = renderContentTypeFields(contentType.fields, localization)
const sys = renderSys(contentType.sys)

return `
Expand All @@ -34,7 +34,7 @@ function descriptionComment(description: string | undefined) {
return ""
}

function renderContentTypeFields(fields: Field[]): string {
function renderContentTypeFields(fields: Field[], localization: boolean): string {
return fields
.filter(field => !field.omitted)
.map<string>(field => {
Expand All @@ -52,7 +52,7 @@ function renderContentTypeFields(fields: Field[]): string {
Text: renderSymbol,
}

return renderField(field, functionMap[field.type](field))
return renderField(field, functionMap[field.type](field), localization)
})
.join("\n\n")
}
Expand Down
11 changes: 10 additions & 1 deletion src/renderers/contentful/renderContentfulImports.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
export default function renderContentfulImports(): string {
export default function renderContentfulImports(localization: boolean = false): string {
if (localization) {
return `
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT.
import { Entry } from 'contentful'
import { Document } from '@contentful/rich-text-types'
`
}

return `
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT.
Expand Down
8 changes: 6 additions & 2 deletions src/renderers/contentful/renderField.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Field } from "contentful"
import renderInterfaceProperty from "../typescript/renderInterfaceProperty"

export default function renderField(field: Field, type: string): string {
return renderInterfaceProperty(field.id, type, field.required, field.name)
export default function renderField(
field: Field,
type: string,
localization: boolean = false,
): string {
return renderInterfaceProperty(field.id, type, field.required, localization, field.name)
}
30 changes: 30 additions & 0 deletions src/renderers/contentful/renderLocalizedTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/** renders helper types for --localization flag */
export default function renderLocalizedTypes(localization: boolean) {
if (!localization) return null

return `
export type LocalizedField<T> = Partial<Record<LOCALE_CODE, T>>
// We have to use our own localized version of Asset because of a bug in contentful https://github.com/contentful/contentful.js/issues/208
export interface Asset {
sys: Sys
fields: {
title: LocalizedField<string>
description: LocalizedField<string>
file: LocalizedField<{
url: string
details: {
size: number
image?: {
width: number
height: number
}
}
fileName: string
contentType: string
}>
}
toPlainObject(): object
}
`
}
20 changes: 15 additions & 5 deletions src/renderers/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,35 @@ import renderContentType from "./contentful/renderContentType"
import renderUnion from "./typescript/renderUnion"
import renderAllLocales from "./contentful/renderAllLocales"
import renderDefaultLocale from "./contentful/renderDefaultLocale"
import renderLocalizedTypes from "./contentful/renderLocalizedTypes"

export default async function render(contentTypes: ContentType[], locales: Locale[]) {
interface Options {
localization?: boolean
}

export default async function render(
contentTypes: ContentType[],
locales: Locale[],
{ localization = false }: Options = {},
) {
const sortedContentTypes = contentTypes.sort((a, b) => a.sys.id.localeCompare(b.sys.id))
const sortedLocales = locales.sort((a, b) => a.code.localeCompare(b.code))

const source = [
renderContentfulImports(),
renderAllContentTypes(sortedContentTypes),
renderContentfulImports(localization),
renderAllContentTypes(sortedContentTypes, localization),
renderAllContentTypeIds(sortedContentTypes),
renderAllLocales(sortedLocales),
renderDefaultLocale(sortedLocales),
renderLocalizedTypes(localization),
].join("\n\n")

const prettierConfig = await resolveConfig(process.cwd())
return format(source, { ...prettierConfig, parser: "typescript" })
}

function renderAllContentTypes(contentTypes: ContentType[]): string {
return contentTypes.map(contentType => renderContentType(contentType)).join("\n\n")
function renderAllContentTypes(contentTypes: ContentType[], localization: boolean): string {
return contentTypes.map(contentType => renderContentType(contentType, localization)).join("\n\n")
}

function renderAllContentTypeIds(contentTypes: ContentType[]): string {
Expand Down
3 changes: 2 additions & 1 deletion src/renderers/typescript/renderInterfaceProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ export default function renderInterfaceProperty(
name: string,
type: string,
required: boolean,
localization: boolean,
description?: string,
): string {
return [
descriptionComment(description),
name,
required ? "" : "?",
": ",
type,
localization ? `LocalizedField<${type}>` : type,
required ? "" : " | undefined",
";",
].join("")
Expand Down
33 changes: 31 additions & 2 deletions test/renderers/contentful/renderContentType.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe("renderContentType()", () => {
}

it("works with miscellaneous field types", () => {
expect(format(renderContentType(contentType))).toMatchInlineSnapshot(`
expect(format(renderContentType(contentType, false))).toMatchInlineSnapshot(`
"export interface IMyContentTypeFields {
/** Symbol Field™ */
symbolField?: string | undefined;
Expand Down Expand Up @@ -84,7 +84,7 @@ describe("renderContentType()", () => {
})

it("supports descriptions", () => {
expect(format(renderContentType(contentTypeWithDescription))).toMatchInlineSnapshot(`
expect(format(renderContentType(contentTypeWithDescription, false))).toMatchInlineSnapshot(`
"export interface IMyContentTypeFields {}
/** This is a description */
Expand All @@ -107,4 +107,33 @@ describe("renderContentType()", () => {
}"
`)
})

it("works with localized fields", () => {
expect(format(renderContentType(contentType, true))).toMatchInlineSnapshot(`
"export interface IMyContentTypeFields {
/** Symbol Field™ */
symbolField?: LocalizedField<string> | undefined;
/** Array field */
arrayField: LocalizedField<(\\"one\\" | \\"of\\" | \\"the\\" | \\"above\\")[]>;
}
export interface IMyContentType extends Entry<IMyContentTypeFields> {
sys: {
id: string,
type: string,
createdAt: string,
updatedAt: string,
locale: string,
contentType: {
sys: {
id: \\"myContentType\\",
linkType: \\"ContentType\\",
type: \\"Link\\"
}
}
};
}"
`)
})
})
17 changes: 13 additions & 4 deletions test/renderers/contentful/renderContentfulImports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ import format from "../../support/format"
describe("renderContentfulImports()", () => {
it("renders the top of the codegen file", () => {
expect(format(renderContentfulImports())).toMatchInlineSnapshot(`
"// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT.
"// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT.
import { Asset, Entry } from \\"contentful\\";
import { Document } from \\"@contentful/rich-text-types\\";"
`)
import { Asset, Entry } from \\"contentful\\";
import { Document } from \\"@contentful/rich-text-types\\";"
`)
})

it("renders the localized top of the codegen file", () => {
expect(format(renderContentfulImports(true))).toMatchInlineSnapshot(`
"// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY IT.
import { Entry } from \\"contentful\\";
import { Document } from \\"@contentful/rich-text-types\\";"
`)
})
})
Loading

0 comments on commit 0a13f48

Please sign in to comment.