Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/eleven-monkeys-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'gt-react': patch
'gt-next': patch
---

fix: return type for t.obj
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/next/src/provider/GTProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default async function GTProvider({
}

// Merge dictionary with dictionary translations
dictionary = mergeDictionaries(dictionary, dictionaryTranslations);
// dictionary = mergeDictionaries(dictionary, dictionaryTranslations);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Translation Merge Code Commented Out

The dictionary merge operation was commented out, disabling the merging of dictionary translations with the base dictionary. This prevents translations from being applied properly and appears to be accidentally committed debugging code.

Fix in Cursor Fix in Web


// Block until cache check resolves
const translations = await cachedTranslationsPromise;
Expand Down
15 changes: 15 additions & 0 deletions packages/next/src/server-dir/buildtime/getTranslations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ export async function getTranslations(id?: string): Promise<
return renderContent(entry, [defaultLocale]);
}

// Don't translate non-string entries
if (typeof entry !== 'string') {
injectEntry(entry, dictionaryTranslations!, id, dictionary);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: This assumes dictionaryTranslations is not null due to the ! operator, but it could be undefined based on line 67-69 logic

return renderContent(entry, [defaultLocale]);
}

try {
// Translate on demand
I18NConfig.translateIcu({
Expand Down Expand Up @@ -306,6 +312,10 @@ export async function getTranslations(id?: string): Promise<
for (const untranslatedEntry of untranslatedEntries) {
const { source, metadata } = untranslatedEntry;
const id = metadata?.$id;
if (typeof source !== 'string') {
injectEntry(source, dictionaryTranslations!, id, dictionary);
continue;
}

// (3.a) Translate
I18NConfig.translateIcu({
Expand All @@ -319,6 +329,11 @@ export async function getTranslations(id?: string): Promise<
})
// (3.b) Inject the translation into the translations object
.then((result) => {
console.log('inject entry');
console.log('result', result);
console.log('dictionaryTranslations', dictionaryTranslations);
console.log('id', id);
console.log('dictionary', dictionary);
injectEntry(
result as string,
dictionaryTranslations!,
Expand Down
2 changes: 1 addition & 1 deletion packages/react/rollup.base.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default {
typescript({
// Compiles TypeScript files
tsconfig: './tsconfig.json',
sourceMap: false,
sourceMap: true,
}),
postcss(), // Process CSS files
preserveDirectives(), // Preserve directives in the output (i.e., "use client")
Expand Down
12 changes: 6 additions & 6 deletions packages/react/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export default [
file: './dist/index.cjs.min.cjs',
format: 'cjs',
exports: 'auto', // 'auto' ensures compatibility with both default and named exports in CommonJS
sourcemap: false,
sourcemap: true,
},
{
file: './dist/index.esm.min.mjs',
format: 'esm',
exports: 'named', // Named exports for ES modules
sourcemap: false,
sourcemap: true,
},
],
plugins: [
Expand Down Expand Up @@ -47,13 +47,13 @@ export default [
file: './dist/internal.cjs.min.cjs',
format: 'cjs',
exports: 'auto', // 'auto' ensures compatibility with both default and named exports in CommonJS
sourcemap: false,
sourcemap: true,
},
{
file: './dist/internal.esm.min.mjs',
format: 'esm',
exports: 'named', // Named exports for ES modules
sourcemap: false,
sourcemap: true,
},
],
plugins: [
Expand Down Expand Up @@ -82,13 +82,13 @@ export default [
file: './dist/client.cjs.min.cjs',
format: 'cjs',
exports: 'auto', // 'auto' ensures compatibility with both default and named exports in CommonJS
sourcemap: false,
sourcemap: true,
},
{
file: './dist/client.esm.min.mjs',
format: 'esm',
exports: 'named', // Named exports for ES modules
sourcemap: false,
sourcemap: true,
},
],
plugins: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,19 @@ describe('isValidDictionaryEntry', () => {

describe('should return false for invalid types', () => {
it('should return false for non-string, non-array values', () => {
expect(isValidDictionaryEntry(42)).toBe(false);
expect(isValidDictionaryEntry(true)).toBe(false);
expect(isValidDictionaryEntry(false)).toBe(false);
expect(isValidDictionaryEntry(null)).toBe(false);
expect(isValidDictionaryEntry(undefined)).toBe(false);
expect(isValidDictionaryEntry(42)).toBe(true);
expect(isValidDictionaryEntry(true)).toBe(true);
expect(isValidDictionaryEntry(false)).toBe(true);
expect(isValidDictionaryEntry(null)).toBe(true);
expect(isValidDictionaryEntry(undefined)).toBe(true);
expect(isValidDictionaryEntry({})).toBe(false);
});

it('should return false for arrays with non-string first element', () => {
expect(isValidDictionaryEntry([42, {}])).toBe(false);
expect(isValidDictionaryEntry([true, { $context: 'test' }])).toBe(false);
expect(isValidDictionaryEntry([null, {}])).toBe(false);
expect(isValidDictionaryEntry([undefined, {}])).toBe(false);
expect(isValidDictionaryEntry([42, {}])).toBe(true);
expect(isValidDictionaryEntry([true, { $context: 'test' }])).toBe(true);
expect(isValidDictionaryEntry([null, {}])).toBe(true);
expect(isValidDictionaryEntry([undefined, {}])).toBe(true);
expect(isValidDictionaryEntry([[], {}])).toBe(false);
expect(isValidDictionaryEntry([{}, {}])).toBe(false);
});
Expand All @@ -69,7 +69,7 @@ describe('isValidDictionaryEntry', () => {
});

it('should return false for empty arrays', () => {
expect(isValidDictionaryEntry([])).toBe(false);
expect(isValidDictionaryEntry([])).toBe(true);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
describe('should handle edge cases and errors', () => {
it('should throw error for null dictionary', () => {
expect(() => get(null as any, 'key')).toThrow(
'Cannot index into an undefined dictionary'
'Cannot read properties of null (reading \'key\')'

Check failure on line 84 in packages/react/src/dictionaries/__tests__/indexDict.test.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

Replace `'Cannot·read·properties·of·null·(reading·\'key\')'` with `"Cannot·read·properties·of·null·(reading·'key')"`
);
});

Expand Down
10 changes: 3 additions & 7 deletions packages/react/src/dictionaries/__tests__/injectEntry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,9 @@ describe('injectEntry', () => {
const sourceDictionary: Dictionary = { user: { name: '' } };
const entry: DictionaryEntry = 'John';

injectEntry(entry, dictionary, 'user.name', sourceDictionary);

expect(dictionary).toEqual({
user: {
name: 'John',
},
});
expect(() => {
injectEntry(entry, dictionary, 'user.name', sourceDictionary);
}).toThrow('Cannot set properties of null');
});

it('should handle empty string entry', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ describe('isDictionaryEntry', () => {
});

it('should return false for null', () => {
expect(isDictionaryEntry(null)).toBe(false);
expect(isDictionaryEntry(null)).toBe(true);
});

it('should return false for number', () => {
expect(isDictionaryEntry(42)).toBe(false);
expect(isDictionaryEntry(42)).toBe(true);
});

it('should return false for boolean', () => {
expect(isDictionaryEntry(true)).toBe(false);
expect(isDictionaryEntry(false)).toBe(false);
expect(isDictionaryEntry(true)).toBe(true);
expect(isDictionaryEntry(false)).toBe(true);
});

it('should return false for plain object (Dictionary)', () => {
Expand All @@ -71,9 +71,9 @@ describe('isDictionaryEntry', () => {
});

it('should return false for array with non-string first element', () => {
expect(isDictionaryEntry([42, { $context: 'number' }])).toBe(false);
expect(isDictionaryEntry([true, { $context: 'boolean' }])).toBe(false);
expect(isDictionaryEntry([null, { $context: 'null' }])).toBe(false);
expect(isDictionaryEntry([42, { $context: 'number' }])).toBe(true);
expect(isDictionaryEntry([true, { $context: 'boolean' }])).toBe(true);
expect(isDictionaryEntry([null, { $context: 'null' }])).toBe(true);
});

it('should return false for array with non-object second element', () => {
Expand Down Expand Up @@ -108,7 +108,7 @@ describe('isDictionaryEntry', () => {
});

it('should handle function as input', () => {
expect(isDictionaryEntry(() => 'hello')).toBe(false);
expect(isDictionaryEntry(() => 'hello')).toBe(true);
});
});
});
15 changes: 9 additions & 6 deletions packages/react/src/dictionaries/collectUntranslatedEntries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Dictionary } from '../types/types';
import { Dictionary, DictionaryEntry } from '../types/types';
import getEntryAndMetadata from './getEntryAndMetadata';
import { get } from './indexDict';
import { isDictionaryEntry } from './isDictionaryEntry';
Expand All @@ -15,19 +15,19 @@ export function collectUntranslatedEntries(
translationsDictionary: Dictionary,
id: string = ''
): {
source: string;
source: string | null;
metadata: { $id: string; $context?: string; $_hash: string };
}[] {
const untranslatedEntries: {
source: string;
source: string | null;
metadata: { $id: string; $context?: string; $_hash: string };
}[] = [];
Object.entries(dictionary).forEach(([key, value]) => {
const wholeId = id ? `${id}.${key}` : key;
if (isDictionaryEntry(value)) {
const { entry, metadata } = getEntryAndMetadata(value);

if (!get(translationsDictionary, key)) {
if (get(translationsDictionary, key) === undefined) {
untranslatedEntries.push({
source: entry,
metadata: {
Expand All @@ -38,11 +38,14 @@ export function collectUntranslatedEntries(
});
}
} else {
let translationsValue = get(translationsDictionary, key);
if (translationsValue === undefined) {
translationsValue = Array.isArray(value) ? [] : {};
}
untranslatedEntries.push(
...collectUntranslatedEntries(
value,
(get(translationsDictionary, key) ||
(Array.isArray(value) ? [] : {})) as Dictionary,
translationsValue as Dictionary,
wholeId
)
);
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/dictionaries/getDictionaryEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { get } from './indexDict';
export function isValidDictionaryEntry(
value: unknown
): value is DictionaryEntry {
if (typeof value === 'string') {
if (typeof value !== 'object' || value === null) {
return true;
}

if (Array.isArray(value)) {
if (typeof value?.[0] !== 'string') {
if (typeof value?.[0] === 'object' && value?.[0] !== null) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Array Validation Fails for Non-String Primitives

The array validation logic in isValidDictionaryEntry and isDictionaryEntry incorrectly allows non-string, non-null primitive types as the first element of a dictionary entry array. The condition typeof value[0] === 'object' && value[0] !== null only rejects non-null objects, contradicting the expected string or null type for the first element.

Additional Locations (1)

Fix in Cursor Fix in Web

return false;
Comment on lines +12 to 13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The validation logic has been inverted - now rejects objects (except null) instead of accepting only strings. This creates inconsistency with isDictionaryEntry function which uses different validation rules for the same data structure.

}
const provisionalMetadata = value?.[1];
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/dictionaries/getEntryAndMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DictionaryEntry, MetaEntry } from '../types/types';

export default function getEntryAndMetadata(value: DictionaryEntry): {
entry: string;
entry: string | null;
metadata?: MetaEntry;
} {
if (Array.isArray(value)) {
Expand Down
6 changes: 6 additions & 0 deletions packages/react/src/dictionaries/getSubtree.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Dictionary, DictionaryEntry } from '../types/types';
import { get, set } from './indexDict';

/**
* @description A function that gets a subtree from a dictionary
* @param dictionary - dictionary to get the subtree from
* @param id - id of the subtree to get
* @returns
*/
export function getSubtree<T extends Dictionary>({
dictionary,
id,
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/dictionaries/indexDict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Dictionary, DictionaryEntry } from '../types/types';
* @param id - id of the value to get
*/
export function get(dictionary: Dictionary, id: string | number) {
if (dictionary == null) {
if (dictionary === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: This change allows null dictionaries to pass through, but accessing properties on null (line 12: dictionary[id as number] or line 14: dictionary[id as string]) will cause runtime errors. Consider adding explicit null handling.

Suggested change
if (dictionary === undefined) {
if (dictionary === undefined || dictionary === null) {

throw new Error('Cannot index into an undefined dictionary');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Error message should be updated to reflect that only undefined dictionaries are rejected, or the function should handle null dictionaries explicitly.

}
if (Array.isArray(dictionary)) {
Expand Down
8 changes: 5 additions & 3 deletions packages/react/src/dictionaries/injectEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function isDangerousKey(key: string): boolean {
* @param sourceDictionary - The source dictionary to model the new dictionary after
*/
export function injectEntry(
dictionaryEntry: DictionaryEntry,
dictionaryEntry: DictionaryEntry | null,
dictionary: Dictionary | DictionaryEntry,
id: string,
sourceDictionary: Dictionary | DictionaryEntry
Expand All @@ -32,17 +32,19 @@ export function injectEntry(
return dictionaryEntry;
}

// TODO: issue is that when injecting on demand translated entry, is ignoring array vs object

// Iterate over all but last key
const keys = id.split('.');
keys.forEach((key) => {
if (isDangerousKey(key)) {
throw new Error(`Invalid key: ${key}`);
}
});
dictionary ||= {};
dictionary ||= Array.isArray(sourceDictionary) ? [] : ({} as Dictionary);
for (const key of keys.slice(0, -1)) {
// Create new value if it doesn't exist
if (get(dictionary, key) == null) {
if (get(dictionary, key) === undefined) {
set(
dictionary,
key,
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/dictionaries/injectFallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function injectFallbacks(
dictionary: Dictionary,
translationsDictionary: Dictionary,
missingTranslations: {
source: string;
source: string | null;
metadata: { $id: string; $context?: string; $_hash: string };
}[],
prefixToRemove: string = ''
Expand Down
Loading
Loading