Localization with pages context
This commit is contained in:
@@ -13,32 +13,32 @@ const FeatureGridContainer = memo<FeatureGridProps>(
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
backgroundColor: "bg-[var(--color-surface-default-brand-royal)]",
|
backgroundColor: "bg-[var(--color-surface-default-brand-royal)]",
|
||||||
labelLine1: t("featureGrid.features.decisionMaking.labelLine1"),
|
labelLine1: t("pages.home.featureGrid.features.decisionMaking.labelLine1"),
|
||||||
labelLine2: t("featureGrid.features.decisionMaking.labelLine2"),
|
labelLine2: t("pages.home.featureGrid.features.decisionMaking.labelLine2"),
|
||||||
panelContent: "/assets/Feature_Support.png",
|
panelContent: "/assets/Feature_Support.png",
|
||||||
ariaLabel: t("featureGrid.features.decisionMaking.ariaLabel"),
|
ariaLabel: t("featureGrid.features.decisionMaking.ariaLabel"),
|
||||||
href: "#decision-making",
|
href: "#decision-making",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
backgroundColor: "bg-[#D1FFE2]",
|
backgroundColor: "bg-[#D1FFE2]",
|
||||||
labelLine1: t("featureGrid.features.valuesAlignment.labelLine1"),
|
labelLine1: t("pages.home.featureGrid.features.valuesAlignment.labelLine1"),
|
||||||
labelLine2: t("featureGrid.features.valuesAlignment.labelLine2"),
|
labelLine2: t("pages.home.featureGrid.features.valuesAlignment.labelLine2"),
|
||||||
panelContent: "/assets/Feature_Exercises.png",
|
panelContent: "/assets/Feature_Exercises.png",
|
||||||
ariaLabel: t("featureGrid.features.valuesAlignment.ariaLabel"),
|
ariaLabel: t("featureGrid.features.valuesAlignment.ariaLabel"),
|
||||||
href: "#values-alignment",
|
href: "#values-alignment",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
backgroundColor: "bg-[#F4CAFF]",
|
backgroundColor: "bg-[#F4CAFF]",
|
||||||
labelLine1: t("featureGrid.features.membershipGuidance.labelLine1"),
|
labelLine1: t("pages.home.featureGrid.features.membershipGuidance.labelLine1"),
|
||||||
labelLine2: t("featureGrid.features.membershipGuidance.labelLine2"),
|
labelLine2: t("pages.home.featureGrid.features.membershipGuidance.labelLine2"),
|
||||||
panelContent: "/assets/Feature_Guidance.png",
|
panelContent: "/assets/Feature_Guidance.png",
|
||||||
ariaLabel: t("featureGrid.features.membershipGuidance.ariaLabel"),
|
ariaLabel: t("featureGrid.features.membershipGuidance.ariaLabel"),
|
||||||
href: "#membership-guidance",
|
href: "#membership-guidance",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
backgroundColor: "bg-[#CBDDFF]",
|
backgroundColor: "bg-[#CBDDFF]",
|
||||||
labelLine1: t("featureGrid.features.conflictResolution.labelLine1"),
|
labelLine1: t("pages.home.featureGrid.features.conflictResolution.labelLine1"),
|
||||||
labelLine2: t("featureGrid.features.conflictResolution.labelLine2"),
|
labelLine2: t("pages.home.featureGrid.features.conflictResolution.labelLine2"),
|
||||||
panelContent: "/assets/Feature_Tools.png",
|
panelContent: "/assets/Feature_Tools.png",
|
||||||
ariaLabel: t("featureGrid.features.conflictResolution.ariaLabel"),
|
ariaLabel: t("featureGrid.features.conflictResolution.ariaLabel"),
|
||||||
href: "#conflict-resolution",
|
href: "#conflict-resolution",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function RuleStackView({
|
|||||||
className,
|
className,
|
||||||
onTemplateClick,
|
onTemplateClick,
|
||||||
}: RuleStackViewProps) {
|
}: RuleStackViewProps) {
|
||||||
const t = useTranslation("ruleStack");
|
const t = useTranslation("pages.home.ruleStack");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
|
|||||||
+12
-9
@@ -1,3 +1,5 @@
|
|||||||
|
import messages from "../../messages/en/index";
|
||||||
|
import { getTranslation } from "../../lib/i18n/getTranslation";
|
||||||
import ContentThumbnailTemplate from "../components/ContentThumbnailTemplate";
|
import ContentThumbnailTemplate from "../components/ContentThumbnailTemplate";
|
||||||
import ContentLockup from "../components/ContentLockup";
|
import ContentLockup from "../components/ContentLockup";
|
||||||
import AskOrganizer from "../components/AskOrganizer";
|
import AskOrganizer from "../components/AskOrganizer";
|
||||||
@@ -7,21 +9,22 @@ export default function LearnPage() {
|
|||||||
// Get real blog posts from the content system
|
// Get real blog posts from the content system
|
||||||
const allPosts = getAllBlogPosts();
|
const allPosts = getAllBlogPosts();
|
||||||
|
|
||||||
|
// Use direct message access for server components
|
||||||
|
const t = (key: string) => getTranslation(messages, key);
|
||||||
|
|
||||||
const contentLockupData = {
|
const contentLockupData = {
|
||||||
title: "Organizing is hard",
|
title: t("pages.learn.contentLockup.title"),
|
||||||
subtitle:
|
subtitle: t("pages.learn.contentLockup.subtitle"),
|
||||||
"Find answers to your questions and see how other groups have solved similar challenges.",
|
|
||||||
variant: "learn" as const,
|
variant: "learn" as const,
|
||||||
alignment: "left" as const,
|
alignment: "left" as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
const askOrganizerData = {
|
const askOrganizerData = {
|
||||||
title: "Still have questions?",
|
title: t("pages.learn.askOrganizer.title"),
|
||||||
subtitle: "Get answers from an experienced organizer",
|
subtitle: t("pages.learn.askOrganizer.subtitle"),
|
||||||
description:
|
description: t("pages.learn.askOrganizer.description"),
|
||||||
"Our community of organizers is here to help you navigate the challenges of building and maintaining effective community organizations.",
|
buttonText: t("pages.learn.askOrganizer.buttonText"),
|
||||||
buttonText: "Ask an organizer",
|
buttonHref: t("pages.learn.askOrganizer.buttonHref"),
|
||||||
buttonHref: "/contact",
|
|
||||||
variant: "centered" as const,
|
variant: "centered" as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -46,29 +46,29 @@ export default function Page() {
|
|||||||
const t = (key: string) => getTranslation(messages, key);
|
const t = (key: string) => getTranslation(messages, key);
|
||||||
|
|
||||||
const heroBannerData = {
|
const heroBannerData = {
|
||||||
title: t("heroBanner.title"),
|
title: t("pages.home.heroBanner.title"),
|
||||||
subtitle: t("heroBanner.subtitle"),
|
subtitle: t("pages.home.heroBanner.subtitle"),
|
||||||
description: t("heroBanner.description"),
|
description: t("pages.home.heroBanner.description"),
|
||||||
ctaText: t("heroBanner.ctaText"),
|
ctaText: t("pages.home.heroBanner.ctaText"),
|
||||||
ctaHref: t("heroBanner.ctaHref"),
|
ctaHref: t("pages.home.heroBanner.ctaHref"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const numberedCardsData = {
|
const numberedCardsData = {
|
||||||
title: t("numberedCards.title"),
|
title: t("pages.home.numberedCards.title"),
|
||||||
subtitle: t("numberedCards.subtitle"),
|
subtitle: t("pages.home.numberedCards.subtitle"),
|
||||||
cards: [
|
cards: [
|
||||||
{
|
{
|
||||||
text: t("numberedCards.cards.card1.text"),
|
text: t("pages.home.numberedCards.cards.card1.text"),
|
||||||
iconShape: "blob",
|
iconShape: "blob",
|
||||||
iconColor: "green",
|
iconColor: "green",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: t("numberedCards.cards.card2.text"),
|
text: t("pages.home.numberedCards.cards.card2.text"),
|
||||||
iconShape: "gear",
|
iconShape: "gear",
|
||||||
iconColor: "purple",
|
iconColor: "purple",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: t("numberedCards.cards.card3.text"),
|
text: t("pages.home.numberedCards.cards.card3.text"),
|
||||||
iconShape: "star",
|
iconShape: "star",
|
||||||
iconColor: "orange",
|
iconColor: "orange",
|
||||||
},
|
},
|
||||||
@@ -76,15 +76,15 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const featureGridData = {
|
const featureGridData = {
|
||||||
title: t("featureGrid.title"),
|
title: t("pages.home.featureGrid.title"),
|
||||||
subtitle: t("featureGrid.subtitle"),
|
subtitle: t("pages.home.featureGrid.subtitle"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const askOrganizerData = {
|
const askOrganizerData = {
|
||||||
title: t("askOrganizer.title"),
|
title: t("pages.home.askOrganizer.title"),
|
||||||
subtitle: t("askOrganizer.subtitle"),
|
subtitle: t("pages.home.askOrganizer.subtitle"),
|
||||||
buttonText: t("askOrganizer.buttonText"),
|
buttonText: t("pages.home.askOrganizer.buttonText"),
|
||||||
buttonHref: t("askOrganizer.buttonHref"),
|
buttonHref: t("pages.home.askOrganizer.buttonHref"),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,26 +1,87 @@
|
|||||||
# i18n Translation Workflow Guide
|
# i18n Translation Workflow Guide
|
||||||
|
|
||||||
This guide explains how to work with translations in the CommunityRule application. The app uses `next-intl` for managing UI text content, making it easy for content creators and contributors to update text without modifying component code.
|
This guide explains how to work with translations in the CommunityRule application. The app uses a hybrid approach combining globalized, shared UI elements with context-aware, localized content pages, making it easy for content creators and contributors to update text without modifying component code.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
All UI text is stored in JSON files under `messages/en/`. Components reference these translations using keys, allowing content to be edited independently of the codebase.
|
All UI text is stored in JSON files under `messages/en/`. The structure follows best practices:
|
||||||
|
- **Page-specific content** lives in `pages/` (varies by page context)
|
||||||
|
- **Component defaults** live in `components/` (shared across pages)
|
||||||
|
- **Common strings** live in `common.json` (shared UI elements)
|
||||||
|
|
||||||
|
Components reference these translations using keys, allowing content to be edited independently of the codebase.
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
messages/
|
messages/
|
||||||
en/
|
en/
|
||||||
common.json # Shared UI strings (buttons, links, labels)
|
pages/
|
||||||
|
home.json # Home page specific content
|
||||||
|
learn.json # Learn page specific content
|
||||||
components/
|
components/
|
||||||
heroBanner.json # HeroBanner component translations
|
heroBanner.json # Component defaults (aria-labels, alt texts)
|
||||||
numberedCards.json # NumberedCards component translations
|
numberedCards.json # Component defaults
|
||||||
askOrganizer.json # AskOrganizer component translations
|
askOrganizer.json # Component defaults
|
||||||
featureGrid.json # FeatureGrid component translations
|
featureGrid.json # Component defaults
|
||||||
footer.json # Footer component translations
|
footer.json # Shared across pages
|
||||||
|
header.json # Shared across pages
|
||||||
|
common.json # Shared UI strings (buttons, links, labels)
|
||||||
navigation.json # Navigation items
|
navigation.json # Navigation items
|
||||||
metadata.json # Page metadata (title, description)
|
metadata.json # Page metadata (title, description)
|
||||||
index.ts # Exports all messages
|
index.ts # Exports all messages
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use `pages/` vs `components/`
|
||||||
|
|
||||||
|
### Use `pages/` for:
|
||||||
|
- **Page-specific content**: Titles, subtitles, descriptions that vary by page
|
||||||
|
- **Context-aware text**: Content that changes based on where the component is used
|
||||||
|
- **User-facing content**: All text that users see on a specific page
|
||||||
|
|
||||||
|
**Example:** The home page hero banner title "Collaborate" goes in `pages/home.json`, not `components/heroBanner.json`
|
||||||
|
|
||||||
|
### Use `components/` for:
|
||||||
|
- **Component defaults**: Aria-labels, alt text patterns, shared behavior text
|
||||||
|
- **Shared across pages**: Text that doesn't vary by page context
|
||||||
|
- **Accessibility text**: Aria-labels and alt texts that are component-level
|
||||||
|
|
||||||
|
**Example:** The hero banner image alt text "Hero illustration" stays in `components/heroBanner.json` because it's the same across all pages
|
||||||
|
|
||||||
|
### Use `common.json` for:
|
||||||
|
- **Shared UI strings**: Buttons, links, labels used across multiple components
|
||||||
|
- **Global strings**: Text that appears in many places
|
||||||
|
|
||||||
|
## Page-Specific Translations
|
||||||
|
|
||||||
|
For page-specific content, use the `pages.*` namespace pattern:
|
||||||
|
|
||||||
|
**Server Components:**
|
||||||
|
```typescript
|
||||||
|
import messages from "../../messages/en/index";
|
||||||
|
import { getTranslation } from "../../lib/i18n/getTranslation";
|
||||||
|
|
||||||
|
export default function LearnPage() {
|
||||||
|
const t = (key: string) => getTranslation(messages, key);
|
||||||
|
|
||||||
|
const contentLockupData = {
|
||||||
|
title: t("pages.learn.contentLockup.title"),
|
||||||
|
subtitle: t("pages.learn.contentLockup.subtitle"),
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ContentLockup {...contentLockupData} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client Components:**
|
||||||
|
```typescript
|
||||||
|
"use client";
|
||||||
|
import { useTranslation } from "../../contexts/MessagesContext";
|
||||||
|
|
||||||
|
export default function MyComponent() {
|
||||||
|
const t = useTranslation("pages.home.heroBanner");
|
||||||
|
return <h1>{t("title")}</h1>;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Adding New Translation Keys
|
## Adding New Translation Keys
|
||||||
@@ -67,37 +128,42 @@ Group related translations together:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Update the Component
|
### 4. Update the Component or Page
|
||||||
|
|
||||||
In your component, use the translation hook:
|
**For Page Components (Server Components):**
|
||||||
|
|
||||||
**Server Components:**
|
|
||||||
```typescript
|
```typescript
|
||||||
import { getTranslations } from "next-intl/server";
|
import messages from "../../messages/en/index";
|
||||||
|
import { getTranslation } from "../../lib/i18n/getTranslation";
|
||||||
|
|
||||||
export default async function MyComponent() {
|
export default function MyPage() {
|
||||||
const t = await getTranslations();
|
const t = (key: string) => getTranslation(messages, key);
|
||||||
return <h1>{t("heroBanner.title")}</h1>;
|
|
||||||
|
// Use page-specific keys
|
||||||
|
const data = {
|
||||||
|
title: t("pages.home.heroBanner.title"),
|
||||||
|
subtitle: t("pages.home.heroBanner.subtitle"),
|
||||||
|
};
|
||||||
|
|
||||||
|
return <HeroBanner {...data} />;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Client Components:**
|
**For Client Components:**
|
||||||
```typescript
|
```typescript
|
||||||
"use client";
|
"use client";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslation } from "../../contexts/MessagesContext";
|
||||||
|
|
||||||
export default function MyComponent() {
|
export default function MyComponent() {
|
||||||
const t = useTranslations();
|
// For page-specific content
|
||||||
return <h1>{t("heroBanner.title")}</h1>;
|
const t = useTranslation("pages.home.heroBanner");
|
||||||
|
return <h1>{t("title")}</h1>;
|
||||||
|
|
||||||
|
// For component defaults
|
||||||
|
const tDefault = useTranslation("heroBanner");
|
||||||
|
return <img alt={tDefault("imageAlt")} />;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Namespace-specific (recommended for component files):**
|
|
||||||
```typescript
|
|
||||||
const t = useTranslations("heroBanner");
|
|
||||||
return <h1>{t("title")}</h1>;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Translation Key Naming Conventions
|
## Translation Key Naming Conventions
|
||||||
|
|
||||||
1. **Use camelCase** for keys: `buttonText`, `ariaLabel`
|
1. **Use camelCase** for keys: `buttonText`, `ariaLabel`
|
||||||
@@ -155,15 +221,38 @@ export default function HeroBanner() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Adding a New Page
|
||||||
|
|
||||||
|
When creating a new page that needs translations:
|
||||||
|
|
||||||
|
1. **Create a page translation file**: `messages/en/pages/about.json` (for example)
|
||||||
|
2. **Add page-specific content**: All user-facing text for that page
|
||||||
|
3. **Import in index.ts**: Add the import and export in `messages/en/index.ts`
|
||||||
|
4. **Use in page component**: Use `t("pages.about.*")` pattern in your page
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```typescript
|
||||||
|
// messages/en/pages/about.json
|
||||||
|
{
|
||||||
|
"hero": {
|
||||||
|
"title": "About Us",
|
||||||
|
"subtitle": "Learn more about our mission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// app/about/page.tsx
|
||||||
|
const t = (key: string) => getTranslation(messages, key);
|
||||||
|
const title = t("pages.about.hero.title");
|
||||||
|
```
|
||||||
|
|
||||||
## Adding New Languages (Future)
|
## Adding New Languages (Future)
|
||||||
|
|
||||||
When adding support for a new language:
|
When adding support for a new language:
|
||||||
|
|
||||||
1. **Create a new locale directory**: `messages/es/` (for Spanish, for example)
|
1. **Create a new locale directory**: `messages/es/` (for Spanish, for example)
|
||||||
2. **Copy the English files** as a starting point
|
2. **Copy the English files** as a starting point (including `pages/` structure)
|
||||||
3. **Translate all strings** in the JSON files
|
3. **Translate all strings** in the JSON files
|
||||||
4. **Update `app/i18n/routing.ts`** to include the new locale
|
4. **Test thoroughly** to ensure all translations are present
|
||||||
5. **Test thoroughly** to ensure all translations are present
|
|
||||||
|
|
||||||
## Testing Translations
|
## Testing Translations
|
||||||
|
|
||||||
@@ -242,14 +331,27 @@ If TypeScript complains about translation keys:
|
|||||||
|
|
||||||
If text doesn't appear:
|
If text doesn't appear:
|
||||||
1. Check the browser console for errors
|
1. Check the browser console for errors
|
||||||
2. Verify the component is wrapped in `NextIntlClientProvider` (for client components)
|
2. Verify the component is wrapped in `MessagesProvider` (for client components)
|
||||||
3. Ensure `getMessages()` is called in server components
|
3. Ensure `getTranslation()` is called correctly in server components
|
||||||
|
4. Check if you're using the correct namespace (`pages.*` vs component defaults)
|
||||||
|
|
||||||
|
## Architecture: Hybrid Approach
|
||||||
|
|
||||||
|
This implementation follows the recognized best practice of combining:
|
||||||
|
- **Globalized, shared UI elements**: Component defaults in `components/` (aria-labels, alt texts)
|
||||||
|
- **Context-aware, localized content pages**: Page-specific content in `pages/` (titles, descriptions)
|
||||||
|
|
||||||
|
This allows:
|
||||||
|
- Components to remain flexible and reusable
|
||||||
|
- Page content to be easily edited without code changes
|
||||||
|
- Clear separation between shared defaults and page-specific content
|
||||||
|
- Scalable structure for adding new pages
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- [next-intl Documentation](https://next-intl.dev/docs)
|
- Component defaults in `messages/en/components/`
|
||||||
- [Next.js Internationalization](https://nextjs.org/docs/app/guides/internationalization)
|
- Page-specific content in `messages/en/pages/`
|
||||||
- Component-specific translation files in `messages/en/components/`
|
- Shared UI strings in `messages/en/common.json`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"_comment": "AskOrganizer component translations",
|
"_comment": "AskOrganizer component defaults (shared across pages)",
|
||||||
"title": "Still have questions?",
|
|
||||||
"subtitle": "Get answers from an experienced organizer",
|
|
||||||
"buttonText": "Ask an organizer",
|
|
||||||
"buttonHref": "#contact",
|
|
||||||
"ariaLabel": "Ask an organizer - Contact an organizer for help"
|
"ariaLabel": "Ask an organizer - Contact an organizer for help"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,19 @@
|
|||||||
{
|
{
|
||||||
"_comment": "FeatureGrid component translations",
|
"_comment": "FeatureGrid component defaults (shared across pages)",
|
||||||
"title": "We've got your back, every step of the way",
|
|
||||||
"subtitle": "Use our toolkit to improve, document, and evolve your organization.",
|
|
||||||
"linkText": "Learn more",
|
"linkText": "Learn more",
|
||||||
"linkHref": "#",
|
"linkHref": "#",
|
||||||
"ariaLabel": "Feature tools and services",
|
"ariaLabel": "Feature tools and services",
|
||||||
"features": {
|
"features": {
|
||||||
"decisionMaking": {
|
"decisionMaking": {
|
||||||
"labelLine1": "Decision-making",
|
|
||||||
"labelLine2": "support",
|
|
||||||
"ariaLabel": "Decision-making support tools"
|
"ariaLabel": "Decision-making support tools"
|
||||||
},
|
},
|
||||||
"valuesAlignment": {
|
"valuesAlignment": {
|
||||||
"labelLine1": "Values alignment",
|
|
||||||
"labelLine2": "exercises",
|
|
||||||
"ariaLabel": "Values alignment exercises"
|
"ariaLabel": "Values alignment exercises"
|
||||||
},
|
},
|
||||||
"membershipGuidance": {
|
"membershipGuidance": {
|
||||||
"labelLine1": "Membership",
|
|
||||||
"labelLine2": "guidance",
|
|
||||||
"ariaLabel": "Membership guidance resources"
|
"ariaLabel": "Membership guidance resources"
|
||||||
},
|
},
|
||||||
"conflictResolution": {
|
"conflictResolution": {
|
||||||
"labelLine1": "Conflict resolution",
|
|
||||||
"labelLine2": "tools",
|
|
||||||
"ariaLabel": "Conflict resolution tools"
|
"ariaLabel": "Conflict resolution tools"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
{
|
{
|
||||||
"_comment": "HeroBanner component translations",
|
"_comment": "HeroBanner component defaults (shared across pages)",
|
||||||
"title": "Collaborate",
|
|
||||||
"subtitle": "with clarity",
|
|
||||||
"description": "Help your community make important decisions in a way that reflects its unique values.",
|
|
||||||
"ctaText": "Learn how CommunityRule works",
|
|
||||||
"ctaHref": "#",
|
|
||||||
"imageAlt": "Hero illustration"
|
"imageAlt": "Hero illustration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,8 @@
|
|||||||
{
|
{
|
||||||
"_comment": "NumberedCards component translations",
|
"_comment": "NumberedCards component defaults (shared across pages)",
|
||||||
"title": "How CommunityRule works",
|
|
||||||
"subtitle": "Here's a quick overview of the process, from start to finish.",
|
|
||||||
"titleLg": "How CommunityRule helps",
|
"titleLg": "How CommunityRule helps",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"createCommunityRule": "Create CommunityRule",
|
"createCommunityRule": "Create CommunityRule",
|
||||||
"seeHowItWorks": "See how it works"
|
"seeHowItWorks": "See how it works"
|
||||||
},
|
|
||||||
"cards": {
|
|
||||||
"card1": {
|
|
||||||
"text": "Document how your community makes decisions",
|
|
||||||
"_comment": "First step card"
|
|
||||||
},
|
|
||||||
"card2": {
|
|
||||||
"text": "Build an operating manual for a successful community",
|
|
||||||
"_comment": "Second step card"
|
|
||||||
},
|
|
||||||
"card3": {
|
|
||||||
"text": "Get a link to your manual for your group to review and evolve",
|
|
||||||
"_comment": "Third step card"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,4 @@
|
|||||||
{
|
{
|
||||||
"cards": {
|
"_comment": "RuleStack component defaults (shared across pages)",
|
||||||
"consensusClusters": {
|
|
||||||
"title": "Consensus clusters",
|
|
||||||
"description": "Units called Circles have the ability to decide and act on matters in their domains, which their members agree on through a Council.",
|
|
||||||
"iconAlt": "Sociocracy"
|
|
||||||
},
|
|
||||||
"consensus": {
|
|
||||||
"title": "Consensus",
|
|
||||||
"description": "Decisions that affect the group collectively should involve participation of all participants.",
|
|
||||||
"iconAlt": "Consensus"
|
|
||||||
},
|
|
||||||
"electedBoard": {
|
|
||||||
"title": "Elected Board",
|
|
||||||
"description": "An elected board determines policies and organizes their implementation.",
|
|
||||||
"iconAlt": "Elected Board"
|
|
||||||
},
|
|
||||||
"petition": {
|
|
||||||
"title": "Petition",
|
|
||||||
"description": "All participants can propose and vote on proposals for the group.",
|
|
||||||
"iconAlt": "Petition"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"button": {
|
|
||||||
"seeAllTemplates": "See all templates"
|
|
||||||
},
|
|
||||||
"ariaLabel": "Learn more about {title} governance pattern"
|
"ariaLabel": "Learn more about {title} governance pattern"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import menuBar from "./components/menuBar.json";
|
|||||||
import quoteBlock from "./components/quoteBlock.json";
|
import quoteBlock from "./components/quoteBlock.json";
|
||||||
import ruleCard from "./components/ruleCard.json";
|
import ruleCard from "./components/ruleCard.json";
|
||||||
import ruleStack from "./components/ruleStack.json";
|
import ruleStack from "./components/ruleStack.json";
|
||||||
|
import home from "./pages/home.json";
|
||||||
|
import learn from "./pages/learn.json";
|
||||||
import navigation from "./navigation.json";
|
import navigation from "./navigation.json";
|
||||||
import metadata from "./metadata.json";
|
import metadata from "./metadata.json";
|
||||||
|
|
||||||
@@ -28,6 +30,10 @@ export default {
|
|||||||
quoteBlock,
|
quoteBlock,
|
||||||
ruleCard,
|
ruleCard,
|
||||||
ruleStack,
|
ruleStack,
|
||||||
|
pages: {
|
||||||
|
home,
|
||||||
|
learn,
|
||||||
|
},
|
||||||
navigation,
|
navigation,
|
||||||
metadata,
|
metadata,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Home page specific content",
|
||||||
|
"heroBanner": {
|
||||||
|
"title": "Collaborate",
|
||||||
|
"subtitle": "with clarity",
|
||||||
|
"description": "Help your community make important decisions in a way that reflects its unique values.",
|
||||||
|
"ctaText": "Learn how CommunityRule works",
|
||||||
|
"ctaHref": "#"
|
||||||
|
},
|
||||||
|
"numberedCards": {
|
||||||
|
"title": "How CommunityRule works",
|
||||||
|
"subtitle": "Here's a quick overview of the process, from start to finish.",
|
||||||
|
"cards": {
|
||||||
|
"card1": {
|
||||||
|
"text": "Document how your community makes decisions",
|
||||||
|
"_comment": "First step card"
|
||||||
|
},
|
||||||
|
"card2": {
|
||||||
|
"text": "Build an operating manual for a successful community",
|
||||||
|
"_comment": "Second step card"
|
||||||
|
},
|
||||||
|
"card3": {
|
||||||
|
"text": "Get a link to your manual for your group to review and evolve",
|
||||||
|
"_comment": "Third step card"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"featureGrid": {
|
||||||
|
"title": "We've got your back, every step of the way",
|
||||||
|
"subtitle": "Use our toolkit to improve, document, and evolve your organization.",
|
||||||
|
"features": {
|
||||||
|
"decisionMaking": {
|
||||||
|
"labelLine1": "Decision-making",
|
||||||
|
"labelLine2": "support"
|
||||||
|
},
|
||||||
|
"valuesAlignment": {
|
||||||
|
"labelLine1": "Values alignment",
|
||||||
|
"labelLine2": "exercises"
|
||||||
|
},
|
||||||
|
"membershipGuidance": {
|
||||||
|
"labelLine1": "Membership",
|
||||||
|
"labelLine2": "guidance"
|
||||||
|
},
|
||||||
|
"conflictResolution": {
|
||||||
|
"labelLine1": "Conflict resolution",
|
||||||
|
"labelLine2": "tools"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"askOrganizer": {
|
||||||
|
"title": "Still have questions?",
|
||||||
|
"subtitle": "Get answers from an experienced organizer",
|
||||||
|
"buttonText": "Ask an organizer",
|
||||||
|
"buttonHref": "#contact"
|
||||||
|
},
|
||||||
|
"ruleStack": {
|
||||||
|
"cards": {
|
||||||
|
"consensusClusters": {
|
||||||
|
"title": "Consensus clusters",
|
||||||
|
"description": "Units called Circles have the ability to decide and act on matters in their domains, which their members agree on through a Council.",
|
||||||
|
"iconAlt": "Sociocracy"
|
||||||
|
},
|
||||||
|
"consensus": {
|
||||||
|
"title": "Consensus",
|
||||||
|
"description": "Decisions that affect the group collectively should involve participation of all participants.",
|
||||||
|
"iconAlt": "Consensus"
|
||||||
|
},
|
||||||
|
"electedBoard": {
|
||||||
|
"title": "Elected Board",
|
||||||
|
"description": "An elected board determines policies and organizes their implementation.",
|
||||||
|
"iconAlt": "Elected Board"
|
||||||
|
},
|
||||||
|
"petition": {
|
||||||
|
"title": "Petition",
|
||||||
|
"description": "All participants can propose and vote on proposals for the group.",
|
||||||
|
"iconAlt": "Petition"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"seeAllTemplates": "See all templates"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Learn page specific content",
|
||||||
|
"contentLockup": {
|
||||||
|
"title": "Organizing is hard",
|
||||||
|
"subtitle": "Find answers to your questions and see how other groups have solved similar challenges."
|
||||||
|
},
|
||||||
|
"askOrganizer": {
|
||||||
|
"title": "Still have questions?",
|
||||||
|
"subtitle": "Get answers from an experienced organizer",
|
||||||
|
"description": "Our community of organizers is here to help you navigate the challenges of building and maintaining effective community organizations.",
|
||||||
|
"buttonText": "Ask an organizer",
|
||||||
|
"buttonHref": "/contact"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user